From eae821d6454ee4bdc919d6fe7116d1d29147827e Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Mon, 23 Mar 2026 10:54:01 +0800 Subject: [PATCH 01/70] chore: update deps (#33862) --- web/eslint-suppressions.json | 500 ++- web/eslint.config.mjs | 52 +- web/knip.config.ts | 2 - web/package.json | 96 +- web/pnpm-lock.yaml | 2753 +++++++++-------- web/tailwind-common-config.ts | 6 +- ...{tailwind.config.js => tailwind.config.ts} | 6 +- web/taze.config.js | 1 - 8 files changed, 1906 insertions(+), 1510 deletions(-) rename web/{tailwind.config.js => tailwind.config.ts} (72%) diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index d5c59f4f2d..36c77e051c 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -56,7 +56,7 @@ "no-console": { "count": 16 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 4 }, "ts/no-explicit-any": { @@ -74,7 +74,7 @@ } }, "app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "ts/no-explicit-any": { @@ -173,7 +173,7 @@ } }, "app/(humanInputLayout)/form/[token]/form.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -186,7 +186,7 @@ } }, "app/(shareLayout)/components/splash.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -292,7 +292,7 @@ } }, "app/account/(commonLayout)/delete-account/components/verify-email.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -323,7 +323,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 4 } }, @@ -411,10 +411,10 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react-refresh/only-export-components": { "count": 1 }, - "react-refresh/only-export-components": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -433,10 +433,10 @@ "erasable-syntax-only/enums": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react-refresh/only-export-components": { "count": 1 }, - "react-refresh/only-export-components": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -470,6 +470,9 @@ "no-restricted-imports": { "count": 1 }, + "react/component-hook-factories": { + "count": 1 + }, "react/no-nested-component-definitions": { "count": 1 }, @@ -489,7 +492,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -523,7 +526,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 5 }, "tailwindcss/enforce-consistent-class-order": { @@ -672,6 +675,9 @@ "app/components/app/configuration/config-var/index.tsx": { "no-restricted-imports": { "count": 3 + }, + "react-refresh/only-export-components": { + "count": 1 } }, "app/components/app/configuration/config-var/input-type-icon.tsx": { @@ -738,7 +744,7 @@ } }, "app/components/app/configuration/config/agent/agent-setting/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -775,7 +781,10 @@ } }, "app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react-hooks/exhaustive-deps": { + "count": 1 + }, + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -808,7 +817,7 @@ "no-restricted-imports": { "count": 3 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 4 }, "tailwindcss/enforce-consistent-class-order": { @@ -840,7 +849,7 @@ } }, "app/components/app/configuration/config/automatic/prompt-res.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -869,7 +878,7 @@ "no-restricted-imports": { "count": 3 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 4 }, "ts/no-explicit-any": { @@ -952,7 +961,7 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -975,7 +984,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "ts/no-explicit-any": { @@ -1050,7 +1059,7 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "ts/no-explicit-any": { @@ -1066,7 +1075,7 @@ "no-restricted-imports": { "count": 3 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "style/multiline-ternary": { @@ -1145,7 +1154,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -1167,11 +1176,11 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { - "count": 2 - }, "react-refresh/only-export-components": { "count": 1 + }, + "react/set-state-in-effect": { + "count": 2 } }, "app/components/app/create-from-dsl-modal/uploader.tsx": { @@ -1198,7 +1207,7 @@ } }, "app/components/app/log/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -1209,7 +1218,7 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 6 }, "style/multiline-ternary": { @@ -1249,7 +1258,7 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 3 }, "tailwindcss/enforce-consistent-class-order": { @@ -1279,7 +1288,7 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -1293,7 +1302,7 @@ "no-restricted-imports": { "count": 4 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 3 }, "regexp/no-unused-capturing-group": { @@ -1312,7 +1321,7 @@ "no-restricted-imports": { "count": 3 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -1320,12 +1329,12 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { - "count": 3 - }, "react-refresh/only-export-components": { "count": 1 }, + "react/set-state-in-effect": { + "count": 3 + }, "tailwindcss/enforce-consistent-class-order": { "count": 6 }, @@ -1389,7 +1398,7 @@ } }, "app/components/app/workflow-log/list.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "tailwindcss/enforce-consistent-class-order": { @@ -1408,12 +1417,15 @@ "no-restricted-imports": { "count": 5 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/component-hook-factories": { "count": 1 }, "react/no-nested-component-definitions": { "count": 1 }, + "react/set-state-in-effect": { + "count": 1 + }, "ts/no-explicit-any": { "count": 2 } @@ -1428,7 +1440,21 @@ "count": 1 } }, + "app/components/apps/list.tsx": { + "react-hooks/exhaustive-deps": { + "count": 1 + }, + "react/unsupported-syntax": { + "count": 2 + } + }, "app/components/apps/new-app-card.tsx": { + "react-hooks-extra/no-direct-set-state-in-use-effect": { + "count": 1 + }, + "react/set-state-in-effect": { + "count": 1 + }, "ts/no-explicit-any": { "count": 1 } @@ -1461,7 +1487,7 @@ } }, "app/components/base/agent-log-modal/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -1533,6 +1559,12 @@ } }, "app/components/base/auto-height-textarea/index.tsx": { + "react-hooks/rules-of-hooks": { + "count": 1 + }, + "react/rules-of-hooks": { + "count": 1 + }, "tailwindcss/no-unnecessary-whitespace": { "count": 1 } @@ -1551,14 +1583,17 @@ } }, "app/components/base/block-input/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { - "count": 1 - }, "react-refresh/only-export-components": { "count": 1 }, + "react/component-hook-factories": { + "count": 1 + }, "react/no-nested-component-definitions": { "count": 1 + }, + "react/set-state-in-effect": { + "count": 1 } }, "app/components/base/button/add-button.stories.tsx": { @@ -1582,8 +1617,14 @@ } }, "app/components/base/carousel/index.tsx": { + "react-hooks-extra/no-direct-set-state-in-use-effect": { + "count": 1 + }, "react-refresh/only-export-components": { "count": 1 + }, + "react/set-state-in-effect": { + "count": 3 } }, "app/components/base/chat/chat-with-history/chat-wrapper.tsx": { @@ -1627,7 +1668,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 4 }, "ts/no-explicit-any": { @@ -1635,7 +1676,7 @@ } }, "app/components/base/chat/chat-with-history/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -1679,7 +1720,7 @@ } }, "app/components/base/chat/chat-with-history/sidebar/operation.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -1705,7 +1746,7 @@ } }, "app/components/base/chat/chat/answer/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 3 }, "ts/no-explicit-any": { @@ -1723,7 +1764,7 @@ } }, "app/components/base/chat/chat/answer/workflow-process.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -1744,7 +1785,10 @@ } }, "app/components/base/chat/chat/citation/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react-hooks/exhaustive-deps": { + "count": 1 + }, + "react/set-state-in-effect": { "count": 1 } }, @@ -1752,7 +1796,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "ts/no-explicit-any": { @@ -1760,7 +1804,7 @@ } }, "app/components/base/chat/chat/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -1803,6 +1847,9 @@ }, "react-hooks-extra/no-direct-set-state-in-use-effect": { "count": 3 + }, + "react/set-state-in-effect": { + "count": 6 } }, "app/components/base/chat/embedded-chatbot/index.tsx": { @@ -1848,7 +1895,7 @@ } }, "app/components/base/confirm/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "tailwindcss/enforce-consistent-class-order": { @@ -1856,7 +1903,7 @@ } }, "app/components/base/content-dialog/index.stories.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -1896,7 +1943,7 @@ } }, "app/components/base/date-and-time-picker/date-picker/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 4 } }, @@ -1906,7 +1953,7 @@ } }, "app/components/base/date-and-time-picker/time-picker/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 } }, @@ -1931,7 +1978,7 @@ } }, "app/components/base/dialog/index.stories.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -1940,13 +1987,18 @@ "count": 1 } }, + "app/components/base/drawer-plus/index.stories.tsx": { + "react/component-hook-factories": { + "count": 1 + } + }, "app/components/base/drawer-plus/index.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 2 } }, "app/components/base/emoji-picker/Inner.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -1964,6 +2016,9 @@ "react-refresh/only-export-components": { "count": 3 }, + "react/component-hook-factories": { + "count": 1 + }, "ts/no-explicit-any": { "count": 2 } @@ -2012,7 +2067,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -2218,6 +2273,9 @@ } }, "app/components/base/form/form-scenarios/base/field.tsx": { + "react/component-hook-factories": { + "count": 1 + }, "ts/no-explicit-any": { "count": 1 } @@ -2241,6 +2299,9 @@ } }, "app/components/base/form/form-scenarios/input-field/field.tsx": { + "react/component-hook-factories": { + "count": 1 + }, "ts/no-explicit-any": { "count": 1 } @@ -2254,6 +2315,9 @@ } }, "app/components/base/form/form-scenarios/node-panel/field.tsx": { + "react/component-hook-factories": { + "count": 1 + }, "ts/no-explicit-any": { "count": 1 } @@ -2297,7 +2361,7 @@ "count": 1 }, "react-refresh/only-export-components": { - "count": 1 + "count": 4 } }, "app/components/base/icons/utils.ts": { @@ -2351,6 +2415,11 @@ "count": 1 } }, + "app/components/base/input-with-copy/index.tsx": { + "react/unsupported-syntax": { + "count": 1 + } + }, "app/components/base/input/index.stories.tsx": { "no-console": { "count": 2 @@ -2393,7 +2462,7 @@ } }, "app/components/base/markdown-blocks/code-block.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 7 }, "ts/no-explicit-any": { @@ -2416,17 +2485,17 @@ } }, "app/components/base/markdown-blocks/plugin-img.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 } }, "app/components/base/markdown-blocks/plugin-paragraph.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 } }, "app/components/base/markdown-blocks/think-block.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -2449,7 +2518,7 @@ } }, "app/components/base/mermaid/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 7 }, "regexp/no-super-linear-backtracking": { @@ -2476,7 +2545,7 @@ } }, "app/components/base/message-log-modal/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -2489,7 +2558,7 @@ } }, "app/components/base/modal/index.stories.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -2497,7 +2566,7 @@ "no-console": { "count": 4 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -2528,12 +2597,12 @@ } }, "app/components/base/notion-page-selector/base.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 } }, "app/components/base/notion-page-selector/page-selector/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -2636,6 +2705,11 @@ "count": 2 } }, + "app/components/base/prompt-editor/plugins/hitl-input-block/component-ui.tsx": { + "react-hooks/exhaustive-deps": { + "count": 1 + } + }, "app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx": { "ts/no-explicit-any": { "count": 1 @@ -2687,11 +2761,17 @@ } }, "app/components/base/prompt-editor/plugins/shortcuts-popup-plugin/index.tsx": { + "react-refresh/only-export-components": { + "count": 1 + }, "ts/no-explicit-any": { "count": 2 } }, "app/components/base/prompt-editor/plugins/update-block.tsx": { + "react-refresh/only-export-components": { + "count": 2 + }, "ts/no-explicit-any": { "count": 2 } @@ -2722,7 +2802,7 @@ } }, "app/components/base/prompt-log-modal/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -2783,7 +2863,7 @@ } }, "app/components/base/select/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "style/multiline-ternary": { @@ -2835,7 +2915,7 @@ } }, "app/components/base/tab-slider/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 } }, @@ -2852,6 +2932,14 @@ "count": 1 } }, + "app/components/base/tag-management/__tests__/selector.spec.tsx": { + "react-hooks/rules-of-hooks": { + "count": 1 + }, + "react/rules-of-hooks": { + "count": 1 + } + }, "app/components/base/tag-management/index.stories.tsx": { "no-restricted-imports": { "count": 1 @@ -2904,7 +2992,7 @@ } }, "app/components/base/video-gallery/VideoPlayer.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -2924,9 +3012,15 @@ "app/components/base/with-input-validation/index.stories.tsx": { "no-console": { "count": 1 + }, + "react/component-hook-factories": { + "count": 1 } }, "app/components/base/with-input-validation/index.tsx": { + "react/component-hook-factories": { + "count": 1 + }, "ts/no-explicit-any": { "count": 1 } @@ -3090,6 +3184,11 @@ "count": 3 } }, + "app/components/datasets/common/image-uploader/__tests__/store.spec.tsx": { + "react/error-boundaries": { + "count": 1 + } + }, "app/components/datasets/common/image-uploader/hooks/use-upload.ts": { "no-restricted-imports": { "count": 1 @@ -3255,7 +3354,7 @@ } }, "app/components/datasets/create/file-preview/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -3265,7 +3364,7 @@ } }, "app/components/datasets/create/notion-page-preview/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -3311,7 +3410,7 @@ "erasable-syntax-only/enums": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 3 } }, @@ -3319,6 +3418,9 @@ "no-restricted-imports": { "count": 1 }, + "react-hooks/exhaustive-deps": { + "count": 1 + }, "tailwindcss/enforce-consistent-class-order": { "count": 1 } @@ -3389,7 +3491,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -3416,7 +3518,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -3451,7 +3553,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -3535,7 +3637,7 @@ } }, "app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -3606,7 +3708,7 @@ } }, "app/components/datasets/documents/create-from-pipeline/data-source/online-drive/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 5 } }, @@ -3734,7 +3836,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -3767,7 +3869,7 @@ } }, "app/components/datasets/documents/detail/completed/common/chunk-content.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -3848,6 +3950,9 @@ "app/components/datasets/documents/detail/completed/index.tsx": { "react-refresh/only-export-components": { "count": 1 + }, + "react/use-memo": { + "count": 1 } }, "app/components/datasets/documents/detail/completed/segment-card/chunk-content.tsx": { @@ -3916,6 +4021,12 @@ "app/components/datasets/documents/detail/metadata/hooks/use-metadata-state.ts": { "no-restricted-imports": { "count": 1 + }, + "react-hooks-extra/no-direct-set-state-in-use-effect": { + "count": 4 + }, + "react/set-state-in-effect": { + "count": 4 } }, "app/components/datasets/documents/detail/segment-add/index.tsx": { @@ -3964,7 +4075,7 @@ "no-restricted-imports": { "count": 4 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -3982,7 +4093,7 @@ } }, "app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -3990,7 +4101,7 @@ } }, "app/components/datasets/external-knowledge-base/create/ExternalApiSelection.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -4103,6 +4214,11 @@ "count": 1 } }, + "app/components/datasets/hit-testing/index.tsx": { + "react/unsupported-syntax": { + "count": 1 + } + }, "app/components/datasets/hit-testing/modify-external-retrieval-modal.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 1 @@ -4145,7 +4261,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -4201,7 +4317,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -4397,11 +4513,23 @@ } }, "app/components/explore/banner/banner-item.tsx": { + "react-hooks-extra/no-direct-set-state-in-use-effect": { + "count": 1 + }, + "react/set-state-in-effect": { + "count": 3 + }, "tailwindcss/enforce-consistent-class-order": { "count": 4 } }, "app/components/explore/banner/indicator-button.tsx": { + "react-hooks-extra/no-direct-set-state-in-use-effect": { + "count": 1 + }, + "react/set-state-in-effect": { + "count": 2 + }, "tailwindcss/enforce-consistent-class-order": { "count": 1 } @@ -4432,7 +4560,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -4508,12 +4636,27 @@ "count": 2 } }, + "app/components/goto-anything/command-selector.tsx": { + "react/unsupported-syntax": { + "count": 2 + } + }, + "app/components/goto-anything/components/footer.tsx": { + "react/unsupported-syntax": { + "count": 1 + } + }, "app/components/goto-anything/context.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { - "count": 4 - }, "react-refresh/only-export-components": { "count": 1 + }, + "react/set-state-in-effect": { + "count": 4 + } + }, + "app/components/goto-anything/hooks/use-goto-anything-results.ts": { + "@tanstack/query/exhaustive-deps": { + "count": 1 } }, "app/components/goto-anything/index.tsx": { @@ -4534,6 +4677,11 @@ "count": 1 } }, + "app/components/header/account-dropdown/menu-item-content.tsx": { + "react-refresh/only-export-components": { + "count": 1 + } + }, "app/components/header/account-dropdown/workplace-selector/index.tsx": { "no-restricted-imports": { "count": 1 @@ -4697,7 +4845,7 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 3 } }, @@ -4885,7 +5033,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 } }, @@ -4916,7 +5064,7 @@ "no-restricted-imports": { "count": 3 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -4944,7 +5092,7 @@ } }, "app/components/header/app-nav/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "ts/no-explicit-any": { @@ -5053,7 +5201,12 @@ } }, "app/components/plugins/install-plugin/install-bundle/item/github-item.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { + "count": 1 + } + }, + "app/components/plugins/install-plugin/install-bundle/steps/hooks/use-install-multi-state.ts": { + "react-hooks/exhaustive-deps": { "count": 1 } }, @@ -5640,7 +5793,7 @@ } }, "app/components/plugins/plugin-page/empty/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "tailwindcss/enforce-consistent-class-order": { @@ -5672,7 +5825,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "tailwindcss/enforce-consistent-class-order": { @@ -5704,6 +5857,11 @@ "count": 2 } }, + "app/components/plugins/readme-panel/index.tsx": { + "react/unsupported-syntax": { + "count": 1 + } + }, "app/components/plugins/readme-panel/store.ts": { "erasable-syntax-only/enums": { "count": 1 @@ -5813,6 +5971,9 @@ } }, "app/components/rag-pipeline/components/panel/input-field/editor/form/hidden-fields.tsx": { + "react/component-hook-factories": { + "count": 1 + }, "ts/no-explicit-any": { "count": 1 } @@ -5828,11 +5989,17 @@ } }, "app/components/rag-pipeline/components/panel/input-field/editor/form/initial-fields.tsx": { + "react/component-hook-factories": { + "count": 1 + }, "ts/no-explicit-any": { "count": 2 } }, "app/components/rag-pipeline/components/panel/input-field/editor/form/show-all-settings.tsx": { + "react/component-hook-factories": { + "count": 1 + }, "tailwindcss/enforce-consistent-class-order": { "count": 2 }, @@ -5861,7 +6028,7 @@ } }, "app/components/rag-pipeline/components/panel/input-field/hooks.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -6074,7 +6241,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -6090,7 +6257,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -6130,7 +6297,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -6151,6 +6318,9 @@ } }, "app/components/signin/countdown.tsx": { + "react-refresh/only-export-components": { + "count": 2 + }, "tailwindcss/enforce-consistent-class-order": { "count": 2 } @@ -6178,7 +6348,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 4 }, "tailwindcss/enforce-consistent-class-order": { @@ -6462,7 +6632,7 @@ } }, "app/components/workflow/block-selector/all-start-blocks.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -6486,7 +6656,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "tailwindcss/enforce-consistent-class-order": { @@ -6503,7 +6673,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "tailwindcss/enforce-consistent-class-order": { @@ -6517,13 +6687,13 @@ } }, "app/components/workflow/block-selector/hooks.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, "app/components/workflow/block-selector/index-bar.tsx": { "react-refresh/only-export-components": { - "count": 1 + "count": 5 } }, "app/components/workflow/block-selector/main.tsx": { @@ -6535,7 +6705,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -6559,7 +6729,7 @@ } }, "app/components/workflow/block-selector/rag-tool-recommendations/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -6603,7 +6773,7 @@ } }, "app/components/workflow/block-selector/tool/tool.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "tailwindcss/enforce-consistent-class-order": { @@ -6619,7 +6789,7 @@ } }, "app/components/workflow/block-selector/trigger-plugin/item.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "tailwindcss/enforce-consistent-class-order": { @@ -6635,7 +6805,7 @@ } }, "app/components/workflow/block-selector/use-check-vertical-scrollbar.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -6757,6 +6927,11 @@ "count": 6 } }, + "app/components/workflow/hooks/__tests__/use-checklist.spec.ts": { + "react/error-boundaries": { + "count": 1 + } + }, "app/components/workflow/hooks/use-checklist.ts": { "no-restricted-imports": { "count": 1 @@ -6904,7 +7079,7 @@ } }, "app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -7103,7 +7278,7 @@ } }, "app/components/workflow/nodes/_base/components/node-handle.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -7232,12 +7407,17 @@ "count": 1 } }, + "app/components/workflow/nodes/_base/components/variable/var-list.tsx": { + "react/unsupported-syntax": { + "count": 1 + } + }, "app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx": { "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { - "count": 2 + "react/set-state-in-effect": { + "count": 1 }, "tailwindcss/enforce-consistent-class-order": { "count": 4 @@ -7284,7 +7464,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 3 }, "tailwindcss/enforce-consistent-class-order": { @@ -7298,7 +7478,7 @@ } }, "app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 7 }, "ts/no-explicit-any": { @@ -7314,7 +7494,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -7336,7 +7516,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "ts/no-explicit-any": { @@ -7349,7 +7529,7 @@ } }, "app/components/workflow/nodes/_base/hooks/use-toggle-expend.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -7385,6 +7565,9 @@ "app/components/workflow/nodes/agent/components/tool-icon.tsx": { "no-restricted-imports": { "count": 1 + }, + "react/unsupported-syntax": { + "count": 1 } }, "app/components/workflow/nodes/agent/default.ts": { @@ -7490,7 +7673,7 @@ } }, "app/components/workflow/nodes/code/use-config.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "regexp/no-useless-assertions": { @@ -7635,7 +7818,7 @@ } }, "app/components/workflow/nodes/http/use-config.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "ts/no-explicit-any": { @@ -7734,13 +7917,21 @@ "count": 2 } }, + "app/components/workflow/nodes/human-input/components/form-content-preview.tsx": { + "react/unsupported-syntax": { + "count": 1 + } + }, "app/components/workflow/nodes/human-input/components/form-content.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/component-hook-factories": { "count": 1 }, "react/no-nested-component-definitions": { "count": 1 }, + "react/set-state-in-effect": { + "count": 1 + }, "tailwindcss/enforce-consistent-class-order": { "count": 2 }, @@ -7884,7 +8075,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -8073,7 +8264,7 @@ } }, "app/components/workflow/nodes/knowledge-retrieval/node.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "tailwindcss/enforce-consistent-class-order": { @@ -8149,6 +8340,9 @@ } }, "app/components/workflow/nodes/llm/components/config-prompt.tsx": { + "react/unsupported-syntax": { + "count": 1 + }, "ts/no-explicit-any": { "count": 1 } @@ -8208,7 +8402,7 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 } }, @@ -8241,7 +8435,7 @@ } }, "app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -8298,7 +8492,7 @@ } }, "app/components/workflow/nodes/llm/use-config.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "ts/no-explicit-any": { @@ -8458,7 +8652,7 @@ } }, "app/components/workflow/nodes/parameter-extractor/use-config.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "ts/no-explicit-any": { @@ -8476,13 +8670,16 @@ } }, "app/components/workflow/nodes/question-classifier/components/class-item.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, "app/components/workflow/nodes/question-classifier/components/class-list.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 + }, + "react/unsupported-syntax": { + "count": 2 } }, "app/components/workflow/nodes/question-classifier/default.ts": { @@ -8499,7 +8696,7 @@ } }, "app/components/workflow/nodes/question-classifier/use-config.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 }, "ts/no-explicit-any": { @@ -8660,6 +8857,9 @@ } }, "app/components/workflow/nodes/trigger-plugin/node.tsx": { + "react/unsupported-syntax": { + "count": 1 + }, "ts/no-explicit-any": { "count": 2 } @@ -8767,7 +8967,7 @@ } }, "app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -8905,7 +9105,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 8 }, "ts/no-explicit-any": { @@ -8979,7 +9179,7 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 4 }, "ts/no-explicit-any": { @@ -9134,7 +9334,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 2 } }, @@ -9184,7 +9384,7 @@ "no-restricted-imports": { "count": 1 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -9439,7 +9639,7 @@ } }, "app/components/workflow/variable-inspect/value-content.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 5 }, "regexp/no-super-linear-backtracking": { @@ -9513,7 +9713,7 @@ } }, "app/education-apply/hooks.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 5 } }, @@ -9542,7 +9742,7 @@ } }, "app/education-apply/verify-state-modal.tsx": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "tailwindcss/enforce-consistent-class-order": { @@ -9678,7 +9878,7 @@ } }, "context/hooks/use-trigger-events-limit-modal.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 3 } }, @@ -9736,12 +9936,15 @@ } }, "hooks/use-mitt.ts": { + "react/component-hook-factories": { + "count": 1 + }, "ts/no-explicit-any": { "count": 2 } }, "hooks/use-moderate.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 } }, @@ -9754,11 +9957,11 @@ "no-restricted-imports": { "count": 2 }, - "react-hooks-extra/no-direct-set-state-in-use-effect": { - "count": 4 - }, "react-refresh/only-export-components": { "count": 3 + }, + "react/set-state-in-effect": { + "count": 4 } }, "i18n/de-DE/billing.json": { @@ -9970,7 +10173,7 @@ } }, "service/use-plugins.ts": { - "react-hooks-extra/no-direct-set-state-in-use-effect": { + "react/set-state-in-effect": { "count": 1 }, "regexp/no-unused-capturing-group": { @@ -10069,6 +10272,11 @@ "count": 1 } }, + "utils/context.ts": { + "react/component-hook-factories": { + "count": 1 + } + }, "utils/error-parser.ts": { "no-console": { "count": 1 diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index 916c1fd287..d5d833ba69 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -1,11 +1,13 @@ // @ts-check -import antfu, { GLOB_MARKDOWN, GLOB_TESTS, GLOB_TS, GLOB_TSX, isInEditorEnv, isInGitHooksOrLintStaged } from '@antfu/eslint-config' +import antfu, { GLOB_MARKDOWN, GLOB_MARKDOWN_CODE, GLOB_TESTS, GLOB_TS, GLOB_TSX, isInEditorEnv, isInGitHooksOrLintStaged } from '@antfu/eslint-config' +import pluginReact from '@eslint-react/eslint-plugin' import pluginQuery from '@tanstack/eslint-plugin-query' import md from 'eslint-markdown' import tailwindcss from 'eslint-plugin-better-tailwindcss' import hyoban from 'eslint-plugin-hyoban' import markdownPreferences from 'eslint-plugin-markdown-preferences' +import { reactRefresh } from 'eslint-plugin-react-refresh' import sonar from 'eslint-plugin-sonarjs' import storybook from 'eslint-plugin-storybook' import { @@ -23,22 +25,11 @@ process.env.TAILWIND_MODE ??= 'ESLINT' const disableRuleAutoFix = !(isInEditorEnv() || isInGitHooksOrLintStaged()) +const plugins = pluginReact.configs.all.plugins + export default antfu( { - react: { - // This react compiler rules are pretty slow - // We can wait for https://github.com/Rel1cx/eslint-react/issues/1237 - reactCompiler: false, - overrides: { - 'react/no-context-provider': 'off', - 'react/no-forward-ref': 'off', - 'react/no-use-context': 'off', - - // prefer react-hooks-extra/no-direct-set-state-in-use-effect - 'react-hooks/set-state-in-effect': 'off', - 'react-hooks-extra/no-direct-set-state-in-use-effect': 'error', - }, - }, + react: false, nextjs: true, ignores: ['public', 'types/doc-paths.ts', 'eslint-suppressions.json'], typescript: { @@ -60,6 +51,30 @@ export default antfu( }, e18e: false, }, + { + plugins: { + 'react': plugins?.['@eslint-react'], + 'react-dom': plugins?.['@eslint-react/dom'], + 'react-naming-convention': plugins?.['@eslint-react/naming-convention'], + 'react-rsc': plugins?.['@eslint-react/rsc'], + 'react-web-api': plugins?.['@eslint-react/web-api'], + }, + }, + { + files: [GLOB_TS, GLOB_TSX], + rules: { + ...pluginReact.configs['recommended-typescript'].rules, + 'react/prefer-namespace-import': 'error', + 'react/set-state-in-effect': 'error', + }, + }, + { + files: [...GLOB_TESTS, GLOB_MARKDOWN_CODE, 'vitest.setup.ts', 'test/i18n-mock.ts'], + rules: { + 'react/component-hook-factories': 'off', + }, + }, + reactRefresh.configs.next(), markdownPreferences.configs.standard, { files: [GLOB_MARKDOWN], @@ -194,3 +209,10 @@ export default antfu( 'tailwindcss/no-unnecessary-whitespace', ] : []) + .renamePlugins({ + '@eslint-react': 'react', + '@eslint-react/dom': 'react-dom', + '@eslint-react/naming-convention': 'react-naming-convention', + '@eslint-react/rsc': 'react-rsc', + '@eslint-react/web-api': 'react-web-api', + }) diff --git a/web/knip.config.ts b/web/knip.config.ts index b7090ef8b1..c1f4a49615 100644 --- a/web/knip.config.ts +++ b/web/knip.config.ts @@ -21,7 +21,6 @@ const config: KnipConfig = { '@storybook/addon-onboarding', - '@voidzero-dev/vite-plus-core', ], rules: { files: 'warn', @@ -32,7 +31,6 @@ const config: KnipConfig = { unresolved: 'warn', exports: 'warn', nsExports: 'warn', - classMembers: 'warn', types: 'warn', nsTypes: 'warn', enumMembers: 'warn', diff --git a/web/package.json b/web/package.json index e499d39fef..fdff69acb6 100644 --- a/web/package.json +++ b/web/package.json @@ -58,8 +58,8 @@ "uglify-embed": "node ./bin/uglify-embed" }, "dependencies": { - "@amplitude/analytics-browser": "2.36.7", - "@amplitude/plugin-session-replay-browser": "1.26.4", + "@amplitude/analytics-browser": "2.37.0", + "@amplitude/plugin-session-replay-browser": "1.27.1", "@base-ui/react": "1.3.0", "@emoji-mart/data": "1.2.1", "@floating-ui/react": "0.27.19", @@ -67,28 +67,28 @@ "@headlessui/react": "2.2.9", "@heroicons/react": "2.2.0", "@hono/node-server": "1.19.11", - "@lexical/code": "0.41.0", - "@lexical/link": "0.41.0", - "@lexical/list": "0.41.0", - "@lexical/react": "0.41.0", - "@lexical/selection": "0.41.0", - "@lexical/text": "0.41.0", - "@lexical/utils": "0.41.0", + "@lexical/code": "0.42.0", + "@lexical/link": "0.42.0", + "@lexical/list": "0.42.0", + "@lexical/react": "0.42.0", + "@lexical/selection": "0.42.0", + "@lexical/text": "0.42.0", + "@lexical/utils": "0.42.0", "@monaco-editor/react": "4.7.0", "@octokit/core": "7.0.6", "@octokit/request-error": "7.1.0", - "@orpc/client": "1.13.8", - "@orpc/contract": "1.13.8", - "@orpc/openapi-client": "1.13.8", - "@orpc/tanstack-query": "1.13.8", + "@orpc/client": "1.13.9", + "@orpc/contract": "1.13.9", + "@orpc/openapi-client": "1.13.9", + "@orpc/tanstack-query": "1.13.9", "@remixicon/react": "4.9.0", - "@sentry/react": "10.44.0", + "@sentry/react": "10.45.0", "@streamdown/math": "1.0.2", "@svgdotjs/svg.js": "3.2.5", - "@t3-oss/env-nextjs": "0.13.10", + "@t3-oss/env-nextjs": "0.13.11", "@tailwindcss/typography": "0.5.19", "@tanstack/react-form": "1.28.5", - "@tanstack/react-query": "5.91.0", + "@tanstack/react-query": "5.95.0", "abcjs": "6.6.2", "ahooks": "3.9.6", "class-variance-authority": "0.7.1", @@ -111,7 +111,7 @@ "hono": "4.12.8", "html-entities": "2.6.0", "html-to-image": "1.11.13", - "i18next": "25.8.18", + "i18next": "25.10.4", "i18next-resources-to-backend": "1.2.1", "immer": "11.1.4", "jotai": "2.18.1", @@ -119,15 +119,15 @@ "js-cookie": "3.0.5", "js-yaml": "4.1.1", "jsonschema": "1.5.0", - "katex": "0.16.38", + "katex": "0.16.40", "ky": "1.14.3", "lamejs": "1.2.1", - "lexical": "0.41.0", + "lexical": "0.42.0", "mermaid": "11.13.0", "mime": "4.1.0", "mitt": "3.0.1", "negotiator": "1.0.0", - "next": "16.2.0", + "next": "16.2.1", "next-themes": "0.4.6", "nuqs": "2.8.9", "pinyin-pro": "3.28.0", @@ -138,7 +138,7 @@ "react-dom": "19.2.4", "react-easy-crop": "5.5.6", "react-hotkeys-hook": "5.2.4", - "react-i18next": "16.5.8", + "react-i18next": "16.6.1", "react-multi-email": "1.0.25", "react-papaparse": "4.4.0", "react-pdf-highlighter": "8.0.0-rc.0", @@ -157,7 +157,7 @@ "streamdown": "2.5.0", "string-ts": "2.3.1", "tailwind-merge": "2.6.1", - "tldts": "7.0.26", + "tldts": "7.0.27", "unist-util-visit": "5.1.0", "use-context-selector": "2.0.0", "uuid": "13.0.0", @@ -167,27 +167,27 @@ }, "devDependencies": { "@antfu/eslint-config": "7.7.3", - "@chromatic-com/storybook": "5.0.1", + "@chromatic-com/storybook": "5.0.2", "@egoist/tailwindcss-icons": "1.9.2", - "@eslint-react/eslint-plugin": "2.13.0", + "@eslint-react/eslint-plugin": "3.0.0", "@iconify-json/heroicons": "1.2.3", "@iconify-json/ri": "1.2.10", "@mdx-js/loader": "3.1.1", "@mdx-js/react": "3.1.1", "@mdx-js/rollup": "3.1.1", - "@next/eslint-plugin-next": "16.2.0", - "@next/mdx": "16.2.0", + "@next/eslint-plugin-next": "16.2.1", + "@next/mdx": "16.2.1", "@rgrove/parse-xml": "4.2.0", - "@storybook/addon-docs": "10.3.0", - "@storybook/addon-links": "10.3.0", - "@storybook/addon-onboarding": "10.3.0", - "@storybook/addon-themes": "10.3.0", - "@storybook/nextjs-vite": "10.3.0", - "@storybook/react": "10.3.0", - "@tanstack/eslint-plugin-query": "5.91.5", + "@storybook/addon-docs": "10.3.1", + "@storybook/addon-links": "10.3.1", + "@storybook/addon-onboarding": "10.3.1", + "@storybook/addon-themes": "10.3.1", + "@storybook/nextjs-vite": "10.3.1", + "@storybook/react": "10.3.1", + "@tanstack/eslint-plugin-query": "5.95.0", "@tanstack/react-devtools": "0.10.0", "@tanstack/react-form-devtools": "0.2.19", - "@tanstack/react-query-devtools": "5.91.3", + "@tanstack/react-query-devtools": "5.95.0", "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "16.3.2", @@ -208,14 +208,14 @@ "@types/react-window": "1.8.8", "@types/sortablejs": "1.15.9", "@typescript-eslint/parser": "8.57.1", - "@typescript/native-preview": "7.0.0-dev.20260318.1", + "@typescript/native-preview": "7.0.0-dev.20260322.1", "@vitejs/plugin-react": "6.0.1", "@vitejs/plugin-rsc": "0.5.21", "@vitest/coverage-v8": "4.1.0", "agentation": "2.3.3", "autoprefixer": "10.4.27", - "code-inspector-plugin": "1.4.4", - "eslint": "10.0.3", + "code-inspector-plugin": "1.4.5", + "eslint": "10.1.0", "eslint-markdown": "0.6.0", "eslint-plugin-better-tailwindcss": "4.3.2", "eslint-plugin-hyoban": "0.14.1", @@ -223,29 +223,29 @@ "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-react-refresh": "0.5.2", "eslint-plugin-sonarjs": "4.0.2", - "eslint-plugin-storybook": "10.3.0", + "eslint-plugin-storybook": "10.3.1", "husky": "9.1.7", "iconify-import-svg": "0.1.2", - "jsdom": "29.0.0", + "jsdom": "29.0.1", "jsdom-testing-mocks": "1.16.0", - "knip": "5.88.0", + "knip": "6.0.2", "lint-staged": "16.4.0", "nock": "14.0.11", "postcss": "8.5.8", "postcss-js": "5.1.0", "react-server-dom-webpack": "19.2.4", "sass": "1.98.0", - "storybook": "10.3.0", + "storybook": "10.3.1", "tailwindcss": "3.4.19", "taze": "19.10.0", "tsx": "4.21.0", "typescript": "5.9.3", "uglify-js": "3.19.3", - "vinext": "0.0.31", - "vite": "npm:@voidzero-dev/vite-plus-core@0.1.12", + "vinext": "0.0.34", + "vite": "npm:@voidzero-dev/vite-plus-core@0.1.13", "vite-plugin-inspect": "11.3.3", - "vite-plus": "0.1.12", - "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.12", + "vite-plus": "0.1.13", + "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.13", "vitest-canvas-mock": "1.1.3" }, "pnpm": { @@ -261,7 +261,7 @@ "array.prototype.tosorted": "npm:@nolyfill/array.prototype.tosorted@^1.0.44", "assert": "npm:@nolyfill/assert@^1.0.26", "brace-expansion@<2.0.2": "2.0.2", - "canvas": "^3.2.1", + "canvas": "^3.2.2", "devalue@<5.3.2": "5.3.2", "dompurify@>=3.1.3 <=3.3.1": "3.3.2", "es-iterator-helpers": "npm:@nolyfill/es-iterator-helpers@^1.0.21", @@ -297,8 +297,8 @@ "tar@<=7.5.10": "7.5.11", "typed-array-buffer": "npm:@nolyfill/typed-array-buffer@^1.0.44", "undici@>=7.0.0 <7.24.0": "7.24.0", - "vite": "npm:@voidzero-dev/vite-plus-core@0.1.12", - "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.12", + "vite": "npm:@voidzero-dev/vite-plus-core@0.1.13", + "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.13", "which-typed-array": "npm:@nolyfill/which-typed-array@^1.0.44", "yauzl@<3.2.1": "3.2.1" }, diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 3132e98a25..f72b889788 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -16,7 +16,7 @@ overrides: array.prototype.tosorted: npm:@nolyfill/array.prototype.tosorted@^1.0.44 assert: npm:@nolyfill/assert@^1.0.26 brace-expansion@<2.0.2: 2.0.2 - canvas: ^3.2.1 + canvas: ^3.2.2 devalue@<5.3.2: 5.3.2 dompurify@>=3.1.3 <=3.3.1: 3.3.2 es-iterator-helpers: npm:@nolyfill/es-iterator-helpers@^1.0.21 @@ -52,8 +52,8 @@ overrides: tar@<=7.5.10: 7.5.11 typed-array-buffer: npm:@nolyfill/typed-array-buffer@^1.0.44 undici@>=7.0.0 <7.24.0: 7.24.0 - vite: npm:@voidzero-dev/vite-plus-core@0.1.12 - vitest: npm:@voidzero-dev/vite-plus-test@0.1.12 + vite: npm:@voidzero-dev/vite-plus-core@0.1.13 + vitest: npm:@voidzero-dev/vite-plus-test@0.1.13 which-typed-array: npm:@nolyfill/which-typed-array@^1.0.44 yauzl@<3.2.1: 3.2.1 @@ -62,11 +62,11 @@ importers: .: dependencies: '@amplitude/analytics-browser': - specifier: 2.36.7 - version: 2.36.7 + specifier: 2.37.0 + version: 2.37.0 '@amplitude/plugin-session-replay-browser': - specifier: 1.26.4 - version: 1.26.4(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0) + specifier: 1.27.1 + version: 1.27.1(@amplitude/rrweb@2.0.0-alpha.36)(rollup@4.59.0) '@base-ui/react': specifier: 1.3.0 version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -90,25 +90,25 @@ importers: version: 1.19.11(hono@4.12.8) '@lexical/code': specifier: npm:lexical-code-no-prism@0.41.0 - version: lexical-code-no-prism@0.41.0(@lexical/utils@0.41.0)(lexical@0.41.0) + version: lexical-code-no-prism@0.41.0(@lexical/utils@0.42.0)(lexical@0.42.0) '@lexical/link': - specifier: 0.41.0 - version: 0.41.0 + specifier: 0.42.0 + version: 0.42.0 '@lexical/list': - specifier: 0.41.0 - version: 0.41.0 + specifier: 0.42.0 + version: 0.42.0 '@lexical/react': - specifier: 0.41.0 - version: 0.41.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(yjs@13.6.30) + specifier: 0.42.0 + version: 0.42.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(yjs@13.6.30) '@lexical/selection': - specifier: 0.41.0 - version: 0.41.0 + specifier: 0.42.0 + version: 0.42.0 '@lexical/text': - specifier: 0.41.0 - version: 0.41.0 + specifier: 0.42.0 + version: 0.42.0 '@lexical/utils': - specifier: 0.41.0 - version: 0.41.0 + specifier: 0.42.0 + version: 0.42.0 '@monaco-editor/react': specifier: 4.7.0 version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -119,23 +119,23 @@ importers: specifier: 7.1.0 version: 7.1.0 '@orpc/client': - specifier: 1.13.8 - version: 1.13.8 + specifier: 1.13.9 + version: 1.13.9 '@orpc/contract': - specifier: 1.13.8 - version: 1.13.8 + specifier: 1.13.9 + version: 1.13.9 '@orpc/openapi-client': - specifier: 1.13.8 - version: 1.13.8 + specifier: 1.13.9 + version: 1.13.9 '@orpc/tanstack-query': - specifier: 1.13.8 - version: 1.13.8(@orpc/client@1.13.8)(@tanstack/query-core@5.91.0) + specifier: 1.13.9 + version: 1.13.9(@orpc/client@1.13.9)(@tanstack/query-core@5.95.0) '@remixicon/react': specifier: 4.9.0 version: 4.9.0(react@19.2.4) '@sentry/react': - specifier: 10.44.0 - version: 10.44.0(react@19.2.4) + specifier: 10.45.0 + version: 10.45.0(react@19.2.4) '@streamdown/math': specifier: 1.0.2 version: 1.0.2(react@19.2.4) @@ -143,8 +143,8 @@ importers: specifier: 3.2.5 version: 3.2.5 '@t3-oss/env-nextjs': - specifier: 0.13.10 - version: 0.13.10(typescript@5.9.3)(valibot@1.3.0(typescript@5.9.3))(zod@4.3.6) + specifier: 0.13.11 + version: 0.13.11(typescript@5.9.3)(valibot@1.3.0(typescript@5.9.3))(zod@4.3.6) '@tailwindcss/typography': specifier: 0.5.19 version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) @@ -152,8 +152,8 @@ importers: specifier: 1.28.5 version: 1.28.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-query': - specifier: 5.91.0 - version: 5.91.0(react@19.2.4) + specifier: 5.95.0 + version: 5.95.0(react@19.2.4) abcjs: specifier: 6.6.2 version: 6.6.2 @@ -221,8 +221,8 @@ importers: specifier: 1.11.13 version: 1.11.13 i18next: - specifier: 25.8.18 - version: 25.8.18(typescript@5.9.3) + specifier: 25.10.4 + version: 25.10.4(typescript@5.9.3) i18next-resources-to-backend: specifier: 1.2.1 version: 1.2.1 @@ -245,8 +245,8 @@ importers: specifier: 1.5.0 version: 1.5.0 katex: - specifier: 0.16.38 - version: 0.16.38 + specifier: 0.16.40 + version: 0.16.40 ky: specifier: 1.14.3 version: 1.14.3 @@ -254,8 +254,8 @@ importers: specifier: 1.2.1 version: 1.2.1 lexical: - specifier: 0.41.0 - version: 0.41.0 + specifier: 0.42.0 + version: 0.42.0 mermaid: specifier: 11.13.0 version: 11.13.0 @@ -269,14 +269,14 @@ importers: specifier: 1.0.0 version: 1.0.0 next: - specifier: 16.2.0 - version: 16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) + specifier: 16.2.1 + version: 16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) next-themes: specifier: 0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) nuqs: specifier: 2.8.9 - version: 2.8.9(next@16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react@19.2.4) + version: 2.8.9(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react@19.2.4) pinyin-pro: specifier: 3.28.0 version: 3.28.0 @@ -302,8 +302,8 @@ importers: specifier: 5.2.4 version: 5.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react-i18next: - specifier: 16.5.8 - version: 16.5.8(i18next@25.8.18(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + specifier: 16.6.1 + version: 16.6.1(i18next@25.10.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react-multi-email: specifier: 1.0.25 version: 1.0.25(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -359,8 +359,8 @@ importers: specifier: 2.6.1 version: 2.6.1 tldts: - specifier: 7.0.26 - version: 7.0.26 + specifier: 7.0.27 + version: 7.0.27 unist-util-visit: specifier: 5.1.0 version: 5.1.0 @@ -382,16 +382,16 @@ importers: devDependencies: '@antfu/eslint-config': specifier: 7.7.3 - version: 7.7.3(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.2.0)(@typescript-eslint/rule-tester@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.17.0))(typescript@5.9.3) + version: 7.7.3(@eslint-react/eslint-plugin@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.2.1)(@typescript-eslint/rule-tester@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.1.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.1.0(jiti@1.21.7)))(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))(typescript@5.9.3) '@chromatic-com/storybook': - specifier: 5.0.1 - version: 5.0.1(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + specifier: 5.0.2 + version: 5.0.2(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@egoist/tailwindcss-icons': specifier: 1.9.2 version: 1.9.2(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) '@eslint-react/eslint-plugin': - specifier: 2.13.0 - version: 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + specifier: 3.0.0 + version: 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@iconify-json/heroicons': specifier: 1.2.3 version: 1.2.3 @@ -408,35 +408,35 @@ importers: specifier: 3.1.1 version: 3.1.1(rollup@4.59.0) '@next/eslint-plugin-next': - specifier: 16.2.0 - version: 16.2.0 + specifier: 16.2.1 + version: 16.2.1 '@next/mdx': - specifier: 16.2.0 - version: 16.2.0(@mdx-js/loader@3.1.1(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4)) + specifier: 16.2.1 + version: 16.2.1(@mdx-js/loader@3.1.1(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4)) '@rgrove/parse-xml': specifier: 4.2.0 version: 4.2.0 '@storybook/addon-docs': - specifier: 10.3.0 - version: 10.3.0(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + specifier: 10.3.1 + version: 10.3.1(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/addon-links': - specifier: 10.3.0 - version: 10.3.0(react@19.2.4)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + specifier: 10.3.1 + version: 10.3.1(react@19.2.4)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@storybook/addon-onboarding': - specifier: 10.3.0 - version: 10.3.0(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + specifier: 10.3.1 + version: 10.3.1(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@storybook/addon-themes': - specifier: 10.3.0 - version: 10.3.0(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + specifier: 10.3.1 + version: 10.3.1(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@storybook/nextjs-vite': - specifier: 10.3.0 - version: 10.3.0(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(next@16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + specifier: 10.3.1 + version: 10.3.1(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/react': - specifier: 10.3.0 - version: 10.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + specifier: 10.3.1 + version: 10.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) '@tanstack/eslint-plugin-query': - specifier: 5.91.5 - version: 5.91.5(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + specifier: 5.95.0 + version: 5.95.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@tanstack/react-devtools': specifier: 0.10.0 version: 0.10.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11) @@ -444,8 +444,8 @@ importers: specifier: 0.2.19 version: 0.2.19(@types/react@19.2.14)(csstype@3.2.3)(react@19.2.4)(solid-js@1.9.11) '@tanstack/react-query-devtools': - specifier: 5.91.3 - version: 5.91.3(@tanstack/react-query@5.91.0(react@19.2.4))(react@19.2.4) + specifier: 5.95.0 + version: 5.95.0(@tanstack/react-query@5.95.0(react@19.2.4))(react@19.2.4) '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -505,19 +505,19 @@ importers: version: 1.15.9 '@typescript-eslint/parser': specifier: 8.57.1 - version: 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + version: 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript/native-preview': - specifier: 7.0.0-dev.20260318.1 - version: 7.0.0-dev.20260318.1 + specifier: 7.0.0-dev.20260322.1 + version: 7.0.0-dev.20260322.1 '@vitejs/plugin-react': specifier: 6.0.1 - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) '@vitejs/plugin-rsc': specifier: 0.5.21 - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4) '@vitest/coverage-v8': specifier: 4.1.0 - version: 4.1.0(@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 4.1.0(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) agentation: specifier: 2.3.3 version: 2.3.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -525,35 +525,35 @@ importers: specifier: 10.4.27 version: 10.4.27(postcss@8.5.8) code-inspector-plugin: - specifier: 1.4.4 - version: 1.4.4 + specifier: 1.4.5 + version: 1.4.5 eslint: - specifier: 10.0.3 - version: 10.0.3(jiti@1.21.7) + specifier: 10.1.0 + version: 10.1.0(jiti@1.21.7) eslint-markdown: specifier: 0.6.0 - version: 0.6.0(eslint@10.0.3(jiti@1.21.7)) + version: 0.6.0(eslint@10.1.0(jiti@1.21.7)) eslint-plugin-better-tailwindcss: specifier: 4.3.2 - version: 4.3.2(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.17.0))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3) + version: 4.3.2(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3) eslint-plugin-hyoban: specifier: 0.14.1 - version: 0.14.1(eslint@10.0.3(jiti@1.21.7)) + version: 0.14.1(eslint@10.1.0(jiti@1.21.7)) eslint-plugin-markdown-preferences: specifier: 0.40.3 - version: 0.40.3(@eslint/markdown@7.5.1)(eslint@10.0.3(jiti@1.21.7)) + version: 0.40.3(@eslint/markdown@7.5.1)(eslint@10.1.0(jiti@1.21.7)) eslint-plugin-react-hooks: specifier: 7.0.1 - version: 7.0.1(eslint@10.0.3(jiti@1.21.7)) + version: 7.0.1(eslint@10.1.0(jiti@1.21.7)) eslint-plugin-react-refresh: specifier: 0.5.2 - version: 0.5.2(eslint@10.0.3(jiti@1.21.7)) + version: 0.5.2(eslint@10.1.0(jiti@1.21.7)) eslint-plugin-sonarjs: specifier: 4.0.2 - version: 4.0.2(eslint@10.0.3(jiti@1.21.7)) + version: 4.0.2(eslint@10.1.0(jiti@1.21.7)) eslint-plugin-storybook: - specifier: 10.3.0 - version: 10.3.0(eslint@10.0.3(jiti@1.21.7))(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + specifier: 10.3.1 + version: 10.3.1(eslint@10.1.0(jiti@1.21.7))(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) husky: specifier: 9.1.7 version: 9.1.7 @@ -561,14 +561,14 @@ importers: specifier: 0.1.2 version: 0.1.2 jsdom: - specifier: 29.0.0 - version: 29.0.0(canvas@3.2.1) + specifier: 29.0.1 + version: 29.0.1(canvas@3.2.2) jsdom-testing-mocks: specifier: 1.16.0 version: 1.16.0 knip: - specifier: 5.88.0 - version: 5.88.0(@types/node@25.5.0)(typescript@5.9.3) + specifier: 6.0.2 + version: 6.0.2 lint-staged: specifier: 16.4.0 version: 16.4.0 @@ -588,8 +588,8 @@ importers: specifier: 1.98.0 version: 1.98.0 storybook: - specifier: 10.3.0 - version: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 10.3.1 + version: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tailwindcss: specifier: 3.4.19 version: 3.4.19(tsx@4.21.0)(yaml@2.8.2) @@ -606,23 +606,23 @@ importers: specifier: 3.19.3 version: 3.19.3 vinext: - specifier: 0.0.31 - version: 0.0.31(d43efe4756ad5ea698dcdb002ea787ea) + specifier: 0.0.34 + version: 0.0.34(1a91bf00ec5f7fb5f0ffb625316f9d01) vite: - specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + specifier: npm:@voidzero-dev/vite-plus-core@0.1.13 + version: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vite-plugin-inspect: specifier: 11.3.3 - version: 11.3.3(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 11.3.3(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) vite-plus: - specifier: 0.1.12 - version: 0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + specifier: 0.1.13 + version: 0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) vitest: - specifier: npm:@voidzero-dev/vite-plus-test@0.1.12 - version: '@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + specifier: npm:@voidzero-dev/vite-plus-test@0.1.13 + version: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vitest-canvas-mock: specifier: 1.1.3 - version: 1.1.3(@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 1.1.3(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) packages: @@ -633,17 +633,17 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@amplitude/analytics-browser@2.36.7': - resolution: {integrity: sha512-aqEakThBQI+nEV/ytMqyUhHvSisjqKv9g2hpMA8sQBy3MYWzATWCr63gVyml7U56QwoDvCIomt80HENBSsYVqg==} + '@amplitude/analytics-browser@2.37.0': + resolution: {integrity: sha512-/BWDneHRfq6+9bcPQC09Ep79SEj7aRJLZ1jJrPHtxA9KZJUz2au2COlJc1ReCaNzCcrA1xXv/MQ0Fv7TwoBglg==} - '@amplitude/analytics-client-common@2.4.37': - resolution: {integrity: sha512-mTJY7LXPdOPjUe3wTgSq9J/RX9+gsNpcKWQ3VUDSOgCSgSe5NW/2WopmHBbt8FLZN29OnrApz+WGyEwvMQt/NQ==} + '@amplitude/analytics-client-common@2.4.39': + resolution: {integrity: sha512-DFzi2/D2eu7EBCyslk86lToQa+qo1AmKgvZQVDDqkLG37/meTRcBAZiL0FAdTX21AYwpC/Ym4FWowD04foiBIQ==} '@amplitude/analytics-connector@1.6.4': resolution: {integrity: sha512-SpIv0IQMNIq6SH3UqFGiaZyGSc7PBZwRdq7lvP0pBxW8i4Ny+8zwI0pV+VMfMHQwWY3wdIbWw5WQphNjpdq1/Q==} - '@amplitude/analytics-core@2.41.7': - resolution: {integrity: sha512-6vb7kX/k64A9GzHtxLvm/PJf1kDgYRFxKOqSbKXi0z2N2OVfrrbPD6uFve8lLdT0iVuSGo3HVGG7V1bQ8rIfiQ==} + '@amplitude/analytics-core@2.43.0': + resolution: {integrity: sha512-rcDqi4cmI9Ro7hN5wjAuTm92IdN2i0lhIDAj+JOd9BP3SRMrhhiw2lzcScj3owig8CiV9X7EHPTuZe6XCTfIgQ==} '@amplitude/analytics-types@2.11.1': resolution: {integrity: sha512-wFEgb0t99ly2uJKm5oZ28Lti0Kh5RecR5XBkwfUpDzn84IoCIZ8GJTsMw/nThu8FZFc7xFDA4UAt76zhZKrs9A==} @@ -651,52 +651,55 @@ packages: '@amplitude/experiment-core@0.7.2': resolution: {integrity: sha512-Wc2NWvgQ+bLJLeF0A9wBSPIaw0XuqqgkPKsoNFQrmS7r5Djd56um75In05tqmVntPJZRvGKU46pAp8o5tdf4mA==} - '@amplitude/plugin-autocapture-browser@1.23.7': - resolution: {integrity: sha512-dTUpJEUNbHy9pXpBm/UmNk4wWBcCd14MwpZYFLsJNZzaDT5Iyo9MhE946TEoQG9LwE/wAQHiVUk3n0bIGaCzEQ==} + '@amplitude/plugin-autocapture-browser@1.24.1': + resolution: {integrity: sha512-cvjOFew2MFNBDTbk3+H7WNi3D0Jdp476m6faCaVhY99M5zqRCHDMRS7dC4HczvL9zYXlAcW9jAWucwES2m3TiQ==} - '@amplitude/plugin-network-capture-browser@1.9.7': - resolution: {integrity: sha512-HLHVlb2G9p7HABJvmJRigCO/h06oD6F9AqB47v0it671dstuytOtdSYP5ZBfFCEEshyd4gZzT4Qk5dd3foxtqw==} + '@amplitude/plugin-custom-enrichment-browser@0.1.0': + resolution: {integrity: sha512-y3VmqZvCP1Z3jNgo/mtKVHON9L0P2SyqkMmUsbbFuLu1+TKIkicotnVq/lzlLU1TrW68mkInOM+We8JngasZBA==} - '@amplitude/plugin-page-url-enrichment-browser@0.6.11': - resolution: {integrity: sha512-aRQb2GkW4g4X+Yyb4R5DXaBTUGq0NKIBOEQHL0ywsWMoNY/k3S1SHN0iqIjDLlIlOC23ZyhrBMPnriGiUPGZpA==} + '@amplitude/plugin-network-capture-browser@1.9.9': + resolution: {integrity: sha512-SJIOQN04Mk9vCsnVd9QRcIvkMV7XSGZIKfbaKNQY5O3ueV33Kc8opm7YjPg2sWcxdzTcJijbCkOI0wCwOaRolg==} - '@amplitude/plugin-page-view-tracking-browser@2.8.7': - resolution: {integrity: sha512-imsBOuSdeYu+CMy/RJl3uVL3NzJGf8IORecaCkZoCleeQWt7il8cAmtL5xO0EVPZaOWifZ/juVL+DUdBxpnJrw==} + '@amplitude/plugin-page-url-enrichment-browser@0.7.0': + resolution: {integrity: sha512-MkM7TDq24k7ilUDNZISqjDSkVfmDJxWcnUagwYEXjLILhno5hGm7wdgFvVXXzKlZQHEogBxkbnq7wZXS9/YsMw==} - '@amplitude/plugin-session-replay-browser@1.26.4': - resolution: {integrity: sha512-eJ783UPWvZtf2ThWs0pONZaHY/KtMPjMWIF48YQjkI2Z8e30qJ1kdE0++bNXJ0jchrOUs2UCl2wsWiPlaR0tAQ==} + '@amplitude/plugin-page-view-tracking-browser@2.9.1': + resolution: {integrity: sha512-jkxz2lkJDAfsjj7mpbPUZx9N3qJssC3uYyv8Nk73z+p+v0wjBikWdOoKuNQkcuP09701zRdXp9ziU8+qwkGusw==} - '@amplitude/plugin-web-vitals-browser@1.1.22': - resolution: {integrity: sha512-DjjkWvxUYfR/axvxCcXJQOUXpSfd3nF6kg+a63nJo2pf6EGkSHhWkXz9p7O0/IqWi5P+i6pDWO2m8031+OnG+A==} + '@amplitude/plugin-session-replay-browser@1.27.1': + resolution: {integrity: sha512-IEkAU7O3LbL23piMD7Lu0ej9wT/LQdQsyY1okTW5y2Nov8ZCmqLhZPLk6s9vKCUxGukDi7IL6gqXpURTLYj5rQ==} - '@amplitude/rrdom@2.0.0-alpha.35': - resolution: {integrity: sha512-W9ImCKtgFB8oBKd7td0TH7JKkQ/3iwu5bfLXcOvzxLj7+RSD1k1gfDyncooyobwBV8j4FMiTyj2N53tJ6rFgaw==} + '@amplitude/plugin-web-vitals-browser@1.1.24': + resolution: {integrity: sha512-7AaytUK78RKdyDsblYJCKYan1lQi3Qzsp1WHItHJ+RSXPccmi4mCcvNtx0e8T9LmNJlUnsmYeEGR/6FaWvyvFg==} - '@amplitude/rrweb-packer@2.0.0-alpha.35': - resolution: {integrity: sha512-A6BlcBuiAI8pHJ51mcQWu2Uddnddxj9MaYZMNjIzFm1FK+qYAyYafO1xcoVPXoMUHE/qqITUgAn9tUVWj8N8NQ==} + '@amplitude/rrdom@2.0.0-alpha.36': + resolution: {integrity: sha512-8jNhYEzjp6aaZON7qY/IpZIVbl8SUojb8kxD58StknlvnjKeGV7nHheXbkIz+T1LSVbWsdh+noIWuqhyFWzvgg==} - '@amplitude/rrweb-plugin-console-record@2.0.0-alpha.35': - resolution: {integrity: sha512-8hstBoMHMSEA3FGoQ0LKidhpQypKchyT2sjEDdwTC77xZSg+3LwtjElOSMVdgjrEfxvN4V1g72v+Pwy7LBGUDA==} + '@amplitude/rrweb-packer@2.0.0-alpha.36': + resolution: {integrity: sha512-kqKg6OGoxHZvG4jwyO4kIjLdf8MkL6JcY5iLB09PQNP7O36ysnrH+ecJfa4V1Rld99kX25Pefkw4bzKmmFAqcg==} + + '@amplitude/rrweb-plugin-console-record@2.0.0-alpha.36': + resolution: {integrity: sha512-7VbXu36PpJA8dSOFxpfpMaoDTuPK5uy1C8mN+Wfdm0X4ROdmrvcTdlQj+jGzhLGeK+xbTixHEy23itCNUau7hQ==} peerDependencies: - '@amplitude/rrweb': ^2.0.0-alpha.35 + '@amplitude/rrweb': ^2.0.0-alpha.36 - '@amplitude/rrweb-record@2.0.0-alpha.35': - resolution: {integrity: sha512-C8lr6LLMXLDINWE3SaebDrc4sj1pSFKm9s+zlW5e8CkAuAv8XfA5Wjx5cevxG3LMkIwXdugvrrjYKmEVCODI1g==} + '@amplitude/rrweb-record@2.0.0-alpha.36': + resolution: {integrity: sha512-zSHvmG5NUG4jNgWNVM7Oj3+rJPagv+TiHlnSiJ1X0WWLIg1GbUnOoTqpincZS5QupqTxQchNQaUg9MNu0MM3sQ==} - '@amplitude/rrweb-snapshot@2.0.0-alpha.35': - resolution: {integrity: sha512-n55AdmlRNZ7XuOlCRmSjH2kyyHS1oe5haUS+buxqjfQcamUtam+dSnP+6N1E8dLxIDjynJnbrCOC+8xvenpl1A==} + '@amplitude/rrweb-snapshot@2.0.0-alpha.36': + resolution: {integrity: sha512-vUvTXkNcu+cN736tykQDUVWERetFz1hyzgS0Yib5qSeWJwbse/4BaiWaZ7c5WevbbtcjLbDJqYKySJM92H5SxQ==} - '@amplitude/rrweb-types@2.0.0-alpha.35': - resolution: {integrity: sha512-cR/xlN5fu7Cw6Zh9O6iEgNleqT92wJ3HO2mV19yQE6SRqLGKXXeDeTrUBd5FKCZnXvRsv3JtK+VR4u9vmZze3g==} + '@amplitude/rrweb-types@2.0.0-alpha.36': + resolution: {integrity: sha512-Bd2r3Bs0XIJt5fgPRWVl8bhvA9FCjJn8vQlDTO8ffPxilGPIzUXLQ06+xoLYkK9v+PDKJnCapOTL4A2LilDmgA==} - '@amplitude/rrweb-utils@2.0.0-alpha.35': - resolution: {integrity: sha512-/OpyKKHYGwoy2fvWDg5jiH1LzWag4wlFTQjd2DUgndxlXccQF1+yxYljCDdM+J1GBeZ7DaLZa9qe2JUUtoNOOw==} + '@amplitude/rrweb-utils@2.0.0-alpha.36': + resolution: {integrity: sha512-w5RGROLU1Kyrq9j+trxcvvfkTp05MEKJ70Ig+YvHyZsE0nElh1PCF8PHAjV0/kji68+KqB03c0hoyaV99CDaDw==} - '@amplitude/rrweb@2.0.0-alpha.35': - resolution: {integrity: sha512-qFaZDNMkjolZUVv1OxrWngGl38FH0iF0jtybd/vhuOzvwohJjyKL9Tgoulj8osj21/4BUpGEhWweGeJygjoJJw==} + '@amplitude/rrweb@2.0.0-alpha.36': + resolution: {integrity: sha512-8vhPOk4fvszfxYZTk37EObW3n7uwEgO//funRSMt/QiBWtgQ8jhpFV9FcOAYdgde0Yw1uIM8oUbWZfy/XrexNw==} - '@amplitude/session-replay-browser@1.33.1': - resolution: {integrity: sha512-5Mjd5rWq9VxVvDewH+l7m22fJhnBsTHYGXI0GxZTMZZGKP7PJm77O9oDaar+2cCCr8ckk7RnIfelgWRS1lmbDA==} + '@amplitude/session-replay-browser@1.34.1': + resolution: {integrity: sha512-oQ9Pi/vcEhcRxmMIDMOZopt9vSaGYB4X64kp8idKut2Or8/DBhdztSjujwvkYvU48jNfqmT7oxIY5sCLYdiM6w==} '@amplitude/targeting@0.2.0': resolution: {integrity: sha512-/50ywTrC4hfcfJVBbh5DFbqMPPfaIOivZeb5Gb+OGM03QrA+lsUqdvtnKLNuWtceD4H6QQ2KFzPJ5aAJLyzVDA==} @@ -905,11 +908,11 @@ packages: '@chevrotain/utils@11.1.2': resolution: {integrity: sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==} - '@chromatic-com/storybook@5.0.1': - resolution: {integrity: sha512-v80QBwVd8W6acH5NtDgFlUevIBaMZAh1pYpBiB40tuNzS242NTHeQHBDGYwIAbWKDnt1qfjJpcpL6pj5kAr4LA==} + '@chromatic-com/storybook@5.0.2': + resolution: {integrity: sha512-uLd5gyvcz8q83GI0rYWjml45ryO3ZJwZLretLEZvWFJ3UlFk5C5Km9cwRcKZgZp0F3zYwbb8nEe6PJdgA1eKxg==} engines: {node: '>=20.0.0', yarn: '>=1.22.18'} peerDependencies: - storybook: ^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 + storybook: ^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 '@clack/core@0.3.5': resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==} @@ -923,23 +926,23 @@ packages: '@clack/prompts@1.1.0': resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==} - '@code-inspector/core@1.4.4': - resolution: {integrity: sha512-bQNcbiiTodOiVuJ9JQ/AgyArfc5rH9qexzDya3ugasIbUMfUNBPKCwoq6He4Y6/bwUx6mUqwTODwPtu13BR75Q==} + '@code-inspector/core@1.4.5': + resolution: {integrity: sha512-wskkSRX13TAqJG65d5sq0bRZ4kYktas/iE70xqXMOeqW/A6n2Zqhw5QRHANmEmlBvB9bP/bse+9iBkNN3Q2Skw==} - '@code-inspector/esbuild@1.4.4': - resolution: {integrity: sha512-quGKHsPiFRIPMGOhtHhSQhqDAdvC5aGvKKk4EAhvNvZG1TGxt0nXu99+O0shHdl6TQhlq1NgmPyTWqGyVM5s6g==} + '@code-inspector/esbuild@1.4.5': + resolution: {integrity: sha512-KBwq7waqZ3L1CW7N9ff7aS0HxzamrslR08i5ovkLQe1p6tH9Axe9zzCrBnvgmB0UZsT2r/5wKLOWyEpq5+VYKw==} - '@code-inspector/mako@1.4.4': - resolution: {integrity: sha512-SSs9oo3THS7vAFceAcICvVbbmaU9z6omwiXbCjIGhCxMvm7T6s/au4VHuOyU8Z3+floz+lDg/6W72VdBxWwVSg==} + '@code-inspector/mako@1.4.5': + resolution: {integrity: sha512-yrHgE5+b4ZL29Xt+y0H/9xrXSbRskq7dFhmE9GYFWCcgdWNCMD25hZd7xZVije94++H65Vw6Bu/abfqEx0peog==} - '@code-inspector/turbopack@1.4.4': - resolution: {integrity: sha512-ZK/sHPB4A+qcHXg+sR+0qCSFA2CYTfuPXaHC9GdnwwNdz6lhO3bkG7Ju0csKVxEp3LR8UVfMsKsRYbGSs8Ly8w==} + '@code-inspector/turbopack@1.4.5': + resolution: {integrity: sha512-IG39ikmQthdx/oAxhpV7zsIQZ3Jpycl88JzH+UXHq0ZpfHwa1KdNc/9erP3kFMY4+ANmkmerqBk57knmRTGMRQ==} - '@code-inspector/vite@1.4.4': - resolution: {integrity: sha512-UWnkaRTHwUDezKp1vXUrjr8Q93s91iYHbsyhfjOJGIiqBvmcaa3nqBlEAt7rzEi5hdaQVVeFdh+9q+4cVpK26A==} + '@code-inspector/vite@1.4.5': + resolution: {integrity: sha512-vBtH91afwYL7JV4zWcJJTFd65LJ4SZz5E9AwGgCF30/L1mdDx7U29D+M+JpaxSgsMB6monKSZh+ubbqYe0ixpQ==} - '@code-inspector/webpack@1.4.4': - resolution: {integrity: sha512-icYvkENomjUhlBXhYwkDFMtk62BPEWJCNsfYyHnQlGNJWW8SKuLU3AAbJQJMvA6Nmp++r9D/8xj1OJ2K1Y+/Dg==} + '@code-inspector/webpack@1.4.5': + resolution: {integrity: sha512-lwUv+X1FNSUWz+FKcUsE2dT2pg6VFRRXKt16hg/m+Lwtdet2adfi6BFLZmNz3OPIEGbRB5Kjx6bfaghZhbDCCg==} '@csstools/color-helpers@6.0.2': resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} @@ -1185,44 +1188,40 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint-react/ast@2.13.0': - resolution: {integrity: sha512-43+5gmqV3MpatTzKnu/V2i/jXjmepvwhrb9MaGQvnXHQgq9J7/C7VVCCcwp6Rvp2QHAFquAAdvQDSL8IueTpeA==} - engines: {node: '>=20.19.0'} + '@eslint-react/ast@3.0.0': + resolution: {integrity: sha512-qBasEJqMhcof/pbxhKSgp52rW9TMUMVIYqv3SOgSzvDG3bed+saWFXOQ+YFMj/o5gr/e6Dsi3mAHqErPzJHelA==} + engines: {node: '>=22.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' - '@eslint-react/core@2.13.0': - resolution: {integrity: sha512-m62XDzkf1hpzW4sBc7uh7CT+8rBG2xz/itSADuEntlsg4YA7Jhb8hjU6VHf3wRFDwyfx5VnbV209sbJ7Azey0Q==} - engines: {node: '>=20.19.0'} + '@eslint-react/core@3.0.0': + resolution: {integrity: sha512-PKa13GrqUAilcvcONJMN8BukuVg3dHuaTxjNBdKOHGxkMexCxDF9hjNHBILErJhFs1kGaJPBK9QUYQci8PV/TA==} + engines: {node: '>=22.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' - '@eslint-react/eff@2.13.0': - resolution: {integrity: sha512-rEH2R8FQnUAblUW+v3ZHDU1wEhatbL1+U2B1WVuBXwSKqzF7BGaLqCPIU7o9vofumz5MerVfaCtJgI8jYe2Btg==} - engines: {node: '>=20.19.0'} - - '@eslint-react/eslint-plugin@2.13.0': - resolution: {integrity: sha512-iaMXpqnJCTW7317hg8L4wx7u5aIiPzZ+d1p59X8wXFgMHzFX4hNu4IfV8oygyjmWKdLsjKE9sEpv/UYWczlb+A==} - engines: {node: '>=20.19.0'} + '@eslint-react/eslint-plugin@3.0.0': + resolution: {integrity: sha512-OK8rBrsM/bUr0L918hQ1tWAufz22+m0L6gpSrW3Z/7NSg/imy17IiZHO8UVT99sgcx9euKYAT+QIx45sZUYf1g==} + engines: {node: '>=22.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' - '@eslint-react/shared@2.13.0': - resolution: {integrity: sha512-IOloCqrZ7gGBT4lFf9+0/wn7TfzU7JBRjYwTSyb9SDngsbeRrtW95ZpgUpS8/jen1wUEm6F08duAooTZ2FtsWA==} - engines: {node: '>=20.19.0'} + '@eslint-react/shared@3.0.0': + resolution: {integrity: sha512-oHELwh3FghrMc5UX+4qVEdY7ZLZsO4bgKDVv5i6yk8+/997xe6LAY2wailbeljbIJxppcJSl6eXcRl2yv6ffig==} + engines: {node: '>=22.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' - '@eslint-react/var@2.13.0': - resolution: {integrity: sha512-dM+QaeiHR16qPQoJYg205MkdHYSWVa2B7ore5OFpOPlSwqDV3tLW7I+475WjbK7potq5QNPTxRa7VLp9FGeQqA==} - engines: {node: '>=20.19.0'} + '@eslint-react/var@3.0.0': + resolution: {integrity: sha512-Af/7GEZfXtc9jV1i/Uqfko40Gr256YXDZR9CG6mxROOUOMRYIaBPf3K7oLCnwiKVZXiFJ5qYGLEs6HoG8Ifrjw==} + engines: {node: '>=22.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' '@eslint/compat@2.0.3': resolution: {integrity: sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==} @@ -1583,74 +1582,77 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@lexical/clipboard@0.41.0': - resolution: {integrity: sha512-Ex5lPkb4NBBX1DCPzOAIeHBJFH1bJcmATjREaqpnTfxCbuOeQkt44wchezUA0oDl+iAxNZ3+pLLWiUju9icoSA==} + '@lexical/clipboard@0.42.0': + resolution: {integrity: sha512-D3K2ID0zew/+CKpwxnUTTh/N46yU4IK8bFWV9Htz+g1vFhgUF9UnDOQCmqpJbdP7z+9U1F8rk3fzf9OmP2Fm2w==} - '@lexical/devtools-core@0.41.0': - resolution: {integrity: sha512-FzJtluBhBc8bKS11TUZe72KoZN/hnzIyiiM0SPJAsPwGpoXuM01jqpXQGybWf/1bWB+bmmhOae7O4Nywi/Csuw==} + '@lexical/code-core@0.42.0': + resolution: {integrity: sha512-vrZTUPWDJkHjAAvuV2+Qte4vYE80s7hIO7wxipiJmWojGx6lcmQjO+UqJ8AIrqI4Wjy8kXrK74kisApWmwxuCw==} + + '@lexical/devtools-core@0.42.0': + resolution: {integrity: sha512-8nP8eE9i8JImgSrvInkWFfMCmXVKp3w3VaOvbJysdlK/Zal6xd8EWJEi6elj0mUW5T/oycfipPs2Sfl7Z+n14A==} peerDependencies: react: '>=17.x' react-dom: '>=17.x' - '@lexical/dragon@0.41.0': - resolution: {integrity: sha512-gBEqkk8Q6ZPruvDaRcOdF1EK9suCVBODzOCcR+EnoJTaTjfDkCM7pkPAm4w90Wa1wCZEtFHvCfas+jU9MDSumg==} + '@lexical/dragon@0.42.0': + resolution: {integrity: sha512-/TQzP+7PLJMqq9+MlgQWiJsxS9GOOa8Gp0svCD8vNIOciYmXfd28TR1Go+ZnBWwr7k/2W++3XUYVQU2KUcQsDQ==} - '@lexical/extension@0.41.0': - resolution: {integrity: sha512-sF4SPiP72yXvIGchmmIZ7Yg2XZTxNLOpFEIIzdqG7X/1fa1Ham9P/T7VbrblWpF6Ei5LJtK9JgNVB0hb4l3o1g==} + '@lexical/extension@0.42.0': + resolution: {integrity: sha512-rkZq/h8d1BenKRqU4t/zQUVfY/RinMX1Tz7t+Ee3ss0sk+kzP4W+URXNAxpn7r39Vn6wrFBqmCziah3dLAIqPw==} - '@lexical/hashtag@0.41.0': - resolution: {integrity: sha512-tFWM74RW4KU0E/sj2aowfWl26vmLUTp331CgVESnhQKcZBfT40KJYd57HEqBDTfQKn4MUhylQCCA0hbpw6EeFQ==} + '@lexical/hashtag@0.42.0': + resolution: {integrity: sha512-WOg5nFOfhabNBXzEIutdWDj+TUHtJEezj6w8jyYDGqZ31gu0cgrXSeV8UIynz/1oj+rpzEeEB7P6ODnwgjt7qA==} - '@lexical/history@0.41.0': - resolution: {integrity: sha512-kGoVWsiOn62+RMjRolRa+NXZl8jFwxav6GNDiHH8yzivtoaH8n1SwUfLJELXCzeqzs81HySqD4q30VLJVTGoDg==} + '@lexical/history@0.42.0': + resolution: {integrity: sha512-YfCZ1ICUt6BCg2ncJWFMuS4yftnB7FEHFRf3qqTSTf6oGZ4IZfzabMNEy47xybUuf7FXBbdaCKJrc/zOM+wGxw==} - '@lexical/html@0.41.0': - resolution: {integrity: sha512-3RyZy+H/IDKz2D66rNN/NqYx87xVFrngfEbyu1OWtbY963RUFnopiVHCQvsge/8kT04QSZ7U/DzjVFqeNS6clg==} + '@lexical/html@0.42.0': + resolution: {integrity: sha512-KgBUDLXehufCsXW3w0XsuoI2xecIhouOishnaNOH4zIA7dAtnNAfdPN/kWrWs0s83gz44OrnqccP+Bprw3UDEQ==} - '@lexical/link@0.41.0': - resolution: {integrity: sha512-Rjtx5cGWAkKcnacncbVsZ1TqRnUB2Wm4eEVKpaAEG41+kHgqghzM2P+UGT15yROroxJu8KvAC9ISiYFiU4XE1w==} + '@lexical/link@0.42.0': + resolution: {integrity: sha512-cdeM/+f+kn7aGwW/3FIi6USjl1gBNdEEwg0/ZS+KlYcsy8gxx2e4cyVjsomBu/WU17Qxa0NC0paSr7qEJ/1Fig==} - '@lexical/list@0.41.0': - resolution: {integrity: sha512-RXvB+xcbzVoQLGRDOBRCacztG7V+bI95tdoTwl8pz5xvgPtAaRnkZWMDP+yMNzMJZsqEChdtpxbf0NgtMkun6g==} + '@lexical/list@0.42.0': + resolution: {integrity: sha512-TIezILnmIVuvfqEEbcMnsT4xQRlswI6ysHISqsvKL6l5EBhs1gqmNYjHa/Yrfzaq5y52TM1PAtxbFts+G7N6kg==} - '@lexical/mark@0.41.0': - resolution: {integrity: sha512-UO5WVs9uJAYIKHSlYh4Z1gHrBBchTOi21UCYBIZ7eAs4suK84hPzD+3/LAX5CB7ZltL6ke5Sly3FOwNXv/wfpA==} + '@lexical/mark@0.42.0': + resolution: {integrity: sha512-H1aGjbMEcL4B8GT7bm/ePHm7j3Wema+wIRNPmxMtXGMz5gpVN3gZlvg2UcUHHJb00SrBA95OUVT5I2nu/KP06w==} - '@lexical/markdown@0.41.0': - resolution: {integrity: sha512-bzI73JMXpjGFhqUWNV6KqfjWcgAWzwFT+J3RHtbCF5rysC8HLldBYojOgAAtPfXqfxyv2mDzsY7SoJ75s9uHZA==} + '@lexical/markdown@0.42.0': + resolution: {integrity: sha512-+mOxgBiumlgVX8Acna+9HjJfSOw1jywufGcAQq3/8S11wZ4gE0u13AaR8LMmU8ydVeOQg09y8PNzGNQ/avZJbg==} - '@lexical/offset@0.41.0': - resolution: {integrity: sha512-2RHBXZqC8gm3X9C0AyRb0M8w7zJu5dKiasrif+jSKzsxPjAUeF1m95OtIOsWs1XLNUgASOSUqGovDZxKJslZfA==} + '@lexical/offset@0.42.0': + resolution: {integrity: sha512-V+4af1KmTOnBZrR+kU3e6eD33W/g3QqMPPp3cpFwyXk/dKRc4K8HfyDsSDrjop1mPd9pl3lKSiEmX6uQG8K9XQ==} - '@lexical/overflow@0.41.0': - resolution: {integrity: sha512-Iy6ZiJip8X14EBYt1zKPOrXyQ4eG9JLBEoPoSVBTiSbVd+lYicdUvaOThT0k0/qeVTN9nqTaEltBjm56IrVKCQ==} + '@lexical/overflow@0.42.0': + resolution: {integrity: sha512-wlrHaM27rODJP5m+CTgfZGLg3qWlQ0ptGodcqoGdq6HSbV8nGFY6TvcLMaMtYQ1lm4v9G7Xe9LwjooR6xS3Gug==} - '@lexical/plain-text@0.41.0': - resolution: {integrity: sha512-HIsGgmFUYRUNNyvckun33UQfU7LRzDlxymHUq67+Bxd5bXqdZOrStEKJXuDX+LuLh/GXZbaWNbDLqwLBObfbQg==} + '@lexical/plain-text@0.42.0': + resolution: {integrity: sha512-YWvBwIxLltrIaZDcv0rK4s44P6Yt17yhOb0E+g3+tjF8GGPrrocox+Pglu0m2RHR+G7zULN3isolmWIm/HhWiw==} - '@lexical/react@0.41.0': - resolution: {integrity: sha512-7+GUdZUm6sofWm+zdsWAs6cFBwKNsvsHezZTrf6k8jrZxL461ZQmbz/16b4DvjCGL9r5P1fR7md9/LCmk8TiCg==} + '@lexical/react@0.42.0': + resolution: {integrity: sha512-ujWJXhvlFVVTpwDcnSgEYWRuqUbreZaMB+4bjIDT5r7hkAplUHQndlkeuFHKFiJBasSAreleV7zhXrLL5xa9eA==} peerDependencies: react: '>=17.x' react-dom: '>=17.x' - '@lexical/rich-text@0.41.0': - resolution: {integrity: sha512-yUcr7ZaaVTZNi8bow4CK1M8jy2qyyls1Vr+5dVjwBclVShOL/F/nFyzBOSb6RtXXRbd3Ahuk9fEleppX/RNIdw==} + '@lexical/rich-text@0.42.0': + resolution: {integrity: sha512-v4YgiM3oK3FZcRrfB+LetvLbQ5aee9MRO9tHf0EFweXg19XnSjHV0cfPAW7TyPxRELzB69+K0Q3AybRlTMjG4Q==} - '@lexical/selection@0.41.0': - resolution: {integrity: sha512-1s7/kNyRzcv5uaTwsUL28NpiisqTf5xZ1zNukLsCN1xY+TWbv9RE9OxIv+748wMm4pxNczQe/UbIBODkbeknLw==} + '@lexical/selection@0.42.0': + resolution: {integrity: sha512-iWTjLA5BSEuUnvWe9Xwu9FSdZFl3Yi0NqalabXKI+7KgCIlIVXE74y4NvWPUSLkSCB/Z1RPKiHmZqZ1vyu/yGQ==} - '@lexical/table@0.41.0': - resolution: {integrity: sha512-d3SPThBAr+oZ8O74TXU0iXM3rLbrAVC7/HcOnSAq7/AhWQW8yMutT51JQGN+0fMLP9kqoWSAojNtkdvzXfU/+A==} + '@lexical/table@0.42.0': + resolution: {integrity: sha512-GKiZyjQsHDXRckq5VBrOowyvds51WoVRECfDgcl8pqLMnKyEdCa58E7fkSJrr5LS80Scod+Cjn6SBRzOcdsrKg==} - '@lexical/text@0.41.0': - resolution: {integrity: sha512-gGA+Anc7ck110EXo4KVKtq6Ui3M7Vz3OpGJ4QE6zJHWW8nV5h273koUGSutAMeoZgRVb6t01Izh3ORoFt/j1CA==} + '@lexical/text@0.42.0': + resolution: {integrity: sha512-hT3EYVtBmONXyXe4TFVgtFcG1tf6JhLEuAf95+cOjgFGFSgvkZ/64BPbKLNTj2/9n6cU7EGPUNNwVigCSECJ2g==} - '@lexical/utils@0.41.0': - resolution: {integrity: sha512-Wlsokr5NQCq83D+7kxZ9qs5yQ3dU3Qaf2M+uXxLRoPoDaXqW8xTWZq1+ZFoEzsHzx06QoPa4Vu/40BZR91uQPg==} + '@lexical/utils@0.42.0': + resolution: {integrity: sha512-wGNdCW3QWEyVdFiSTLZfFPtiASPyYLcekIiYYZmoRVxVimT/jY+QPfnkO4JYgkO7Z70g/dsg9OhqyQSChQfvkQ==} - '@lexical/yjs@0.41.0': - resolution: {integrity: sha512-PaKTxSbVC4fpqUjQ7vUL9RkNF1PjL8TFl5jRe03PqoPYpE33buf3VXX6+cOUEfv9+uknSqLCPHoBS/4jN3a97w==} + '@lexical/yjs@0.42.0': + resolution: {integrity: sha512-DplzWnYhfFceGPR+UyDFpZdB287wF/vNOHFuDsBF/nGDdTezvr0Gf60opzyBEF3oXym6p3xTmGygxvO97LZ+vw==} peerDependencies: yjs: '>=13.5.22' @@ -1702,14 +1704,14 @@ packages: '@next/env@16.0.0': resolution: {integrity: sha512-s5j2iFGp38QsG1LWRQaE2iUY3h1jc014/melHFfLdrsMJPqxqDQwWNwyQTcNoUSGZlCVZuM7t7JDMmSyRilsnA==} - '@next/env@16.2.0': - resolution: {integrity: sha512-OZIbODWWAi0epQRCRjNe1VO45LOFBzgiyqmTLzIqWq6u1wrxKnAyz1HH6tgY/Mc81YzIjRPoYsPAEr4QV4l9TA==} + '@next/env@16.2.1': + resolution: {integrity: sha512-n8P/HCkIWW+gVal2Z8XqXJ6aB3J0tuM29OcHpCsobWlChH/SITBs1DFBk/HajgrwDkqqBXPbuUuzgDvUekREPg==} - '@next/eslint-plugin-next@16.2.0': - resolution: {integrity: sha512-3D3pEMcGKfENC9Pzlkr67GOm+205+5hRdYPZvHuNIy5sr9k0ybSU8g+sxOO/R/RLEh/gWZ3UlY+5LmEyZ1xgXQ==} + '@next/eslint-plugin-next@16.2.1': + resolution: {integrity: sha512-r0epZGo24eT4g08jJlg2OEryBphXqO8aL18oajoTKLzHJ6jVr6P6FI58DLMug04MwD3j8Fj0YK0slyzneKVyzA==} - '@next/mdx@16.2.0': - resolution: {integrity: sha512-I+qgh34a9tNfZpz0TdMT8c6CjUEjatFx7njvQXKi3gbQtuRc5MyHYyyP7+GBtOpmtSUocnI+I+SaVQK/8UFIIw==} + '@next/mdx@16.2.1': + resolution: {integrity: sha512-w0YOkOc+WEnsTJ8uxzBOvpe3R+9BnJOxWCE7qcI/62CzJiUEd8JKtF25e3R8cW5BGsKyRW8p4zE2JLyXKa8xdw==} peerDependencies: '@mdx-js/loader': '>=0.15.0' '@mdx-js/react': '>=0.15.0' @@ -1719,54 +1721,54 @@ packages: '@mdx-js/react': optional: true - '@next/swc-darwin-arm64@16.2.0': - resolution: {integrity: sha512-/JZsqKzKt01IFoiLLAzlNqys7qk2F3JkcUhj50zuRhKDQkZNOz9E5N6wAQWprXdsvjRP4lTFj+/+36NSv5AwhQ==} + '@next/swc-darwin-arm64@16.2.1': + resolution: {integrity: sha512-BwZ8w8YTaSEr2HIuXLMLxIdElNMPvY9fLqb20LX9A9OMGtJilhHLbCL3ggyd0TwjmMcTxi0XXt+ur1vWUoxj2Q==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.0': - resolution: {integrity: sha512-/hV8erWq4SNlVgglUiW5UmQ5Hwy5EW/AbbXlJCn6zkfKxTy/E/U3V8U1Ocm2YCTUoFgQdoMxRyRMOW5jYy4ygg==} + '@next/swc-darwin-x64@16.2.1': + resolution: {integrity: sha512-/vrcE6iQSJq3uL3VGVHiXeaKbn8Es10DGTGRJnRZlkNQQk3kaNtAJg8Y6xuAlrx/6INKVjkfi5rY0iEXorZ6uA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.0': - resolution: {integrity: sha512-GkjL/Q7MWOwqWR9zoxu1TIHzkOI2l2BHCf7FzeQG87zPgs+6WDh+oC9Sw9ARuuL/FUk6JNCgKRkA6rEQYadUaw==} + '@next/swc-linux-arm64-gnu@16.2.1': + resolution: {integrity: sha512-uLn+0BK+C31LTVbQ/QU+UaVrV0rRSJQ8RfniQAHPghDdgE+SlroYqcmFnO5iNjNfVWCyKZHYrs3Nl0mUzWxbBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.0': - resolution: {integrity: sha512-1ffhC6KY5qWLg5miMlKJp3dZbXelEfjuXt1qcp5WzSCQy36CV3y+JT7OC1WSFKizGQCDOcQbfkH/IjZP3cdRNA==} + '@next/swc-linux-arm64-musl@16.2.1': + resolution: {integrity: sha512-ssKq6iMRnHdnycGp9hCuGnXJZ0YPr4/wNwrfE5DbmvEcgl9+yv97/Kq3TPVDfYome1SW5geciLB9aiEqKXQjlQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.2.0': - resolution: {integrity: sha512-FmbDcZQ8yJRq93EJSL6xaE0KK/Rslraf8fj1uViGxg7K4CKBCRYSubILJPEhjSgZurpcPQq12QNOJQ0DRJl6Hg==} + '@next/swc-linux-x64-gnu@16.2.1': + resolution: {integrity: sha512-HQm7SrHRELJ30T1TSmT706IWovFFSRGxfgUkyWJZF/RKBMdbdRWJuFrcpDdE5vy9UXjFOx6L3mRdqH04Mmx0hg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.0': - resolution: {integrity: sha512-HzjIHVkmGAwRbh/vzvoBWWEbb8BBZPxBvVbDQDvzHSf3D8RP/4vjw7MNLDXFF9Q1WEzeQyEj2zdxBtVAHu5Oyw==} + '@next/swc-linux-x64-musl@16.2.1': + resolution: {integrity: sha512-aV2iUaC/5HGEpbBkE+4B8aHIudoOy5DYekAKOMSHoIYQ66y/wIVeaRx8MS2ZMdxe/HIXlMho4ubdZs/J8441Tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.0': - resolution: {integrity: sha512-UMiFNQf5H7+1ZsZPxEsA064WEuFbRNq/kEXyepbCnSErp4f5iut75dBA8UeerFIG3vDaQNOfCpevnERPp2V+nA==} + '@next/swc-win32-arm64-msvc@16.2.1': + resolution: {integrity: sha512-IXdNgiDHaSk0ZUJ+xp0OQTdTgnpx1RCfRTalhn3cjOP+IddTMINwA7DXZrwTmGDO8SUr5q2hdP/du4DcrB1GxA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.0': - resolution: {integrity: sha512-DRrNJKW+/eimrZgdhVN1uvkN1OI4j6Lpefwr44jKQ0YQzztlmOBUUzHuV5GxOMPK3nmodAYElUVCY8ZXo/IWeA==} + '@next/swc-win32-x64-msvc@16.2.1': + resolution: {integrity: sha512-qvU+3a39Hay+ieIztkGSbF7+mccbbg1Tk25hc4JDylf8IHjYmY/Zm64Qq1602yPyQqvie+vf5T/uPwNxDNIoeg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1834,48 +1836,175 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@orpc/client@1.13.8': - resolution: {integrity: sha512-7B8NDjBjP17Mrrgc/YeZl9b0YBu2Sk9/lKyVeG3755tyrAPLiezWuwQEaP9T45S2/+g8LTzFmV2R504Wn5R5MQ==} + '@orpc/client@1.13.9': + resolution: {integrity: sha512-RmD2HDgmGgF6zgHHdybE4zH6QJoHjC+/C3n56yLf+fmWbiZtwnOUETgGCroY6S8aK2fpy6hJ3wZaJUjfWVuGHg==} - '@orpc/contract@1.13.8': - resolution: {integrity: sha512-W8hjVYDnsHI63TgQUGB4bb+ldCqR5hdxL1o2b7ytkFEkXTft6HOrHHvv+ncmgK1c1XapD1ScsCj11zzxf5NUGQ==} + '@orpc/contract@1.13.9': + resolution: {integrity: sha512-0zxMyF82pxE8DwHzarCsCtOHQK96PE23qubMMBkxkP0XTtLJ7f8aYhrG8F16pNApypmTHiRlQlqNX8VXNViMqQ==} - '@orpc/openapi-client@1.13.8': - resolution: {integrity: sha512-Cg7oDhbiO9bPpseRaFeWIhZFoA1bCF2pPxAJZj6/YtHkh+VSDI8W1xzbzoKNp2YHnhhJfgpIuVsHD42tX73+Mw==} + '@orpc/openapi-client@1.13.9': + resolution: {integrity: sha512-zvNrc7wgF/INKeewH2ih48U/q9tG7rLZCnmMrb5/1jdZgYYOBAEuILlDAejeQwGdRce6W18GTBjLKIEdP3WwqA==} - '@orpc/shared@1.13.8': - resolution: {integrity: sha512-d7bZW2F8/ov6JFuGEMeh7XYZtW4+zgjxW5DKBv5tNkWmZEC5JJQz8l6Ym9ZRe2VyRzQgo5JarJGsVQlmqVVvhw==} + '@orpc/shared@1.13.9': + resolution: {integrity: sha512-gpMY2e9jDsSyikh4DjBCO2Cs0wGj2I6xo2juIcmogYK5ecsTGO/U5huIftQn+2NUMk1cItwmykJBwc4pqHWVHw==} peerDependencies: '@opentelemetry/api': '>=1.9.0' peerDependenciesMeta: '@opentelemetry/api': optional: true - '@orpc/standard-server-fetch@1.13.8': - resolution: {integrity: sha512-g26Loo7GFTCF/S5QsM3Z6Xd9ZYs90K7jtRtEqbJh03YNrjecvZdpUKd/lTf/9kpJTBTQbhFxC9WCAJH4+8leFA==} + '@orpc/standard-server-fetch@1.13.9': + resolution: {integrity: sha512-/dJmHO+EVONyvmX3CFZkRjlRHeBfq0+6nnpFIVueGo4fNUbtQc+qurKEtpQqPxL/b7GSehskNH21XKLE0IE0gQ==} - '@orpc/standard-server-peer@1.13.8': - resolution: {integrity: sha512-ZyzWT6zZnLJkX15r04ecSDAJmkQ46PXTovORmK7RzOV47qIB7IryiRGR60U4WygBX0VDzZU8cgcXidZTx4v7oA==} + '@orpc/standard-server-peer@1.13.9': + resolution: {integrity: sha512-r8hSykxNIKwXSMuLYWBxQx1c3DU8b6nU8V76DZhtwC5g1SLYIzw+dzT/EgHplOfmsFeyodiEDXXX1k/twRLuzw==} - '@orpc/standard-server@1.13.8': - resolution: {integrity: sha512-/v72eRSPFzWt6SoHDC04cjZfwdW94z3aib7dMBat32aK3eXwfRZmwPPmfVBQO/ZlJYlq+5rSdPoMKkSoirG/5Q==} + '@orpc/standard-server@1.13.9': + resolution: {integrity: sha512-dwsky7CScgOaDBa7CBF85aPGk/3UoB4fJjitVghb/sZD0Nt+CGIeiPHMsjEgxw5rJwgawMWLI5KxFH9euAJlWw==} - '@orpc/tanstack-query@1.13.8': - resolution: {integrity: sha512-ZUwwkAqoGPOCs8gBG7w6vVNxUOAJyTBVUuclmZoyTdbb5xgMVtUGCvyjiwaWOSoL4+N2urZBbvNdTbEMsuoqLQ==} + '@orpc/tanstack-query@1.13.9': + resolution: {integrity: sha512-gOVJkCT9JGfu0e0TlTY3YUueXP2+Kzp6TcgfL2U3yXcYdTLv+jTrNOVJdtAAbeweUIU6dBEtatlhAQ7OgHWbsw==} peerDependencies: - '@orpc/client': 1.13.8 + '@orpc/client': 1.13.9 '@tanstack/query-core': '>=5.80.2' '@ota-meshi/ast-token-store@0.3.0': resolution: {integrity: sha512-XRO0zi2NIUKq2lUk3T1ecFSld1fMWRKE6naRFGkgkdeosx7IslyUKNv5Dcb5PJTja9tHJoFu0v/7yEpAkrkrTg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@oxc-project/runtime@0.115.0': - resolution: {integrity: sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==} + '@oxc-parser/binding-android-arm-eabi@0.120.0': + resolution: {integrity: sha512-WU3qtINx802wOl8RxAF1v0VvmC2O4D9M8Sv486nLeQ7iPHVmncYZrtBhB4SYyX+XZxj2PNnCcN+PW21jHgiOxg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.120.0': + resolution: {integrity: sha512-SEf80EHdhlbjZEgzeWm0ZA/br4GKMenDW3QB/gtyeTV1gStvvZeFi40ioHDZvds2m4Z9J1bUAUL8yn1/+A6iGg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.120.0': + resolution: {integrity: sha512-xVrrbCai8R8CUIBu3CjryutQnEYhZqs1maIqDvtUCFZb8vY33H7uh9mHpL3a0JBIKoBUKjPH8+rzyAeXnS2d6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.120.0': + resolution: {integrity: sha512-xyHBbnJ6mydnQUH7MAcafOkkrNzQC6T+LXgDH/3InEq2BWl/g424IMRiJVSpVqGjB+p2bd0h0WRR8iIwzjU7rw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.120.0': + resolution: {integrity: sha512-UMnVRllquXUYTeNfFKmxTTEdZ/ix1nLl0ducDzMSREoWYGVIHnOOxoKMWlCOvRr9Wk/HZqo2rh1jeumbPGPV9A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.120.0': + resolution: {integrity: sha512-tkvn2CQ7QdcsMnpfiX3fd3wA3EFsWKYlcQzq9cFw/xc89Al7W6Y4O0FgLVkVQpo0Tnq/qtE1XfkJOnRRA9S/NA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.120.0': + resolution: {integrity: sha512-WN5y135Ic42gQDk9grbwY9++fDhqf8knN6fnP+0WALlAUh4odY/BDK1nfTJRSfpJD9P3r1BwU0m3pW2DU89whQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.120.0': + resolution: {integrity: sha512-1GgQBCcXvFMw99EPdMy+4NZ3aYyXsxjf9kbUUg8HuAy3ZBXzOry5KfFEzT9nqmgZI1cuetvApkiJBZLAPo8uaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.120.0': + resolution: {integrity: sha512-gmMQ70gsPdDBgpcErvJEoWNBr7bJooSLlvOBVBSGfOzlP5NvJ3bFvnUeZZ9d+dPrqSngtonf7nyzWUTUj/U+lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.120.0': + resolution: {integrity: sha512-T/kZuU0ajop0xhzVMwH5r3srC9Nqup5HaIo+3uFjIN5uPxa0LvSxC1ZqP4aQGJVW5G0z8/nCkjIfSMS91P/wzw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.120.0': + resolution: {integrity: sha512-vn21KXLAXzaI3N5CZWlBr1iWeXLl9QFIMor7S1hUjUGTeUuWCoE6JZB040/ZNDwf+JXPX8Ao9KbmJq9FMC2iGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.120.0': + resolution: {integrity: sha512-SUbUxlar007LTGmSLGIC5x/WJvwhdX+PwNzFJ9f/nOzZOrCFbOT4ikt7pJIRg1tXVsEfzk5mWpGO1NFiSs4PIw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.120.0': + resolution: {integrity: sha512-hYiPJTxyfJY2+lMBFk3p2bo0R9GN+TtpPFlRqVchL1qvLG+pznstramHNvJlw9AjaoRUHwp9IKR7UZQnRPGjgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.120.0': + resolution: {integrity: sha512-q+5jSVZkprJCIy3dzJpApat0InJaoxQLsJuD6DkX8hrUS61z2lHQ1Fe9L2+TYbKHXCLWbL0zXe7ovkIdopBGMQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.120.0': + resolution: {integrity: sha512-D9QDDZNnH24e7X4ftSa6ar/2hCavETfW3uk0zgcMIrZNy459O5deTbWrjGzZiVrSWigGtlQwzs2McBP0QsfV1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.120.0': + resolution: {integrity: sha512-TBU8ZwOUWAOUWVfmI16CYWbvh4uQb9zHnGBHsw5Cp2JUVG044OIY1CSHODLifqzQIMTXvDvLzcL89GGdUIqNrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.120.0': + resolution: {integrity: sha512-WG/FOZgDJCpJnuF3ToG/K28rcOmSY7FmFmfBKYb2fmLyhDzPpUldFGV7/Fz4ru0Iz/v4KPmf8xVgO8N3lO4KHA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.120.0': + resolution: {integrity: sha512-1T0HKGcsz/BKo77t7+89L8Qvu4f9DoleKWHp3C5sJEcbCjDOLx3m9m722bWZTY+hANlUEs+yjlK+lBFsA+vrVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.120.0': + resolution: {integrity: sha512-L7vfLzbOXsjBXV0rv/6Y3Jd9BRjPeCivINZAqrSyAOZN3moCopDN+Psq9ZrGNZtJzP8946MtlRFZ0Als0wBCOw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.120.0': + resolution: {integrity: sha512-ys+upfqNtSu58huAhJMBKl3XCkGzyVFBlMlGPzHeFKgpFF/OdgNs1MMf8oaJIbgMH8ZxgGF7qfue39eJohmKIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/runtime@0.120.0': + resolution: {integrity: sha512-7fvACzS46TkHuzA+Tag8ac40qfwURXRTdc4AtyItF59AoNPOO/QjPMqPyvJH8CaUdGu0ntWDX1CCUNyLMxxX5g==} engines: {node: ^20.19.0 || >=22.12.0} - '@oxc-project/types@0.115.0': - resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==} + '@oxc-project/types@0.120.0': + resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==} '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} @@ -1985,276 +2114,276 @@ packages: cpu: [x64] os: [win32] - '@oxfmt/binding-android-arm-eabi@0.40.0': - resolution: {integrity: sha512-S6zd5r1w/HmqR8t0CTnGjFTBLDq2QKORPwriCHxo4xFNuhmOTABGjPaNvCJJVnrKBLsohOeiDX3YqQfJPF+FXw==} + '@oxfmt/binding-android-arm-eabi@0.41.0': + resolution: {integrity: sha512-REfrqeMKGkfMP+m/ScX4f5jJBSmVNYcpoDF8vP8f8eYPDuPGZmzp56NIUsYmx3h7f6NzC6cE3gqh8GDWrJHCKw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.40.0': - resolution: {integrity: sha512-/mbS9UUP/5Vbl2D6osIdcYiP0oie63LKMoTyGj5hyMCK/SFkl3EhtyRAfdjPvuvHC0SXdW6ePaTKkBSq1SNcIw==} + '@oxfmt/binding-android-arm64@0.41.0': + resolution: {integrity: sha512-s0b1dxNgb2KomspFV2LfogC2XtSJB42POXF4bMCLJyvQmAGos4ZtjGPfQreToQEaY0FQFjz3030ggI36rF1q5g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.40.0': - resolution: {integrity: sha512-wRt8fRdfLiEhnRMBonlIbKrJWixoEmn6KCjKE9PElnrSDSXETGZfPb8ee+nQNTobXkCVvVLytp2o0obAsxl78Q==} + '@oxfmt/binding-darwin-arm64@0.41.0': + resolution: {integrity: sha512-EGXGualADbv/ZmamE7/2DbsrYmjoPlAmHEpTL4vapLF4EfVD6fr8/uQDFnPJkUBjiSWFJZtFNsGeN1B6V3owmA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.40.0': - resolution: {integrity: sha512-fzowhqbOE/NRy+AE5ob0+Y4X243WbWzDb00W+pKwD7d9tOqsAFbtWUwIyqqCoCLxj791m2xXIEeLH/3uz7zCCg==} + '@oxfmt/binding-darwin-x64@0.41.0': + resolution: {integrity: sha512-WxySJEvdQQYMmyvISH3qDpTvoS0ebnIP63IMxLLWowJyPp/AAH0hdWtlo+iGNK5y3eVfa5jZguwNaQkDKWpGSw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.40.0': - resolution: {integrity: sha512-agZ9ITaqdBjcerRRFEHB8s0OyVcQW8F9ZxsszjxzeSthQ4fcN2MuOtQFWec1ed8/lDa50jSLHVE2/xPmTgtCfQ==} + '@oxfmt/binding-freebsd-x64@0.41.0': + resolution: {integrity: sha512-Y2kzMkv3U3oyuYaR4wTfGjOTYTXiFC/hXmG0yVASKkbh02BJkvD98Ij8bIevr45hNZ0DmZEgqiXF+9buD4yMYQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.40.0': - resolution: {integrity: sha512-ZM2oQ47p28TP1DVIp7HL1QoMUgqlBFHey0ksHct7tMXoU5BqjNvPWw7888azzMt25lnyPODVuye1wvNbvVUFOA==} + '@oxfmt/binding-linux-arm-gnueabihf@0.41.0': + resolution: {integrity: sha512-ptazDjdUyhket01IjPTT6ULS1KFuBfTUU97osTP96X5y/0oso+AgAaJzuH81oP0+XXyrWIHbRzozSAuQm4p48g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.40.0': - resolution: {integrity: sha512-RBFPAxRAIsMisKM47Oe6Lwdv6agZYLz02CUhVCD1sOv5ajAcRMrnwCFBPWwGXpazToW2mjnZxFos8TuFjTU15A==} + '@oxfmt/binding-linux-arm-musleabihf@0.41.0': + resolution: {integrity: sha512-UkoL2OKxFD+56bPEBcdGn+4juTW4HRv/T6w1dIDLnvKKWr6DbarB/mtHXlADKlFiJubJz8pRkttOR7qjYR6lTA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.40.0': - resolution: {integrity: sha512-Nb2XbQ+wV3W2jSIihXdPj7k83eOxeSgYP3N/SRXvQ6ZYPIk6Q86qEh5Gl/7OitX3bQoQrESqm1yMLvZV8/J7dA==} + '@oxfmt/binding-linux-arm64-gnu@0.41.0': + resolution: {integrity: sha512-gofu0PuumSOHYczD8p62CPY4UF6ee+rSLZJdUXkpwxg6pILiwSDBIouPskjF/5nF3A7QZTz2O9KFNkNxxFN9tA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.40.0': - resolution: {integrity: sha512-tGmWhLD/0YMotCdfezlT6tC/MJG/wKpo4vnQ3Cq+4eBk/BwNv7EmkD0VkD5F/dYkT3b8FNU01X2e8vvJuWoM1w==} + '@oxfmt/binding-linux-arm64-musl@0.41.0': + resolution: {integrity: sha512-VfVZxL0+6RU86T8F8vKiDBa+iHsr8PAjQmKGBzSCAX70b6x+UOMFl+2dNihmKmUwqkCazCPfYjt6SuAPOeQJ3g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.40.0': - resolution: {integrity: sha512-rVbFyM3e7YhkVnp0IVYjaSHfrBWcTRWb60LEcdNAJcE2mbhTpbqKufx0FrhWfoxOrW/+7UJonAOShoFFLigDqQ==} + '@oxfmt/binding-linux-ppc64-gnu@0.41.0': + resolution: {integrity: sha512-bwzokz2eGvdfJbc0i+zXMJ4BBjQPqg13jyWpEEZDOrBCQ91r8KeY2Mi2kUeuMTZNFXju+jcAbAbpyJxRGla0eg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.40.0': - resolution: {integrity: sha512-3ZqBw14JtWeEoLiioJcXSJz8RQyPE+3jLARnYM1HdPzZG4vk+Ua8CUupt2+d+vSAvMyaQBTN2dZK+kbBS/j5mA==} + '@oxfmt/binding-linux-riscv64-gnu@0.41.0': + resolution: {integrity: sha512-POLM//PCH9uqDeNDwWL3b3DkMmI3oI2cU6hwc2lnztD1o7dzrQs3R9nq555BZ6wI7t2lyhT9CS+CRaz5X0XqLA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.40.0': - resolution: {integrity: sha512-JJ4PPSdcbGBjPvb+O7xYm2FmAsKCyuEMYhqatBAHMp/6TA6rVlf9Z/sYPa4/3Bommb+8nndm15SPFRHEPU5qFA==} + '@oxfmt/binding-linux-riscv64-musl@0.41.0': + resolution: {integrity: sha512-NNK7PzhFqLUwx/G12Xtm6scGv7UITvyGdAR5Y+TlqsG+essnuRWR4jRNODWRjzLZod0T3SayRbnkSIWMBov33w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.40.0': - resolution: {integrity: sha512-Kp0zNJoX9Ik77wUya2tpBY3W9f40VUoMQLWVaob5SgCrblH/t2xr/9B2bWHfs0WCefuGmqXcB+t0Lq77sbBmZw==} + '@oxfmt/binding-linux-s390x-gnu@0.41.0': + resolution: {integrity: sha512-qVf/zDC5cN9eKe4qI/O/m445er1IRl6swsSl7jHkqmOSVfknwCe5JXitYjZca+V/cNJSU/xPlC5EFMabMMFDpw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.40.0': - resolution: {integrity: sha512-7YTCNzleWTaQTqNGUNQ66qVjpoV6DjbCOea+RnpMBly2bpzrI/uu7Rr+2zcgRfNxyjXaFTVQKaRKjqVdeUfeVA==} + '@oxfmt/binding-linux-x64-gnu@0.41.0': + resolution: {integrity: sha512-ojxYWu7vUb6ysYqVCPHuAPVZHAI40gfZ0PDtZAMwVmh2f0V8ExpPIKoAKr7/8sNbAXJBBpZhs2coypIo2jJX4w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.40.0': - resolution: {integrity: sha512-hWnSzJ0oegeOwfOEeejYXfBqmnRGHusgtHfCPzmvJvHTwy1s3Neo59UKc1CmpE3zxvrCzJoVHos0rr97GHMNPw==} + '@oxfmt/binding-linux-x64-musl@0.41.0': + resolution: {integrity: sha512-O2exZLBxoCMIv2vlvcbkdedazJPTdG0VSup+0QUCfYQtx751zCZNboX2ZUOiQ/gDTdhtXvSiot0h6GEGkOyalA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.40.0': - resolution: {integrity: sha512-28sJC1lR4qtBJGzSRRbPnSW3GxU2+4YyQFE6rCmsUYqZ5XYH8jg0/w+CvEzQ8TuAQz5zLkcA25nFQGwoU0PT3Q==} + '@oxfmt/binding-openharmony-arm64@0.41.0': + resolution: {integrity: sha512-N+31/VoL+z+NNBt8viy3I4NaIdPbiYeOnB884LKqvXldaE2dRztdPv3q5ipfZYv0RwFp7JfqS4I27K/DSHCakg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.40.0': - resolution: {integrity: sha512-cDkRnyT0dqwF5oIX1Cv59HKCeZQFbWWdUpXa3uvnHFT2iwYSSZspkhgjXjU6iDp5pFPaAEAe9FIbMoTgkTmKPg==} + '@oxfmt/binding-win32-arm64-msvc@0.41.0': + resolution: {integrity: sha512-Z7NAtu/RN8kjCQ1y5oDD0nTAeRswh3GJ93qwcW51srmidP7XPBmZbLlwERu1W5veCevQJtPS9xmkpcDTYsGIwQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.40.0': - resolution: {integrity: sha512-7rPemBJjqm5Gkv6ZRCPvK8lE6AqQ/2z31DRdWazyx2ZvaSgL7QGofHXHNouRpPvNsT9yxRNQJgigsWkc+0qg4w==} + '@oxfmt/binding-win32-ia32-msvc@0.41.0': + resolution: {integrity: sha512-uNxxP3l4bJ6VyzIeRqCmBU2Q0SkCFgIhvx9/9dJ9V8t/v+jP1IBsuaLwCXGR8JPHtkj4tFp+RHtUmU2ZYAUpMA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.40.0': - resolution: {integrity: sha512-/Zmj0yTYSvmha6TG1QnoLqVT7ZMRDqXvFXXBQpIjteEwx9qvUYMBH2xbiOFhDeMUJkGwC3D6fdKsFtaqUvkwNA==} + '@oxfmt/binding-win32-x64-msvc@0.41.0': + resolution: {integrity: sha512-49ZSpbZ1noozyPapE8SUOSm3IN0Ze4b5nkO+4+7fq6oEYQQJFhE0saj5k/Gg4oewVPdjn0L3ZFeWk2Vehjcw7A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@0.17.0': - resolution: {integrity: sha512-z3XwCDuOAKgk7bO4y5tyH8Zogwr51G56R0XGKC3tlAbrAq8DecoxAd3qhRZqWBMG2Gzl5bWU3Ghu7lrxuLPzYw==} + '@oxlint-tsgolint/darwin-arm64@0.17.1': + resolution: {integrity: sha512-JNWNwyvSDcUQSBlQRl10XrCeNcN66TMvDw3gIDQeop5SNa1F7wFhsEx4zitYb7fGHwGh9095tsNttmuCaNXCbw==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@0.17.0': - resolution: {integrity: sha512-TZgVXy0MtI8nt0MYiceuZhHPwHcwlIZ/YwzFTAKrgdHiTvVzFbqHVdXi5wbZfT/o1nHGw9fbGWPlb6qKZ4uZ9Q==} + '@oxlint-tsgolint/darwin-x64@0.17.1': + resolution: {integrity: sha512-SluNf6CW88pgGPqQUGC5GoK5qESWo2ct1PRDbza3vbf9SK2npx3igvylGQIgE9qYYOcjgnVdLOJ0+q0gItgUmQ==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@0.17.0': - resolution: {integrity: sha512-IDfhFl/Y8bjidCvAP6QAxVyBsl78TmfCHlfjtEv2XtJXgYmIwzv6muO18XMp74SZ2qAyD4y2n2dUedrmghGHeA==} + '@oxlint-tsgolint/linux-arm64@0.17.1': + resolution: {integrity: sha512-BJxQ7/cdo2dNdGIBs2PIR6BaPA7cPfe+r1HE/uY+K7g2ygip+0LHB3GUO9GaNDZuWpsnDyjLYYowEGrVK8dokA==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@0.17.0': - resolution: {integrity: sha512-Bgdgqx/m8EnfjmmlRLEeYy9Yhdt1GdFrMr5mTu/NyLRGkB1C9VLAikdxB7U9QambAGTAmjMbHNFDFk8Vx69Huw==} + '@oxlint-tsgolint/linux-x64@0.17.1': + resolution: {integrity: sha512-s6UjmuaJbZ4zz/wJKdEw/s5mc0t41rgwxQJCSHPuzMumMK6ylrB7nydhDf8ObTtzhTIZdAS/2S/uayJmDcGbxw==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@0.17.0': - resolution: {integrity: sha512-dO6wyKMDqFWh1vwr+zNZS7/ovlfGgl4S3P1LDy4CKjP6V6NGtdmEwWkWax8j/I8RzGZdfXKnoUfb/qhVg5bx0w==} + '@oxlint-tsgolint/win32-arm64@0.17.1': + resolution: {integrity: sha512-EO/Oj0ixHX+UQdu9hM7YUzibZI888MvPUo/DF8lSxFBt4JNEt8qGkwJEbCYjB/1LhUNmPHzSw2Tr9dCFVfW9nw==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@0.17.0': - resolution: {integrity: sha512-lPGYFp3yX2nh6hLTpIuMnJbZnt3Df42VkoA/fSkMYi2a/LXdDytQGpgZOrb5j47TICARd34RauKm0P3OA4Oxbw==} + '@oxlint-tsgolint/win32-x64@0.17.1': + resolution: {integrity: sha512-jhv7XktAJ1sMRSb//yDYTauFSZ06H81i2SLEBPaSUKxSKoPMK8p1ACUJlnmwZX2MgapRLEj1Ml22B6+HiM2YIA==} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.55.0': - resolution: {integrity: sha512-NhvgAhncTSOhRahQSCnkK/4YIGPjTmhPurQQ2dwt2IvwCMTvZRW5vF2K10UBOxFve4GZDMw6LtXZdC2qeuYIVQ==} + '@oxlint/binding-android-arm-eabi@1.56.0': + resolution: {integrity: sha512-IyfYPthZyiSKwAv/dLjeO18SaK8MxLI9Yss2JrRDyweQAkuL3LhEy7pwIwI7uA3KQc1Vdn20kdmj3q0oUIQL6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.55.0': - resolution: {integrity: sha512-P9iWRh+Ugqhg+D7rkc7boHX8o3H2h7YPcZHQIgvVBgnua5tk4LR2L+IBlreZs58/95cd2x3/004p5VsQM9z4SA==} + '@oxlint/binding-android-arm64@1.56.0': + resolution: {integrity: sha512-Ga5zYrzH6vc/VFxhn6MmyUnYEfy9vRpwTIks99mY3j6Nz30yYpIkWryI0QKPCgvGUtDSXVLEaMum5nA+WrNOSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.55.0': - resolution: {integrity: sha512-esakkJIt7WFAhT30P/Qzn96ehFpzdZ1mNuzpOb8SCW7lI4oB8VsyQnkSHREM671jfpuBb/o2ppzBCx5l0jpgMA==} + '@oxlint/binding-darwin-arm64@1.56.0': + resolution: {integrity: sha512-ogmbdJysnw/D4bDcpf1sPLpFThZ48lYp4aKYm10Z/6Nh1SON6NtnNhTNOlhEY296tDFItsZUz+2tgcSYqh8Eyw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.55.0': - resolution: {integrity: sha512-xDMFRCCAEK9fOH6As2z8ELsC+VDGSFRHwIKVSilw+xhgLwTDFu37rtmRbmUlx8rRGS6cWKQPTc47AVxAZEVVPQ==} + '@oxlint/binding-darwin-x64@1.56.0': + resolution: {integrity: sha512-x8QE1h+RAtQ2g+3KPsP6Fk/tdz6zJQUv5c7fTrJxXV3GHOo+Ry5p/PsogU4U+iUZg0rj6hS+E4xi+mnwwlDCWQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.55.0': - resolution: {integrity: sha512-mYZqnwUD7ALCRxGenyLd1uuG+rHCL+OTT6S8FcAbVm/ZT2AZMGjvibp3F6k1SKOb2aeqFATmwRykrE41Q0GWVw==} + '@oxlint/binding-freebsd-x64@1.56.0': + resolution: {integrity: sha512-6G+WMZvwJpMvY7my+/SHEjb7BTk/PFbePqLpmVmUJRIsJMy/UlyYqjpuh0RCgYYkPLcnXm1rUM04kbTk8yS1Yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.55.0': - resolution: {integrity: sha512-LcX6RYcF9vL9ESGwJW3yyIZ/d/ouzdOKXxCdey1q0XJOW1asrHsIg5MmyKdEBR4plQx+shvYeQne7AzW5f3T1w==} + '@oxlint/binding-linux-arm-gnueabihf@1.56.0': + resolution: {integrity: sha512-YYHBsk/sl7fYwQOok+6W5lBPeUEvisznV/HZD2IfZmF3Bns6cPC3Z0vCtSEOaAWTjYWN3jVsdu55jMxKlsdlhg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.55.0': - resolution: {integrity: sha512-C+8GS1rPtK+dI7mJFkqoRBkDuqbrNihnyYQsJPS9ez+8zF9JzfvU19lawqt4l/Y23o5uQswE/DORa8aiXUih3w==} + '@oxlint/binding-linux-arm-musleabihf@1.56.0': + resolution: {integrity: sha512-+AZK8rOUr78y8WT6XkDb04IbMRqauNV+vgT6f8ZLOH8wnpQ9i7Nol0XLxAu+Cq7Sb+J9wC0j6Km5hG8rj47/yQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.55.0': - resolution: {integrity: sha512-ErLE4XbmcCopA4/CIDiH6J1IAaDOMnf/KSx/aFObs4/OjAAM3sFKWGZ57pNOMxhhyBdcmcXwYymph9GwcpcqgQ==} + '@oxlint/binding-linux-arm64-gnu@1.56.0': + resolution: {integrity: sha512-urse2SnugwJRojUkGSSeH2LPMaje5Q50yQtvtL9HFckiyeqXzoFwOAZqD5TR29R2lq7UHidfFDM9EGcchcbb8A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.55.0': - resolution: {integrity: sha512-/kp65avi6zZfqEng56TTuhiy3P/3pgklKIdf38yvYeJ9/PgEeRA2A2AqKAKbZBNAqUzrzHhz9jF6j/PZvhJzTQ==} + '@oxlint/binding-linux-arm64-musl@1.56.0': + resolution: {integrity: sha512-rkTZkBfJ4TYLjansjSzL6mgZOdN5IvUnSq3oNJSLwBcNvy3dlgQtpHPrRxrCEbbcp7oQ6If0tkNaqfOsphYZ9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.55.0': - resolution: {integrity: sha512-A6pTdXwcEEwL/nmz0eUJ6WxmxcoIS+97GbH96gikAyre3s5deC7sts38ZVVowjS2QQFuSWkpA4ZmQC0jZSNvJQ==} + '@oxlint/binding-linux-ppc64-gnu@1.56.0': + resolution: {integrity: sha512-uqL1kMH3u69/e1CH2EJhP3CP28jw2ExLsku4o8RVAZ7fySo9zOyI2fy9pVlTAp4voBLVgzndXi3SgtdyCTa2aA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.55.0': - resolution: {integrity: sha512-clj0lnIN+V52G9tdtZl0LbdTSurnZ1NZj92Je5X4lC7gP5jiCSW+Y/oiDiSauBAD4wrHt2S7nN3pA0zfKYK/6Q==} + '@oxlint/binding-linux-riscv64-gnu@1.56.0': + resolution: {integrity: sha512-j0CcMBOgV6KsRaBdsebIeiy7hCjEvq2KdEsiULf2LZqAq0v1M1lWjelhCV57LxsqaIGChXFuFJ0RiFrSRHPhSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.55.0': - resolution: {integrity: sha512-NNu08pllN5x/O94/sgR3DA8lbrGBnTHsINZZR0hcav1sj79ksTiKKm1mRzvZvacwQ0hUnGinFo+JO75ok2PxYg==} + '@oxlint/binding-linux-riscv64-musl@1.56.0': + resolution: {integrity: sha512-7VDOiL8cDG3DQ/CY3yKjbV1c4YPvc4vH8qW09Vv+5ukq3l/Kcyr6XGCd5NvxUmxqDb2vjMpM+eW/4JrEEsUetA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.55.0': - resolution: {integrity: sha512-BvfQz3PRlWZRoEZ17dZCqgQsMRdpzGZomJkVATwCIGhHVVeHJMQdmdXPSjcT1DCNUrOjXnVyj1RGDj5+/Je2+Q==} + '@oxlint/binding-linux-s390x-gnu@1.56.0': + resolution: {integrity: sha512-JGRpX0M+ikD3WpwJ7vKcHKV6Kg0dT52BW2Eu2BupXotYeqGXBrbY+QPkAyKO6MNgKozyTNaRh3r7g+VWgyAQYQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.55.0': - resolution: {integrity: sha512-ngSOoFCSBMKVQd24H8zkbcBNc7EHhjnF1sv3mC9NNXQ/4rRjI/4Dj9+9XoDZeFEkF1SX1COSBXF1b2Pr9rqdEw==} + '@oxlint/binding-linux-x64-gnu@1.56.0': + resolution: {integrity: sha512-dNaICPvtmuxFP/VbqdofrLqdS3bM/AKJN3LMJD52si44ea7Be1cBk6NpfIahaysG9Uo+L98QKddU9CD5L8UHnQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.55.0': - resolution: {integrity: sha512-BDpP7W8GlaG7BR6QjGZAleYzxoyKc/D24spZIF2mB3XsfALQJJT/OBmP8YpeTb1rveFSBHzl8T7l0aqwkWNdGA==} + '@oxlint/binding-linux-x64-musl@1.56.0': + resolution: {integrity: sha512-pF1vOtM+GuXmbklM1hV8WMsn6tCNPvkUzklj/Ej98JhlanbmA2RB1BILgOpwSuCTRTIYx2MXssmEyQQ90QF5aA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.55.0': - resolution: {integrity: sha512-PS6GFvmde/pc3fCA2Srt51glr8Lcxhpf6WIBFfLphndjRrD34NEcses4TSxQrEcxYo6qVywGfylM0ZhSCF2gGA==} + '@oxlint/binding-openharmony-arm64@1.56.0': + resolution: {integrity: sha512-bp8NQ4RE6fDIFLa4bdBiOA+TAvkNkg+rslR+AvvjlLTYXLy9/uKAYLQudaQouWihLD/hgkrXIKKzXi5IXOewwg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.55.0': - resolution: {integrity: sha512-P6JcLJGs/q1UOvDLzN8otd9JsH4tsuuPDv+p7aHqHM3PrKmYdmUvkNj4K327PTd35AYcznOCN+l4ZOaq76QzSw==} + '@oxlint/binding-win32-arm64-msvc@1.56.0': + resolution: {integrity: sha512-PxT4OJDfMOQBzo3OlzFb9gkoSD+n8qSBxyVq2wQSZIHFQYGEqIRTo9M0ZStvZm5fdhMqaVYpOnJvH2hUMEDk/g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.55.0': - resolution: {integrity: sha512-gzkk4zE2zsE+WmRxFOiAZHpCpUNDFytEakqNXoNHW+PnYEOTPKDdW6nrzgSeTbGKVPXNAKQnRnMgrh7+n3Xueg==} + '@oxlint/binding-win32-ia32-msvc@1.56.0': + resolution: {integrity: sha512-PTRy6sIEPqy2x8PTP1baBNReN/BNEFmde0L+mYeHmjXE1Vlcc9+I5nsqENsB2yAm5wLkzPoTNCMY/7AnabT4/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.55.0': - resolution: {integrity: sha512-ZFALNow2/og75gvYzNP7qe+rREQ5xunktwA+lgykoozHZ6hw9bqg4fn5j2UvG4gIn1FXqrZHkOAXuPf5+GOYTQ==} + '@oxlint/binding-win32-x64-msvc@1.56.0': + resolution: {integrity: sha512-ZHa0clocjLmIDr+1LwoWtxRcoYniAvERotvwKUYKhH41NVfl0Y4LNbyQkwMZzwDvKklKGvGZ5+DAG58/Ik47tQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2801,32 +2930,32 @@ packages: cpu: [x64] os: [win32] - '@sentry-internal/browser-utils@10.44.0': - resolution: {integrity: sha512-z9xz3T/v+MnfHY6kdUCmOZI8CiAl3LlKYtGH2p3rAsrxhwX+BTnUp01VhMVnEZIDgUXNt3AhJac+4kcDIPu1Hg==} + '@sentry-internal/browser-utils@10.45.0': + resolution: {integrity: sha512-ZPZpeIarXKScvquGx2AfNKcYiVNDA4wegMmjyGVsTA2JPmP0TrJoO3UybJS6KGDeee8V3I3EfD/ruauMm7jOFQ==} engines: {node: '>=18'} - '@sentry-internal/feedback@10.44.0': - resolution: {integrity: sha512-yNS2EGK1bNm8YUI+Orzpa7yr05Da+b1VEe/9x7dl7gTjw/+tfutoXlG6Y+iFZBB3gQ9QU+nxZAhU+KcxiPEURw==} + '@sentry-internal/feedback@10.45.0': + resolution: {integrity: sha512-vCSurazFVq7RUeYiM5X326jA5gOVrWYD6lYX2fbjBOMcyCEhDnveNxMT62zKkZDyNT/jyD194nz/cjntBUkyWA==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@10.44.0': - resolution: {integrity: sha512-RA7XgYZWHY7M+vaHvuMxDFT51wCs4puS2smElM5oh+j3YqbFXY7P16fOCwIAGoyI4gVsj8aTeBgVqUmrmzhAXQ==} + '@sentry-internal/replay-canvas@10.45.0': + resolution: {integrity: sha512-nvq/AocdZTuD7y0KSiWi3gVaY0s5HOFy86mC/v1kDZmT/jsBAzN5LDkk/f1FvsWma1peqQmpUqxvhC+YIW294Q==} engines: {node: '>=18'} - '@sentry-internal/replay@10.44.0': - resolution: {integrity: sha512-KDmoqBsRmkaoc+eKLR2CbScd2eBmLcw+1+D441lLttAO3WWhvYyCaYdu/HIGGUoybuSgt+IcpCJdi7hFuCvYqw==} + '@sentry-internal/replay@10.45.0': + resolution: {integrity: sha512-vjosRoGA1bzhVAEO1oce+CsRdd70quzBeo7WvYqpcUnoLe/Rv8qpOMqWX3j26z7XfFHMExWQNQeLxmtYOArvlw==} engines: {node: '>=18'} - '@sentry/browser@10.44.0': - resolution: {integrity: sha512-UpMx5forbVKieNULma3gT2SsLYqsYT4nLXa6s1io/Y8BFej9sH2dD5ExA8TrkQThQwAWFI3qKsQzYnF+EX/Bfg==} + '@sentry/browser@10.45.0': + resolution: {integrity: sha512-e/a8UMiQhqqv706McSIcG6XK+AoQf9INthi2pD+giZfNRTzXTdqHzUT5OIO5hg8Am6eF63nDJc+vrYNPhzs51Q==} engines: {node: '>=18'} - '@sentry/core@10.44.0': - resolution: {integrity: sha512-aa7CiDaNFZvHpqd97LJhuskolfJ/4IH5xyuVVLnv7l6B0v9KTwskPUxb0tH1ej3FxuzfH+i8iTiTFuqpfHS3QA==} + '@sentry/core@10.45.0': + resolution: {integrity: sha512-s69UXxvefeQxuZ5nY7/THtTrIEvJxNVCp3ns4kwoCw1qMpgpvn/296WCKVmM7MiwnaAdzEKnAvLAwaxZc2nM7Q==} engines: {node: '>=18'} - '@sentry/react@10.44.0': - resolution: {integrity: sha512-blaYoLk/UgFZXj9ieKZeY1JIiqzeL2VegQt22S9IQk8gHpunDZux5XC4CdcPdavcVusddaB/SmHAmhy2RCBdPQ==} + '@sentry/react@10.45.0': + resolution: {integrity: sha512-jLezuxi4BUIU3raKyAPR5xMbQG/nhwnWmKo5p11NCbLmWzkS+lxoyDTUB4B8TAKZLfdtdkKLOn1S0tFc8vbUHw==} engines: {node: '>=18'} peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x @@ -2876,42 +3005,42 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@storybook/addon-docs@10.3.0': - resolution: {integrity: sha512-g9bc4YDiy4g/peLsUDmVcy2q/QXI3eHCQtHrVp2sHWef2SYjwUJ2+TOtJHScO8LuKhGnU3h2UeE59tPWTF2quw==} + '@storybook/addon-docs@10.3.1': + resolution: {integrity: sha512-0FBhfMEg96QUmhdtks3rchktEEWF2hKcEsr3XluybBoBi4xAIw1vm+RJtL9Jm45ppTdg28LF7U+OeMx5LwkMzQ==} peerDependencies: - storybook: ^10.3.0 + storybook: ^10.3.1 - '@storybook/addon-links@10.3.0': - resolution: {integrity: sha512-F0/UPO3HysoJoAFrBSqWkRP3lK2owHSAgQNEFB9mNihsAQbHHg9xer22VROL012saprs98+V/hNUZs4zPy9zlg==} + '@storybook/addon-links@10.3.1': + resolution: {integrity: sha512-ooV8FU9PhcmSwpkSETZW6SYzVwQ0ui3DEp8gx5Kzf0JXAkESwxnzQVikxzHCLaP6KgYPb9gajN6jhin2KUGrhw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.3.0 + storybook: ^10.3.1 peerDependenciesMeta: react: optional: true - '@storybook/addon-onboarding@10.3.0': - resolution: {integrity: sha512-zhSmxO1VDntnAxSCvw1R9h2+KvAnY0PeDdhyrr9hQdVL1j3SEXxegc3dm/YJRhtBk6S2KPLgPU5+UQuFF0p2nA==} + '@storybook/addon-onboarding@10.3.1': + resolution: {integrity: sha512-fXhkG0dPvsuwlOmK2eOmc0CYJXUeWV8hZWlnthkqGKrzUyqXx0YmM3VdnKwk0/OnOCp1zykDoMtnjnDqWW4saQ==} peerDependencies: - storybook: ^10.3.0 + storybook: ^10.3.1 - '@storybook/addon-themes@10.3.0': - resolution: {integrity: sha512-tMNRnEXv91u2lYgyUUAPhWiPD2XTLw2prj6r9/e9wmKYqJ5a2q0gQ7MiGzbgNYWmqq+DZ7g4vvGt8MXt2GmSHQ==} + '@storybook/addon-themes@10.3.1': + resolution: {integrity: sha512-Y4ZCof3C+nsXvfhDmUvxt1klnZ6SPh1tLuDWo4eE8MUG1jQ2tixiIQX6Ups8fqfYCN8RgjcDDHnIyNZRZlgB2Q==} peerDependencies: - storybook: ^10.3.0 + storybook: ^10.3.1 - '@storybook/builder-vite@10.3.0': - resolution: {integrity: sha512-T7LfZPE31j94Jkk66bnsxMibBnbLYmebLIDgPSYzeN3ZkjPfoFhhi2+8Zxneth5cQCGRkCAhRTV0tYmFp1+H6g==} + '@storybook/builder-vite@10.3.1': + resolution: {integrity: sha512-8X3Mv6VxVaVHip51ZuTAjQv7jI3K4GxpgW0ZAhaLi8atSTHezu7hQOuISC1cHAwhMV0GhGHtCCKi33G9EGx5hw==} peerDependencies: - storybook: ^10.3.0 + storybook: ^10.3.1 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@storybook/csf-plugin@10.3.0': - resolution: {integrity: sha512-zlBnNpv0wtmICdQPDoY91HNzn6BNqnS2hur580J+qJtcP+5ZOYU7+gNyU+vfAnQuLEWbPz34rx8b1cTzXZQCDg==} + '@storybook/csf-plugin@10.3.1': + resolution: {integrity: sha512-P1WUSoyueV+ULpNeip4eIjjDvOXDBQI4gaq/s1PdAg1Szz/0GhDPu/CXuwukgkmyHaJP3aVR3pHPvSfeLfMCrA==} peerDependencies: esbuild: 0.27.2 rollup: 4.59.0 - storybook: ^10.3.0 + storybook: ^10.3.1 vite: '*' webpack: '*' peerDependenciesMeta: @@ -2933,40 +3062,40 @@ 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 - '@storybook/nextjs-vite@10.3.0': - resolution: {integrity: sha512-PQSQiUVxiR3eO3lmGbSyuPAbVwNJpOQDzkiC337IqWHhzZZQFVRgGU9j39hsUiP/d23BVuXPOWZtmTPASXDVMQ==} + '@storybook/nextjs-vite@10.3.1': + resolution: {integrity: sha512-//xqijMeZGYSagUMmuRZVW4pHYWqiQozEil2NM6HUseqc3bReFNqPpDAThCVGKAckIulVIIUZbF/4Lh9OYplOA==} peerDependencies: next: ^14.1.0 || ^15.0.0 || ^16.0.0 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 - storybook: ^10.3.0 + storybook: ^10.3.1 typescript: '*' vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - '@storybook/react-dom-shim@10.3.0': - resolution: {integrity: sha512-dmAnIjkMmUYZCdg3FUL83Lavybin3bYKRNRXFZq1okCH8SINa2J+zKEzJhPlqixAKkbd7x1PFDgXnxxM/Nisig==} + '@storybook/react-dom-shim@10.3.1': + resolution: {integrity: sha512-X337d639Bw9ej8vIi29bxgRsHcrFHhux1gMSmDifYjBRhTUXE3/OeDtoEl6ZV5Pgc5BAabUF5L2cl0mb428BYQ==} 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 - storybook: ^10.3.0 + storybook: ^10.3.1 - '@storybook/react-vite@10.3.0': - resolution: {integrity: sha512-34t+30j+gglcRchPuZx4S4uusD746cvPeUPli7iJRWd3+vpnHSct03uGFAlsVJo6DZvVgH5s7vP4QU66C76K8A==} + '@storybook/react-vite@10.3.1': + resolution: {integrity: sha512-6ATC5oZKXtNFdyLR1DyJY9s6qDltFL/Dfew6loJK4bBqd5a46+wpNJebMBhBxdhHa9FDJS5tv2noNSO5kXc+Sw==} 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 - storybook: ^10.3.0 + storybook: ^10.3.1 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@storybook/react@10.3.0': - resolution: {integrity: sha512-pN++HZYVwjyJWeNg+6cewjOPkWlSho+BaUxCq/2e6yYUCr1J6MkBCYN/l1F7/ex9pDTKv9AW0da0o1aRXm3ivg==} + '@storybook/react@10.3.1': + resolution: {integrity: sha512-DoiOwfVG8VVIxA9JD3wz5lE30RTxwOnSHJJv4qdlCCiPIJWBGjxug9bqFxUZlqDkkbUzFLGDOBxYDp05Y66dbQ==} 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 - storybook: ^10.3.0 + storybook: ^10.3.1 typescript: '>= 4.9.x' peerDependenciesMeta: typescript: @@ -2992,8 +3121,8 @@ packages: '@swc/helpers@0.5.19': resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} - '@t3-oss/env-core@0.13.10': - resolution: {integrity: sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g==} + '@t3-oss/env-core@0.13.11': + resolution: {integrity: sha512-sM7GYY+KL7H/Hl0BE0inWfk3nRHZOLhmVn7sHGxaZt9FAR6KqREXAE+6TqKfiavfXmpRxO/OZ2QgKRd+oiBYRQ==} peerDependencies: arktype: ^2.1.0 typescript: '>=5.0.0' @@ -3009,8 +3138,8 @@ packages: zod: optional: true - '@t3-oss/env-nextjs@0.13.10': - resolution: {integrity: sha512-JfSA2WXOnvcc/uMdp31paMsfbYhhdvLLRxlwvrnlPE9bwM/n0Z+Qb9xRv48nPpvfMhOrkrTYw1I5Yc06WIKBJQ==} + '@t3-oss/env-nextjs@0.13.11': + resolution: {integrity: sha512-NC+3j7YWgpzdFu1t5y/8wqibTK0lm5RS4bjXA1n8uwik3wIR4iZM4Fa+U2BaMa5k3Qk8RZiYhoAIX0WogmGkzg==} peerDependencies: arktype: ^2.1.0 typescript: '>=5.0.0' @@ -3079,8 +3208,8 @@ packages: peerDependencies: solid-js: 1.9.11 - '@tanstack/eslint-plugin-query@5.91.5': - resolution: {integrity: sha512-4pqgoT5J+ntkyOoBtnxJu8LYRj3CurfNe92fghJw66mI7pZijKmOulM32Wa48cyVzGtgiuQ2o5KWC9LJVXYcBQ==} + '@tanstack/eslint-plugin-query@5.95.0': + resolution: {integrity: sha512-XvEfgHyZoeGYGt0uOFwEbgkNMrRxoPt8Gy44cu3OwYFw6CU8uPAaUUiDJCqeyvYNNkuhnR4gWRn6vu5fcFSTUQ==} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ^5.4.0 @@ -3100,11 +3229,11 @@ packages: resolution: {integrity: sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==} engines: {node: '>=18'} - '@tanstack/query-core@5.91.0': - resolution: {integrity: sha512-FYXN8Kk9Q5VKuV6AIVaNwMThSi0nvAtR4X7HQoigf6ePOtFcavJYVIzgFhOVdtbBQtCJE3KimDIMMJM2DR1hjw==} + '@tanstack/query-core@5.95.0': + resolution: {integrity: sha512-H1/CWCe8tGL3YIVeo770Z6kPbt0B3M1d/iQXIIK1qlFiFt6G2neYdkHgLapOC8uMYNt9DmHjmGukEKgdMk1P+A==} - '@tanstack/query-devtools@5.93.0': - resolution: {integrity: sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==} + '@tanstack/query-devtools@5.95.0': + resolution: {integrity: sha512-i8IzjIsZSE9y9XGndeVYeUusrZpKyhOnOPIzWKao8iAVzmk8ZesPe5URt02aLwC5A0Rg72N+vgqolXXCXm4fFg==} '@tanstack/react-devtools@0.10.0': resolution: {integrity: sha512-cUMzOQb1IHmkb8MsD0TrxHT8EL92Rx3G0Huq+IFkWeoaZPGlIiaIcGTpS5VvQDeI4BVUT+ZGt6CQTpx8oSTECg==} @@ -3129,14 +3258,14 @@ packages: '@tanstack/react-start': optional: true - '@tanstack/react-query-devtools@5.91.3': - resolution: {integrity: sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==} + '@tanstack/react-query-devtools@5.95.0': + resolution: {integrity: sha512-w4lYQTuyGM6l8C32UDIvxeodCrOwbw0JGSK6sQXYlF24CJnTcNmCxvfvrW2L3f3NObyvEQYcGTfjOr0Vw8jaWA==} peerDependencies: - '@tanstack/react-query': ^5.90.20 + '@tanstack/react-query': ^5.95.0 react: ^18 || ^19 - '@tanstack/react-query@5.91.0': - resolution: {integrity: sha512-S8FODsDTNv0Ym+o/JVBvA6EWiWVhg6K2Q4qFehZyFKk6uW4H9OPbXl4kyiN9hAly0uHJ/1GEbR6kAI4MZWfjEA==} + '@tanstack/react-query@5.95.0': + resolution: {integrity: sha512-EMP8B+BK9zvnAemT8M/y3z/WO0NjZ7fIUY3T3wnHYK6AA3qK/k33i7tPgCXCejhX0cd4I6bJIXN2GmjrHjDBzg==} peerDependencies: react: ^18 || ^19 @@ -3506,43 +3635,43 @@ packages: resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260318.1': - resolution: {integrity: sha512-hsXZC0M5N2F/KdX/wjRywZPovdGBgWw9ARy0GWCw1dAynqdfDcuceKbUw+QwMSdvvsFbUjSomTlyFdT09p1mcA==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260322.1': + resolution: {integrity: sha512-5wSilxwLGX5fMKJgsUkCBwOfW9GMG3WF5j77CVBOdFI7miFaR3JQaPzTA+uyHDMNIIeSDo1KtV77GT48Y/d0Xg==} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260318.1': - resolution: {integrity: sha512-lQl7DQkROqPZrx4C1MpFP0WNxdqv+9r4lErhd+57M2Kmxx1BmX3K5VMLJT9FZQFRtgntnYbwQAQ774Z17fv8rA==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260322.1': + resolution: {integrity: sha512-G806SrfxkYNAgZ9Xk53+OvbmIg9iD5hjaiD2QhDQL2aZjzy10D4MhcdaZEOoMfw0OI/PoJPYOiPD+9/x2kw3Lg==} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260318.1': - resolution: {integrity: sha512-1wv0qpJW4okKadShemVi4s7zGuiIRI7zTInRYDV/FfyQVyKrkTOzMtZXB6CF3Reus1HmRpGp5ADyc4MI7CCeJg==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260322.1': + resolution: {integrity: sha512-+FyomEEt3K8TBO//n3Ijr61SDM2F7cxZCVqGt+Wk3rLcOCQ2i+8+p64gdsZCmImy3CyP0hBnxPydEbyNkZLtvg==} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260318.1': - resolution: {integrity: sha512-tE7uN00Po/oBg5VYaYM0C/QXroo6gdIRmFVZl543o46ihl0YKEZBMnyStRKKgPCI9oeYXyCNT6WR4MxSMz6ndA==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260322.1': + resolution: {integrity: sha512-0a12pp19ELiNHMqTglfQQQNMsxvtzpjAa4qf12oMJoGyy+UnguKEmaaaCHdp75KvBXGDzlssfDAdiy+NirN19A==} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260318.1': - resolution: {integrity: sha512-aSE7xAKYTOrxsFrIgmcaHjgXSSOnWrZ6ozNBeNxpGzd/gl2Ho3FCIwQb0NCXrDwF9AhpFRtHMWPpAPaJk24+rg==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260322.1': + resolution: {integrity: sha512-MviQe5x4WqQGv/Vhu4hcv2A0qTW/BTaZPbOLYCtvhuovNFO6D++ZmJAbHvA0h/bJEaNTgxKZdZPHMpCfSEKfjA==} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260318.1': - resolution: {integrity: sha512-TV/Tn8cgWamb+6mvY45X2wF0vrTkQmRFCiN1pRRehEwxslDkqLVlpGAFpZndLaPlMb/wzwVpz1e/926xdAoO1w==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260322.1': + resolution: {integrity: sha512-ibnMaXDJPSgMXKC61NHiFlww/xjAEINgc1mcn2ntTfuGHwduU4P9Bi038TxXg95Wmu3v6xIPIorXXsBOdE+p3Q==} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260318.1': - resolution: {integrity: sha512-AgOZODSYeTlQWVTioRG3AxHzIBSLbZZhyK19WPzjHW0LtxCcFi59G/Gn1uIshVL3sp1ESRg9SZ5mSiFdgvfK4g==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260322.1': + resolution: {integrity: sha512-O+r1RToWBbGkK7NXC7DpraLObSWyxvSqRiSfr/BlZ351Cdq1q3121zCGzVtqERGeRtVoEMRrzS5ITOd6On/pCw==} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260318.1': - resolution: {integrity: sha512-/7LF/2x29K++k147445omxNixPANTmwJl9p/IIzK8NbOeqVOFv1Gj1GQyOQqRdT4j/X6YDwO/p400/JKE+cBOw==} + '@typescript/native-preview@7.0.0-dev.20260322.1': + resolution: {integrity: sha512-CmzQTKvesYHmz3g92G+XPDis25ocvHqa/gK8m98w+bML99KJLEWQKVlvkLrYA85JiJEK+XBIiz+6lCgUqRkWXA==} hasBin: true '@ungap/structured-clone@1.3.0': @@ -3637,15 +3766,15 @@ packages: '@vitest/utils@4.1.0': resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} - '@voidzero-dev/vite-plus-core@0.1.12': - resolution: {integrity: sha512-j8YNe7A+8JcSoddztf5whvom/yJ7OKUO3Y5a3UoLIUmOL8YEKVv5nPANrxJ7eaFfHJoMnBEwzBpq1YVZ+H3uPA==} + '@voidzero-dev/vite-plus-core@0.1.13': + resolution: {integrity: sha512-72dAIYgGrrmh4ap5Tbvzo0EYCrmVRoPQjz3NERpZ34CWCjFB8+WAyBkxG631Jz9/qC1TR/ZThjOKbdYXQ5z9Aw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@arethetypeswrong/core': ^0.18.1 - '@tsdown/css': 0.21.3 - '@tsdown/exe': 0.21.3 + '@tsdown/css': 0.21.4 + '@tsdown/exe': 0.21.4 '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.0.0-alpha.31 + '@vitejs/devtools': ^0.1.0 esbuild: 0.27.2 jiti: '>=1.21.0' less: ^4.0.0 @@ -3697,34 +3826,34 @@ packages: yaml: optional: true - '@voidzero-dev/vite-plus-darwin-arm64@0.1.12': - resolution: {integrity: sha512-tYQrfmcLxIqqr/de00oN7ayu+rYobEOjyR9AxoeJoNUqRyNQCdT0A5vg78kJNPaQCyL6ctgRRvpEKr0WHVmduQ==} + '@voidzero-dev/vite-plus-darwin-arm64@0.1.13': + resolution: {integrity: sha512-GgQ5dW1VR/Vuc8cRDsdpLMdly2rHiq8ihNKIh1eu8hR85bDjDxE4DSXeadCDMWC0bHTjQiR1HqApzjoPYsVF/w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@voidzero-dev/vite-plus-darwin-x64@0.1.12': - resolution: {integrity: sha512-852hO/Onx9Z5u0tOYOVEUVzYJUmWdlHeqYnNT6pj0IClgVp0+KSabxr7A2paTWEFWp6XbKWvqw5Y5cVwUV3A6Q==} + '@voidzero-dev/vite-plus-darwin-x64@0.1.13': + resolution: {integrity: sha512-X4ZXbjIhNg5jxEkPVn7kJZEVIvNiOCWztrY67nHD94yqsWLy2Hs7yo+DhrpEQihsnlZ1hRRtwDirdCncvEulUg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.12': - resolution: {integrity: sha512-/gTh4tGyJKCNBn9SZUs3sq9QVRUmyuyseZefBgS223QRxdwFaxc7tIKaw91X59WXXYOzUYZOD5zsTcaIF4hc9A==} + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.13': + resolution: {integrity: sha512-oPtwztuF1cierDWA68beais5mwm6dXsmOOvccn6ZHjNpKXig84LvgIoY4bMazA3Z0SE9nWqxmP0kePiO5SoiuA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.12': - resolution: {integrity: sha512-9oN9ITjK/Xq9Werx+6G6jnI3+F1S3g9lB36J1VAHyRlAEtuiCDV0E3YMoW2O7KzM/PlodZIZ8LStVkH7aA5ZCw==} + '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.13': + resolution: {integrity: sha512-RgNHwTXrnYjt60K0g083VxOjaJNXHvZXViBQd/oC7RUwGUvxuHkraq/4mWaI69Pffx2KpyykxgCrtmhWq5Tgjg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-test@0.1.12': - resolution: {integrity: sha512-EE8Y2vQvqS4c/1qSa7qlhUY9koAG6wYev0NFAtDZsijQCHUqE7nYXGJYnyUInAE6GX4zlQDGg7tf2DAl+CISYw==} + '@voidzero-dev/vite-plus-test@0.1.13': + resolution: {integrity: sha512-P3n9adJZsaIUGlgbzyT2YvlA1yr2lCYhNjrZsiLAKMVyQzk2D++ptTre3SnYf9j1TQeMP1VonRXGjtZhTf8wHg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@edge-runtime/vm': '*' @@ -3748,14 +3877,14 @@ packages: jsdom: optional: true - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.12': - resolution: {integrity: sha512-JanAb6Y+6BmPhKNLvpZB/syeyY99bt7EPJCaLlbaCt3V0Y2Iw7c7dWBM4Sg4GZ7szGYdGw385fRz0n2M32f1rg==} + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.13': + resolution: {integrity: sha512-+oygKTgglu0HkA4y9kFs8/BbHFsvShkHuL+8bK++Zek3x2ArKHRjCMgcYUXyj6nYufMIL2ba/Und7aHUK2ZGiQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.12': - resolution: {integrity: sha512-Ei/UtTTp7UgeEGyV83jhDpSMXhwaZZzfS7Xiaj+zj80GGOwsBre0i+oHGZ7+TuVsZ7Im0sD8IZ9enCpKpV//AQ==} + '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.13': + resolution: {integrity: sha512-+7zTnX/HqYCaBKmSLHjmCXQBRSSIJ6EFry55+4C0R4AMyayfn9w3LL0/NuVeCNkG69u3FnkRuwkqdWpzxztoHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -4073,10 +4202,6 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - cac@7.0.0: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} @@ -4095,8 +4220,8 @@ packages: caniuse-lite@1.0.30001780: resolution: {integrity: sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==} - canvas@3.2.1: - resolution: {integrity: sha512-ej1sPFR5+0YWtaVp6S1N1FVz69TQCqmrkGeRvQxZeAB1nAIcjNTHVwrZtYtWFFBmQsF40/uDLehsW5KuYC99mg==} + canvas@3.2.2: + resolution: {integrity: sha512-duEt4h1HHu9sJZyVKfLRXR6tsKPY7cEELzxSRJkwddOXYvQT3P/+es98SV384JA0zMOZ5s+9gatnGfM6sL4Drg==} engines: {node: ^18.12.0 || >= 20.9.0} ccount@2.0.1: @@ -4226,8 +4351,8 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc - code-inspector-plugin@1.4.4: - resolution: {integrity: sha512-fdrSiP5jJ+FFLQmUyaF52xBB1yelJJtGdzr9wwFUJlbq5di4+rfyBHIzSrYgCTU5EAMrsRZ2eSnJb4zFa8Svvw==} + code-inspector-plugin@1.4.5: + resolution: {integrity: sha512-yp3zHd5AZhtVoBNOzKQuJVo1wZe7AIO2vAiVhF8WIAK02IwM9+gY+Pr9deajx+XyJLbzMW+3CgdfLIh+xxW2Hg==} collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -4871,19 +4996,12 @@ packages: peerDependencies: eslint: ^9.0.0 || ^10.0.0 - eslint-plugin-react-dom@2.13.0: - resolution: {integrity: sha512-+2IZzQ1WEFYOWatW+xvNUqmZn55YBCufzKA7hX3XQ/8eu85Mp4vnlOyNvdVHEOGhUnGuC6+9+zLK+IlEHKdKLQ==} - engines: {node: '>=20.19.0'} + eslint-plugin-react-dom@3.0.0: + resolution: {integrity: sha512-NhxPJSGZzR/bW02wop2whWXYKE8ZLZ9JupC5MWRq1AdM+Z84jnUU8c+eobiRzIhy2OupEjKcB8TaqHuQ+3sVoQ==} + engines: {node: '>=22.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - eslint-plugin-react-hooks-extra@2.13.0: - resolution: {integrity: sha512-qIbha1nzuyhXM9SbEfrcGVqmyvQu7GAOB2sy9Y4Qo5S8nCqw4fSBxq+8lSce5Tk5Y7XzIkgHOhNyXEvUHRWFMQ==} - engines: {node: '>=20.19.0'} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' eslint-plugin-react-hooks@7.0.1: resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} @@ -4891,38 +5009,38 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react-naming-convention@2.13.0: - resolution: {integrity: sha512-uSd25JzSg2R4p81s3Wqck0AdwRlO9Yc+cZqTEXv7vW8exGGAM3mWnF6hgrgdqVJqBEGJIbS/Vx1r5BdKcY/MHA==} - engines: {node: '>=20.19.0'} + eslint-plugin-react-naming-convention@3.0.0: + resolution: {integrity: sha512-pAtOZST5/NhWIa/I5yz7H1HEZTtCY7LHMhzmN9zvaOdTWyZYtz2g9pxPRDBnkR9uSmHsNt44gj+2JSAD4xwgew==} + engines: {node: '>=22.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' eslint-plugin-react-refresh@0.5.2: resolution: {integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==} peerDependencies: eslint: ^9 || ^10 - eslint-plugin-react-rsc@2.13.0: - resolution: {integrity: sha512-RaftgITDLQm1zIgYyvR51sBdy4FlVaXFts5VISBaKbSUB0oqXyzOPxMHasfr9BCSjPLKus9zYe+G/Hr6rjFLXQ==} + eslint-plugin-react-rsc@3.0.0: + resolution: {integrity: sha512-HNP1hVO63WsV4wcXxPJJIcnYrvrN5UZyrXIbDOoCNA0axSXjJ6vA63tI2JHgyGcMTdbKxDJwaVd/dJlMudSZBQ==} engines: {node: '>=20.19.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' - eslint-plugin-react-web-api@2.13.0: - resolution: {integrity: sha512-nmJbzIAte7PeAkp22CwcKEASkKi49MshSdiDGO1XuN3f4N4/8sBfDcWbQuLPde6JiuzDT/0+l7Gi8wwTHtR1kg==} - engines: {node: '>=20.19.0'} + eslint-plugin-react-web-api@3.0.0: + resolution: {integrity: sha512-DZZh9DkZp/BE5ibaDOXaV4p8rEuMNnoPkCvAlyifB/Gz6ZhHonFRTpg+PEK6et8sx6uroUfhy5QGducmZU8Oug==} + engines: {node: '>=22.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' - eslint-plugin-react-x@2.13.0: - resolution: {integrity: sha512-cMNX0+ws/fWTgVxn52qAQbaFF2rqvaDAtjrPUzY6XOzPjY0rJQdR2tSlWJttz43r2yBfqu+LGvHlGpWL2wfpTQ==} - engines: {node: '>=20.19.0'} + eslint-plugin-react-x@3.0.0: + resolution: {integrity: sha512-W8QGWk03iqj6EiOhQk2SrrnaiTb2RZFREg1YXgYAh2/zyFztHHnNz4LTeSN+6gFwWDypMFzuFF6uoHO/1KY0Yw==} + engines: {node: '>=22.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^10.0.0 + typescript: '*' eslint-plugin-regexp@3.1.0: resolution: {integrity: sha512-qGXIC3DIKZHcK1H9A9+Byz9gmndY6TTSRkSMTZpNXdyCw2ObSehRgccJv35n9AdUakEjQp5VFNLas6BMXizCZg==} @@ -4935,11 +5053,11 @@ packages: peerDependencies: eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 - eslint-plugin-storybook@10.3.0: - resolution: {integrity: sha512-8R0/RjELXkJ2RxPusX14ZiIj1So90bPnrjbxmQx1BD+4M2VoMHfn3n+6IvzJWQH4FT5tMRRUBqjLBe1fJjRRkg==} + eslint-plugin-storybook@10.3.1: + resolution: {integrity: sha512-zWE8cQTJo2Wuw6I/Ag73rP5rLbaypm5p3G2BV74Y7Lc8NwNclAwNi5u+yl9qBQLW2aSXotDW9fjj3Mx+GeEgfA==} peerDependencies: eslint: '>=8' - storybook: ^10.3.0 + storybook: ^10.3.1 eslint-plugin-toml@1.3.1: resolution: {integrity: sha512-1l00fBP03HIt9IPV7ZxBi7x0y0NMdEZmakL1jBD6N/FoKBvfKxPw5S8XkmzBecOnFBTn5Z8sNJtL5vdf9cpRMQ==} @@ -5012,8 +5130,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.0.3: - resolution: {integrity: sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==} + eslint@10.1.0: + resolution: {integrity: sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -5244,8 +5362,8 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} - get-tsconfig@4.13.6: - resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + get-tsconfig@4.13.7: + resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -5403,8 +5521,8 @@ packages: i18next-resources-to-backend@1.2.1: resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==} - i18next@25.8.18: - resolution: {integrity: sha512-lzY5X83BiL5AP77+9DydbrqkQHFN9hUzWGjqjLpPcp5ZOzuu1aSoKaU3xbBLSjWx9dAzW431y+d+aogxOZaKRA==} + i18next@25.10.4: + resolution: {integrity: sha512-XsE/6eawy090meuFU0BTY9BtmWr1m9NSwLr0NK7/A04LA58wdAvDsi9WNOJ40Qb1E9NIPbvnVLZEN2fWDd3/3Q==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -5531,12 +5649,6 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - is-immutable-type@5.0.1: - resolution: {integrity: sha512-LkHEOGVZZXxGl8vDs+10k3DvP++SEoYEAJLRk6buTFi6kD7QekThV7xHS0j6gpnUCQ0zpud/gMDGiV4dQneLTg==} - peerDependencies: - eslint: '*' - typescript: '>=4.7.4' - is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -5642,11 +5754,11 @@ packages: resolution: {integrity: sha512-wLrulXiLpjmcUYOYGEvz4XARkrmdVpyxzdBl9IAMbQ+ib2/UhUTRCn49McdNfXLff2ysGBUms49ZKX0LR1Q0gg==} engines: {node: '>=14'} - jsdom@29.0.0: - resolution: {integrity: sha512-9FshNB6OepopZ08unmmGpsF7/qCjxGPbo3NbgfJAnPeHXnsODE9WWffXZtRFRFe0ntzaAOcSKNJFz8wiyvF1jQ==} + jsdom@29.0.1: + resolution: {integrity: sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} peerDependencies: - canvas: ^3.2.1 + canvas: ^3.2.2 peerDependenciesMeta: canvas: optional: true @@ -5696,8 +5808,8 @@ packages: resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - katex@0.16.38: - resolution: {integrity: sha512-cjHooZUmIAUmDsHBN+1n8LaZdpmbj03LtYeYPyuYB7OuloiaeaV6N4LcfjcnHVzGWjVQmKrxxTrpDcmSzEZQwQ==} + katex@0.16.40: + resolution: {integrity: sha512-1DJcK/L05k1Y9Gf7wMcyuqFOL6BiY3vY0CFcAM/LPRN04NALxcl6u7lOWNsp3f/bCHWxigzQl6FbR95XJ4R84Q==} hasBin: true keyv@4.5.4: @@ -5706,13 +5818,10 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - knip@5.88.0: - resolution: {integrity: sha512-FZjQYLYwUbVrtC3C1cKyEMMqR4K2ZlkQLZszJgF5cfDo4GUSBZAdAV0P3eyzZrkssRoghLJQA9HTQUW7G+Tc8Q==} - engines: {node: '>=18.18.0'} + knip@6.0.2: + resolution: {integrity: sha512-W17Bo5N9AYn0ZkgWHGBmK/01SrSmr3B6iStr3zudDa2eqi+Kc8VmPjSpTYKDV2Uy/kojrlcH/gS1wypAXfXRRA==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - peerDependencies: - '@types/node': '>=18' - typescript: '>=5.0.4 <7' kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -5747,8 +5856,8 @@ packages: '@lexical/utils': '>=0.28.0' lexical: '>=0.28.0' - lexical@0.41.0: - resolution: {integrity: sha512-pNIm5+n+hVnJHB9gYPDYsIO5Y59dNaDU9rJmPPsfqQhP2ojKFnUoPbcRnrI9FJLXB14sSumcY8LUw7Sq70TZqA==} + lexical@0.42.0: + resolution: {integrity: sha512-GY9Lg3YEIU7nSFaiUlLspZ1fm4NfIcfABaxy9nT+fRVDkX7iV005T5Swil83gXUmxFUNKGal3j+hUxHOUDr+Aw==} lib0@0.2.117: resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==} @@ -6238,8 +6347,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.0: - resolution: {integrity: sha512-NLBVrJy1pbV1Yn00L5sU4vFyAHt5XuSjzrNyFnxo6Com0M0KrL6hHM5B99dbqXb2bE9pm4Ow3Zl1xp6HVY9edQ==} + next@16.2.1: + resolution: {integrity: sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -6348,20 +6457,24 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + oxc-parser@0.120.0: + resolution: {integrity: sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-resolver@11.19.1: resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==} - oxfmt@0.40.0: - resolution: {integrity: sha512-g0C3I7xUj4b4DcagevM9kgH6+pUHytikxUcn3/VUkvzTNaaXBeyZqb7IBsHwojeXm4mTBEC/aBjBTMVUkZwWUQ==} + oxfmt@0.41.0: + resolution: {integrity: sha512-sKLdJZdQ3bw6x9qKiT7+eID4MNEXlDHf5ZacfIircrq6Qwjk0L6t2/JQlZZrVHTXJawK3KaMuBoJnEJPcqCEdg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint-tsgolint@0.17.0: - resolution: {integrity: sha512-TdrKhDZCgEYqONFo/j+KvGan7/k3tP5Ouz88wCqpOvJtI2QmcLfGsm1fcMvDnTik48Jj6z83IJBqlkmK9DnY1A==} + oxlint-tsgolint@0.17.1: + resolution: {integrity: sha512-gJc7hb1ZQFbWjRDYpu1XG+5IRdr1S/Jz/W2ohcpaqIXuDmHU0ujGiM0x05J0nIfwMF3HOEcANi/+j6T0Uecdpg==} hasBin: true - oxlint@1.55.0: - resolution: {integrity: sha512-T+FjepiyWpaZMhekqRpH8Z3I4vNM610p6w+Vjfqgj5TZUxHXl7N8N5IPvmOU8U4XdTRxqtNNTh9Y4hLtr7yvFg==} + oxlint@1.56.0: + resolution: {integrity: sha512-Q+5Mj5PVaH/R6/fhMMFzw4dT+KPB+kQW4kaL8FOIq7tfhlnEVp6+3lcWqFruuTNlUo9srZUW3qH7Id4pskeR6g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -6700,8 +6813,8 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - react-i18next@16.5.8: - resolution: {integrity: sha512-2ABeHHlakxVY+LSirD+OiERxFL6+zip0PaHo979bgwzeHg27Sqc82xxXWIrSFmfWX0ZkrvXMHwhsi/NGUf5VQg==} + react-i18next@16.6.1: + resolution: {integrity: sha512-izjXh+AkBLy3h3xe3sh6Gg1flhFHc3UyzsMftMKYJr2Z7WvAZQIdjjpHypctN41zFoeLdJUNGDgP1+Qich2fYg==} peerDependencies: i18next: '>= 25.6.2' react: '>= 16.8.0' @@ -7137,8 +7250,8 @@ packages: resolution: {integrity: sha512-9SN0XIjBBXCT6ZXXVnScJN4KP2RyFg6B8sEoFlugVHMANysfaEni4LTWlvUQQ/R0wgZl1Ovt9KBQbzn21kHoZA==} engines: {node: '>=20.19.0'} - storybook@10.3.0: - resolution: {integrity: sha512-OpLdng98l7cACuqBoQwewx21Vhgl9XPssgLdXQudW0+N5QPjinWXZpZCquZpXpNCyw5s5BFAcv+jKB3Qkf9jeA==} + storybook@10.3.1: + resolution: {integrity: sha512-i/CA1dUyVcF6cNL3tgPTQ/G6Evh6r3QdATuiiKObrA3QkEKmt3jrY+WeuQA7FCcmHk/vKabeliNrblaff8aY6Q==} hasBin: true peerDependencies: prettier: ^2 || ^3 @@ -7367,11 +7480,11 @@ packages: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} - tldts-core@7.0.26: - resolution: {integrity: sha512-5WJ2SqFsv4G2Dwi7ZFVRnz6b2H1od39QME1lc2y5Ew3eWiZMAeqOAfWpRP9jHvhUl881406QtZTODvjttJs+ew==} + tldts-core@7.0.27: + resolution: {integrity: sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==} - tldts@7.0.26: - resolution: {integrity: sha512-WiGwQjr0qYdNNG8KpMKlSvpxz652lqa3Rd+/hSaDcY4Uo6SKWZq2LAF+hsAhUewTtYhXlorBKgNF3Kk8hnjGoQ==} + tldts@7.0.27: + resolution: {integrity: sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==} hasBin: true to-regex-range@5.0.1: @@ -7507,8 +7620,8 @@ packages: resolution: {integrity: sha512-jxytwMHhsbdpBXxLAcuu0fzlQeXCNnWdDyRHpvWsUl8vd98UwYdl9YTyn8/HcpcJPC3pwUveefsa3zTxyD/ERg==} engines: {node: '>=20.18.1'} - undici@7.24.4: - resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} + undici@7.24.5: + resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==} engines: {node: '>=20.18.1'} unicode-trie@2.0.0: @@ -7657,8 +7770,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vinext@0.0.31: - resolution: {integrity: sha512-4se0fL74VjlUOluvozC3ZfMpKSlvkNc5Akqmm8jehXKKp7ncuTLkggLpuMsEnS9vVMywcSdE+B00d1liMfLVow==} + vinext@0.0.34: + resolution: {integrity: sha512-igBP6UrL/tEtT73hPFt6EKmHTKd4lsSZ8luV5oQjmDWPcgGew3FaSt/7vo6qK42mj7yfg5MjvmQ2VmQrm2z8Cw==} engines: {node: '>=22'} hasBin: true peerDependencies: @@ -7710,8 +7823,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 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - vite-plus@0.1.12: - resolution: {integrity: sha512-8s1RzomZkgrJRiwiYWGq3R0txFPYfBBJGp73XNHQnme0KTTVH5dNm/E2GNyBSMFJbeeF7eh1OSgqWVc2FpR6eA==} + vite-plus@0.1.13: + resolution: {integrity: sha512-DP87+eRFhYYDdcjm2nr3DOKt0cv6mIXCNXn+zc59YHgR0Wh7uL2E/55mjusJ7ajwcXenpGW+c4KPeoqhQAbhxg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -7970,26 +8083,27 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@amplitude/analytics-browser@2.36.7': + '@amplitude/analytics-browser@2.37.0': dependencies: - '@amplitude/analytics-core': 2.41.7 - '@amplitude/plugin-autocapture-browser': 1.23.7 - '@amplitude/plugin-network-capture-browser': 1.9.7 - '@amplitude/plugin-page-url-enrichment-browser': 0.6.11 - '@amplitude/plugin-page-view-tracking-browser': 2.8.7 - '@amplitude/plugin-web-vitals-browser': 1.1.22 + '@amplitude/analytics-core': 2.43.0 + '@amplitude/plugin-autocapture-browser': 1.24.1 + '@amplitude/plugin-custom-enrichment-browser': 0.1.0 + '@amplitude/plugin-network-capture-browser': 1.9.9 + '@amplitude/plugin-page-url-enrichment-browser': 0.7.0 + '@amplitude/plugin-page-view-tracking-browser': 2.9.1 + '@amplitude/plugin-web-vitals-browser': 1.1.24 tslib: 2.8.1 - '@amplitude/analytics-client-common@2.4.37': + '@amplitude/analytics-client-common@2.4.39': dependencies: '@amplitude/analytics-connector': 1.6.4 - '@amplitude/analytics-core': 2.41.7 + '@amplitude/analytics-core': 2.43.0 '@amplitude/analytics-types': 2.11.1 tslib: 2.8.1 '@amplitude/analytics-connector@1.6.4': {} - '@amplitude/analytics-core@2.41.7': + '@amplitude/analytics-core@2.43.0': dependencies: '@amplitude/analytics-connector': 1.6.4 '@types/zen-observable': 0.8.3 @@ -8003,94 +8117,99 @@ snapshots: dependencies: js-base64: 3.7.8 - '@amplitude/plugin-autocapture-browser@1.23.7': + '@amplitude/plugin-autocapture-browser@1.24.1': dependencies: - '@amplitude/analytics-core': 2.41.7 + '@amplitude/analytics-core': 2.43.0 tslib: 2.8.1 - '@amplitude/plugin-network-capture-browser@1.9.7': + '@amplitude/plugin-custom-enrichment-browser@0.1.0': dependencies: - '@amplitude/analytics-core': 2.41.7 + '@amplitude/analytics-core': 2.43.0 tslib: 2.8.1 - '@amplitude/plugin-page-url-enrichment-browser@0.6.11': + '@amplitude/plugin-network-capture-browser@1.9.9': dependencies: - '@amplitude/analytics-core': 2.41.7 + '@amplitude/analytics-core': 2.43.0 tslib: 2.8.1 - '@amplitude/plugin-page-view-tracking-browser@2.8.7': + '@amplitude/plugin-page-url-enrichment-browser@0.7.0': dependencies: - '@amplitude/analytics-core': 2.41.7 + '@amplitude/analytics-core': 2.43.0 tslib: 2.8.1 - '@amplitude/plugin-session-replay-browser@1.26.4(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0)': + '@amplitude/plugin-page-view-tracking-browser@2.9.1': dependencies: - '@amplitude/analytics-client-common': 2.4.37 - '@amplitude/analytics-core': 2.41.7 + '@amplitude/analytics-core': 2.43.0 + tslib: 2.8.1 + + '@amplitude/plugin-session-replay-browser@1.27.1(@amplitude/rrweb@2.0.0-alpha.36)(rollup@4.59.0)': + dependencies: + '@amplitude/analytics-client-common': 2.4.39 + '@amplitude/analytics-core': 2.43.0 '@amplitude/analytics-types': 2.11.1 - '@amplitude/rrweb-plugin-console-record': 2.0.0-alpha.35(@amplitude/rrweb@2.0.0-alpha.35) - '@amplitude/rrweb-record': 2.0.0-alpha.35 - '@amplitude/session-replay-browser': 1.33.1(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0) + '@amplitude/rrweb-plugin-console-record': 2.0.0-alpha.36(@amplitude/rrweb@2.0.0-alpha.36) + '@amplitude/rrweb-record': 2.0.0-alpha.36 + '@amplitude/session-replay-browser': 1.34.1(@amplitude/rrweb@2.0.0-alpha.36)(rollup@4.59.0) idb-keyval: 6.2.2 tslib: 2.8.1 transitivePeerDependencies: - '@amplitude/rrweb' - rollup - '@amplitude/plugin-web-vitals-browser@1.1.22': + '@amplitude/plugin-web-vitals-browser@1.1.24': dependencies: - '@amplitude/analytics-core': 2.41.7 + '@amplitude/analytics-core': 2.43.0 tslib: 2.8.1 web-vitals: 5.1.0 - '@amplitude/rrdom@2.0.0-alpha.35': + '@amplitude/rrdom@2.0.0-alpha.36': dependencies: - '@amplitude/rrweb-snapshot': 2.0.0-alpha.35 + '@amplitude/rrweb-snapshot': 2.0.0-alpha.36 - '@amplitude/rrweb-packer@2.0.0-alpha.35': + '@amplitude/rrweb-packer@2.0.0-alpha.36': dependencies: - '@amplitude/rrweb-types': 2.0.0-alpha.35 + '@amplitude/rrweb-types': 2.0.0-alpha.36 fflate: 0.4.8 - '@amplitude/rrweb-plugin-console-record@2.0.0-alpha.35(@amplitude/rrweb@2.0.0-alpha.35)': + '@amplitude/rrweb-plugin-console-record@2.0.0-alpha.36(@amplitude/rrweb@2.0.0-alpha.36)': dependencies: - '@amplitude/rrweb': 2.0.0-alpha.35 + '@amplitude/rrweb': 2.0.0-alpha.36 - '@amplitude/rrweb-record@2.0.0-alpha.35': + '@amplitude/rrweb-record@2.0.0-alpha.36': dependencies: - '@amplitude/rrweb': 2.0.0-alpha.35 - '@amplitude/rrweb-types': 2.0.0-alpha.35 + '@amplitude/rrweb': 2.0.0-alpha.36 + '@amplitude/rrweb-types': 2.0.0-alpha.36 - '@amplitude/rrweb-snapshot@2.0.0-alpha.35': + '@amplitude/rrweb-snapshot@2.0.0-alpha.36': dependencies: postcss: 8.5.8 - '@amplitude/rrweb-types@2.0.0-alpha.35': {} + '@amplitude/rrweb-types@2.0.0-alpha.36': {} - '@amplitude/rrweb-utils@2.0.0-alpha.35': {} + '@amplitude/rrweb-utils@2.0.0-alpha.36': {} - '@amplitude/rrweb@2.0.0-alpha.35': + '@amplitude/rrweb@2.0.0-alpha.36': dependencies: - '@amplitude/rrdom': 2.0.0-alpha.35 - '@amplitude/rrweb-snapshot': 2.0.0-alpha.35 - '@amplitude/rrweb-types': 2.0.0-alpha.35 - '@amplitude/rrweb-utils': 2.0.0-alpha.35 + '@amplitude/rrdom': 2.0.0-alpha.36 + '@amplitude/rrweb-snapshot': 2.0.0-alpha.36 + '@amplitude/rrweb-types': 2.0.0-alpha.36 + '@amplitude/rrweb-utils': 2.0.0-alpha.36 '@types/css-font-loading-module': 0.0.7 '@xstate/fsm': 1.6.5 base64-arraybuffer: 1.0.2 mitt: 3.0.1 - '@amplitude/session-replay-browser@1.33.1(@amplitude/rrweb@2.0.0-alpha.35)(rollup@4.59.0)': + '@amplitude/session-replay-browser@1.34.1(@amplitude/rrweb@2.0.0-alpha.36)(rollup@4.59.0)': dependencies: - '@amplitude/analytics-client-common': 2.4.37 - '@amplitude/analytics-core': 2.41.7 + '@amplitude/analytics-client-common': 2.4.39 + '@amplitude/analytics-core': 2.43.0 '@amplitude/analytics-types': 2.11.1 '@amplitude/experiment-core': 0.7.2 - '@amplitude/rrweb-packer': 2.0.0-alpha.35 - '@amplitude/rrweb-plugin-console-record': 2.0.0-alpha.35(@amplitude/rrweb@2.0.0-alpha.35) - '@amplitude/rrweb-record': 2.0.0-alpha.35 - '@amplitude/rrweb-types': 2.0.0-alpha.35 - '@amplitude/rrweb-utils': 2.0.0-alpha.35 + '@amplitude/rrweb-packer': 2.0.0-alpha.36 + '@amplitude/rrweb-plugin-console-record': 2.0.0-alpha.36(@amplitude/rrweb@2.0.0-alpha.36) + '@amplitude/rrweb-record': 2.0.0-alpha.36 + '@amplitude/rrweb-types': 2.0.0-alpha.36 + '@amplitude/rrweb-utils': 2.0.0-alpha.36 '@amplitude/targeting': 0.2.0 '@rollup/plugin-replace': 6.0.3(rollup@4.59.0) idb: 8.0.0 @@ -8101,57 +8220,57 @@ snapshots: '@amplitude/targeting@0.2.0': dependencies: - '@amplitude/analytics-client-common': 2.4.37 - '@amplitude/analytics-core': 2.41.7 + '@amplitude/analytics-client-common': 2.4.39 + '@amplitude/analytics-core': 2.43.0 '@amplitude/analytics-types': 2.11.1 '@amplitude/experiment-core': 0.7.2 idb: 8.0.0 tslib: 2.8.1 - '@antfu/eslint-config@7.7.3(@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.2.0)(@typescript-eslint/rule-tester@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)))(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.17.0))(typescript@5.9.3)': + '@antfu/eslint-config@7.7.3(@eslint-react/eslint-plugin@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@16.2.1)(@typescript-eslint/rule-tester@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(@vue/compiler-sfc@3.5.30)(eslint-plugin-react-hooks@7.0.1(eslint@10.1.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.5.2(eslint@10.1.0(jiti@1.21.7)))(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))(typescript@5.9.3)': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 1.1.0 - '@e18e/eslint-plugin': 0.2.0(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.17.0)) - '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.0.3(jiti@1.21.7)) + '@e18e/eslint-plugin': 0.2.0(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1)) + '@eslint-community/eslint-plugin-eslint-comments': 4.7.1(eslint@10.1.0(jiti@1.21.7)) '@eslint/markdown': 7.5.1 - '@stylistic/eslint-plugin': 5.10.0(eslint@10.0.3(jiti@1.21.7)) - '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/parser': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@vitest/eslint-plugin': 1.6.12(@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.1.0(jiti@1.21.7)) + '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@vitest/eslint-plugin': 1.6.12(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) ansis: 4.2.0 cac: 7.0.0 - eslint: 10.0.3(jiti@1.21.7) - eslint-config-flat-gitignore: 2.2.1(eslint@10.0.3(jiti@1.21.7)) + eslint: 10.1.0(jiti@1.21.7) + eslint-config-flat-gitignore: 2.2.1(eslint@10.1.0(jiti@1.21.7)) eslint-flat-config-utils: 3.0.2 - eslint-merge-processors: 2.0.0(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-antfu: 3.2.2(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-command: 3.5.2(@typescript-eslint/rule-tester@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-import-lite: 0.5.2(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-jsdoc: 62.8.0(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-jsonc: 3.1.2(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-n: 17.24.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + eslint-merge-processors: 2.0.0(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-antfu: 3.2.2(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-command: 3.5.2(@typescript-eslint/rule-tester@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-import-lite: 0.5.2(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-jsdoc: 62.8.0(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-jsonc: 3.1.2(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-n: 17.24.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-perfectionist: 5.7.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-pnpm: 1.6.0(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-regexp: 3.1.0(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-toml: 1.3.1(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-unicorn: 63.0.0(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.0.3(jiti@1.21.7)))(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(vue-eslint-parser@10.4.0(eslint@10.0.3(jiti@1.21.7))) - eslint-plugin-yml: 3.3.1(eslint@10.0.3(jiti@1.21.7)) - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.30)(eslint@10.0.3(jiti@1.21.7)) + eslint-plugin-perfectionist: 5.7.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-pnpm: 1.6.0(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-regexp: 3.1.0(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-toml: 1.3.1(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-unicorn: 63.0.0(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-vue: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@1.21.7)))(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@1.21.7))) + eslint-plugin-yml: 3.3.1(eslint@10.1.0(jiti@1.21.7)) + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.30)(eslint@10.1.0(jiti@1.21.7)) globals: 17.4.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.0.3(jiti@1.21.7)) + vue-eslint-parser: 10.4.0(eslint@10.1.0(jiti@1.21.7)) yaml-eslint-parser: 2.0.0 optionalDependencies: - '@eslint-react/eslint-plugin': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@next/eslint-plugin-next': 16.2.0 - eslint-plugin-react-hooks: 7.0.1(eslint@10.0.3(jiti@1.21.7)) - eslint-plugin-react-refresh: 0.5.2(eslint@10.0.3(jiti@1.21.7)) + '@eslint-react/eslint-plugin': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@next/eslint-plugin-next': 16.2.1 + eslint-plugin-react-hooks: 7.0.1(eslint@10.1.0(jiti@1.21.7)) + eslint-plugin-react-refresh: 0.5.2(eslint@10.1.0(jiti@1.21.7)) transitivePeerDependencies: - '@eslint/json' - '@typescript-eslint/rule-tester' @@ -8347,13 +8466,13 @@ snapshots: '@chevrotain/utils@11.1.2': {} - '@chromatic-com/storybook@5.0.1(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': + '@chromatic-com/storybook@5.0.2(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': dependencies: '@neoconfetti/react': 1.0.0 chromatic: 13.3.5 filesize: 10.1.6 jsonfile: 6.2.0 - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) strip-ansi: 7.2.0 transitivePeerDependencies: - '@chromatic-com/cypress' @@ -8379,7 +8498,7 @@ snapshots: '@clack/core': 1.1.0 sisteransi: 1.0.5 - '@code-inspector/core@1.4.4': + '@code-inspector/core@1.4.5': dependencies: '@vue/compiler-dom': 3.5.30 chalk: 4.1.2 @@ -8389,35 +8508,35 @@ snapshots: transitivePeerDependencies: - supports-color - '@code-inspector/esbuild@1.4.4': + '@code-inspector/esbuild@1.4.5': dependencies: - '@code-inspector/core': 1.4.4 + '@code-inspector/core': 1.4.5 transitivePeerDependencies: - supports-color - '@code-inspector/mako@1.4.4': + '@code-inspector/mako@1.4.5': dependencies: - '@code-inspector/core': 1.4.4 + '@code-inspector/core': 1.4.5 transitivePeerDependencies: - supports-color - '@code-inspector/turbopack@1.4.4': + '@code-inspector/turbopack@1.4.5': dependencies: - '@code-inspector/core': 1.4.4 - '@code-inspector/webpack': 1.4.4 + '@code-inspector/core': 1.4.5 + '@code-inspector/webpack': 1.4.5 transitivePeerDependencies: - supports-color - '@code-inspector/vite@1.4.4': + '@code-inspector/vite@1.4.5': dependencies: - '@code-inspector/core': 1.4.4 + '@code-inspector/core': 1.4.5 chalk: 4.1.1 transitivePeerDependencies: - supports-color - '@code-inspector/webpack@1.4.4': + '@code-inspector/webpack@1.4.5': dependencies: - '@code-inspector/core': 1.4.4 + '@code-inspector/core': 1.4.5 transitivePeerDependencies: - supports-color @@ -8445,12 +8564,12 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} - '@e18e/eslint-plugin@0.2.0(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.17.0))': + '@e18e/eslint-plugin@0.2.0(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))': dependencies: - eslint-plugin-depend: 1.5.0(eslint@10.0.3(jiti@1.21.7)) + eslint-plugin-depend: 1.5.0(eslint@10.1.0(jiti@1.21.7)) optionalDependencies: - eslint: 10.0.3(jiti@1.21.7) - oxlint: 1.55.0(oxlint-tsgolint@0.17.0) + eslint: 10.1.0(jiti@1.21.7) + oxlint: 1.56.0(oxlint-tsgolint@0.17.1) '@egoist/tailwindcss-icons@1.9.2(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))': dependencies: @@ -8563,15 +8682,15 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.0.3(jiti@1.21.7))': + '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.1.0(jiti@1.21.7))': dependencies: escape-string-regexp: 4.0.0 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) ignore: 7.0.5 - '@eslint-community/eslint-utils@4.9.1(eslint@10.0.3(jiti@1.21.7))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.1.0(jiti@1.21.7))': dependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) eslint-visitor-keys: 3.4.3 '@eslint-community/eslint-utils@4.9.1(eslint@9.27.0(jiti@1.21.7))': @@ -8581,85 +8700,77 @@ snapshots: '@eslint-community/regexpp@4.12.2': {} - '@eslint-react/ast@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@eslint-react/ast@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-react/eff': 2.13.0 '@typescript-eslint/types': 8.57.1 '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) string-ts: 2.3.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@eslint-react/core@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@eslint-react/core@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-react/ast': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 2.13.0 - '@eslint-react/shared': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/shared': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.57.1 '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) ts-pattern: 5.9.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@eslint-react/eff@2.13.0': {} - - '@eslint-react/eslint-plugin@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@eslint-react/eslint-plugin@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-react/eff': 2.13.0 - '@eslint-react/shared': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/shared': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) - eslint-plugin-react-dom: 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-hooks-extra: 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-naming-convention: 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-rsc: 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-web-api: 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-x: 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) + eslint-plugin-react-dom: 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-naming-convention: 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-rsc: 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-web-api: 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-x: 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@eslint-react/shared@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@eslint-react/shared@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-react/eff': 2.13.0 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) ts-pattern: 5.9.0 typescript: 5.9.3 zod: 4.3.6 transitivePeerDependencies: - supports-color - '@eslint-react/var@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@eslint-react/var@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-react/ast': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 2.13.0 - '@eslint-react/shared': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/shared': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.57.1 '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) ts-pattern: 5.9.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@eslint/compat@2.0.3(eslint@10.0.3(jiti@1.21.7))': + '@eslint/compat@2.0.3(eslint@10.1.0(jiti@1.21.7))': dependencies: '@eslint/core': 1.1.1 optionalDependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) '@eslint/config-array@0.20.1': dependencies: @@ -8968,11 +9079,11 @@ snapshots: dependencies: minipass: 7.1.3 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3)': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3)': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@5.9.3) - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' optionalDependencies: typescript: 5.9.3 @@ -9000,157 +9111,161 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@lexical/clipboard@0.41.0': + '@lexical/clipboard@0.42.0': dependencies: - '@lexical/html': 0.41.0 - '@lexical/list': 0.41.0 - '@lexical/selection': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/html': 0.42.0 + '@lexical/list': 0.42.0 + '@lexical/selection': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/devtools-core@0.41.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@lexical/code-core@0.42.0': dependencies: - '@lexical/html': 0.41.0 - '@lexical/link': 0.41.0 - '@lexical/mark': 0.41.0 - '@lexical/table': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + lexical: 0.42.0 + + '@lexical/devtools-core@0.42.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@lexical/html': 0.42.0 + '@lexical/link': 0.42.0 + '@lexical/mark': 0.42.0 + '@lexical/table': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@lexical/dragon@0.41.0': + '@lexical/dragon@0.42.0': dependencies: - '@lexical/extension': 0.41.0 - lexical: 0.41.0 + '@lexical/extension': 0.42.0 + lexical: 0.42.0 - '@lexical/extension@0.41.0': + '@lexical/extension@0.42.0': dependencies: - '@lexical/utils': 0.41.0 + '@lexical/utils': 0.42.0 '@preact/signals-core': 1.14.0 - lexical: 0.41.0 + lexical: 0.42.0 - '@lexical/hashtag@0.41.0': + '@lexical/hashtag@0.42.0': dependencies: - '@lexical/text': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/text': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/history@0.41.0': + '@lexical/history@0.42.0': dependencies: - '@lexical/extension': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/extension': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/html@0.41.0': + '@lexical/html@0.42.0': dependencies: - '@lexical/selection': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/selection': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/link@0.41.0': + '@lexical/link@0.42.0': dependencies: - '@lexical/extension': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/extension': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/list@0.41.0': + '@lexical/list@0.42.0': dependencies: - '@lexical/extension': 0.41.0 - '@lexical/selection': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/extension': 0.42.0 + '@lexical/selection': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/mark@0.41.0': + '@lexical/mark@0.42.0': dependencies: - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/markdown@0.41.0': + '@lexical/markdown@0.42.0': dependencies: - '@lexical/code': lexical-code-no-prism@0.41.0(@lexical/utils@0.41.0)(lexical@0.41.0) - '@lexical/link': 0.41.0 - '@lexical/list': 0.41.0 - '@lexical/rich-text': 0.41.0 - '@lexical/text': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/code-core': 0.42.0 + '@lexical/link': 0.42.0 + '@lexical/list': 0.42.0 + '@lexical/rich-text': 0.42.0 + '@lexical/text': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/offset@0.41.0': + '@lexical/offset@0.42.0': dependencies: - lexical: 0.41.0 + lexical: 0.42.0 - '@lexical/overflow@0.41.0': + '@lexical/overflow@0.42.0': dependencies: - lexical: 0.41.0 + lexical: 0.42.0 - '@lexical/plain-text@0.41.0': + '@lexical/plain-text@0.42.0': dependencies: - '@lexical/clipboard': 0.41.0 - '@lexical/dragon': 0.41.0 - '@lexical/selection': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/clipboard': 0.42.0 + '@lexical/dragon': 0.42.0 + '@lexical/selection': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/react@0.41.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(yjs@13.6.30)': + '@lexical/react@0.42.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(yjs@13.6.30)': dependencies: '@floating-ui/react': 0.27.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@lexical/devtools-core': 0.41.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@lexical/dragon': 0.41.0 - '@lexical/extension': 0.41.0 - '@lexical/hashtag': 0.41.0 - '@lexical/history': 0.41.0 - '@lexical/link': 0.41.0 - '@lexical/list': 0.41.0 - '@lexical/mark': 0.41.0 - '@lexical/markdown': 0.41.0 - '@lexical/overflow': 0.41.0 - '@lexical/plain-text': 0.41.0 - '@lexical/rich-text': 0.41.0 - '@lexical/table': 0.41.0 - '@lexical/text': 0.41.0 - '@lexical/utils': 0.41.0 - '@lexical/yjs': 0.41.0(yjs@13.6.30) - lexical: 0.41.0 + '@lexical/devtools-core': 0.42.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@lexical/dragon': 0.42.0 + '@lexical/extension': 0.42.0 + '@lexical/hashtag': 0.42.0 + '@lexical/history': 0.42.0 + '@lexical/link': 0.42.0 + '@lexical/list': 0.42.0 + '@lexical/mark': 0.42.0 + '@lexical/markdown': 0.42.0 + '@lexical/overflow': 0.42.0 + '@lexical/plain-text': 0.42.0 + '@lexical/rich-text': 0.42.0 + '@lexical/table': 0.42.0 + '@lexical/text': 0.42.0 + '@lexical/utils': 0.42.0 + '@lexical/yjs': 0.42.0(yjs@13.6.30) + lexical: 0.42.0 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) react-error-boundary: 6.1.1(react@19.2.4) transitivePeerDependencies: - yjs - '@lexical/rich-text@0.41.0': + '@lexical/rich-text@0.42.0': dependencies: - '@lexical/clipboard': 0.41.0 - '@lexical/dragon': 0.41.0 - '@lexical/selection': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/clipboard': 0.42.0 + '@lexical/dragon': 0.42.0 + '@lexical/selection': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/selection@0.41.0': + '@lexical/selection@0.42.0': dependencies: - lexical: 0.41.0 + lexical: 0.42.0 - '@lexical/table@0.41.0': + '@lexical/table@0.42.0': dependencies: - '@lexical/clipboard': 0.41.0 - '@lexical/extension': 0.41.0 - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/clipboard': 0.42.0 + '@lexical/extension': 0.42.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - '@lexical/text@0.41.0': + '@lexical/text@0.42.0': dependencies: - lexical: 0.41.0 + lexical: 0.42.0 - '@lexical/utils@0.41.0': + '@lexical/utils@0.42.0': dependencies: - '@lexical/selection': 0.41.0 - lexical: 0.41.0 + '@lexical/selection': 0.42.0 + lexical: 0.42.0 - '@lexical/yjs@0.41.0(yjs@13.6.30)': + '@lexical/yjs@0.42.0(yjs@13.6.30)': dependencies: - '@lexical/offset': 0.41.0 - '@lexical/selection': 0.41.0 - lexical: 0.41.0 + '@lexical/offset': 0.42.0 + '@lexical/selection': 0.42.0 + lexical: 0.42.0 yjs: 13.6.30 '@mdx-js/loader@3.1.1(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': @@ -9243,41 +9358,41 @@ snapshots: '@next/env@16.0.0': {} - '@next/env@16.2.0': {} + '@next/env@16.2.1': {} - '@next/eslint-plugin-next@16.2.0': + '@next/eslint-plugin-next@16.2.1': dependencies: fast-glob: 3.3.1 - '@next/mdx@16.2.0(@mdx-js/loader@3.1.1(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4))': + '@next/mdx@16.2.1(@mdx-js/loader@3.1.1(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4))': dependencies: source-map: 0.7.6 optionalDependencies: '@mdx-js/loader': 3.1.1(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.4) - '@next/swc-darwin-arm64@16.2.0': + '@next/swc-darwin-arm64@16.2.1': optional: true - '@next/swc-darwin-x64@16.2.0': + '@next/swc-darwin-x64@16.2.1': optional: true - '@next/swc-linux-arm64-gnu@16.2.0': + '@next/swc-linux-arm64-gnu@16.2.1': optional: true - '@next/swc-linux-arm64-musl@16.2.0': + '@next/swc-linux-arm64-musl@16.2.1': optional: true - '@next/swc-linux-x64-gnu@16.2.0': + '@next/swc-linux-x64-gnu@16.2.1': optional: true - '@next/swc-linux-x64-musl@16.2.0': + '@next/swc-linux-x64-musl@16.2.1': optional: true - '@next/swc-win32-arm64-msvc@16.2.0': + '@next/swc-win32-arm64-msvc@16.2.1': optional: true - '@next/swc-win32-x64-msvc@16.2.0': + '@next/swc-win32-x64-msvc@16.2.1': optional: true '@nodelib/fs.scandir@2.1.5': @@ -9349,71 +9464,133 @@ snapshots: '@open-draft/until@2.1.0': {} - '@orpc/client@1.13.8': + '@orpc/client@1.13.9': dependencies: - '@orpc/shared': 1.13.8 - '@orpc/standard-server': 1.13.8 - '@orpc/standard-server-fetch': 1.13.8 - '@orpc/standard-server-peer': 1.13.8 + '@orpc/shared': 1.13.9 + '@orpc/standard-server': 1.13.9 + '@orpc/standard-server-fetch': 1.13.9 + '@orpc/standard-server-peer': 1.13.9 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/contract@1.13.8': + '@orpc/contract@1.13.9': dependencies: - '@orpc/client': 1.13.8 - '@orpc/shared': 1.13.8 + '@orpc/client': 1.13.9 + '@orpc/shared': 1.13.9 '@standard-schema/spec': 1.1.0 openapi-types: 12.1.3 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/openapi-client@1.13.8': + '@orpc/openapi-client@1.13.9': dependencies: - '@orpc/client': 1.13.8 - '@orpc/contract': 1.13.8 - '@orpc/shared': 1.13.8 - '@orpc/standard-server': 1.13.8 + '@orpc/client': 1.13.9 + '@orpc/contract': 1.13.9 + '@orpc/shared': 1.13.9 + '@orpc/standard-server': 1.13.9 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/shared@1.13.8': + '@orpc/shared@1.13.9': dependencies: radash: 12.1.1 type-fest: 5.4.4 - '@orpc/standard-server-fetch@1.13.8': + '@orpc/standard-server-fetch@1.13.9': dependencies: - '@orpc/shared': 1.13.8 - '@orpc/standard-server': 1.13.8 + '@orpc/shared': 1.13.9 + '@orpc/standard-server': 1.13.9 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server-peer@1.13.8': + '@orpc/standard-server-peer@1.13.9': dependencies: - '@orpc/shared': 1.13.8 - '@orpc/standard-server': 1.13.8 + '@orpc/shared': 1.13.9 + '@orpc/standard-server': 1.13.9 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/standard-server@1.13.8': + '@orpc/standard-server@1.13.9': dependencies: - '@orpc/shared': 1.13.8 + '@orpc/shared': 1.13.9 transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/tanstack-query@1.13.8(@orpc/client@1.13.8)(@tanstack/query-core@5.91.0)': + '@orpc/tanstack-query@1.13.9(@orpc/client@1.13.9)(@tanstack/query-core@5.95.0)': dependencies: - '@orpc/client': 1.13.8 - '@orpc/shared': 1.13.8 - '@tanstack/query-core': 5.91.0 + '@orpc/client': 1.13.9 + '@orpc/shared': 1.13.9 + '@tanstack/query-core': 5.95.0 transitivePeerDependencies: - '@opentelemetry/api' '@ota-meshi/ast-token-store@0.3.0': {} - '@oxc-project/runtime@0.115.0': {} + '@oxc-parser/binding-android-arm-eabi@0.120.0': + optional: true - '@oxc-project/types@0.115.0': {} + '@oxc-parser/binding-android-arm64@0.120.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.120.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.120.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.120.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.120.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.120.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.120.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.120.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.120.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.120.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.120.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.120.0': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.120.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.120.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.120.0': + optional: true + + '@oxc-project/runtime@0.120.0': {} + + '@oxc-project/types@0.120.0': {} '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -9477,136 +9654,136 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.19.1': optional: true - '@oxfmt/binding-android-arm-eabi@0.40.0': + '@oxfmt/binding-android-arm-eabi@0.41.0': optional: true - '@oxfmt/binding-android-arm64@0.40.0': + '@oxfmt/binding-android-arm64@0.41.0': optional: true - '@oxfmt/binding-darwin-arm64@0.40.0': + '@oxfmt/binding-darwin-arm64@0.41.0': optional: true - '@oxfmt/binding-darwin-x64@0.40.0': + '@oxfmt/binding-darwin-x64@0.41.0': optional: true - '@oxfmt/binding-freebsd-x64@0.40.0': + '@oxfmt/binding-freebsd-x64@0.41.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.40.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.41.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.40.0': + '@oxfmt/binding-linux-arm-musleabihf@0.41.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.40.0': + '@oxfmt/binding-linux-arm64-gnu@0.41.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.40.0': + '@oxfmt/binding-linux-arm64-musl@0.41.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.40.0': + '@oxfmt/binding-linux-ppc64-gnu@0.41.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.40.0': + '@oxfmt/binding-linux-riscv64-gnu@0.41.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.40.0': + '@oxfmt/binding-linux-riscv64-musl@0.41.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.40.0': + '@oxfmt/binding-linux-s390x-gnu@0.41.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.40.0': + '@oxfmt/binding-linux-x64-gnu@0.41.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.40.0': + '@oxfmt/binding-linux-x64-musl@0.41.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.40.0': + '@oxfmt/binding-openharmony-arm64@0.41.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.40.0': + '@oxfmt/binding-win32-arm64-msvc@0.41.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.40.0': + '@oxfmt/binding-win32-ia32-msvc@0.41.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.40.0': + '@oxfmt/binding-win32-x64-msvc@0.41.0': optional: true - '@oxlint-tsgolint/darwin-arm64@0.17.0': + '@oxlint-tsgolint/darwin-arm64@0.17.1': optional: true - '@oxlint-tsgolint/darwin-x64@0.17.0': + '@oxlint-tsgolint/darwin-x64@0.17.1': optional: true - '@oxlint-tsgolint/linux-arm64@0.17.0': + '@oxlint-tsgolint/linux-arm64@0.17.1': optional: true - '@oxlint-tsgolint/linux-x64@0.17.0': + '@oxlint-tsgolint/linux-x64@0.17.1': optional: true - '@oxlint-tsgolint/win32-arm64@0.17.0': + '@oxlint-tsgolint/win32-arm64@0.17.1': optional: true - '@oxlint-tsgolint/win32-x64@0.17.0': + '@oxlint-tsgolint/win32-x64@0.17.1': optional: true - '@oxlint/binding-android-arm-eabi@1.55.0': + '@oxlint/binding-android-arm-eabi@1.56.0': optional: true - '@oxlint/binding-android-arm64@1.55.0': + '@oxlint/binding-android-arm64@1.56.0': optional: true - '@oxlint/binding-darwin-arm64@1.55.0': + '@oxlint/binding-darwin-arm64@1.56.0': optional: true - '@oxlint/binding-darwin-x64@1.55.0': + '@oxlint/binding-darwin-x64@1.56.0': optional: true - '@oxlint/binding-freebsd-x64@1.55.0': + '@oxlint/binding-freebsd-x64@1.56.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.55.0': + '@oxlint/binding-linux-arm-gnueabihf@1.56.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.55.0': + '@oxlint/binding-linux-arm-musleabihf@1.56.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.55.0': + '@oxlint/binding-linux-arm64-gnu@1.56.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.55.0': + '@oxlint/binding-linux-arm64-musl@1.56.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.55.0': + '@oxlint/binding-linux-ppc64-gnu@1.56.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.55.0': + '@oxlint/binding-linux-riscv64-gnu@1.56.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.55.0': + '@oxlint/binding-linux-riscv64-musl@1.56.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.55.0': + '@oxlint/binding-linux-s390x-gnu@1.56.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.55.0': + '@oxlint/binding-linux-x64-gnu@1.56.0': optional: true - '@oxlint/binding-linux-x64-musl@1.55.0': + '@oxlint/binding-linux-x64-musl@1.56.0': optional: true - '@oxlint/binding-openharmony-arm64@1.55.0': + '@oxlint/binding-openharmony-arm64@1.56.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.55.0': + '@oxlint/binding-win32-arm64-msvc@1.56.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.55.0': + '@oxlint/binding-win32-ia32-msvc@1.56.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.55.0': + '@oxlint/binding-win32-x64-msvc@1.56.0': optional: true '@parcel/watcher-android-arm64@2.5.6': @@ -10068,38 +10245,38 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true - '@sentry-internal/browser-utils@10.44.0': + '@sentry-internal/browser-utils@10.45.0': dependencies: - '@sentry/core': 10.44.0 + '@sentry/core': 10.45.0 - '@sentry-internal/feedback@10.44.0': + '@sentry-internal/feedback@10.45.0': dependencies: - '@sentry/core': 10.44.0 + '@sentry/core': 10.45.0 - '@sentry-internal/replay-canvas@10.44.0': + '@sentry-internal/replay-canvas@10.45.0': dependencies: - '@sentry-internal/replay': 10.44.0 - '@sentry/core': 10.44.0 + '@sentry-internal/replay': 10.45.0 + '@sentry/core': 10.45.0 - '@sentry-internal/replay@10.44.0': + '@sentry-internal/replay@10.45.0': dependencies: - '@sentry-internal/browser-utils': 10.44.0 - '@sentry/core': 10.44.0 + '@sentry-internal/browser-utils': 10.45.0 + '@sentry/core': 10.45.0 - '@sentry/browser@10.44.0': + '@sentry/browser@10.45.0': dependencies: - '@sentry-internal/browser-utils': 10.44.0 - '@sentry-internal/feedback': 10.44.0 - '@sentry-internal/replay': 10.44.0 - '@sentry-internal/replay-canvas': 10.44.0 - '@sentry/core': 10.44.0 + '@sentry-internal/browser-utils': 10.45.0 + '@sentry-internal/feedback': 10.45.0 + '@sentry-internal/replay': 10.45.0 + '@sentry-internal/replay-canvas': 10.45.0 + '@sentry/core': 10.45.0 - '@sentry/core@10.44.0': {} + '@sentry/core@10.45.0': {} - '@sentry/react@10.44.0(react@19.2.4)': + '@sentry/react@10.45.0(react@19.2.4)': dependencies: - '@sentry/browser': 10.44.0 - '@sentry/core': 10.44.0 + '@sentry/browser': 10.45.0 + '@sentry/core': 10.45.0 react: 19.2.4 '@shuding/opentype.js@1.4.0-beta.0': @@ -10147,15 +10324,15 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@storybook/addon-docs@10.3.0(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/addon-docs@10.3.1(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.4) - '@storybook/csf-plugin': 10.3.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/csf-plugin': 10.3.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@storybook/react-dom-shim': 10.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + '@storybook/react-dom-shim': 10.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' @@ -10164,41 +10341,41 @@ snapshots: - vite - webpack - '@storybook/addon-links@10.3.0(react@19.2.4)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': + '@storybook/addon-links@10.3.1(react@19.2.4)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': dependencies: '@storybook/global': 5.0.0 - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) optionalDependencies: react: 19.2.4 - '@storybook/addon-onboarding@10.3.0(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': + '@storybook/addon-onboarding@10.3.1(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': dependencies: - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@storybook/addon-themes@10.3.0(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': + '@storybook/addon-themes@10.3.1(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': dependencies: - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - '@storybook/builder-vite@10.3.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/builder-vite@10.3.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@storybook/csf-plugin': 10.3.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@storybook/csf-plugin': 10.3.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.3.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/csf-plugin@10.3.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.2 rollup: 4.59.0 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' webpack: 5.105.4(esbuild@0.27.2)(uglify-js@3.19.3) '@storybook/global@5.0.0': {} @@ -10208,18 +10385,18 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@storybook/nextjs-vite@10.3.0(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(next@16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/nextjs-vite@10.3.1(@babel/core@7.29.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@storybook/builder-vite': 10.3.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) - '@storybook/react': 10.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) - '@storybook/react-vite': 10.3.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) - next: 16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) + '@storybook/builder-vite': 10.3.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/react': 10.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@storybook/react-vite': 10.3.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + next: 16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4) - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - vite-plugin-storybook-nextjs: 3.2.3(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-plugin-storybook-nextjs: 3.2.3(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -10230,27 +10407,27 @@ snapshots: - supports-color - webpack - '@storybook/react-dom-shim@10.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': + '@storybook/react-dom-shim@10.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': dependencies: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@storybook/react-vite@10.3.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': + '@storybook/react-vite@10.3.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.4(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) - '@storybook/builder-vite': 10.3.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) - '@storybook/react': 10.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@storybook/builder-vite': 10.3.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(rollup@4.59.0)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) + '@storybook/react': 10.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 react: 19.2.4 react-docgen: 8.0.3 react-dom: 19.2.4(react@19.2.4) resolve: 1.22.11 - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tsconfig-paths: 4.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - esbuild - rollup @@ -10258,15 +10435,15 @@ snapshots: - typescript - webpack - '@storybook/react@10.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@storybook/react@10.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 10.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + '@storybook/react-dom-shim': 10.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) react: 19.2.4 react-docgen: 8.0.3 react-docgen-typescript: 2.4.0(typescript@5.9.3) react-dom: 19.2.4(react@19.2.4) - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -10274,18 +10451,18 @@ snapshots: '@streamdown/math@1.0.2(react@19.2.4)': dependencies: - katex: 0.16.38 + katex: 0.16.40 react: 19.2.4 rehype-katex: 7.0.1 remark-math: 6.0.0 transitivePeerDependencies: - supports-color - '@stylistic/eslint-plugin@5.10.0(eslint@10.0.3(jiti@1.21.7))': + '@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@1.21.7))': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) '@typescript-eslint/types': 8.57.1 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -10301,15 +10478,15 @@ snapshots: dependencies: tslib: 2.8.1 - '@t3-oss/env-core@0.13.10(typescript@5.9.3)(valibot@1.3.0(typescript@5.9.3))(zod@4.3.6)': + '@t3-oss/env-core@0.13.11(typescript@5.9.3)(valibot@1.3.0(typescript@5.9.3))(zod@4.3.6)': optionalDependencies: typescript: 5.9.3 valibot: 1.3.0(typescript@5.9.3) zod: 4.3.6 - '@t3-oss/env-nextjs@0.13.10(typescript@5.9.3)(valibot@1.3.0(typescript@5.9.3))(zod@4.3.6)': + '@t3-oss/env-nextjs@0.13.11(typescript@5.9.3)(valibot@1.3.0(typescript@5.9.3))(zod@4.3.6)': dependencies: - '@t3-oss/env-core': 0.13.10(typescript@5.9.3)(valibot@1.3.0(typescript@5.9.3))(zod@4.3.6) + '@t3-oss/env-core': 0.13.11(typescript@5.9.3)(valibot@1.3.0(typescript@5.9.3))(zod@4.3.6) optionalDependencies: typescript: 5.9.3 valibot: 1.3.0(typescript@5.9.3) @@ -10364,10 +10541,10 @@ snapshots: - csstype - utf-8-validate - '@tanstack/eslint-plugin-query@5.91.5(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@tanstack/eslint-plugin-query@5.95.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -10397,9 +10574,9 @@ snapshots: '@tanstack/pacer-lite@0.1.1': {} - '@tanstack/query-core@5.91.0': {} + '@tanstack/query-core@5.95.0': {} - '@tanstack/query-devtools@5.93.0': {} + '@tanstack/query-devtools@5.95.0': {} '@tanstack/react-devtools@0.10.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)': dependencies: @@ -10434,15 +10611,15 @@ snapshots: transitivePeerDependencies: - react-dom - '@tanstack/react-query-devtools@5.91.3(@tanstack/react-query@5.91.0(react@19.2.4))(react@19.2.4)': + '@tanstack/react-query-devtools@5.95.0(@tanstack/react-query@5.95.0(react@19.2.4))(react@19.2.4)': dependencies: - '@tanstack/query-devtools': 5.93.0 - '@tanstack/react-query': 5.91.0(react@19.2.4) + '@tanstack/query-devtools': 5.95.0 + '@tanstack/react-query': 5.95.0(react@19.2.4) react: 19.2.4 - '@tanstack/react-query@5.91.0(react@19.2.4)': + '@tanstack/react-query@5.95.0(react@19.2.4)': dependencies: - '@tanstack/query-core': 5.91.0 + '@tanstack/query-core': 5.95.0 react: 19.2.4 '@tanstack/react-store@0.9.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': @@ -10795,15 +10972,15 @@ snapshots: '@types/zen-observable@0.8.3': {} - '@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.57.1 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -10811,14 +10988,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.57.1 '@typescript-eslint/types': 8.57.1 '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.57.1 debug: 4.4.3 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -10844,13 +11021,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/rule-tester@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/rule-tester@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/parser': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) ajv: 6.14.0 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 semver: 7.7.4 @@ -10867,13 +11044,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.57.1 '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) debug: 4.4.3 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -10896,13 +11073,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) '@typescript-eslint/scope-manager': 8.57.1 '@typescript-eslint/types': 8.57.1 '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -10912,36 +11089,36 @@ snapshots: '@typescript-eslint/types': 8.57.1 eslint-visitor-keys: 5.0.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260318.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260322.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260318.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260322.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260318.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260322.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260318.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260322.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260318.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260322.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260318.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260322.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260318.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260322.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260318.1': + '@typescript/native-preview@7.0.0-dev.20260322.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260318.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260318.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260318.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260318.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260318.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260318.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260318.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260322.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260322.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260322.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260322.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260322.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260322.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260322.1 '@ungap/structured-clone@1.3.0': {} @@ -10949,13 +11126,13 @@ snapshots: dependencies: unpic: 4.2.2 - '@unpic/react@1.0.2(next@16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@unpic/react@1.0.2(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@unpic/core': 1.0.3 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - next: 16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) + next: 16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) '@upsetjs/venn.js@2.0.0': optionalDependencies: @@ -10971,12 +11148,12 @@ snapshots: '@resvg/resvg-wasm': 2.4.0 satori: 0.16.0 - '@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': + '@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - '@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)': + '@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4)': dependencies: '@rolldown/pluginutils': 1.0.0-rc.5 es-module-lexer: 2.0.0 @@ -10988,12 +11165,12 @@ snapshots: srvx: 0.11.12 strip-literal: 3.1.0 turbo-stream: 3.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - vitefu: 1.1.2(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vitefu: 1.1.2(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) optionalDependencies: react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) - '@vitest/coverage-v8@4.1.0(@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': + '@vitest/coverage-v8@4.1.0(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.0 @@ -11005,16 +11182,16 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: '@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - '@vitest/eslint-plugin@1.6.12(@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3)': + '@vitest/eslint-plugin@1.6.12(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 - vitest: '@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color @@ -11050,10 +11227,10 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': + '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': dependencies: - '@oxc-project/runtime': 0.115.0 - '@oxc-project/types': 0.115.0 + '@oxc-project/runtime': 0.120.0 + '@oxc-project/types': 0.120.0 lightningcss: 1.32.0 postcss: 8.5.8 optionalDependencies: @@ -11067,23 +11244,23 @@ snapshots: typescript: 5.9.3 yaml: 2.8.2 - '@voidzero-dev/vite-plus-darwin-arm64@0.1.12': + '@voidzero-dev/vite-plus-darwin-arm64@0.1.13': optional: true - '@voidzero-dev/vite-plus-darwin-x64@0.1.12': + '@voidzero-dev/vite-plus-darwin-x64@0.1.13': optional: true - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.12': + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.13': optional: true - '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.12': + '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.13': optional: true - '@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': + '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + '@voidzero-dev/vite-plus-core': 0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) es-module-lexer: 1.7.0 obug: 2.1.1 pixelmatch: 7.1.0 @@ -11093,11 +11270,11 @@ snapshots: tinybench: 2.9.0 tinyexec: 1.0.4 tinyglobby: 0.2.15 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' ws: 8.19.0 optionalDependencies: '@types/node': 25.5.0 - jsdom: 29.0.0(canvas@3.2.1) + jsdom: 29.0.1(canvas@3.2.2) transitivePeerDependencies: - '@arethetypeswrong/core' - '@tsdown/css' @@ -11119,10 +11296,10 @@ snapshots: - utf-8-validate - yaml - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.12': + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.13': optional: true - '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.12': + '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.13': optional: true '@volar/language-core@2.4.28': @@ -11456,8 +11633,6 @@ snapshots: bytes@3.1.2: {} - cac@6.7.14: {} - cac@7.0.0: {} callsites@3.1.0: {} @@ -11468,7 +11643,7 @@ snapshots: caniuse-lite@1.0.30001780: {} - canvas@3.2.1: + canvas@3.2.2: dependencies: node-addon-api: 7.1.1 prebuild-install: 7.1.3 @@ -11613,14 +11788,14 @@ snapshots: - '@types/react' - '@types/react-dom' - code-inspector-plugin@1.4.4: + code-inspector-plugin@1.4.5: dependencies: - '@code-inspector/core': 1.4.4 - '@code-inspector/esbuild': 1.4.4 - '@code-inspector/mako': 1.4.4 - '@code-inspector/turbopack': 1.4.4 - '@code-inspector/vite': 1.4.4 - '@code-inspector/webpack': 1.4.4 + '@code-inspector/core': 1.4.5 + '@code-inspector/esbuild': 1.4.5 + '@code-inspector/mako': 1.4.5 + '@code-inspector/turbopack': 1.4.5 + '@code-inspector/vite': 1.4.5 + '@code-inspector/webpack': 1.4.5 chalk: 4.1.1 transitivePeerDependencies: - supports-color @@ -12143,46 +12318,46 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.5.1(eslint@10.0.3(jiti@1.21.7)): + eslint-compat-utils@0.5.1(eslint@10.1.0(jiti@1.21.7)): dependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) semver: 7.7.4 - eslint-config-flat-gitignore@2.2.1(eslint@10.0.3(jiti@1.21.7)): + eslint-config-flat-gitignore@2.2.1(eslint@10.1.0(jiti@1.21.7)): dependencies: - '@eslint/compat': 2.0.3(eslint@10.0.3(jiti@1.21.7)) - eslint: 10.0.3(jiti@1.21.7) + '@eslint/compat': 2.0.3(eslint@10.1.0(jiti@1.21.7)) + eslint: 10.1.0(jiti@1.21.7) eslint-flat-config-utils@3.0.2: dependencies: '@eslint/config-helpers': 0.5.3 pathe: 2.0.3 - eslint-json-compat-utils@0.2.3(eslint@10.0.3(jiti@1.21.7))(jsonc-eslint-parser@3.1.0): + eslint-json-compat-utils@0.2.3(eslint@10.1.0(jiti@1.21.7))(jsonc-eslint-parser@3.1.0): dependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) esquery: 1.7.0 jsonc-eslint-parser: 3.1.0 - eslint-markdown@0.6.0(eslint@10.0.3(jiti@1.21.7)): + eslint-markdown@0.6.0(eslint@10.1.0(jiti@1.21.7)): dependencies: '@eslint/markdown': 7.5.1 micromark-util-normalize-identifier: 2.0.1 parse5: 8.0.0 optionalDependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) transitivePeerDependencies: - supports-color - eslint-merge-processors@2.0.0(eslint@10.0.3(jiti@1.21.7)): + eslint-merge-processors@2.0.0(eslint@10.1.0(jiti@1.21.7)): dependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) - eslint-plugin-antfu@3.2.2(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-antfu@3.2.2(eslint@10.1.0(jiti@1.21.7)): dependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) - eslint-plugin-better-tailwindcss@4.3.2(eslint@10.0.3(jiti@1.21.7))(oxlint@1.55.0(oxlint-tsgolint@0.17.0))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3): + eslint-plugin-better-tailwindcss@4.3.2(eslint@10.1.0(jiti@1.21.7))(oxlint@1.56.0(oxlint-tsgolint@0.17.1))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))(typescript@5.9.3): dependencies: '@eslint/css-tree': 3.6.9 '@valibot/to-json-schema': 1.6.0(valibot@1.3.0(typescript@5.9.3)) @@ -12194,43 +12369,43 @@ snapshots: tsconfig-paths-webpack-plugin: 4.2.0 valibot: 1.3.0(typescript@5.9.3) optionalDependencies: - eslint: 10.0.3(jiti@1.21.7) - oxlint: 1.55.0(oxlint-tsgolint@0.17.0) + eslint: 10.1.0(jiti@1.21.7) + oxlint: 1.56.0(oxlint-tsgolint@0.17.1) transitivePeerDependencies: - '@eslint/css' - typescript - eslint-plugin-command@3.5.2(@typescript-eslint/rule-tester@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-command@3.5.2(@typescript-eslint/rule-tester@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3))(@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7)): dependencies: '@es-joy/jsdoccomment': 0.84.0 - '@typescript-eslint/rule-tester': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/rule-tester': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) - eslint-plugin-depend@1.5.0(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-depend@1.5.0(eslint@10.1.0(jiti@1.21.7)): dependencies: empathic: 2.0.0 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) module-replacements: 2.11.0 semver: 7.7.4 - eslint-plugin-es-x@7.8.0(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-es-x@7.8.0(eslint@10.1.0(jiti@1.21.7)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.2 - eslint: 10.0.3(jiti@1.21.7) - eslint-compat-utils: 0.5.1(eslint@10.0.3(jiti@1.21.7)) + eslint: 10.1.0(jiti@1.21.7) + eslint-compat-utils: 0.5.1(eslint@10.1.0(jiti@1.21.7)) - eslint-plugin-hyoban@0.14.1(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-hyoban@0.14.1(eslint@10.1.0(jiti@1.21.7)): dependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) - eslint-plugin-import-lite@0.5.2(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-import-lite@0.5.2(eslint@10.1.0(jiti@1.21.7)): dependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) - eslint-plugin-jsdoc@62.8.0(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-jsdoc@62.8.0(eslint@10.1.0(jiti@1.21.7)): dependencies: '@es-joy/jsdoccomment': 0.84.0 '@es-joy/resolve.exports': 1.2.0 @@ -12238,7 +12413,7 @@ snapshots: comment-parser: 1.4.5 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) espree: 11.2.0 esquery: 1.7.0 html-entities: 2.6.0 @@ -12250,27 +12425,27 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-jsonc@3.1.2(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-jsonc@3.1.2(eslint@10.1.0(jiti@1.21.7)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) '@eslint/core': 1.1.1 '@eslint/plugin-kit': 0.6.1 '@ota-meshi/ast-token-store': 0.3.0 diff-sequences: 29.6.3 - eslint: 10.0.3(jiti@1.21.7) - eslint-json-compat-utils: 0.2.3(eslint@10.0.3(jiti@1.21.7))(jsonc-eslint-parser@3.1.0) + eslint: 10.1.0(jiti@1.21.7) + eslint-json-compat-utils: 0.2.3(eslint@10.1.0(jiti@1.21.7))(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.40.3(@eslint/markdown@7.5.1)(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-markdown-preferences@0.40.3(@eslint/markdown@7.5.1)(eslint@10.1.0(jiti@1.21.7)): dependencies: '@eslint/markdown': 7.5.1 diff-sequences: 29.6.3 emoji-regex-xs: 2.0.1 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) mdast-util-from-markdown: 2.0.3 mdast-util-frontmatter: 2.0.1 mdast-util-gfm: 3.1.0 @@ -12285,13 +12460,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-n@17.24.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-n@17.24.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) enhanced-resolve: 5.20.1 - eslint: 10.0.3(jiti@1.21.7) - eslint-plugin-es-x: 7.8.0(eslint@10.0.3(jiti@1.21.7)) - get-tsconfig: 4.13.6 + eslint: 10.1.0(jiti@1.21.7) + eslint-plugin-es-x: 7.8.0(eslint@10.1.0(jiti@1.21.7)) + get-tsconfig: 4.13.7 globals: 15.15.0 globrex: 0.1.2 ignore: 5.3.2 @@ -12302,19 +12477,19 @@ snapshots: eslint-plugin-no-only-tests@3.3.0: {} - eslint-plugin-perfectionist@5.7.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-perfectionist@5.7.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) natural-orderby: 5.0.0 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-pnpm@1.6.0(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-pnpm@1.6.0(eslint@10.1.0(jiti@1.21.7)): dependencies: empathic: 2.0.0 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) jsonc-eslint-parser: 3.1.0 pathe: 2.0.3 pnpm-workspace-yaml: 1.6.0 @@ -12322,141 +12497,122 @@ snapshots: yaml: 2.8.2 yaml-eslint-parser: 2.0.0 - eslint-plugin-react-dom@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-react-dom@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 2.13.0 - '@eslint-react/shared': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/core': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/shared': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.57.1 '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) compare-versions: 6.1.1 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) ts-pattern: 5.9.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks-extra@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3): - dependencies: - '@eslint-react/ast': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 2.13.0 - '@eslint-react/shared': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) - ts-pattern: 5.9.0 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - eslint-plugin-react-hooks@7.0.1(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-react-hooks@7.0.1(eslint@10.1.0(jiti@1.21.7)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.2 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) hermes-parser: 0.25.1 zod: 4.3.6 zod-validation-error: 4.0.2(zod@4.3.6) transitivePeerDependencies: - supports-color - eslint-plugin-react-naming-convention@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-react-naming-convention@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 2.13.0 - '@eslint-react/shared': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/core': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/shared': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) compare-versions: 6.1.1 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) string-ts: 2.3.1 ts-pattern: 5.9.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.5.2(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-react-refresh@0.5.2(eslint@10.1.0(jiti@1.21.7)): dependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) - eslint-plugin-react-rsc@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-react-rsc@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/shared': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/shared': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.57.1 + '@typescript-eslint/type-utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) ts-pattern: 5.9.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-web-api@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-react-web-api@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 2.13.0 - '@eslint-react/shared': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/core': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/shared': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.57.1 '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) birecord: 0.1.1 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) ts-pattern: 5.9.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-x@2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-react-x@3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 2.13.0 - '@eslint-react/shared': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 2.13.0(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/core': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/shared': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) compare-versions: 6.1.1 - eslint: 10.0.3(jiti@1.21.7) - is-immutable-type: 5.0.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) + string-ts: 2.3.1 ts-api-utils: 2.4.0(typescript@5.9.3) ts-pattern: 5.9.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-regexp@3.1.0(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-regexp@3.1.0(eslint@10.1.0(jiti@1.21.7)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.2 comment-parser: 1.4.5 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) jsdoc-type-pratt-parser: 7.1.1 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-sonarjs@4.0.2(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-sonarjs@4.0.2(eslint@10.1.0(jiti@1.21.7)): dependencies: '@eslint-community/regexpp': 4.12.2 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) functional-red-black-tree: 1.0.1 globals: 17.4.0 jsx-ast-utils-x: 0.1.0 @@ -12467,35 +12623,35 @@ snapshots: ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 - eslint-plugin-storybook@10.3.0(eslint@10.0.3(jiti@1.21.7))(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3): + eslint-plugin-storybook@10.3.1(eslint@10.1.0(jiti@1.21.7))(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + eslint: 10.1.0(jiti@1.21.7) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-toml@1.3.1(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-toml@1.3.1(eslint@10.1.0(jiti@1.21.7)): dependencies: '@eslint/core': 1.1.1 '@eslint/plugin-kit': 0.6.1 '@ota-meshi/ast-token-store': 0.3.0 debug: 4.4.3 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) toml-eslint-parser: 1.0.3 transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@63.0.0(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-unicorn@63.0.0(eslint@10.1.0(jiti@1.21.7)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) change-case: 5.4.4 ci-info: 4.4.0 clean-regexp: 1.0.0 core-js-compat: 3.49.0 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) find-up-simple: 1.0.1 globals: 16.5.0 indent-string: 5.0.0 @@ -12507,27 +12663,27 @@ snapshots: semver: 7.7.4 strip-indent: 4.1.1 - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7)): dependencies: - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.0.3(jiti@1.21.7)))(@typescript-eslint/parser@8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3))(eslint@10.0.3(jiti@1.21.7))(vue-eslint-parser@10.4.0(eslint@10.0.3(jiti@1.21.7))): + eslint-plugin-vue@10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@1.21.7)))(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3))(eslint@10.1.0(jiti@1.21.7))(vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@1.21.7))): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7)) - eslint: 10.0.3(jiti@1.21.7) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) + eslint: 10.1.0(jiti@1.21.7) 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.0.3(jiti@1.21.7)) + vue-eslint-parser: 10.4.0(eslint@10.1.0(jiti@1.21.7)) xml-name-validator: 4.0.0 optionalDependencies: - '@stylistic/eslint-plugin': 5.10.0(eslint@10.0.3(jiti@1.21.7)) - '@typescript-eslint/parser': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.1.0(jiti@1.21.7)) + '@typescript-eslint/parser': 8.57.1(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-yml@3.3.1(eslint@10.0.3(jiti@1.21.7)): + eslint-plugin-yml@3.3.1(eslint@10.1.0(jiti@1.21.7)): dependencies: '@eslint/core': 1.1.1 '@eslint/plugin-kit': 0.6.1 @@ -12535,16 +12691,16 @@ snapshots: debug: 4.4.3 diff-sequences: 29.6.3 escape-string-regexp: 5.0.0 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) natural-compare: 1.4.0 yaml-eslint-parser: 2.0.0 transitivePeerDependencies: - supports-color - eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.30)(eslint@10.0.3(jiti@1.21.7)): + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.30)(eslint@10.1.0(jiti@1.21.7)): dependencies: '@vue/compiler-sfc': 3.5.30 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) eslint-scope@5.1.1: dependencies: @@ -12569,9 +12725,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.0.3(jiti@1.21.7): + eslint@10.1.0(jiti@1.21.7): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.3 '@eslint/config-helpers': 0.5.3 @@ -12845,7 +13001,7 @@ snapshots: dependencies: pump: 3.0.4 - get-tsconfig@4.13.6: + get-tsconfig@4.13.7: dependencies: resolve-pkg-maps: 1.0.0 @@ -13080,7 +13236,7 @@ snapshots: dependencies: '@babel/runtime': 7.29.2 - i18next@25.8.18(typescript@5.9.3): + i18next@25.10.4(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 optionalDependencies: @@ -13182,16 +13338,6 @@ snapshots: is-hexadecimal@2.0.1: {} - is-immutable-type@5.0.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3): - dependencies: - '@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3(jiti@1.21.7))(typescript@5.9.3) - eslint: 10.0.3(jiti@1.21.7) - ts-api-utils: 2.4.0(typescript@5.9.3) - ts-declaration-location: 1.0.7(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -13269,7 +13415,7 @@ snapshots: bezier-easing: 2.1.0 css-mediaquery: 0.1.2 - jsdom@29.0.0(canvas@3.2.1): + jsdom@29.0.1(canvas@3.2.2): dependencies: '@asamuzakjp/css-color': 5.0.1 '@asamuzakjp/dom-selector': 7.0.3 @@ -13286,14 +13432,14 @@ snapshots: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 6.0.1 - undici: 7.24.4 + undici: 7.24.5 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 whatwg-url: 16.0.1 xml-name-validator: 5.0.0 optionalDependencies: - canvas: 3.2.1 + canvas: 3.2.2 transitivePeerDependencies: - '@noble/hashes' @@ -13331,7 +13477,7 @@ snapshots: jsx-ast-utils-x@0.1.0: {} - katex@0.16.38: + katex@0.16.40: dependencies: commander: 8.3.0 @@ -13341,20 +13487,20 @@ snapshots: khroma@2.1.0: {} - knip@5.88.0(@types/node@25.5.0)(typescript@5.9.3): + knip@6.0.2: dependencies: '@nodelib/fs.walk': 1.2.8 - '@types/node': 25.5.0 fast-glob: 3.3.3 formatly: 0.3.0 + get-tsconfig: 4.13.7 jiti: 2.6.1 minimist: 1.2.8 + oxc-parser: 0.120.0 oxc-resolver: 11.19.1 picocolors: 1.1.1 picomatch: 4.0.3 smol-toml: 1.6.0 strip-json-comments: 5.0.3 - typescript: 5.9.3 unbash: 2.2.0 yaml: 2.8.2 zod: 4.3.6 @@ -13389,12 +13535,12 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lexical-code-no-prism@0.41.0(@lexical/utils@0.41.0)(lexical@0.41.0): + lexical-code-no-prism@0.41.0(@lexical/utils@0.42.0)(lexical@0.42.0): dependencies: - '@lexical/utils': 0.41.0 - lexical: 0.41.0 + '@lexical/utils': 0.42.0 + lexical: 0.42.0 - lexical@0.41.0: {} + lexical@0.42.0: {} lib0@0.2.117: dependencies: @@ -13783,7 +13929,7 @@ snapshots: dagre-d3-es: 7.0.14 dayjs: 1.11.20 dompurify: 3.3.2 - katex: 0.16.38 + katex: 0.16.40 khroma: 2.1.0 lodash-es: 4.17.23 marked: 16.4.2 @@ -13890,7 +14036,7 @@ snapshots: dependencies: '@types/katex': 0.16.8 devlop: 1.1.0 - katex: 0.16.38 + katex: 0.16.40 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 @@ -14172,9 +14318,9 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - next@16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0): + next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0): dependencies: - '@next/env': 16.2.0 + '@next/env': 16.2.1 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.8 caniuse-lite: 1.0.30001780 @@ -14183,14 +14329,14 @@ snapshots: react-dom: 19.2.4(react@19.2.4) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.0 - '@next/swc-darwin-x64': 16.2.0 - '@next/swc-linux-arm64-gnu': 16.2.0 - '@next/swc-linux-arm64-musl': 16.2.0 - '@next/swc-linux-x64-gnu': 16.2.0 - '@next/swc-linux-x64-musl': 16.2.0 - '@next/swc-win32-arm64-msvc': 16.2.0 - '@next/swc-win32-x64-msvc': 16.2.0 + '@next/swc-darwin-arm64': 16.2.1 + '@next/swc-darwin-x64': 16.2.1 + '@next/swc-linux-arm64-gnu': 16.2.1 + '@next/swc-linux-arm64-musl': 16.2.1 + '@next/swc-linux-x64-gnu': 16.2.1 + '@next/swc-linux-x64-musl': 16.2.1 + '@next/swc-win32-arm64-msvc': 16.2.1 + '@next/swc-win32-x64-msvc': 16.2.1 sass: 1.98.0 sharp: 0.34.5 transitivePeerDependencies: @@ -14223,12 +14369,12 @@ snapshots: dependencies: boolbase: 1.0.0 - nuqs@2.8.9(next@16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react@19.2.4): + nuqs@2.8.9(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react@19.2.4): dependencies: '@standard-schema/spec': 1.0.0 react: 19.2.4 optionalDependencies: - next: 16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) + next: 16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) object-assign@4.1.1: {} @@ -14274,6 +14420,31 @@ snapshots: outvariant@1.4.3: {} + oxc-parser@0.120.0: + dependencies: + '@oxc-project/types': 0.120.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.120.0 + '@oxc-parser/binding-android-arm64': 0.120.0 + '@oxc-parser/binding-darwin-arm64': 0.120.0 + '@oxc-parser/binding-darwin-x64': 0.120.0 + '@oxc-parser/binding-freebsd-x64': 0.120.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.120.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.120.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.120.0 + '@oxc-parser/binding-linux-arm64-musl': 0.120.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.120.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.120.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.120.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.120.0 + '@oxc-parser/binding-linux-x64-gnu': 0.120.0 + '@oxc-parser/binding-linux-x64-musl': 0.120.0 + '@oxc-parser/binding-openharmony-arm64': 0.120.0 + '@oxc-parser/binding-wasm32-wasi': 0.120.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.120.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.120.0 + '@oxc-parser/binding-win32-x64-msvc': 0.120.0 + oxc-resolver@11.19.1: optionalDependencies: '@oxc-resolver/binding-android-arm-eabi': 11.19.1 @@ -14297,61 +14468,61 @@ snapshots: '@oxc-resolver/binding-win32-ia32-msvc': 11.19.1 '@oxc-resolver/binding-win32-x64-msvc': 11.19.1 - oxfmt@0.40.0: + oxfmt@0.41.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.40.0 - '@oxfmt/binding-android-arm64': 0.40.0 - '@oxfmt/binding-darwin-arm64': 0.40.0 - '@oxfmt/binding-darwin-x64': 0.40.0 - '@oxfmt/binding-freebsd-x64': 0.40.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.40.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.40.0 - '@oxfmt/binding-linux-arm64-gnu': 0.40.0 - '@oxfmt/binding-linux-arm64-musl': 0.40.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.40.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.40.0 - '@oxfmt/binding-linux-riscv64-musl': 0.40.0 - '@oxfmt/binding-linux-s390x-gnu': 0.40.0 - '@oxfmt/binding-linux-x64-gnu': 0.40.0 - '@oxfmt/binding-linux-x64-musl': 0.40.0 - '@oxfmt/binding-openharmony-arm64': 0.40.0 - '@oxfmt/binding-win32-arm64-msvc': 0.40.0 - '@oxfmt/binding-win32-ia32-msvc': 0.40.0 - '@oxfmt/binding-win32-x64-msvc': 0.40.0 + '@oxfmt/binding-android-arm-eabi': 0.41.0 + '@oxfmt/binding-android-arm64': 0.41.0 + '@oxfmt/binding-darwin-arm64': 0.41.0 + '@oxfmt/binding-darwin-x64': 0.41.0 + '@oxfmt/binding-freebsd-x64': 0.41.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.41.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.41.0 + '@oxfmt/binding-linux-arm64-gnu': 0.41.0 + '@oxfmt/binding-linux-arm64-musl': 0.41.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.41.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.41.0 + '@oxfmt/binding-linux-riscv64-musl': 0.41.0 + '@oxfmt/binding-linux-s390x-gnu': 0.41.0 + '@oxfmt/binding-linux-x64-gnu': 0.41.0 + '@oxfmt/binding-linux-x64-musl': 0.41.0 + '@oxfmt/binding-openharmony-arm64': 0.41.0 + '@oxfmt/binding-win32-arm64-msvc': 0.41.0 + '@oxfmt/binding-win32-ia32-msvc': 0.41.0 + '@oxfmt/binding-win32-x64-msvc': 0.41.0 - oxlint-tsgolint@0.17.0: + oxlint-tsgolint@0.17.1: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.17.0 - '@oxlint-tsgolint/darwin-x64': 0.17.0 - '@oxlint-tsgolint/linux-arm64': 0.17.0 - '@oxlint-tsgolint/linux-x64': 0.17.0 - '@oxlint-tsgolint/win32-arm64': 0.17.0 - '@oxlint-tsgolint/win32-x64': 0.17.0 + '@oxlint-tsgolint/darwin-arm64': 0.17.1 + '@oxlint-tsgolint/darwin-x64': 0.17.1 + '@oxlint-tsgolint/linux-arm64': 0.17.1 + '@oxlint-tsgolint/linux-x64': 0.17.1 + '@oxlint-tsgolint/win32-arm64': 0.17.1 + '@oxlint-tsgolint/win32-x64': 0.17.1 - oxlint@1.55.0(oxlint-tsgolint@0.17.0): + oxlint@1.56.0(oxlint-tsgolint@0.17.1): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.55.0 - '@oxlint/binding-android-arm64': 1.55.0 - '@oxlint/binding-darwin-arm64': 1.55.0 - '@oxlint/binding-darwin-x64': 1.55.0 - '@oxlint/binding-freebsd-x64': 1.55.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.55.0 - '@oxlint/binding-linux-arm-musleabihf': 1.55.0 - '@oxlint/binding-linux-arm64-gnu': 1.55.0 - '@oxlint/binding-linux-arm64-musl': 1.55.0 - '@oxlint/binding-linux-ppc64-gnu': 1.55.0 - '@oxlint/binding-linux-riscv64-gnu': 1.55.0 - '@oxlint/binding-linux-riscv64-musl': 1.55.0 - '@oxlint/binding-linux-s390x-gnu': 1.55.0 - '@oxlint/binding-linux-x64-gnu': 1.55.0 - '@oxlint/binding-linux-x64-musl': 1.55.0 - '@oxlint/binding-openharmony-arm64': 1.55.0 - '@oxlint/binding-win32-arm64-msvc': 1.55.0 - '@oxlint/binding-win32-ia32-msvc': 1.55.0 - '@oxlint/binding-win32-x64-msvc': 1.55.0 - oxlint-tsgolint: 0.17.0 + '@oxlint/binding-android-arm-eabi': 1.56.0 + '@oxlint/binding-android-arm64': 1.56.0 + '@oxlint/binding-darwin-arm64': 1.56.0 + '@oxlint/binding-darwin-x64': 1.56.0 + '@oxlint/binding-freebsd-x64': 1.56.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.56.0 + '@oxlint/binding-linux-arm-musleabihf': 1.56.0 + '@oxlint/binding-linux-arm64-gnu': 1.56.0 + '@oxlint/binding-linux-arm64-musl': 1.56.0 + '@oxlint/binding-linux-ppc64-gnu': 1.56.0 + '@oxlint/binding-linux-riscv64-gnu': 1.56.0 + '@oxlint/binding-linux-riscv64-musl': 1.56.0 + '@oxlint/binding-linux-s390x-gnu': 1.56.0 + '@oxlint/binding-linux-x64-gnu': 1.56.0 + '@oxlint/binding-linux-x64-musl': 1.56.0 + '@oxlint/binding-openharmony-arm64': 1.56.0 + '@oxlint/binding-win32-arm64-msvc': 1.56.0 + '@oxlint/binding-win32-ia32-msvc': 1.56.0 + '@oxlint/binding-win32-x64-msvc': 1.56.0 + oxlint-tsgolint: 0.17.1 p-limit@3.1.0: dependencies: @@ -14444,7 +14615,7 @@ snapshots: pdfjs-dist@4.4.168: optionalDependencies: - canvas: 3.2.1 + canvas: 3.2.2 path2d: 0.2.2 pend@1.2.0: {} @@ -14696,11 +14867,11 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-i18next@16.5.8(i18next@25.8.18(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + react-i18next@16.6.1(i18next@25.10.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 html-parse-stringify: 3.0.1 - i18next: 25.8.18(typescript@5.9.3) + i18next: 25.10.4(typescript@5.9.3) react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: @@ -14919,7 +15090,7 @@ snapshots: '@types/katex': 0.16.8 hast-util-from-html-isomorphic: 2.0.0 hast-util-to-text: 4.0.2 - katex: 0.16.38 + katex: 0.16.40 unist-util-visit-parents: 6.0.2 vfile: 6.0.3 @@ -15259,7 +15430,7 @@ snapshots: std-semver@1.0.8: {} - storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 2.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -15532,11 +15703,11 @@ snapshots: tinyspy@4.0.4: {} - tldts-core@7.0.26: {} + tldts-core@7.0.27: {} - tldts@7.0.26: + tldts@7.0.27: dependencies: - tldts-core: 7.0.26 + tldts-core: 7.0.27 to-regex-range@5.0.1: dependencies: @@ -15557,7 +15728,7 @@ snapshots: tough-cookie@6.0.1: dependencies: - tldts: 7.0.26 + tldts: 7.0.27 tr46@6.0.0: dependencies: @@ -15610,7 +15781,7 @@ snapshots: tsx@4.21.0: dependencies: esbuild: 0.27.2 - get-tsconfig: 4.13.6 + get-tsconfig: 4.13.7 optionalDependencies: fsevents: 2.3.3 @@ -15654,7 +15825,7 @@ snapshots: undici@7.24.0: {} - undici@7.24.4: {} + undici@7.24.5: {} unicode-trie@2.0.0: dependencies: @@ -15806,36 +15977,36 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinext@0.0.31(d43efe4756ad5ea698dcdb002ea787ea): + vinext@0.0.34(1a91bf00ec5f7fb5f0ffb625316f9d01): dependencies: - '@unpic/react': 1.0.2(next@16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@unpic/react': 1.0.2(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@vercel/og': 0.8.6 - '@vitejs/plugin-react': 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + '@vitejs/plugin-react': 6.0.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) magic-string: 0.30.21 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rsc-html-stream: 0.0.7 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vite-plugin-commonjs: 0.10.4 - vite-tsconfig-paths: 6.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) + vite-tsconfig-paths: 6.1.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) optionalDependencies: '@mdx-js/rollup': 3.1.1(rollup@4.59.0) - '@vitejs/plugin-rsc': 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4) + '@vitejs/plugin-rsc': 0.5.21(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)))(react@19.2.4) react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)) transitivePeerDependencies: - next - supports-color - typescript - vite-dev-rpc@1.1.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): + vite-dev-rpc@1.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: birpc: 2.9.0 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - vite-hot-client: 2.1.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-hot-client: 2.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) - vite-hot-client@2.1.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): + vite-hot-client@2.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' vite-plugin-commonjs@0.10.4: dependencies: @@ -15850,7 +16021,7 @@ snapshots: fast-glob: 3.3.3 magic-string: 0.30.21 - vite-plugin-inspect@11.3.3(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): + vite-plugin-inspect@11.3.3(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: ansis: 4.2.0 debug: 4.4.3 @@ -15860,44 +16031,44 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - vite-dev-rpc: 1.1.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-dev-rpc: 1.1.0(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - vite-plugin-storybook-nextjs@3.2.3(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3): + vite-plugin-storybook-nextjs@3.2.3(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(next@16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3): dependencies: '@next/env': 16.0.0 image-size: 2.0.2 magic-string: 0.30.21 module-alias: 2.3.4 - next: 16.2.0(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) - storybook: 10.3.0(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.2.1(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0) + storybook: 10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) ts-dedent: 2.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - vite-tsconfig-paths: 5.1.4(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite-tsconfig-paths: 5.1.4(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3) transitivePeerDependencies: - supports-color - typescript - vite-plus@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + vite-plus@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): dependencies: - '@oxc-project/types': 0.115.0 - '@voidzero-dev/vite-plus-core': 0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - '@voidzero-dev/vite-plus-test': 0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - cac: 6.7.14 + '@oxc-project/types': 0.120.0 + '@voidzero-dev/vite-plus-core': 0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + '@voidzero-dev/vite-plus-test': 0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + cac: 7.0.0 cross-spawn: 7.0.6 - oxfmt: 0.40.0 - oxlint: 1.55.0(oxlint-tsgolint@0.17.0) - oxlint-tsgolint: 0.17.0 + oxfmt: 0.41.0 + oxlint: 1.56.0(oxlint-tsgolint@0.17.1) + oxlint-tsgolint: 0.17.1 picocolors: 1.1.1 optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 0.1.12 - '@voidzero-dev/vite-plus-darwin-x64': 0.1.12 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.12 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.12 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.12 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.12 + '@voidzero-dev/vite-plus-darwin-arm64': 0.1.13 + '@voidzero-dev/vite-plus-darwin-x64': 0.1.13 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.13 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.13 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.13 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.13 transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' @@ -15926,36 +16097,36 @@ snapshots: - vite - yaml - vite-tsconfig-paths@5.1.4(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3): + vite-tsconfig-paths@5.1.4(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3): + vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(typescript@5.9.3): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' transitivePeerDependencies: - supports-color - typescript - vitefu@1.1.2(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): + vitefu@1.1.2(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vite: '@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' - vitest-canvas-mock@1.1.3(@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): + vitest-canvas-mock@1.1.3(@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: cssfontparser: 1.2.1 moo-color: 1.0.3 - vitest: '@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.0(canvas@3.2.1))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' + vitest: '@voidzero-dev/vite-plus-test@0.1.13(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.13(@types/node@25.5.0)(esbuild@0.27.2)(jiti@1.21.7)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(esbuild@0.27.2)(jiti@1.21.7)(jsdom@29.0.1(canvas@3.2.2))(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)' void-elements@3.1.0: {} @@ -15976,10 +16147,10 @@ snapshots: vscode-uri@3.1.0: {} - vue-eslint-parser@10.4.0(eslint@10.0.3(jiti@1.21.7)): + vue-eslint-parser@10.4.0(eslint@10.1.0(jiti@1.21.7)): dependencies: debug: 4.4.3 - eslint: 10.0.3(jiti@1.21.7) + eslint: 10.1.0(jiti@1.21.7) eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 espree: 11.2.0 diff --git a/web/tailwind-common-config.ts b/web/tailwind-common-config.ts index 3a813274d4..73b5a1c2ce 100644 --- a/web/tailwind-common-config.ts +++ b/web/tailwind-common-config.ts @@ -3,10 +3,8 @@ import { fileURLToPath } from 'node:url' import { getIconCollections, iconsPlugin } from '@egoist/tailwindcss-icons' import tailwindTypography from '@tailwindcss/typography' import { importSvgCollections } from 'iconify-import-svg' -// @ts-expect-error workaround for turbopack issue -import { cssAsPlugin } from './tailwind-css-plugin.ts' -// @ts-expect-error workaround for turbopack issue -import tailwindThemeVarDefine from './themes/tailwind-theme-var-define.ts' +import { cssAsPlugin } from './tailwind-css-plugin' +import tailwindThemeVarDefine from './themes/tailwind-theme-var-define' import typography from './typography.js' const _dirname = typeof __dirname !== 'undefined' diff --git a/web/tailwind.config.js b/web/tailwind.config.ts similarity index 72% rename from web/tailwind.config.js rename to web/tailwind.config.ts index db0a172e1c..32b889e707 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.ts @@ -1,7 +1,7 @@ -// import type { Config } from 'tailwindcss' -import commonConfig from './tailwind-common-config.ts' +import type { Config } from 'tailwindcss' +import commonConfig from './tailwind-common-config' -const config = { +const config: Config = { content: [ './app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', diff --git a/web/taze.config.js b/web/taze.config.js index 4e97a50d2e..7ffd76f94e 100644 --- a/web/taze.config.js +++ b/web/taze.config.js @@ -10,7 +10,6 @@ export default defineConfig({ // We can not upgrade these yet 'tailwind-merge', 'tailwindcss', - '@eslint-react/eslint-plugin', ], write: true, From 110b8c925e5265f48f8c74b937fa4204ca321c6e Mon Sep 17 00:00:00 2001 From: enci Date: Mon, 23 Mar 2026 10:58:10 +0800 Subject: [PATCH 02/70] fix: remove contradictory optional chain in chat/utils.ts (#33841) Co-authored-by: yoloni --- web/app/components/base/chat/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index b47fec1d0a..5881f565a4 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -158,7 +158,7 @@ function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] { rootNodes.push(questionNode) } else { - map[parentMessageId]?.children!.push(questionNode) + map[parentMessageId].children!.push(questionNode) } } } From 047802390059d3bdf99aa8f99af9bb4252449cf2 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:13:52 +0800 Subject: [PATCH 03/70] refactor(web): migrate dataset-related toast callsites to base/ui/toast and update tests (#33892) --- .../datasets/dataset-settings-flow.test.tsx | 18 +++--- .../params-config/index.spec.tsx | 13 ++-- .../dataset-config/params-config/index.tsx | 7 +-- .../__tests__/auto-disabled-document.spec.tsx | 17 +++--- .../auto-disabled-document.tsx | 4 +- .../hooks/__tests__/use-upload.spec.tsx | 59 +++++++------------ .../common/image-uploader/hooks/use-upload.ts | 17 +++--- .../__tests__/index.spec.tsx | 16 ++--- .../common/retrieval-param-config/index.tsx | 6 +- .../__tests__/rename-modal.spec.tsx | 14 +++++ .../__tests__/use-document-actions.spec.ts | 33 +++++------ .../hooks/use-document-actions.ts | 12 ++-- .../documents/components/rename-modal.tsx | 6 +- .../__tests__/use-dataset-card-state.spec.ts | 40 ++++++------- .../hooks/use-dataset-card-state.ts | 10 ++-- .../settings/form/__tests__/index.spec.tsx | 18 +++--- .../hooks/__tests__/use-form-state.spec.ts | 40 ++++++------- .../settings/form/hooks/use-form-state.ts | 10 ++-- web/eslint-suppressions.json | 27 +-------- web/i18n/en-US/dataset.json | 1 + 20 files changed, 160 insertions(+), 208 deletions(-) diff --git a/web/__tests__/datasets/dataset-settings-flow.test.tsx b/web/__tests__/datasets/dataset-settings-flow.test.tsx index 607cd8c2d5..b4a5e78326 100644 --- a/web/__tests__/datasets/dataset-settings-flow.test.tsx +++ b/web/__tests__/datasets/dataset-settings-flow.test.tsx @@ -19,6 +19,10 @@ import { RETRIEVE_METHOD } from '@/types/app' // --- Mocks --- +const { mockToastError } = vi.hoisted(() => ({ + mockToastError: vi.fn(), +})) + const mockMutateDatasets = vi.fn() const mockInvalidDatasetList = vi.fn() const mockUpdateDatasetSetting = vi.fn().mockResolvedValue({}) @@ -55,8 +59,11 @@ vi.mock('@/app/components/datasets/common/check-rerank-model', () => ({ isReRankModelSelected: () => true, })) -vi.mock('@/app/components/base/toast', () => ({ - default: { notify: vi.fn() }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + error: mockToastError, + success: vi.fn(), + }, })) // --- Dataset factory --- @@ -311,7 +318,7 @@ describe('Dataset Settings Flow - Cross-Module Configuration Cascade', () => { describe('Form Submission Validation → All Fields Together', () => { it('should reject empty name on save', async () => { - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') const { result } = renderHook(() => useFormState()) act(() => { @@ -322,10 +329,7 @@ describe('Dataset Settings Flow - Cross-Module Configuration Cascade', () => { await result.current.handleSave() }) - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) expect(mockUpdateDatasetSetting).not.toHaveBeenCalled() }) diff --git a/web/app/components/app/configuration/dataset-config/params-config/index.spec.tsx b/web/app/components/app/configuration/dataset-config/params-config/index.spec.tsx index 9366039414..024432112d 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/index.spec.tsx @@ -3,7 +3,7 @@ import type { DatasetConfigs } from '@/models/debug' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel, @@ -75,7 +75,7 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-param const mockedUseModelListAndDefaultModelAndCurrentProviderAndModel = useModelListAndDefaultModelAndCurrentProviderAndModel as MockedFunction const mockedUseCurrentProviderAndModel = useCurrentProviderAndModel as MockedFunction -let toastNotifySpy: MockInstance +let toastErrorSpy: MockInstance const createDatasetConfigs = (overrides: Partial = {}): DatasetConfigs => { return { @@ -140,7 +140,7 @@ describe('dataset-config/params-config', () => { beforeEach(() => { vi.clearAllMocks() vi.useRealTimers() - toastNotifySpy = vi.spyOn(Toast, 'notify').mockImplementation(() => ({})) + toastErrorSpy = vi.spyOn(toast, 'error').mockImplementation(() => '') mockedUseModelListAndDefaultModelAndCurrentProviderAndModel.mockReturnValue({ modelList: [], defaultModel: undefined, @@ -154,7 +154,7 @@ describe('dataset-config/params-config', () => { }) afterEach(() => { - toastNotifySpy.mockRestore() + toastErrorSpy.mockRestore() }) // Rendering tests (REQUIRED) @@ -254,10 +254,7 @@ describe('dataset-config/params-config', () => { await user.click(dialogScope.getByRole('button', { name: 'common.operation.save' })) // Assert - expect(toastNotifySpy).toHaveBeenCalledWith({ - type: 'error', - message: 'appDebug.datasetConfig.rerankModelRequired', - }) + expect(toastErrorSpy).toHaveBeenCalledWith('appDebug.datasetConfig.rerankModelRequired') expect(screen.getByRole('dialog')).toBeInTheDocument() }) }) diff --git a/web/app/components/app/configuration/dataset-config/params-config/index.tsx b/web/app/components/app/configuration/dataset-config/params-config/index.tsx index 692ae12022..89410203df 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/index.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import { @@ -66,10 +66,7 @@ const ParamsConfig = ({ } } if (errMsg) { - Toast.notify({ - type: 'error', - message: errMsg, - }) + toast.error(errMsg) } return !errMsg } diff --git a/web/app/components/datasets/common/document-status-with-action/__tests__/auto-disabled-document.spec.tsx b/web/app/components/datasets/common/document-status-with-action/__tests__/auto-disabled-document.spec.tsx index 1103da3f36..fcaca86e89 100644 --- a/web/app/components/datasets/common/document-status-with-action/__tests__/auto-disabled-document.spec.tsx +++ b/web/app/components/datasets/common/document-status-with-action/__tests__/auto-disabled-document.spec.tsx @@ -1,10 +1,14 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useAutoDisabledDocuments } from '@/service/knowledge/use-document' import AutoDisabledDocument from '../auto-disabled-document' +const { mockToastSuccess } = vi.hoisted(() => ({ + mockToastSuccess: vi.fn(), +})) + type AutoDisabledDocumentsResponse = { document_ids: string[] } const createMockQueryResult = ( @@ -26,9 +30,9 @@ vi.mock('@/service/knowledge/use-document', () => ({ useInvalidDisabledDocument: vi.fn(() => mockInvalidDisabledDocument), })) -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: vi.fn(), +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + success: mockToastSuccess, }, })) @@ -134,10 +138,7 @@ describe('AutoDisabledDocument', () => { fireEvent.click(actionButton) await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith({ - type: 'success', - message: expect.any(String), - }) + expect(toast.success).toHaveBeenCalledWith(expect.any(String)) }) }) }) diff --git a/web/app/components/datasets/common/document-status-with-action/auto-disabled-document.tsx b/web/app/components/datasets/common/document-status-with-action/auto-disabled-document.tsx index a67c110849..c6c7e03bd1 100644 --- a/web/app/components/datasets/common/document-status-with-action/auto-disabled-document.tsx +++ b/web/app/components/datasets/common/document-status-with-action/auto-disabled-document.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useAutoDisabledDocuments, useDocumentEnable, useInvalidDisabledDocument } from '@/service/knowledge/use-document' import StatusWithAction from './status-with-action' @@ -23,7 +23,7 @@ const AutoDisabledDocument: FC = ({ const handleEnableDocuments = useCallback(async () => { await enableDocument({ datasetId, documentIds }) invalidDisabledDocument() - Toast.notify({ type: 'success', message: t('actionMsg.modifiedSuccessfully', { ns: 'common' }) }) + toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' })) }, []) if (!hasDisabledDocument || isLoading) return null diff --git a/web/app/components/datasets/common/image-uploader/hooks/__tests__/use-upload.spec.tsx b/web/app/components/datasets/common/image-uploader/hooks/__tests__/use-upload.spec.tsx index f37dbd41f4..47a29fcfa1 100644 --- a/web/app/components/datasets/common/image-uploader/hooks/__tests__/use-upload.spec.tsx +++ b/web/app/components/datasets/common/image-uploader/hooks/__tests__/use-upload.spec.tsx @@ -3,10 +3,14 @@ import type { FileEntity } from '../../types' import { act, fireEvent, render, renderHook, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { FileContextProvider } from '../../store' import { useUpload } from '../use-upload' +const { mockToastError } = vi.hoisted(() => ({ + mockToastError: vi.fn(), +})) + vi.mock('@/service/use-common', () => ({ useFileUploadConfig: vi.fn(() => ({ data: { @@ -17,9 +21,9 @@ vi.mock('@/service/use-common', () => ({ })), })) -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: vi.fn(), +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + error: mockToastError, }, })) @@ -177,10 +181,7 @@ describe('useUpload hook', () => { }) await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) }) @@ -204,13 +205,11 @@ describe('useUpload hook', () => { result.current.fileChangeHandle(mockEvent) }) - // Should not show type error for valid image type - type ToastCall = [{ type: string, message: string }] - const mockNotify = vi.mocked(Toast.notify) + // Should not show file-extension error for valid image type + type ToastCall = [string] + const mockNotify = vi.mocked(toast.error) const calls = mockNotify.mock.calls as ToastCall[] - const typeErrorCalls = calls.filter( - (call: ToastCall) => call[0].type === 'error' && call[0].message.includes('Extension'), - ) + const typeErrorCalls = calls.filter(call => call[0].includes('common.fileUploader.fileExtensionNotSupport')) expect(typeErrorCalls.length).toBe(0) }) }) @@ -261,7 +260,7 @@ describe('useUpload hook', () => { }) // Should not throw and not show error - expect(Toast.notify).not.toHaveBeenCalled() + expect(toast.error).not.toHaveBeenCalled() }) it('should handle null files', () => { @@ -314,10 +313,7 @@ describe('useUpload hook', () => { }) await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) }) }) @@ -419,10 +415,7 @@ describe('useUpload hook', () => { }) await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith({ - type: 'error', - message: 'Upload error', - }) + expect(toast.error).toHaveBeenCalledWith('Upload error') }) }) }) @@ -481,10 +474,7 @@ describe('useUpload hook', () => { }) await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith({ - type: 'error', - message: 'Upload error', - }) + expect(toast.error).toHaveBeenCalledWith('Upload error') }) }) }) @@ -522,10 +512,7 @@ describe('useUpload hook', () => { }) await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) }) }) @@ -610,10 +597,7 @@ describe('useUpload hook', () => { }) await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) // Restore original MockFileReader @@ -773,10 +757,7 @@ describe('useUpload hook', () => { // Should show error toast for invalid file type await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) }) diff --git a/web/app/components/datasets/common/image-uploader/hooks/use-upload.ts b/web/app/components/datasets/common/image-uploader/hooks/use-upload.ts index ab7b8cbf28..d262401f4b 100644 --- a/web/app/components/datasets/common/image-uploader/hooks/use-upload.ts +++ b/web/app/components/datasets/common/image-uploader/hooks/use-upload.ts @@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { v4 as uuid4 } from 'uuid' import { fileUpload, getFileUploadErrorMessage } from '@/app/components/base/file-uploader/utils' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useFileUploadConfig } from '@/service/use-common' import { ACCEPT_TYPES } from '../constants' import { useFileStore } from '../store' @@ -54,9 +54,9 @@ export const useUpload = () => { const showErrorMessage = useCallback((type: 'type' | 'size') => { if (type === 'type') - Toast.notify({ type: 'error', message: t('fileUploader.fileExtensionNotSupport', { ns: 'common' }) }) + toast.error(t('fileUploader.fileExtensionNotSupport', { ns: 'common' })) else - Toast.notify({ type: 'error', message: t('imageUploader.fileSizeLimitExceeded', { ns: 'dataset', size: fileUploadConfig.imageFileSizeLimit }) }) + toast.error(t('imageUploader.fileSizeLimitExceeded', { ns: 'dataset', size: fileUploadConfig.imageFileSizeLimit })) }, [fileUploadConfig, t]) const getValidFiles = useCallback((files: File[]) => { @@ -146,7 +146,7 @@ export const useUpload = () => { }, onErrorCallback: (error?: any) => { const errorMessage = getFileUploadErrorMessage(error, t('fileUploader.uploadFromComputerUploadError', { ns: 'common' }), t) - Toast.notify({ type: 'error', message: errorMessage }) + toast.error(errorMessage) handleUpdateFile({ ...uploadingFile, progress: -1 }) }, }) @@ -188,7 +188,7 @@ export const useUpload = () => { }, onErrorCallback: (error?: any) => { const errorMessage = getFileUploadErrorMessage(error, t('fileUploader.uploadFromComputerUploadError', { ns: 'common' }), t) - Toast.notify({ type: 'error', message: errorMessage }) + toast.error(errorMessage) handleUpdateFile({ ...uploadingFile, progress: -1 }) }, }) @@ -198,7 +198,7 @@ export const useUpload = () => { reader.addEventListener( 'error', () => { - Toast.notify({ type: 'error', message: t('fileUploader.uploadFromComputerReadError', { ns: 'common' }) }) + toast.error(t('fileUploader.uploadFromComputerReadError', { ns: 'common' })) }, false, ) @@ -211,10 +211,7 @@ export const useUpload = () => { if (newFiles.length === 0) return if (files.length + newFiles.length > singleChunkAttachmentLimit) { - Toast.notify({ - type: 'error', - message: t('imageUploader.singleChunkAttachmentLimitTooltip', { ns: 'datasetHitTesting', limit: singleChunkAttachmentLimit }), - }) + toast.error(t('imageUploader.singleChunkAttachmentLimitTooltip', { ns: 'datasetHitTesting', limit: singleChunkAttachmentLimit })) return } for (const file of newFiles) diff --git a/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx b/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx index f5b41688e1..db6086ca34 100644 --- a/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx +++ b/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx @@ -5,9 +5,9 @@ import { RETRIEVE_METHOD } from '@/types/app' import RetrievalParamConfig from '../index' const mockNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: (params: { type: string, message: string }) => mockNotify(params), +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + error: (message: string) => mockNotify(message), }, })) @@ -260,10 +260,7 @@ describe('RetrievalParamConfig', () => { fireEvent.click(screen.getByTestId('rerank-switch')) - expect(mockNotify).toHaveBeenCalledWith({ - type: 'error', - message: 'workflow.errorMsg.rerankModelRequired', - }) + expect(mockNotify).toHaveBeenCalledWith('workflow.errorMsg.rerankModelRequired') }) it('should update reranking model on selection', () => { @@ -618,10 +615,7 @@ describe('RetrievalParamConfig', () => { const rerankModelCard = radioCards.find(card => card.getAttribute('data-title') === 'common.modelProvider.rerankModel.key') fireEvent.click(rerankModelCard!) - expect(mockNotify).toHaveBeenCalledWith({ - type: 'error', - message: 'workflow.errorMsg.rerankModelRequired', - }) + expect(mockNotify).toHaveBeenCalledWith('workflow.errorMsg.rerankModelRequired') }) it('should update weights when WeightedScore changes', () => { diff --git a/web/app/components/datasets/common/retrieval-param-config/index.tsx b/web/app/components/datasets/common/retrieval-param-config/index.tsx index 2414c29a8c..e0fd245ef5 100644 --- a/web/app/components/datasets/common/retrieval-param-config/index.tsx +++ b/web/app/components/datasets/common/retrieval-param-config/index.tsx @@ -11,8 +11,8 @@ import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold import TopKItem from '@/app/components/base/param-item/top-k-item' import RadioCard from '@/app/components/base/radio-card' import Switch from '@/app/components/base/switch' -import Toast from '@/app/components/base/toast' import Tooltip from '@/app/components/base/tooltip' +import { toast } from '@/app/components/base/ui/toast' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useCurrentProviderAndModel, useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' @@ -59,7 +59,7 @@ const RetrievalParamConfig: FC = ({ const handleToggleRerankEnable = useCallback((enable: boolean) => { if (enable && !currentModel) - Toast.notify({ type: 'error', message: t('errorMsg.rerankModelRequired', { ns: 'workflow' }) }) + toast.error(t('errorMsg.rerankModelRequired', { ns: 'workflow' })) onChange({ ...value, reranking_enable: enable, @@ -96,7 +96,7 @@ const RetrievalParamConfig: FC = ({ } } if (v === RerankingModeEnum.RerankingModel && !currentModel) - Toast.notify({ type: 'error', message: t('errorMsg.rerankModelRequired', { ns: 'workflow' }) }) + toast.error(t('errorMsg.rerankModelRequired', { ns: 'workflow' })) onChange(result) } diff --git a/web/app/components/datasets/documents/components/__tests__/rename-modal.spec.tsx b/web/app/components/datasets/documents/components/__tests__/rename-modal.spec.tsx index 9ed61a66e0..ad40e52752 100644 --- a/web/app/components/datasets/documents/components/__tests__/rename-modal.spec.tsx +++ b/web/app/components/datasets/documents/components/__tests__/rename-modal.spec.tsx @@ -5,11 +5,23 @@ import { renameDocumentName } from '@/service/datasets' import RenameModal from '../rename-modal' +const { mockToastSuccess, mockToastError } = vi.hoisted(() => ({ + mockToastSuccess: vi.fn(), + mockToastError: vi.fn(), +})) + // Mock the service vi.mock('@/service/datasets', () => ({ renameDocumentName: vi.fn(), })) +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + success: mockToastSuccess, + error: mockToastError, + }, +})) + const mockRenameDocumentName = vi.mocked(renameDocumentName) describe('RenameModal', () => { @@ -118,6 +130,7 @@ describe('RenameModal', () => { await waitFor(() => { expect(handleSaved).toHaveBeenCalledTimes(1) expect(handleClose).toHaveBeenCalledTimes(1) + expect(mockToastSuccess).toHaveBeenCalledWith(expect.any(String)) }) }) }) @@ -163,6 +176,7 @@ describe('RenameModal', () => { // onSaved and onClose should not be called on error expect(handleSaved).not.toHaveBeenCalled() expect(handleClose).not.toHaveBeenCalled() + expect(mockToastError).toHaveBeenCalledWith('Error: API Error') }) }) }) diff --git a/web/app/components/datasets/documents/components/document-list/hooks/__tests__/use-document-actions.spec.ts b/web/app/components/datasets/documents/components/document-list/hooks/__tests__/use-document-actions.spec.ts index 5f48be084e..449478eb7b 100644 --- a/web/app/components/datasets/documents/components/document-list/hooks/__tests__/use-document-actions.spec.ts +++ b/web/app/components/datasets/documents/components/document-list/hooks/__tests__/use-document-actions.spec.ts @@ -3,6 +3,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { DocumentActionType } from '@/models/datasets' import { useDocumentActions } from '../use-document-actions' +const { mockToastSuccess, mockToastError } = vi.hoisted(() => ({ + mockToastSuccess: vi.fn(), + mockToastError: vi.fn(), +})) + const mockArchive = vi.fn() const mockSummary = vi.fn() const mockEnable = vi.fn() @@ -22,9 +27,11 @@ vi.mock('@/service/knowledge/use-document', () => ({ useDocumentDownloadZip: () => ({ mutateAsync: mockDownloadZip, isPending: mockIsDownloadingZip }), })) -const mockToastNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { notify: (...args: unknown[]) => mockToastNotify(...args) }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + success: mockToastSuccess, + error: mockToastError, + }, })) const mockDownloadBlob = vi.fn() @@ -67,9 +74,7 @@ describe('useDocumentActions', () => { datasetId: 'ds-1', documentIds: ['doc-1', 'doc-2'], }) - expect(mockToastNotify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'success' }), - ) + expect(mockToastSuccess).toHaveBeenCalledWith(expect.any(String)) expect(defaultOptions.onUpdate).toHaveBeenCalled() }) @@ -142,9 +147,7 @@ describe('useDocumentActions', () => { await result.current.handleAction(DocumentActionType.archive)() }) - expect(mockToastNotify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'error' }), - ) + expect(mockToastError).toHaveBeenCalledWith(expect.any(String)) expect(defaultOptions.onUpdate).not.toHaveBeenCalled() }) }) @@ -174,9 +177,7 @@ describe('useDocumentActions', () => { await result.current.handleBatchReIndex() }) - expect(mockToastNotify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'error' }), - ) + expect(mockToastError).toHaveBeenCalledWith(expect.any(String)) }) }) @@ -210,9 +211,7 @@ describe('useDocumentActions', () => { await result.current.handleBatchDownload() }) - expect(mockToastNotify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'error' }), - ) + expect(mockToastError).toHaveBeenCalledWith(expect.any(String)) }) it('should show error toast when blob is null', async () => { @@ -223,9 +222,7 @@ describe('useDocumentActions', () => { await result.current.handleBatchDownload() }) - expect(mockToastNotify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'error' }), - ) + expect(mockToastError).toHaveBeenCalledWith(expect.any(String)) }) }) }) diff --git a/web/app/components/datasets/documents/components/document-list/hooks/use-document-actions.ts b/web/app/components/datasets/documents/components/document-list/hooks/use-document-actions.ts index 56553faa9e..8b6c40e2be 100644 --- a/web/app/components/datasets/documents/components/document-list/hooks/use-document-actions.ts +++ b/web/app/components/datasets/documents/components/document-list/hooks/use-document-actions.ts @@ -1,7 +1,7 @@ import type { CommonResponse } from '@/models/common' import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { DocumentActionType } from '@/models/datasets' import { useDocumentArchive, @@ -79,11 +79,11 @@ export const useDocumentActions = ({ if (!e) { if (actionName === DocumentActionType.delete) onClearSelection() - Toast.notify({ type: 'success', message: t('actionMsg.modifiedSuccessfully', { ns: 'common' }) }) + toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' })) onUpdate() } else { - Toast.notify({ type: 'error', message: t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }) }) + toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' })) } } }, [actionMutationMap, datasetId, selectedIds, onClearSelection, onUpdate, t]) @@ -94,11 +94,11 @@ export const useDocumentActions = ({ ) if (!e) { onClearSelection() - Toast.notify({ type: 'success', message: t('actionMsg.modifiedSuccessfully', { ns: 'common' }) }) + toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' })) onUpdate() } else { - Toast.notify({ type: 'error', message: t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }) }) + toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' })) } }, [retryIndexDocument, datasetId, selectedIds, onClearSelection, onUpdate, t]) @@ -110,7 +110,7 @@ export const useDocumentActions = ({ requestDocumentsZip({ datasetId, documentIds: downloadableSelectedIds }), ) if (e || !blob) { - Toast.notify({ type: 'error', message: t('actionMsg.downloadUnsuccessfully', { ns: 'common' }) }) + toast.error(t('actionMsg.downloadUnsuccessfully', { ns: 'common' })) return } diff --git a/web/app/components/datasets/documents/components/rename-modal.tsx b/web/app/components/datasets/documents/components/rename-modal.tsx index a119a2da9e..364aaf48e6 100644 --- a/web/app/components/datasets/documents/components/rename-modal.tsx +++ b/web/app/components/datasets/documents/components/rename-modal.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Modal from '@/app/components/base/modal' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { renameDocumentName } from '@/service/datasets' type Props = { @@ -41,13 +41,13 @@ const RenameModal: FC = ({ documentId, name: newName, }) - Toast.notify({ type: 'success', message: t('actionMsg.modifiedSuccessfully', { ns: 'common' }) }) + toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' })) onSaved() onClose() } catch (error) { if (error) - Toast.notify({ type: 'error', message: error.toString() }) + toast.error(error.toString()) } finally { setSaveLoadingFalse() diff --git a/web/app/components/datasets/list/dataset-card/hooks/__tests__/use-dataset-card-state.spec.ts b/web/app/components/datasets/list/dataset-card/hooks/__tests__/use-dataset-card-state.spec.ts index 63ac30630e..f29d85b460 100644 --- a/web/app/components/datasets/list/dataset-card/hooks/__tests__/use-dataset-card-state.spec.ts +++ b/web/app/components/datasets/list/dataset-card/hooks/__tests__/use-dataset-card-state.spec.ts @@ -5,9 +5,15 @@ import { IndexingType } from '@/app/components/datasets/create/step-two' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' import { useDatasetCardState } from '../use-dataset-card-state' -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: vi.fn(), +const { mockToastSuccess, mockToastError } = vi.hoisted(() => ({ + mockToastSuccess: vi.fn(), + mockToastError: vi.fn(), +})) + +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + success: mockToastSuccess, + error: mockToastError, }, })) @@ -299,7 +305,7 @@ describe('useDatasetCardState', () => { describe('Error Handling', () => { it('should show error toast when export pipeline fails', async () => { - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') mockExportPipeline.mockRejectedValue(new Error('Export failed')) const dataset = createMockDataset({ pipeline_id: 'pipeline-1' }) @@ -311,14 +317,11 @@ describe('useDatasetCardState', () => { await result.current.handleExportPipeline() }) - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) it('should handle Response error in detectIsUsedByApp', async () => { - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') const mockResponse = new Response(JSON.stringify({ message: 'API Error' }), { status: 400, }) @@ -333,14 +336,11 @@ describe('useDatasetCardState', () => { await result.current.detectIsUsedByApp() }) - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.stringContaining('API Error'), - }) + expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('API Error')) }) it('should handle generic Error in detectIsUsedByApp', async () => { - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') mockCheckUsage.mockRejectedValue(new Error('Network error')) const dataset = createMockDataset() @@ -352,14 +352,11 @@ describe('useDatasetCardState', () => { await result.current.detectIsUsedByApp() }) - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'error', - message: 'Network error', - }) + expect(toast.error).toHaveBeenCalledWith('Network error') }) it('should handle error without message in detectIsUsedByApp', async () => { - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') mockCheckUsage.mockRejectedValue({}) const dataset = createMockDataset() @@ -371,10 +368,7 @@ describe('useDatasetCardState', () => { await result.current.detectIsUsedByApp() }) - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'error', - message: 'Unknown error', - }) + expect(toast.error).toHaveBeenCalledWith('dataset.unknownError') }) it('should handle exporting state correctly', async () => { diff --git a/web/app/components/datasets/list/dataset-card/hooks/use-dataset-card-state.ts b/web/app/components/datasets/list/dataset-card/hooks/use-dataset-card-state.ts index 4bd8357f1c..850eee4364 100644 --- a/web/app/components/datasets/list/dataset-card/hooks/use-dataset-card-state.ts +++ b/web/app/components/datasets/list/dataset-card/hooks/use-dataset-card-state.ts @@ -2,7 +2,7 @@ import type { Tag } from '@/app/components/base/tag-management/constant' import type { DataSet } from '@/models/datasets' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useCheckDatasetUsage, useDeleteDataset } from '@/service/use-dataset-card' import { useExportPipelineDSL } from '@/service/use-pipeline' import { downloadBlob } from '@/utils/download' @@ -70,7 +70,7 @@ export const useDatasetCardState = ({ dataset, onSuccess }: UseDatasetCardStateO downloadBlob({ data: file, fileName: `${name}.pipeline` }) } catch { - Toast.notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) }) + toast.error(t('exportFailed', { ns: 'app' })) } finally { setExporting(false) @@ -93,10 +93,10 @@ export const useDatasetCardState = ({ dataset, onSuccess }: UseDatasetCardStateO catch (e: unknown) { if (e instanceof Response) { const res = await e.json() - Toast.notify({ type: 'error', message: res?.message || 'Unknown error' }) + toast.error(res?.message || t('unknownError', { ns: 'dataset' })) } else { - Toast.notify({ type: 'error', message: (e as Error)?.message || 'Unknown error' }) + toast.error((e as Error)?.message || t('unknownError', { ns: 'dataset' })) } } }, [dataset.id, checkUsage, t]) @@ -104,7 +104,7 @@ export const useDatasetCardState = ({ dataset, onSuccess }: UseDatasetCardStateO const onConfirmDelete = useCallback(async () => { try { await deleteDatasetMutation(dataset.id) - Toast.notify({ type: 'success', message: t('datasetDeleted', { ns: 'dataset' }) }) + toast.success(t('datasetDeleted', { ns: 'dataset' })) onSuccess?.() } finally { diff --git a/web/app/components/datasets/settings/form/__tests__/index.spec.tsx b/web/app/components/datasets/settings/form/__tests__/index.spec.tsx index 9eeb62b8e8..a3a22e000b 100644 --- a/web/app/components/datasets/settings/form/__tests__/index.spec.tsx +++ b/web/app/components/datasets/settings/form/__tests__/index.spec.tsx @@ -6,6 +6,10 @@ import { RETRIEVE_METHOD } from '@/types/app' import { IndexingType } from '../../../create/step-two' import Form from '../index' +const { mockToastError } = vi.hoisted(() => ({ + mockToastError: vi.fn(), +})) + // Mock contexts const mockMutateDatasets = vi.fn() const mockInvalidDatasetList = vi.fn() @@ -189,9 +193,10 @@ vi.mock('@/app/components/datasets/common/check-rerank-model', () => ({ isReRankModelSelected: () => true, })) -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: vi.fn(), +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + error: mockToastError, + success: vi.fn(), }, })) @@ -391,7 +396,7 @@ describe('Form', () => { }) it('should show error when trying to save with empty name', async () => { - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') render(
) // Clear the name @@ -403,10 +408,7 @@ describe('Form', () => { fireEvent.click(saveButton) await waitFor(() => { - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) }) diff --git a/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts b/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts index f27b542b1e..00462619aa 100644 --- a/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts +++ b/web/app/components/datasets/settings/form/hooks/__tests__/use-form-state.spec.ts @@ -6,6 +6,11 @@ import { RETRIEVE_METHOD } from '@/types/app' import { IndexingType } from '../../../../create/step-two' import { useFormState } from '../use-form-state' +const { mockToastSuccess, mockToastError } = vi.hoisted(() => ({ + mockToastSuccess: vi.fn(), + mockToastError: vi.fn(), +})) + // Mock contexts const mockMutateDatasets = vi.fn() const mockInvalidDatasetList = vi.fn() @@ -122,9 +127,10 @@ vi.mock('@/app/components/datasets/common/check-rerank-model', () => ({ isReRankModelSelected: () => true, })) -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: vi.fn(), +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + success: mockToastSuccess, + error: mockToastError, }, })) @@ -423,7 +429,7 @@ describe('useFormState', () => { describe('handleSave', () => { it('should show error toast when name is empty', async () => { - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') const { result } = renderHook(() => useFormState()) act(() => { @@ -434,14 +440,11 @@ describe('useFormState', () => { await result.current.handleSave() }) - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) it('should show error toast when name is whitespace only', async () => { - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') const { result } = renderHook(() => useFormState()) act(() => { @@ -452,10 +455,7 @@ describe('useFormState', () => { await result.current.handleSave() }) - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) it('should call updateDatasetSetting with correct params', async () => { @@ -477,7 +477,7 @@ describe('useFormState', () => { }) it('should show success toast on successful save', async () => { - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') const { result } = renderHook(() => useFormState()) await act(async () => { @@ -485,10 +485,7 @@ describe('useFormState', () => { }) await waitFor(() => { - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'success', - message: expect.any(String), - }) + expect(toast.success).toHaveBeenCalledWith(expect.any(String)) }) }) @@ -553,7 +550,7 @@ describe('useFormState', () => { it('should show error toast on save failure', async () => { const { updateDatasetSetting } = await import('@/service/datasets') - const Toast = await import('@/app/components/base/toast') + const { toast } = await import('@/app/components/base/ui/toast') vi.mocked(updateDatasetSetting).mockRejectedValueOnce(new Error('Network error')) const { result } = renderHook(() => useFormState()) @@ -562,10 +559,7 @@ describe('useFormState', () => { await result.current.handleSave() }) - expect(Toast.default.notify).toHaveBeenCalledWith({ - type: 'error', - message: expect.any(String), - }) + expect(toast.error).toHaveBeenCalledWith(expect.any(String)) }) it('should include partial_member_list when permission is partialMembers', async () => { diff --git a/web/app/components/datasets/settings/form/hooks/use-form-state.ts b/web/app/components/datasets/settings/form/hooks/use-form-state.ts index 614995d43a..d00534f7f4 100644 --- a/web/app/components/datasets/settings/form/hooks/use-form-state.ts +++ b/web/app/components/datasets/settings/form/hooks/use-form-state.ts @@ -6,7 +6,7 @@ import type { IconInfo, SummaryIndexSetting as SummaryIndexSettingType } from '@ import type { RetrievalConfig } from '@/types/app' import { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -123,12 +123,12 @@ export const useFormState = () => { return if (!name?.trim()) { - Toast.notify({ type: 'error', message: t('form.nameError', { ns: 'datasetSettings' }) }) + toast.error(t('form.nameError', { ns: 'datasetSettings' })) return } if (!isReRankModelSelected({ rerankModelList, retrievalConfig, indexMethod })) { - Toast.notify({ type: 'error', message: t('datasetConfig.rerankModelRequired', { ns: 'appDebug' }) }) + toast.error(t('datasetConfig.rerankModelRequired', { ns: 'appDebug' })) return } @@ -176,7 +176,7 @@ export const useFormState = () => { } await updateDatasetSetting({ datasetId: currentDataset!.id, body }) - Toast.notify({ type: 'success', message: t('actionMsg.modifiedSuccessfully', { ns: 'common' }) }) + toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' })) if (mutateDatasets) { await mutateDatasets() @@ -184,7 +184,7 @@ export const useFormState = () => { } } catch { - Toast.notify({ type: 'error', message: t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }) }) + toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' })) } finally { setLoading(false) diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 36c77e051c..d0aa842e11 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -959,7 +959,7 @@ }, "app/components/app/configuration/dataset-config/params-config/index.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 }, "react/set-state-in-effect": { "count": 1 @@ -3166,11 +3166,6 @@ "count": 2 } }, - "app/components/datasets/common/document-status-with-action/auto-disabled-document.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "app/components/datasets/common/image-list/more.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 1 @@ -3190,9 +3185,6 @@ } }, "app/components/datasets/common/image-uploader/hooks/use-upload.ts": { - "no-restricted-imports": { - "count": 1 - }, "ts/no-explicit-any": { "count": 3 } @@ -3222,7 +3214,7 @@ }, "app/components/datasets/common/retrieval-param-config/index.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 } }, "app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/dsl-confirm-modal.tsx": { @@ -3573,11 +3565,6 @@ "count": 1 } }, - "app/components/datasets/documents/components/document-list/hooks/use-document-actions.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "app/components/datasets/documents/components/documents-header.tsx": { "no-restricted-imports": { "count": 1 @@ -3590,7 +3577,7 @@ }, "app/components/datasets/documents/components/rename-modal.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 } }, "app/components/datasets/documents/create-from-pipeline/actions/index.tsx": { @@ -4258,9 +4245,6 @@ } }, "app/components/datasets/list/dataset-card/hooks/use-dataset-card-state.ts": { - "no-restricted-imports": { - "count": 1 - }, "react/set-state-in-effect": { "count": 1 } @@ -4430,11 +4414,6 @@ "count": 7 } }, - "app/components/datasets/settings/form/hooks/use-form-state.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "app/components/datasets/settings/index-method/index.tsx": { "no-restricted-imports": { "count": 1 diff --git a/web/i18n/en-US/dataset.json b/web/i18n/en-US/dataset.json index 72d0a7b909..c6b15fbe9b 100644 --- a/web/i18n/en-US/dataset.json +++ b/web/i18n/en-US/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "Enabled", "serviceApi.title": "Service API", "unavailable": "Unavailable", + "unknownError": "Unknown error", "updated": "Updated", "weightedScore.customized": "Customized", "weightedScore.description": "By adjusting the weights assigned, this rerank strategy determines whether to prioritize semantic or keyword matching.", From 01d97fa2cf09f8d31418e683b80d81dec1f4162d Mon Sep 17 00:00:00 2001 From: wangxiaolei Date: Mon, 23 Mar 2026 13:51:56 +0800 Subject: [PATCH 04/70] fix: type object 'str' has no attribute 'LLM' (#33899) --- api/pyproject.toml | 1 + ...t_sqlalchemy_workflow_node_execution_repository.py | 8 ++++---- api/uv.lock | 11 +++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index f824fe7c23..fb71f3cd6c 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -79,6 +79,7 @@ dependencies = [ "tiktoken~=0.12.0", "transformers~=5.3.0", "unstructured[docx,epub,md,ppt,pptx]~=0.21.5", + "pypandoc~=1.13", "yarl~=1.23.0", "webvtt-py~=0.5.1", "sseclient-py~=1.9.0", diff --git a/api/tests/unit_tests/core/repositories/test_sqlalchemy_workflow_node_execution_repository.py b/api/tests/unit_tests/core/repositories/test_sqlalchemy_workflow_node_execution_repository.py index c7af32789b..73de15e2cf 100644 --- a/api/tests/unit_tests/core/repositories/test_sqlalchemy_workflow_node_execution_repository.py +++ b/api/tests/unit_tests/core/repositories/test_sqlalchemy_workflow_node_execution_repository.py @@ -24,7 +24,7 @@ from core.repositories.sqlalchemy_workflow_node_execution_repository import ( ) from dify_graph.entities import WorkflowNodeExecution from dify_graph.enums import ( - NodeType, + BuiltinNodeTypes, WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus, ) @@ -67,7 +67,7 @@ def _execution( index=1, predecessor_node_id=None, node_id="node-id", - node_type=NodeType.LLM, + node_type=BuiltinNodeTypes.LLM, title="Title", inputs=inputs, outputs=outputs, @@ -387,7 +387,7 @@ def test_to_domain_model_loads_offloaded_files(monkeypatch: pytest.MonkeyPatch) db_model.index = 1 db_model.predecessor_node_id = None db_model.node_id = "node" - db_model.node_type = NodeType.LLM + db_model.node_type = BuiltinNodeTypes.LLM db_model.title = "t" db_model.inputs = json.dumps({"trunc": "i"}) db_model.process_data = json.dumps({"trunc": "p"}) @@ -441,7 +441,7 @@ def test_to_domain_model_returns_early_when_no_offload_data(monkeypatch: pytest. db_model.index = 1 db_model.predecessor_node_id = None db_model.node_id = "node" - db_model.node_type = NodeType.LLM + db_model.node_type = BuiltinNodeTypes.LLM db_model.title = "t" db_model.inputs = json.dumps({"i": 1}) db_model.process_data = json.dumps({"p": 2}) diff --git a/api/uv.lock b/api/uv.lock index ebfc6678fe..952ec87273 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1605,6 +1605,7 @@ dependencies = [ { name = "pydantic-extra-types" }, { name = "pydantic-settings" }, { name = "pyjwt" }, + { name = "pypandoc" }, { name = "pypdfium2" }, { name = "python-docx" }, { name = "python-dotenv" }, @@ -1807,6 +1808,7 @@ requires-dist = [ { name = "pydantic-extra-types", specifier = "~=2.11.0" }, { name = "pydantic-settings", specifier = "~=2.13.1" }, { name = "pyjwt", specifier = "~=2.12.0" }, + { name = "pypandoc", specifier = "~=1.13" }, { name = "pypdfium2", specifier = "==5.6.0" }, { name = "python-docx", specifier = "~=1.2.0" }, { name = "python-dotenv", specifier = "==1.2.2" }, @@ -5380,6 +5382,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/7d/037401cecb34728d1c28ea05e196ea3c9d50a1ce0f2172e586e075ff55d8/pyobvector-0.2.25-py3-none-any.whl", hash = "sha256:ae0153f99bd0222783ed7e3951efc31a0d2b462d926b6f86ebd2033409aede8f", size = 64663, upload-time = "2026-03-10T07:18:29.789Z" }, ] +[[package]] +name = "pypandoc" +version = "1.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/d6/410615fc433e5d1eacc00db2044ae2a9c82302df0d35366fe2bd15de024d/pypandoc-1.17.tar.gz", hash = "sha256:51179abfd6e582a25ed03477541b48836b5bba5a4c3b282a547630793934d799", size = 69071, upload-time = "2026-03-14T22:39:07.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/86/e2ffa604eacfbec3f430b1d850e7e04c4101eca1a5828f9ae54bf51dfba4/pypandoc-1.17-py3-none-any.whl", hash = "sha256:01fdbffa61edb9f8e82e8faad6954efcb7b6f8f0634aead4d89e322a00225a67", size = 23554, upload-time = "2026-03-14T22:38:46.007Z" }, +] + [[package]] name = "pypandoc-binary" version = "1.17" From abd68d2ea66c1324b55e909fa2259533b3732277 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:05:47 +0800 Subject: [PATCH 05/70] chore(i18n): sync translations with en-US (#33894) Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> --- web/i18n/ar-TN/dataset.json | 1 + web/i18n/de-DE/dataset.json | 1 + web/i18n/es-ES/dataset.json | 1 + web/i18n/fa-IR/dataset.json | 1 + web/i18n/fr-FR/dataset.json | 1 + web/i18n/hi-IN/dataset.json | 1 + web/i18n/id-ID/dataset.json | 1 + web/i18n/it-IT/dataset.json | 1 + web/i18n/ja-JP/dataset.json | 1 + web/i18n/ko-KR/dataset.json | 1 + web/i18n/nl-NL/dataset.json | 1 + web/i18n/pl-PL/dataset.json | 1 + web/i18n/pt-BR/dataset.json | 1 + web/i18n/ro-RO/dataset.json | 1 + web/i18n/ru-RU/dataset.json | 1 + web/i18n/sl-SI/dataset.json | 1 + web/i18n/th-TH/dataset.json | 1 + web/i18n/tr-TR/dataset.json | 1 + web/i18n/uk-UA/dataset.json | 1 + web/i18n/vi-VN/dataset.json | 1 + web/i18n/zh-Hans/dataset.json | 1 + web/i18n/zh-Hant/dataset.json | 1 + 22 files changed, 22 insertions(+) diff --git a/web/i18n/ar-TN/dataset.json b/web/i18n/ar-TN/dataset.json index 9a4c07f432..0f89fa8a0d 100644 --- a/web/i18n/ar-TN/dataset.json +++ b/web/i18n/ar-TN/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "في الخدمة", "serviceApi.title": "واجهة برمجة تطبيقات الخدمة", "unavailable": "غير متاح", + "unknownError": "خطأ غير معروف", "updated": "محدث", "weightedScore.customized": "مخصص", "weightedScore.description": "من خلال تعديل الأوزان المخصصة، تحدد استراتيجية إعادة الترتيب هذه ما إذا كانت الأولوية للمطابقة الدلالية أو الكلمات الرئيسية.", diff --git a/web/i18n/de-DE/dataset.json b/web/i18n/de-DE/dataset.json index 678efa682a..566a1bb0fc 100644 --- a/web/i18n/de-DE/dataset.json +++ b/web/i18n/de-DE/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "Im Dienst", "serviceApi.title": "Service-API", "unavailable": "Nicht verfügbar", + "unknownError": "Unbekannter Fehler", "updated": "Aktualisierte", "weightedScore.customized": "Angepasst", "weightedScore.description": "Durch Anpassung der zugewiesenen Gewichte bestimmt diese Rerank-Strategie, ob semantische oder Schlüsselwort-Übereinstimmung priorisiert werden soll.", diff --git a/web/i18n/es-ES/dataset.json b/web/i18n/es-ES/dataset.json index 99690702cf..fd88db145c 100644 --- a/web/i18n/es-ES/dataset.json +++ b/web/i18n/es-ES/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "En servicio", "serviceApi.title": "API de servicios", "unavailable": "No disponible", + "unknownError": "Error desconocido", "updated": "Actualizado", "weightedScore.customized": "Personalizado", "weightedScore.description": "Al ajustar los pesos asignados, esta estrategia de reclasificación determina si se debe priorizar la coincidencia semántica o de palabras clave.", diff --git a/web/i18n/fa-IR/dataset.json b/web/i18n/fa-IR/dataset.json index 76d3147fe4..d56adf94b5 100644 --- a/web/i18n/fa-IR/dataset.json +++ b/web/i18n/fa-IR/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "در حال خدمت", "serviceApi.title": "رابط برنامه‌نویسی سرویس", "unavailable": "در دسترس نیست", + "unknownError": "خطای ناشناخته", "updated": "بروز رسانی", "weightedScore.customized": "سفارشی‌سازی شده", "weightedScore.description": "با تنظیم وزن‌های اختصاص داده شده، این استراتژی دوباره رتبه‌بندی تعیین می‌کند که آیا اولویت با تطابق معنایی یا کلمات کلیدی است.", diff --git a/web/i18n/fr-FR/dataset.json b/web/i18n/fr-FR/dataset.json index 3cda7fee7e..062df39faa 100644 --- a/web/i18n/fr-FR/dataset.json +++ b/web/i18n/fr-FR/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "En service", "serviceApi.title": "API de service", "unavailable": "Indisponible", + "unknownError": "Erreur inconnue", "updated": "Actualisé", "weightedScore.customized": "Personnalisé", "weightedScore.description": "En ajustant les poids attribués, cette stratégie de reclassement détermine s'il faut prioriser la correspondance sémantique ou par mots-clés.", diff --git a/web/i18n/hi-IN/dataset.json b/web/i18n/hi-IN/dataset.json index 0ac9a79d1a..f2f857583b 100644 --- a/web/i18n/hi-IN/dataset.json +++ b/web/i18n/hi-IN/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "सेवा में", "serviceApi.title": "सेवा एपीआई", "unavailable": "उपलब्ध नहीं", + "unknownError": "अज्ञात त्रुटि", "updated": "अपडेट किया गया", "weightedScore.customized": "अनुकूलित", "weightedScore.description": "आवंटित भारों को समायोजित करके, यह पुनः रैंकिंग रणनीति निर्धारित करती है कि शब्दार्थ या कीवर्ड मिलान को प्राथमिकता दी जाए।", diff --git a/web/i18n/id-ID/dataset.json b/web/i18n/id-ID/dataset.json index 80fddf0dd8..d4fe6cd12a 100644 --- a/web/i18n/id-ID/dataset.json +++ b/web/i18n/id-ID/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "Sedang Beroperasi", "serviceApi.title": "API Layanan", "unavailable": "Tidak tersedia", + "unknownError": "Kesalahan tidak diketahui", "updated": "Diperbarui", "weightedScore.customized": "Disesuaikan", "weightedScore.description": "Dengan menyesuaikan bobot yang ditetapkan, strategi rerank ini menentukan apakah akan memprioritaskan pencocokan semantik atau kata kunci.", diff --git a/web/i18n/it-IT/dataset.json b/web/i18n/it-IT/dataset.json index 4599b0de07..a29568f3fb 100644 --- a/web/i18n/it-IT/dataset.json +++ b/web/i18n/it-IT/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "In servizio", "serviceApi.title": "API di servizio", "unavailable": "Non disponibile", + "unknownError": "Errore sconosciuto", "updated": "Aggiornato", "weightedScore.customized": "Personalizzato", "weightedScore.description": "Regolando i pesi assegnati, questa strategia di riclassificazione determina se dare priorità alla corrispondenza semantica o per parole chiave.", diff --git a/web/i18n/ja-JP/dataset.json b/web/i18n/ja-JP/dataset.json index 7f4a24e405..220e8d88cc 100644 --- a/web/i18n/ja-JP/dataset.json +++ b/web/i18n/ja-JP/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "サービス中", "serviceApi.title": "サービスAPI", "unavailable": "利用不可", + "unknownError": "不明なエラー", "updated": "更新された", "weightedScore.customized": "カスタマイズ", "weightedScore.description": "重みを調整することで、並べ替え戦略はセマンティックマッチングとキーワードマッチングのどちらを優先するかを決定します。", diff --git a/web/i18n/ko-KR/dataset.json b/web/i18n/ko-KR/dataset.json index 1af31e896e..64f5461ae6 100644 --- a/web/i18n/ko-KR/dataset.json +++ b/web/i18n/ko-KR/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "서비스 중", "serviceApi.title": "서비스 API", "unavailable": "사용 불가", + "unknownError": "알 수 없는 오류", "updated": "업데이트", "weightedScore.customized": "사용자 정의", "weightedScore.description": "할당된 가중치를 조정함으로써, 이 재순위 전략은 의미론적 일치 또는 키워드 일치 중 어느 것을 우선시할지 결정합니다.", diff --git a/web/i18n/nl-NL/dataset.json b/web/i18n/nl-NL/dataset.json index d953485a24..4a3366524c 100644 --- a/web/i18n/nl-NL/dataset.json +++ b/web/i18n/nl-NL/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "Enabled", "serviceApi.title": "Service API", "unavailable": "Unavailable", + "unknownError": "Onbekende fout", "updated": "Updated", "weightedScore.customized": "Customized", "weightedScore.description": "By adjusting the weights assigned, this rerank strategy determines whether to prioritize semantic or keyword matching.", diff --git a/web/i18n/pl-PL/dataset.json b/web/i18n/pl-PL/dataset.json index 7602b419c1..2d15214c06 100644 --- a/web/i18n/pl-PL/dataset.json +++ b/web/i18n/pl-PL/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "W serwisie", "serviceApi.title": "Interfejs API usługi", "unavailable": "Niedostępny", + "unknownError": "Nieznany błąd", "updated": "Aktualizowano", "weightedScore.customized": "Dostosowane", "weightedScore.description": "Poprzez dostosowanie przypisanych wag, ta strategia ponownego rankingu określa, czy priorytetowo traktować dopasowanie semantyczne czy słów kluczowych.", diff --git a/web/i18n/pt-BR/dataset.json b/web/i18n/pt-BR/dataset.json index b4403e65ac..414ff9da97 100644 --- a/web/i18n/pt-BR/dataset.json +++ b/web/i18n/pt-BR/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "Em serviço", "serviceApi.title": "API de Serviço", "unavailable": "Indisponível", + "unknownError": "Erro desconhecido", "updated": "Atualizado", "weightedScore.customized": "Personalizado", "weightedScore.description": "Ao ajustar os pesos atribuídos, esta estratégia de reclassificação determina se deve priorizar a correspondência semântica ou por palavras-chave.", diff --git a/web/i18n/ro-RO/dataset.json b/web/i18n/ro-RO/dataset.json index 2aef8a25d5..704868be26 100644 --- a/web/i18n/ro-RO/dataset.json +++ b/web/i18n/ro-RO/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "În serviciu", "serviceApi.title": "API de servicii", "unavailable": "Indisponibil", + "unknownError": "Eroare necunoscută", "updated": "Actualizat", "weightedScore.customized": "Personalizat", "weightedScore.description": "Prin ajustarea ponderilor atribuite, această strategie de reclasificare determină dacă să prioritizeze potrivirea semantică sau pe cea a cuvintelor cheie.", diff --git a/web/i18n/ru-RU/dataset.json b/web/i18n/ru-RU/dataset.json index eae48194c8..65f5af81bc 100644 --- a/web/i18n/ru-RU/dataset.json +++ b/web/i18n/ru-RU/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "На службе", "serviceApi.title": "Сервисный API", "unavailable": "Недоступно", + "unknownError": "Неизвестная ошибка", "updated": "Обновлено", "weightedScore.customized": "Настраиваемый", "weightedScore.description": "Регулируя назначенные веса, эта стратегия переранжирования определяет, следует ли отдавать приоритет семантическому или ключевому соответствию.", diff --git a/web/i18n/sl-SI/dataset.json b/web/i18n/sl-SI/dataset.json index fa5daab001..26b81ba131 100644 --- a/web/i18n/sl-SI/dataset.json +++ b/web/i18n/sl-SI/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "V storitvi", "serviceApi.title": "Storitveni API", "unavailable": "Ni na voljo", + "unknownError": "Neznana napaka", "updated": "Posodobljene", "weightedScore.customized": "Prilagojeno", "weightedScore.description": "Z nastavljanjem dodeljenih uteži ta strategija za ponovno razvrščanje določa, ali naj se daje prednost semantičnemu ali ključnemu ujemanju.", diff --git a/web/i18n/th-TH/dataset.json b/web/i18n/th-TH/dataset.json index f90e86a63a..7b320f90dd 100644 --- a/web/i18n/th-TH/dataset.json +++ b/web/i18n/th-TH/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "ให้บริการ", "serviceApi.title": "บริการ API", "unavailable": "ไม่", + "unknownError": "ข้อผิดพลาดที่ไม่รู้จัก", "updated": "ปรับ ปรุง", "weightedScore.customized": "กำหนด เอง", "weightedScore.description": "กลยุทธ์การจัดอันดับใหม่นี้จะกําหนดว่าควรจัดลําดับความสําคัญของการจับคู่ความหมายหรือคีย์เวิร์ด", diff --git a/web/i18n/tr-TR/dataset.json b/web/i18n/tr-TR/dataset.json index a0147d266d..842fb7491b 100644 --- a/web/i18n/tr-TR/dataset.json +++ b/web/i18n/tr-TR/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "Hizmette", "serviceApi.title": "Servis API'si", "unavailable": "Kullanılamıyor", + "unknownError": "Bilinmeyen hata", "updated": "Güncel -leştirilmiş", "weightedScore.customized": "Özelleştirilmiş", "weightedScore.description": "Verilen ağırlıkları ayarlayarak bu yeniden sıralama stratejisi, anlamsal mı yoksa anahtar kelime eşleştirmesini mi önceliklendireceğini belirler.", diff --git a/web/i18n/uk-UA/dataset.json b/web/i18n/uk-UA/dataset.json index 508c00a1e2..ca077d3439 100644 --- a/web/i18n/uk-UA/dataset.json +++ b/web/i18n/uk-UA/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "У службі", "serviceApi.title": "Сервісний API", "unavailable": "Недоступно", + "unknownError": "Невідома помилка", "updated": "Оновлено", "weightedScore.customized": "Налаштований", "weightedScore.description": "Регулюючи призначені ваги, ця стратегія перерангування визначає, чи надавати пріоритет семантичному чи ключовому відповідності.", diff --git a/web/i18n/vi-VN/dataset.json b/web/i18n/vi-VN/dataset.json index 8a800953a4..ec4970ce9d 100644 --- a/web/i18n/vi-VN/dataset.json +++ b/web/i18n/vi-VN/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "Đang phục vụ", "serviceApi.title": "Giao diện lập trình dịch vụ", "unavailable": "Không khả dụng", + "unknownError": "Lỗi không xác định", "updated": "Cập nhật", "weightedScore.customized": "Tùy chỉnh", "weightedScore.description": "Bằng cách điều chỉnh trọng số được gán, chiến lược xếp hạng lại này xác định liệu ưu tiên khớp ngữ nghĩa hay từ khóa.", diff --git a/web/i18n/zh-Hans/dataset.json b/web/i18n/zh-Hans/dataset.json index b40c750b7a..31cd8f0b66 100644 --- a/web/i18n/zh-Hans/dataset.json +++ b/web/i18n/zh-Hans/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "已启用", "serviceApi.title": "服务 API", "unavailable": "不可用", + "unknownError": "未知错误", "updated": "更新于", "weightedScore.customized": "自定义", "weightedScore.description": "通过调整分配的权重,重新排序策略确定是优先进行语义匹配还是关键字匹配。", diff --git a/web/i18n/zh-Hant/dataset.json b/web/i18n/zh-Hant/dataset.json index 5781702c33..b21f9d3e8b 100644 --- a/web/i18n/zh-Hant/dataset.json +++ b/web/i18n/zh-Hant/dataset.json @@ -176,6 +176,7 @@ "serviceApi.enabled": "使用中", "serviceApi.title": "服務 API", "unavailable": "不可用", + "unknownError": "未知錯誤", "updated": "更新時間", "weightedScore.customized": "自定義", "weightedScore.description": "通過調整分配的權重,此重新排序策略決定是優先考慮語義匹配還是關鍵詞匹配。", From 244f9e0c114b84dbcdda7357b5fdf867d0fbfc2d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:53:03 +0800 Subject: [PATCH 06/70] fix: handle null email/name from GitHub API for private-email users (#33882) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: QuantumGhost --- api/controllers/console/auth/oauth.py | 4 ++ api/libs/oauth.py | 27 ++++++--- .../unit_tests/libs/test_oauth_clients.py | 57 ++++++++++++++++--- 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/api/controllers/console/auth/oauth.py b/api/controllers/console/auth/oauth.py index 112e152432..5c9023f27b 100644 --- a/api/controllers/console/auth/oauth.py +++ b/api/controllers/console/auth/oauth.py @@ -1,4 +1,5 @@ import logging +import urllib.parse import httpx from flask import current_app, redirect, request @@ -112,6 +113,9 @@ class OAuthCallback(Resource): error_text = e.response.text logger.exception("An error occurred during the OAuth process with %s: %s", provider, error_text) return {"error": "OAuth process failed"}, 400 + except ValueError as e: + logger.warning("OAuth error with %s", provider, exc_info=True) + return redirect(f"{dify_config.CONSOLE_WEB_URL}/signin?message={urllib.parse.quote(str(e))}") if invite_token and RegisterService.is_valid_invite_token(invite_token): invitation = RegisterService.get_invitation_by_token(token=invite_token) diff --git a/api/libs/oauth.py b/api/libs/oauth.py index efce13f6f1..1afb42304d 100644 --- a/api/libs/oauth.py +++ b/api/libs/oauth.py @@ -1,16 +1,19 @@ +import logging import sys import urllib.parse from dataclasses import dataclass from typing import NotRequired import httpx -from pydantic import TypeAdapter +from pydantic import TypeAdapter, ValidationError if sys.version_info >= (3, 12): from typing import TypedDict else: from typing_extensions import TypedDict +logger = logging.getLogger(__name__) + JsonObject = dict[str, object] JsonObjectList = list[JsonObject] @@ -30,8 +33,8 @@ class GitHubEmailRecord(TypedDict, total=False): class GitHubRawUserInfo(TypedDict): id: int | str login: str - name: NotRequired[str] - email: NotRequired[str] + name: NotRequired[str | None] + email: NotRequired[str | None] class GoogleRawUserInfo(TypedDict): @@ -127,9 +130,14 @@ class GitHubOAuth(OAuth): response.raise_for_status() user_info = GITHUB_RAW_USER_INFO_ADAPTER.validate_python(_json_object(response)) - email_response = httpx.get(self._EMAIL_INFO_URL, headers=headers) - email_info = GITHUB_EMAIL_RECORDS_ADAPTER.validate_python(_json_list(email_response)) - primary_email = next((email for email in email_info if email.get("primary") is True), None) + try: + email_response = httpx.get(self._EMAIL_INFO_URL, headers=headers) + email_response.raise_for_status() + email_info = GITHUB_EMAIL_RECORDS_ADAPTER.validate_python(_json_list(email_response)) + primary_email = next((email for email in email_info if email.get("primary") is True), None) + except (httpx.HTTPStatusError, ValidationError): + logger.warning("Failed to retrieve email from GitHub /user/emails endpoint", exc_info=True) + primary_email = None return {**user_info, "email": primary_email.get("email", "") if primary_email else ""} @@ -137,8 +145,11 @@ class GitHubOAuth(OAuth): payload = GITHUB_RAW_USER_INFO_ADAPTER.validate_python(raw_info) email = payload.get("email") if not email: - email = f"{payload['id']}+{payload['login']}@users.noreply.github.com" - return OAuthUserInfo(id=str(payload["id"]), name=str(payload.get("name", "")), email=email) + raise ValueError( + 'Dify currently not supports the "Keep my email addresses private" feature,' + " please disable it and login again" + ) + return OAuthUserInfo(id=str(payload["id"]), name=str(payload.get("name") or ""), email=email) class GoogleOAuth(OAuth): diff --git a/api/tests/unit_tests/libs/test_oauth_clients.py b/api/tests/unit_tests/libs/test_oauth_clients.py index bc7880ccc8..3918e8ee4b 100644 --- a/api/tests/unit_tests/libs/test_oauth_clients.py +++ b/api/tests/unit_tests/libs/test_oauth_clients.py @@ -95,13 +95,11 @@ class TestGitHubOAuth(BaseOAuthTest): ], "primary@example.com", ), - # User with no emails - fallback to noreply - ({"id": 12345, "login": "testuser", "name": "Test User"}, [], "12345+testuser@users.noreply.github.com"), - # User with only secondary email - fallback to noreply + # User with private email (null email and name from API) ( - {"id": 12345, "login": "testuser", "name": "Test User"}, - [{"email": "secondary@example.com", "primary": False}], - "12345+testuser@users.noreply.github.com", + {"id": 12345, "login": "testuser", "name": None, "email": None}, + [{"email": "primary@example.com", "primary": True}], + "primary@example.com", ), ], ) @@ -118,9 +116,54 @@ class TestGitHubOAuth(BaseOAuthTest): user_info = oauth.get_user_info("test_token") assert user_info.id == str(user_data["id"]) - assert user_info.name == user_data["name"] + assert user_info.name == (user_data["name"] or "") assert user_info.email == expected_email + @pytest.mark.parametrize( + ("user_data", "email_data"), + [ + # User with no emails + ({"id": 12345, "login": "testuser", "name": "Test User"}, []), + # User with only secondary email + ( + {"id": 12345, "login": "testuser", "name": "Test User"}, + [{"email": "secondary@example.com", "primary": False}], + ), + # User with private email and no primary in emails endpoint + ( + {"id": 12345, "login": "testuser", "name": None, "email": None}, + [], + ), + ], + ) + @patch("httpx.get", autospec=True) + def test_should_raise_error_when_no_primary_email(self, mock_get, oauth, user_data, email_data): + user_response = MagicMock() + user_response.json.return_value = user_data + + email_response = MagicMock() + email_response.json.return_value = email_data + + mock_get.side_effect = [user_response, email_response] + + with pytest.raises(ValueError, match="Keep my email addresses private"): + oauth.get_user_info("test_token") + + @patch("httpx.get", autospec=True) + def test_should_raise_error_when_email_endpoint_fails(self, mock_get, oauth): + user_response = MagicMock() + user_response.json.return_value = {"id": 12345, "login": "testuser", "name": "Test User"} + + email_response = MagicMock() + email_response.raise_for_status.side_effect = httpx.HTTPStatusError( + "Forbidden", request=MagicMock(), response=MagicMock() + ) + + mock_get.side_effect = [user_response, email_response] + + with pytest.raises(ValueError, match="Keep my email addresses private"): + oauth.get_user_info("test_token") + @patch("httpx.get", autospec=True) def test_should_handle_network_errors(self, mock_get, oauth): mock_get.side_effect = httpx.RequestError("Network error") From e844edcf26b89a35f85d9c38c1bc0406203c3c90 Mon Sep 17 00:00:00 2001 From: Bipin Rimal <146849810+BipinRimal314@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:43:51 +0545 Subject: [PATCH 07/70] docs: EU AI Act compliance guide for Dify deployers (#33838) --- docs/eu-ai-act-compliance.md | 186 +++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 docs/eu-ai-act-compliance.md diff --git a/docs/eu-ai-act-compliance.md b/docs/eu-ai-act-compliance.md new file mode 100644 index 0000000000..5fa29eed3f --- /dev/null +++ b/docs/eu-ai-act-compliance.md @@ -0,0 +1,186 @@ +# EU AI Act Compliance Guide for Dify Deployers + +Dify is an LLMOps platform for building RAG pipelines, agents, and AI workflows. If you deploy Dify in the EU — whether self-hosted or using a cloud provider — the EU AI Act applies to your deployment. This guide covers what the regulation requires and how Dify's architecture maps to those requirements. + +## Is your system in scope? + +The detailed obligations in Articles 12, 13, and 14 only apply to **high-risk AI systems** as defined in Annex III of the EU AI Act. A Dify application is high-risk if it is used for: + +- **Recruitment and HR** — screening candidates, evaluating employee performance, allocating tasks +- **Credit scoring and insurance** — assessing creditworthiness or setting premiums +- **Law enforcement** — profiling, criminal risk assessment, border control +- **Critical infrastructure** — managing energy, water, transport, or telecommunications systems +- **Education assessment** — grading students, determining admissions +- **Essential public services** — evaluating eligibility for benefits, housing, or emergency services + +Most Dify deployments (customer-facing chatbots, internal knowledge bases, content generation workflows) are **not** high-risk. If your Dify application does not fall into one of the categories above: + +- **Article 50** (end-user transparency) still applies if users interact with your application directly. See the [Article 50 section](#article-50-end-user-transparency) below. +- **GDPR** still applies if you process personal data. See the [GDPR section](#gdpr-considerations) below. +- The high-risk obligations (Articles 9-15) are less likely to apply, but risk classification is context-dependent. **Do not self-classify without legal review.** Focus on Article 50 (transparency) and GDPR (data protection) as your baseline obligations. + +If you are unsure whether your use case qualifies as high-risk, consult a qualified legal professional before proceeding. + +## Self-hosted vs cloud: different compliance profiles + +| Deployment | Your role | Dify's role | Who handles compliance? | +|-----------|----------|-------------|------------------------| +| **Self-hosted** | Provider and deployer | Framework provider — obligations under Article 25 apply only if Dify is placed on the market or put into service as part of a complete AI system bearing its name or trademark | You | +| **Dify Cloud** | Deployer | Provider and processor | Shared — Dify handles SOC 2 and GDPR for the platform; you handle AI Act obligations for your specific use case | + +Dify Cloud already has SOC 2 Type II and GDPR compliance for the platform itself. But the EU AI Act adds obligations specific to AI systems that SOC 2 does not cover: risk classification, technical documentation, transparency, and human oversight. + +## Supported providers and services + +Dify integrates with a broad range of AI providers and data stores. The following are the key ones relevant to compliance: + +- **AI providers:** HuggingFace (core), plus integrations with OpenAI, Anthropic, Google, and 100+ models via provider plugins +- **Model identifiers include:** gpt-4o, gpt-3.5-turbo, claude-3-opus, gemini-2.5-flash, whisper-1, and others +- **Vector database connections:** Extensive RAG infrastructure supporting numerous vector stores + +Dify's plugin architecture means actual provider usage depends on your configuration. Document which providers and models are active in your deployment. + +## Data flow diagram + +A typical Dify RAG deployment: + +```mermaid +graph LR + USER((User)) -->|query| DIFY[Dify Platform] + DIFY -->|prompts| LLM([LLM Provider]) + LLM -->|responses| DIFY + DIFY -->|documents| EMBED([Embedding Model]) + EMBED -->|vectors| DIFY + DIFY -->|store/retrieve| VS[(Vector Store)] + DIFY -->|knowledge| KB[(Knowledge Base)] + DIFY -->|response| USER + + classDef processor fill:#60a5fa,stroke:#1e40af,color:#000 + classDef controller fill:#4ade80,stroke:#166534,color:#000 + classDef app fill:#a78bfa,stroke:#5b21b6,color:#000 + classDef user fill:#f472b6,stroke:#be185d,color:#000 + + class USER user + class DIFY app + class LLM processor + class EMBED processor + class VS controller + class KB controller +``` + +**GDPR roles** (providers are typically processors for customer-submitted data, but the exact role depends on each provider's terms of service and processing purpose; deployers should review each provider's DPA): +- **Cloud LLM providers (OpenAI, Anthropic, Google)** typically act as processors — requires DPA. +- **Cloud embedding services** typically act as processors — requires DPA. +- **Self-hosted vector stores (Weaviate, Qdrant, pgvector):** Your organization remains the controller — no third-party transfer. +- **Cloud vector stores (Pinecone, Zilliz Cloud)** typically act as processors — requires DPA. +- **Knowledge base documents:** Your organization is the controller — stored in your infrastructure. + +## Article 11: Technical documentation + +High-risk systems need Annex IV documentation. For Dify deployments, key sections include: + +| Section | What Dify provides | What you must document | +|---------|-------------------|----------------------| +| General description | Platform capabilities, supported models | Your specific use case, intended users, deployment context | +| Development process | Dify's architecture, plugin system | Your RAG pipeline design, prompt engineering, knowledge base curation | +| Monitoring | Dify's built-in logging and analytics | Your monitoring plan, alert thresholds, incident response | +| Performance metrics | Dify's evaluation features | Your accuracy benchmarks, quality thresholds, bias testing | +| Risk management | — | Risk assessment for your specific use case | + +Some sections can be derived from Dify's architecture and your deployment configuration, as shown in the table above. The remaining sections require your input. + +## Article 12: Record-keeping + +Dify's built-in logging covers several Article 12 requirements: + +| Requirement | Dify Feature | Status | +|------------|-------------|--------| +| Conversation logs | Full conversation history with timestamps | **Covered** | +| Model tracking | Model name recorded per interaction | **Covered** | +| Token usage | Token counts per message | **Covered** | +| Cost tracking | Cost per conversation (if provider reports it) | **Partial** | +| Document retrieval | RAG source documents logged | **Covered** | +| User identification | User session tracking | **Covered** | +| Error logging | Failed generation logs | **Covered** | +| Data retention | Configurable | **Your responsibility** | + +**Retention periods:** The required retention period depends on your role under the Act. Article 18 requires **providers** of high-risk systems to retain logs and technical documentation for **10 years** after market placement. Article 26(6) requires **deployers** to retain logs for at least **6 months**. If you self-host Dify and have substantially modified the system, you may be classified as a provider rather than a deployer. Confirm the applicable retention period with legal counsel. + +## Article 13: Transparency to deployers + +Article 13 requires providers of high-risk AI systems to supply deployers with the information needed to understand and operate the system correctly. This is a **documentation obligation**, not a logging obligation. For Dify deployments, this means the upstream LLM and embedding providers must give you: + +- Instructions for use, including intended purpose and known limitations +- Accuracy metrics and performance benchmarks +- Known or foreseeable risks and residual risks after mitigation +- Technical specifications: input/output formats, training data characteristics, model architecture details + +As a deployer, collect model cards, system documentation, and accuracy reports from each AI provider your Dify application uses. Maintain these as part of your Annex IV technical documentation. + +Dify's platform features provide **supporting evidence** that can inform Article 13 documentation, but they do not satisfy Article 13 on their own: +- **Source attribution** — Dify's RAG citation feature shows which documents informed the response, supporting deployer-side auditing +- **Model identification** — Dify logs which LLM model generates responses, providing evidence for system documentation +- **Conversation logs** — execution history helps compile performance and behavior evidence + +You must independently produce system documentation covering how your specific Dify deployment uses AI, its intended purpose, performance characteristics, and residual risks. + +## Article 50: End-user transparency + +Article 50 requires deployers to inform end users that they are interacting with an AI system. This is a separate obligation from Article 13 and applies even to limited-risk systems. + +For Dify applications serving end users: + +1. **Disclose AI involvement** — tell users they are interacting with an AI system +2. **AI-generated content labeling** — identify AI-generated content as such (e.g., clear labeling in the UI) + +Dify's "citation" feature also supports end-user transparency by showing users which knowledge base documents informed the answer. + +> **Note:** Article 50 applies to chatbots and systems interacting directly with natural persons. It has a separate scope from the high-risk designation under Annex III — it applies even to limited-risk systems. + +## Article 14: Human oversight + +Article 14 requires that high-risk AI systems be designed so that natural persons can effectively oversee them. Dify provides **automated technical safeguards** that support human oversight, but they are not a substitute for it: + +| Dify Feature | What It Does | Oversight Role | +|-------------|-------------|----------------| +| Annotation/feedback system | Human review of AI outputs | **Direct oversight** — humans evaluate and correct AI responses | +| Content moderation | Built-in filtering before responses reach users | **Automated safeguard** — reduces harmful outputs but does not replace human judgment on edge cases | +| Rate limiting | Controls on API usage | **Automated safeguard** — bounds system behavior, supports overseer's ability to maintain control | +| Workflow control | Insert human review steps between AI generation and output | **Oversight enabler** — allows building approval gates into the pipeline | + +These automated controls are necessary building blocks, but Article 14 compliance requires **human oversight procedures** on top of them: +- **Escalation procedures** — define what happens when moderation triggers or edge cases arise (who is notified, what action is taken) +- **Human review pipeline** — for high-stakes decisions, route AI outputs to a qualified person before they take effect +- **Override mechanism** — a human must be able to halt AI responses or override the system's output +- **Competence requirements** — the human overseer must understand the system's capabilities, limitations, and the context of its outputs + +### Recommended pattern + +For high-risk use cases (HR, legal, medical), configure your Dify workflow to require human approval before the AI response is delivered to the end user or acted upon. + +## Knowledge base compliance + +Dify's knowledge base feature has specific compliance implications: + +1. **Data provenance:** Document where your knowledge base documents come from. Article 10 requires data governance for training data; knowledge bases are analogous. +2. **Update tracking:** When you add, remove, or update documents in the knowledge base, log the change. The AI system's behavior changes with its knowledge base. +3. **PII in documents:** If knowledge base documents contain personal data, GDPR applies to the entire RAG pipeline. Implement access controls and consider PII redaction before indexing. +4. **Copyright:** Ensure you have the right to use the documents in your knowledge base for AI-assisted generation. + +## GDPR considerations + +1. **Legal basis** (Article 6): Document why AI processing of user queries is necessary +2. **Data Processing Agreements** (Article 28): Required for each cloud LLM and embedding provider +3. **Data minimization:** Only include necessary context in prompts; avoid sending entire documents when a relevant excerpt suffices +4. **Right to erasure:** If a user requests deletion, ensure their conversations are removed from Dify's logs AND any vector store entries derived from their data +5. **Cross-border transfers:** Providers based outside the EEA — including US-based providers (OpenAI, Anthropic), and any other non-EEA providers you route to — require Standard Contractual Clauses (SCCs) or equivalent safeguards under Chapter V of the GDPR. Review each provider's transfer mechanism individually. + +## Resources + +- [EU AI Act full text](https://artificialintelligenceact.eu/) +- [Dify documentation](https://docs.dify.ai/) +- [Dify SOC 2 compliance](https://dify.ai/trust) + +--- + +*This is not legal advice. Consult a qualified professional for compliance decisions.* From 6ecf89e262ee10c7949dc989e39c18cf0700add6 Mon Sep 17 00:00:00 2001 From: Desel72 Date: Mon, 23 Mar 2026 01:59:16 -0500 Subject: [PATCH 08/70] refactor: migrate credit pool service tests to testcontainers (#33898) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../services/test_credit_pool_service.py | 103 ++++++++++++ .../services/test_credit_pool_service.py | 157 ------------------ 2 files changed, 103 insertions(+), 157 deletions(-) create mode 100644 api/tests/test_containers_integration_tests/services/test_credit_pool_service.py delete mode 100644 api/tests/unit_tests/services/test_credit_pool_service.py diff --git a/api/tests/test_containers_integration_tests/services/test_credit_pool_service.py b/api/tests/test_containers_integration_tests/services/test_credit_pool_service.py new file mode 100644 index 0000000000..25de0588fa --- /dev/null +++ b/api/tests/test_containers_integration_tests/services/test_credit_pool_service.py @@ -0,0 +1,103 @@ +"""Testcontainers integration tests for CreditPoolService.""" + +from uuid import uuid4 + +import pytest + +from core.errors.error import QuotaExceededError +from models import TenantCreditPool +from services.credit_pool_service import CreditPoolService + + +class TestCreditPoolService: + def _create_tenant_id(self) -> str: + return str(uuid4()) + + def test_create_default_pool(self, db_session_with_containers): + tenant_id = self._create_tenant_id() + + pool = CreditPoolService.create_default_pool(tenant_id) + + assert isinstance(pool, TenantCreditPool) + assert pool.tenant_id == tenant_id + assert pool.pool_type == "trial" + assert pool.quota_used == 0 + assert pool.quota_limit > 0 + + def test_get_pool_returns_pool_when_exists(self, db_session_with_containers): + tenant_id = self._create_tenant_id() + CreditPoolService.create_default_pool(tenant_id) + + result = CreditPoolService.get_pool(tenant_id=tenant_id, pool_type="trial") + + assert result is not None + assert result.tenant_id == tenant_id + assert result.pool_type == "trial" + + def test_get_pool_returns_none_when_not_exists(self, db_session_with_containers): + result = CreditPoolService.get_pool(tenant_id=self._create_tenant_id(), pool_type="trial") + + assert result is None + + def test_check_credits_available_returns_false_when_no_pool(self, db_session_with_containers): + result = CreditPoolService.check_credits_available(tenant_id=self._create_tenant_id(), credits_required=10) + + assert result is False + + def test_check_credits_available_returns_true_when_sufficient(self, db_session_with_containers): + tenant_id = self._create_tenant_id() + CreditPoolService.create_default_pool(tenant_id) + + result = CreditPoolService.check_credits_available(tenant_id=tenant_id, credits_required=10) + + assert result is True + + def test_check_credits_available_returns_false_when_insufficient(self, db_session_with_containers): + tenant_id = self._create_tenant_id() + pool = CreditPoolService.create_default_pool(tenant_id) + # Exhaust credits + pool.quota_used = pool.quota_limit + db_session_with_containers.commit() + + result = CreditPoolService.check_credits_available(tenant_id=tenant_id, credits_required=1) + + assert result is False + + def test_check_and_deduct_credits_raises_when_no_pool(self, db_session_with_containers): + with pytest.raises(QuotaExceededError, match="Credit pool not found"): + CreditPoolService.check_and_deduct_credits(tenant_id=self._create_tenant_id(), credits_required=10) + + def test_check_and_deduct_credits_raises_when_no_remaining(self, db_session_with_containers): + tenant_id = self._create_tenant_id() + pool = CreditPoolService.create_default_pool(tenant_id) + pool.quota_used = pool.quota_limit + db_session_with_containers.commit() + + with pytest.raises(QuotaExceededError, match="No credits remaining"): + CreditPoolService.check_and_deduct_credits(tenant_id=tenant_id, credits_required=10) + + def test_check_and_deduct_credits_deducts_required_amount(self, db_session_with_containers): + tenant_id = self._create_tenant_id() + CreditPoolService.create_default_pool(tenant_id) + credits_required = 10 + + result = CreditPoolService.check_and_deduct_credits(tenant_id=tenant_id, credits_required=credits_required) + + assert result == credits_required + db_session_with_containers.expire_all() + pool = CreditPoolService.get_pool(tenant_id=tenant_id) + assert pool.quota_used == credits_required + + def test_check_and_deduct_credits_caps_at_remaining(self, db_session_with_containers): + tenant_id = self._create_tenant_id() + pool = CreditPoolService.create_default_pool(tenant_id) + remaining = 5 + pool.quota_used = pool.quota_limit - remaining + db_session_with_containers.commit() + + result = CreditPoolService.check_and_deduct_credits(tenant_id=tenant_id, credits_required=200) + + assert result == remaining + db_session_with_containers.expire_all() + updated_pool = CreditPoolService.get_pool(tenant_id=tenant_id) + assert updated_pool.quota_used == pool.quota_limit diff --git a/api/tests/unit_tests/services/test_credit_pool_service.py b/api/tests/unit_tests/services/test_credit_pool_service.py deleted file mode 100644 index 9ef314cb9e..0000000000 --- a/api/tests/unit_tests/services/test_credit_pool_service.py +++ /dev/null @@ -1,157 +0,0 @@ -from types import SimpleNamespace -from unittest.mock import MagicMock, patch - -import pytest - -import services.credit_pool_service as credit_pool_service_module -from core.errors.error import QuotaExceededError -from models import TenantCreditPool -from services.credit_pool_service import CreditPoolService - - -@pytest.fixture -def mock_credit_deduction_setup(): - """Fixture providing common setup for credit deduction tests.""" - pool = SimpleNamespace(remaining_credits=50) - fake_engine = MagicMock() - session = MagicMock() - session_context = MagicMock() - session_context.__enter__.return_value = session - session_context.__exit__.return_value = None - - mock_get_pool = patch.object(CreditPoolService, "get_pool", return_value=pool) - mock_db = patch.object(credit_pool_service_module, "db", new=SimpleNamespace(engine=fake_engine)) - mock_session = patch.object(credit_pool_service_module, "Session", return_value=session_context) - - return { - "pool": pool, - "fake_engine": fake_engine, - "session": session, - "session_context": session_context, - "patches": (mock_get_pool, mock_db, mock_session), - } - - -class TestCreditPoolService: - def test_should_create_default_pool_with_trial_type_and_configured_quota(self): - """Test create_default_pool persists a trial pool using configured hosted credits.""" - tenant_id = "tenant-123" - hosted_pool_credits = 5000 - - with ( - patch.object(credit_pool_service_module.dify_config, "HOSTED_POOL_CREDITS", hosted_pool_credits), - patch.object(credit_pool_service_module, "db") as mock_db, - ): - pool = CreditPoolService.create_default_pool(tenant_id) - - assert isinstance(pool, TenantCreditPool) - assert pool.tenant_id == tenant_id - assert pool.pool_type == "trial" - assert pool.quota_limit == hosted_pool_credits - assert pool.quota_used == 0 - mock_db.session.add.assert_called_once_with(pool) - mock_db.session.commit.assert_called_once() - - def test_should_return_first_pool_from_query_when_get_pool_called(self): - """Test get_pool queries by tenant and pool_type and returns first result.""" - tenant_id = "tenant-123" - pool_type = "enterprise" - expected_pool = MagicMock(spec=TenantCreditPool) - - with patch.object(credit_pool_service_module, "db") as mock_db: - query = mock_db.session.query.return_value - filtered_query = query.filter_by.return_value - filtered_query.first.return_value = expected_pool - - result = CreditPoolService.get_pool(tenant_id=tenant_id, pool_type=pool_type) - - assert result == expected_pool - mock_db.session.query.assert_called_once_with(TenantCreditPool) - query.filter_by.assert_called_once_with(tenant_id=tenant_id, pool_type=pool_type) - filtered_query.first.assert_called_once() - - def test_should_return_false_when_pool_not_found_in_check_credits_available(self): - """Test check_credits_available returns False when tenant has no pool.""" - with patch.object(CreditPoolService, "get_pool", return_value=None) as mock_get_pool: - result = CreditPoolService.check_credits_available(tenant_id="tenant-123", credits_required=10) - - assert result is False - mock_get_pool.assert_called_once_with("tenant-123", "trial") - - def test_should_return_true_when_remaining_credits_cover_required_amount(self): - """Test check_credits_available returns True when remaining credits are sufficient.""" - pool = SimpleNamespace(remaining_credits=100) - - with patch.object(CreditPoolService, "get_pool", return_value=pool) as mock_get_pool: - result = CreditPoolService.check_credits_available(tenant_id="tenant-123", credits_required=60) - - assert result is True - mock_get_pool.assert_called_once_with("tenant-123", "trial") - - def test_should_return_false_when_remaining_credits_are_insufficient(self): - """Test check_credits_available returns False when required credits exceed remaining credits.""" - pool = SimpleNamespace(remaining_credits=30) - - with patch.object(CreditPoolService, "get_pool", return_value=pool): - result = CreditPoolService.check_credits_available(tenant_id="tenant-123", credits_required=60) - - assert result is False - - def test_should_raise_quota_exceeded_when_pool_not_found_in_check_and_deduct(self): - """Test check_and_deduct_credits raises when tenant credit pool does not exist.""" - with patch.object(CreditPoolService, "get_pool", return_value=None): - with pytest.raises(QuotaExceededError, match="Credit pool not found"): - CreditPoolService.check_and_deduct_credits(tenant_id="tenant-123", credits_required=10) - - def test_should_raise_quota_exceeded_when_pool_has_no_remaining_credits(self): - """Test check_and_deduct_credits raises when remaining credits are zero or negative.""" - pool = SimpleNamespace(remaining_credits=0) - - with patch.object(CreditPoolService, "get_pool", return_value=pool): - with pytest.raises(QuotaExceededError, match="No credits remaining"): - CreditPoolService.check_and_deduct_credits(tenant_id="tenant-123", credits_required=10) - - def test_should_deduct_minimum_of_required_and_remaining_credits(self, mock_credit_deduction_setup): - """Test check_and_deduct_credits updates quota_used by the actual deducted amount.""" - tenant_id = "tenant-123" - pool_type = "trial" - credits_required = 200 - remaining_credits = 120 - expected_deducted_credits = 120 - - mock_credit_deduction_setup["pool"].remaining_credits = remaining_credits - patches = mock_credit_deduction_setup["patches"] - session = mock_credit_deduction_setup["session"] - - with patches[0], patches[1], patches[2]: - result = CreditPoolService.check_and_deduct_credits( - tenant_id=tenant_id, - credits_required=credits_required, - pool_type=pool_type, - ) - - assert result == expected_deducted_credits - session.execute.assert_called_once() - session.commit.assert_called_once() - - stmt = session.execute.call_args.args[0] - compiled_params = stmt.compile().params - assert tenant_id in compiled_params.values() - assert pool_type in compiled_params.values() - assert expected_deducted_credits in compiled_params.values() - - def test_should_raise_quota_exceeded_when_deduction_update_fails(self, mock_credit_deduction_setup): - """Test check_and_deduct_credits translates DB update failures to QuotaExceededError.""" - mock_credit_deduction_setup["pool"].remaining_credits = 50 - mock_credit_deduction_setup["session"].execute.side_effect = Exception("db failure") - session = mock_credit_deduction_setup["session"] - - patches = mock_credit_deduction_setup["patches"] - mock_logger = patch.object(credit_pool_service_module, "logger") - - with patches[0], patches[1], patches[2], mock_logger as mock_logger_obj: - with pytest.raises(QuotaExceededError, match="Failed to deduct credits"): - CreditPoolService.check_and_deduct_credits(tenant_id="tenant-123", credits_required=10) - - session.commit.assert_not_called() - mock_logger_obj.exception.assert_called_once() From 2b6f761dfef8d55ce95d55ca4d6749b2f7945d49 Mon Sep 17 00:00:00 2001 From: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com> Date: Mon, 23 Mar 2026 08:03:35 +0100 Subject: [PATCH 09/70] refactor: use EnumText for Conversation/Message invoke_from and from_source (#33901) --- api/core/tools/builtin_tool/tool.py | 2 +- api/core/tools/tool_label_manager.py | 4 ++-- api/core/tools/utils/model_invocation_utils.py | 3 ++- api/models/model.py | 3 ++- api/models/tools.py | 12 ++++++++---- api/services/tag_service.py | 3 ++- .../services/test_tag_service.py | 6 +++--- .../controllers/console/tag/test_tags.py | 3 ++- .../service_api/dataset/test_dataset.py | 9 +++++---- api/tests/unit_tests/models/test_tool_models.py | 14 +++++++------- api/tests/unit_tests/services/test_tag_service.py | 5 +++-- 11 files changed, 37 insertions(+), 27 deletions(-) diff --git a/api/core/tools/builtin_tool/tool.py b/api/core/tools/builtin_tool/tool.py index 00f5931088..bcf58394ba 100644 --- a/api/core/tools/builtin_tool/tool.py +++ b/api/core/tools/builtin_tool/tool.py @@ -50,7 +50,7 @@ class BuiltinTool(Tool): return ModelInvocationUtils.invoke( user_id=user_id, tenant_id=self.runtime.tenant_id or "", - tool_type="builtin", + tool_type=ToolProviderType.BUILT_IN, tool_name=self.entity.identity.name, prompt_messages=prompt_messages, ) diff --git a/api/core/tools/tool_label_manager.py b/api/core/tools/tool_label_manager.py index 90d5a647e9..250dd91bfd 100644 --- a/api/core/tools/tool_label_manager.py +++ b/api/core/tools/tool_label_manager.py @@ -38,7 +38,7 @@ class ToolLabelManager: db.session.add( ToolLabelBinding( tool_id=provider_id, - tool_type=controller.provider_type.value, + tool_type=controller.provider_type, label_name=label, ) ) @@ -58,7 +58,7 @@ class ToolLabelManager: raise ValueError("Unsupported tool type") stmt = select(ToolLabelBinding.label_name).where( ToolLabelBinding.tool_id == provider_id, - ToolLabelBinding.tool_type == controller.provider_type.value, + ToolLabelBinding.tool_type == controller.provider_type, ) labels = db.session.scalars(stmt).all() diff --git a/api/core/tools/utils/model_invocation_utils.py b/api/core/tools/utils/model_invocation_utils.py index 8f958563bd..373bd1b1c8 100644 --- a/api/core/tools/utils/model_invocation_utils.py +++ b/api/core/tools/utils/model_invocation_utils.py @@ -9,6 +9,7 @@ from decimal import Decimal from typing import cast from core.model_manager import ModelManager +from core.tools.entities.tool_entities import ToolProviderType from dify_graph.model_runtime.entities.llm_entities import LLMResult from dify_graph.model_runtime.entities.message_entities import PromptMessage from dify_graph.model_runtime.entities.model_entities import ModelPropertyKey, ModelType @@ -78,7 +79,7 @@ class ModelInvocationUtils: @staticmethod def invoke( - user_id: str, tenant_id: str, tool_type: str, tool_name: str, prompt_messages: list[PromptMessage] + user_id: str, tenant_id: str, tool_type: ToolProviderType, tool_name: str, prompt_messages: list[PromptMessage] ) -> LLMResult: """ invoke model with parameters in user's own context diff --git a/api/models/model.py b/api/models/model.py index a08e43d128..b098966052 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -43,6 +43,7 @@ from .enums import ( MessageChainType, MessageFileBelongsTo, MessageStatus, + TagType, ) from .provider_ids import GenericProviderID from .types import EnumText, LongText, StringUUID @@ -2404,7 +2405,7 @@ class Tag(TypeBase): StringUUID, insert_default=lambda: str(uuid4()), default_factory=lambda: str(uuid4()), init=False ) tenant_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True) - type: Mapped[str] = mapped_column(String(16), nullable=False) + type: Mapped[TagType] = mapped_column(EnumText(TagType, length=16), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) created_by: Mapped[str] = mapped_column(StringUUID, nullable=False) created_at: Mapped[datetime] = mapped_column( diff --git a/api/models/tools.py b/api/models/tools.py index c09f054e7d..01182af867 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -13,12 +13,16 @@ from sqlalchemy.orm import Mapped, mapped_column from core.tools.entities.common_entities import I18nObject from core.tools.entities.tool_bundle import ApiToolBundle -from core.tools.entities.tool_entities import ApiProviderSchemaType, WorkflowToolParameterConfiguration +from core.tools.entities.tool_entities import ( + ApiProviderSchemaType, + ToolProviderType, + WorkflowToolParameterConfiguration, +) from .base import TypeBase from .engine import db from .model import Account, App, Tenant -from .types import LongText, StringUUID +from .types import EnumText, LongText, StringUUID if TYPE_CHECKING: from core.entities.mcp_provider import MCPProviderEntity @@ -208,7 +212,7 @@ class ToolLabelBinding(TypeBase): # tool id tool_id: Mapped[str] = mapped_column(String(64), nullable=False) # tool type - tool_type: Mapped[str] = mapped_column(String(40), nullable=False) + tool_type: Mapped[ToolProviderType] = mapped_column(EnumText(ToolProviderType, length=40), nullable=False) # label name label_name: Mapped[str] = mapped_column(String(40), nullable=False) @@ -386,7 +390,7 @@ class ToolModelInvoke(TypeBase): # provider provider: Mapped[str] = mapped_column(String(255), nullable=False) # type - tool_type: Mapped[str] = mapped_column(String(40), nullable=False) + tool_type: Mapped[ToolProviderType] = mapped_column(EnumText(ToolProviderType, length=40), nullable=False) # tool name tool_name: Mapped[str] = mapped_column(String(128), nullable=False) # invoke parameters diff --git a/api/services/tag_service.py b/api/services/tag_service.py index bd3585acf4..70bf7f16f2 100644 --- a/api/services/tag_service.py +++ b/api/services/tag_service.py @@ -7,6 +7,7 @@ from werkzeug.exceptions import NotFound from extensions.ext_database import db from models.dataset import Dataset +from models.enums import TagType from models.model import App, Tag, TagBinding @@ -83,7 +84,7 @@ class TagService: raise ValueError("Tag name already exists") tag = Tag( name=args["name"], - type=args["type"], + type=TagType(args["type"]), created_by=current_user.id, tenant_id=current_user.current_tenant_id, ) diff --git a/api/tests/test_containers_integration_tests/services/test_tag_service.py b/api/tests/test_containers_integration_tests/services/test_tag_service.py index fa6e651529..1a72e3b6c2 100644 --- a/api/tests/test_containers_integration_tests/services/test_tag_service.py +++ b/api/tests/test_containers_integration_tests/services/test_tag_service.py @@ -9,7 +9,7 @@ from werkzeug.exceptions import NotFound from models import Account, Tenant, TenantAccountJoin, TenantAccountRole from models.dataset import Dataset -from models.enums import DataSourceType +from models.enums import DataSourceType, TagType from models.model import App, Tag, TagBinding from services.tag_service import TagService @@ -547,7 +547,7 @@ class TestTagService: assert result is not None assert len(result) == 1 assert result[0].name == "python_tag" - assert result[0].type == "app" + assert result[0].type == TagType.APP assert result[0].tenant_id == tenant.id def test_get_tag_by_tag_name_no_matches( @@ -638,7 +638,7 @@ class TestTagService: # Verify all tags are returned for tag in result: - assert tag.type == "app" + assert tag.type == TagType.APP assert tag.tenant_id == tenant.id assert tag.id in [t.id for t in tags] diff --git a/api/tests/unit_tests/controllers/console/tag/test_tags.py b/api/tests/unit_tests/controllers/console/tag/test_tags.py index 769edc8d1c..e89b89c8b1 100644 --- a/api/tests/unit_tests/controllers/console/tag/test_tags.py +++ b/api/tests/unit_tests/controllers/console/tag/test_tags.py @@ -11,6 +11,7 @@ from controllers.console.tag.tags import ( TagListApi, TagUpdateDeleteApi, ) +from models.enums import TagType def unwrap(func): @@ -52,7 +53,7 @@ def tag(): tag = MagicMock() tag.id = "tag-1" tag.name = "test-tag" - tag.type = "knowledge" + tag.type = TagType.KNOWLEDGE return tag diff --git a/api/tests/unit_tests/controllers/service_api/dataset/test_dataset.py b/api/tests/unit_tests/controllers/service_api/dataset/test_dataset.py index 7cb2f1050c..8fe41cd19f 100644 --- a/api/tests/unit_tests/controllers/service_api/dataset/test_dataset.py +++ b/api/tests/unit_tests/controllers/service_api/dataset/test_dataset.py @@ -35,6 +35,7 @@ from controllers.service_api.dataset.dataset import ( from controllers.service_api.dataset.error import DatasetInUseError, DatasetNameDuplicateError, InvalidActionError from models.account import Account from models.dataset import DatasetPermissionEnum +from models.enums import TagType from services.dataset_service import DatasetPermissionService, DatasetService, DocumentService from services.tag_service import TagService @@ -277,7 +278,7 @@ class TestDatasetTagsApi: mock_tag = Mock() mock_tag.id = "tag_1" mock_tag.name = "Test Tag" - mock_tag.type = "knowledge" + mock_tag.type = TagType.KNOWLEDGE mock_tag.binding_count = "0" # Required for Pydantic validation - must be string mock_tag_service.get_tags.return_value = [mock_tag] @@ -316,7 +317,7 @@ class TestDatasetTagsApi: mock_tag = Mock() mock_tag.id = "new_tag_1" mock_tag.name = "New Tag" - mock_tag.type = "knowledge" + mock_tag.type = TagType.KNOWLEDGE mock_tag_service.save_tags.return_value = mock_tag mock_service_api_ns.payload = {"name": "New Tag"} @@ -378,7 +379,7 @@ class TestDatasetTagsApi: mock_tag = Mock() mock_tag.id = "tag_1" mock_tag.name = "Updated Tag" - mock_tag.type = "knowledge" + mock_tag.type = TagType.KNOWLEDGE mock_tag.binding_count = "5" mock_tag_service.update_tags.return_value = mock_tag mock_tag_service.get_tag_binding_count.return_value = 5 @@ -866,7 +867,7 @@ class TestTagService: mock_tag = Mock() mock_tag.id = str(uuid.uuid4()) mock_tag.name = "New Tag" - mock_tag.type = "knowledge" + mock_tag.type = TagType.KNOWLEDGE mock_save.return_value = mock_tag result = TagService.save_tags({"name": "New Tag", "type": "knowledge"}) diff --git a/api/tests/unit_tests/models/test_tool_models.py b/api/tests/unit_tests/models/test_tool_models.py index 1a75eb9a01..a6c2eae2c0 100644 --- a/api/tests/unit_tests/models/test_tool_models.py +++ b/api/tests/unit_tests/models/test_tool_models.py @@ -12,7 +12,7 @@ This test suite covers: import json from uuid import uuid4 -from core.tools.entities.tool_entities import ApiProviderSchemaType +from core.tools.entities.tool_entities import ApiProviderSchemaType, ToolProviderType from models.tools import ( ApiToolProvider, BuiltinToolProvider, @@ -631,7 +631,7 @@ class TestToolLabelBinding: """Test creating a tool label binding.""" # Arrange tool_id = "google.search" - tool_type = "builtin" + tool_type = ToolProviderType.BUILT_IN label_name = "search" # Act @@ -655,7 +655,7 @@ class TestToolLabelBinding: # Act label_binding = ToolLabelBinding( tool_id=tool_id, - tool_type="builtin", + tool_type=ToolProviderType.BUILT_IN, label_name=label_name, ) @@ -667,7 +667,7 @@ class TestToolLabelBinding: """Test multiple labels can be bound to the same tool.""" # Arrange tool_id = "google.search" - tool_type = "builtin" + tool_type = ToolProviderType.BUILT_IN # Act binding1 = ToolLabelBinding( @@ -688,7 +688,7 @@ class TestToolLabelBinding: def test_tool_label_binding_different_tool_types(self): """Test label bindings for different tool types.""" # Arrange - tool_types = ["builtin", "api", "workflow"] + tool_types = [ToolProviderType.BUILT_IN, ToolProviderType.API, ToolProviderType.WORKFLOW] # Act & Assert for tool_type in tool_types: @@ -951,12 +951,12 @@ class TestToolProviderRelationships: # Act binding1 = ToolLabelBinding( tool_id=tool_id, - tool_type="builtin", + tool_type=ToolProviderType.BUILT_IN, label_name="search", ) binding2 = ToolLabelBinding( tool_id=tool_id, - tool_type="builtin", + tool_type=ToolProviderType.BUILT_IN, label_name="web", ) diff --git a/api/tests/unit_tests/services/test_tag_service.py b/api/tests/unit_tests/services/test_tag_service.py index 264eac4d77..4d2d63e501 100644 --- a/api/tests/unit_tests/services/test_tag_service.py +++ b/api/tests/unit_tests/services/test_tag_service.py @@ -75,6 +75,7 @@ import pytest from werkzeug.exceptions import NotFound from models.dataset import Dataset +from models.enums import TagType from models.model import App, Tag, TagBinding from services.tag_service import TagService @@ -102,7 +103,7 @@ class TagServiceTestDataFactory: def create_tag_mock( tag_id: str = "tag-123", name: str = "Test Tag", - tag_type: str = "app", + tag_type: TagType = TagType.APP, tenant_id: str = "tenant-123", **kwargs, ) -> Mock: @@ -705,7 +706,7 @@ class TestTagServiceCRUD: # Verify tag attributes added_tag = mock_db_session.add.call_args[0][0] assert added_tag.name == "New Tag", "Tag name should match" - assert added_tag.type == "app", "Tag type should match" + assert added_tag.type == TagType.APP, "Tag type should match" assert added_tag.created_by == "user-123", "Created by should match current user" assert added_tag.tenant_id == "tenant-123", "Tenant ID should match current tenant" From 1bf296982bd086df46c6166e5bc7e20f10bb8088 Mon Sep 17 00:00:00 2001 From: Desel72 Date: Mon, 23 Mar 2026 02:04:47 -0500 Subject: [PATCH 10/70] refactor: migrate workflow deletion tests to testcontainers (#33904) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../workflow/test_workflow_deletion.py | 158 ++++++++++++++++++ .../workflow/test_workflow_deletion.py | 127 -------------- 2 files changed, 158 insertions(+), 127 deletions(-) create mode 100644 api/tests/test_containers_integration_tests/services/workflow/test_workflow_deletion.py delete mode 100644 api/tests/unit_tests/services/workflow/test_workflow_deletion.py diff --git a/api/tests/test_containers_integration_tests/services/workflow/test_workflow_deletion.py b/api/tests/test_containers_integration_tests/services/workflow/test_workflow_deletion.py new file mode 100644 index 0000000000..29e1e240b4 --- /dev/null +++ b/api/tests/test_containers_integration_tests/services/workflow/test_workflow_deletion.py @@ -0,0 +1,158 @@ +"""Testcontainers integration tests for WorkflowService.delete_workflow.""" + +import json +from uuid import uuid4 + +import pytest +from sqlalchemy.orm import Session, sessionmaker + +from extensions.ext_database import db +from models.account import Account, Tenant, TenantAccountJoin +from models.model import App +from models.tools import WorkflowToolProvider +from models.workflow import Workflow +from services.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError, WorkflowService + + +class TestWorkflowDeletion: + def _create_tenant_and_account(self, session: Session) -> tuple[Tenant, Account]: + tenant = Tenant(name=f"Tenant {uuid4()}") + session.add(tenant) + session.flush() + + account = Account( + name=f"Account {uuid4()}", + email=f"wf_del_{uuid4()}@example.com", + password="hashed", + password_salt="salt", + interface_language="en-US", + timezone="UTC", + ) + session.add(account) + session.flush() + + join = TenantAccountJoin( + tenant_id=tenant.id, + account_id=account.id, + role="owner", + current=True, + ) + session.add(join) + session.flush() + return tenant, account + + def _create_app(self, session: Session, *, tenant: Tenant, account: Account, workflow_id: str | None = None) -> App: + app = App( + tenant_id=tenant.id, + name=f"App {uuid4()}", + description="", + mode="workflow", + icon_type="emoji", + icon="bot", + icon_background="#FFFFFF", + enable_site=False, + enable_api=True, + api_rpm=100, + api_rph=100, + is_demo=False, + is_public=False, + is_universal=False, + created_by=account.id, + updated_by=account.id, + workflow_id=workflow_id, + ) + session.add(app) + session.flush() + return app + + def _create_workflow( + self, session: Session, *, tenant: Tenant, app: App, account: Account, version: str = "1.0" + ) -> Workflow: + workflow = Workflow( + id=str(uuid4()), + tenant_id=tenant.id, + app_id=app.id, + type="workflow", + version=version, + graph=json.dumps({"nodes": [], "edges": []}), + _features=json.dumps({}), + created_by=account.id, + updated_by=account.id, + ) + session.add(workflow) + session.flush() + return workflow + + def _create_tool_provider( + self, session: Session, *, tenant: Tenant, app: App, account: Account, version: str + ) -> WorkflowToolProvider: + provider = WorkflowToolProvider( + name=f"tool-{uuid4()}", + label=f"Tool {uuid4()}", + icon="wrench", + app_id=app.id, + version=version, + user_id=account.id, + tenant_id=tenant.id, + description="test tool provider", + ) + session.add(provider) + session.flush() + return provider + + def test_delete_workflow_success(self, db_session_with_containers): + tenant, account = self._create_tenant_and_account(db_session_with_containers) + app = self._create_app(db_session_with_containers, tenant=tenant, account=account) + workflow = self._create_workflow( + db_session_with_containers, tenant=tenant, app=app, account=account, version="1.0" + ) + db_session_with_containers.commit() + workflow_id = workflow.id + + service = WorkflowService(sessionmaker(bind=db.engine)) + result = service.delete_workflow( + session=db_session_with_containers, workflow_id=workflow_id, tenant_id=tenant.id + ) + + assert result is True + db_session_with_containers.expire_all() + assert db_session_with_containers.get(Workflow, workflow_id) is None + + def test_delete_draft_workflow_raises_error(self, db_session_with_containers): + tenant, account = self._create_tenant_and_account(db_session_with_containers) + app = self._create_app(db_session_with_containers, tenant=tenant, account=account) + workflow = self._create_workflow( + db_session_with_containers, tenant=tenant, app=app, account=account, version="draft" + ) + db_session_with_containers.commit() + + service = WorkflowService(sessionmaker(bind=db.engine)) + with pytest.raises(DraftWorkflowDeletionError): + service.delete_workflow(session=db_session_with_containers, workflow_id=workflow.id, tenant_id=tenant.id) + + def test_delete_workflow_in_use_by_app_raises_error(self, db_session_with_containers): + tenant, account = self._create_tenant_and_account(db_session_with_containers) + app = self._create_app(db_session_with_containers, tenant=tenant, account=account) + workflow = self._create_workflow( + db_session_with_containers, tenant=tenant, app=app, account=account, version="1.0" + ) + # Point app to this workflow + app.workflow_id = workflow.id + db_session_with_containers.commit() + + service = WorkflowService(sessionmaker(bind=db.engine)) + with pytest.raises(WorkflowInUseError, match="currently in use by app"): + service.delete_workflow(session=db_session_with_containers, workflow_id=workflow.id, tenant_id=tenant.id) + + def test_delete_workflow_published_as_tool_raises_error(self, db_session_with_containers): + tenant, account = self._create_tenant_and_account(db_session_with_containers) + app = self._create_app(db_session_with_containers, tenant=tenant, account=account) + workflow = self._create_workflow( + db_session_with_containers, tenant=tenant, app=app, account=account, version="1.0" + ) + self._create_tool_provider(db_session_with_containers, tenant=tenant, app=app, account=account, version="1.0") + db_session_with_containers.commit() + + service = WorkflowService(sessionmaker(bind=db.engine)) + with pytest.raises(WorkflowInUseError, match="published as a tool"): + service.delete_workflow(session=db_session_with_containers, workflow_id=workflow.id, tenant_id=tenant.id) diff --git a/api/tests/unit_tests/services/workflow/test_workflow_deletion.py b/api/tests/unit_tests/services/workflow/test_workflow_deletion.py deleted file mode 100644 index dfe325648d..0000000000 --- a/api/tests/unit_tests/services/workflow/test_workflow_deletion.py +++ /dev/null @@ -1,127 +0,0 @@ -from unittest.mock import MagicMock - -import pytest -from sqlalchemy.orm import Session - -from models.model import App -from models.workflow import Workflow -from services.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError, WorkflowService - - -@pytest.fixture -def workflow_setup(): - mock_session_maker = MagicMock() - workflow_service = WorkflowService(mock_session_maker) - session = MagicMock(spec=Session) - tenant_id = "test-tenant-id" - workflow_id = "test-workflow-id" - - # Mock workflow - workflow = MagicMock(spec=Workflow) - workflow.id = workflow_id - workflow.tenant_id = tenant_id - workflow.version = "1.0" # Not a draft - workflow.tool_published = False # Not published as a tool by default - - # Mock app - app = MagicMock(spec=App) - app.id = "test-app-id" - app.name = "Test App" - app.workflow_id = None # Not used by an app by default - - return { - "workflow_service": workflow_service, - "session": session, - "tenant_id": tenant_id, - "workflow_id": workflow_id, - "workflow": workflow, - "app": app, - } - - -def test_delete_workflow_success(workflow_setup): - # Setup mocks - - # Mock the tool provider query to return None (not published as a tool) - workflow_setup["session"].query.return_value.where.return_value.first.return_value = None - - workflow_setup["session"].scalar = MagicMock( - side_effect=[workflow_setup["workflow"], None] - ) # Return workflow first, then None for app - - # Call the method - result = workflow_setup["workflow_service"].delete_workflow( - session=workflow_setup["session"], - workflow_id=workflow_setup["workflow_id"], - tenant_id=workflow_setup["tenant_id"], - ) - - # Verify - assert result is True - workflow_setup["session"].delete.assert_called_once_with(workflow_setup["workflow"]) - - -def test_delete_workflow_draft_error(workflow_setup): - # Setup mocks - workflow_setup["workflow"].version = "draft" - workflow_setup["session"].scalar = MagicMock(return_value=workflow_setup["workflow"]) - - # Call the method and verify exception - with pytest.raises(DraftWorkflowDeletionError): - workflow_setup["workflow_service"].delete_workflow( - session=workflow_setup["session"], - workflow_id=workflow_setup["workflow_id"], - tenant_id=workflow_setup["tenant_id"], - ) - - # Verify - workflow_setup["session"].delete.assert_not_called() - - -def test_delete_workflow_in_use_by_app_error(workflow_setup): - # Setup mocks - workflow_setup["app"].workflow_id = workflow_setup["workflow_id"] - workflow_setup["session"].scalar = MagicMock( - side_effect=[workflow_setup["workflow"], workflow_setup["app"]] - ) # Return workflow first, then app - - # Call the method and verify exception - with pytest.raises(WorkflowInUseError) as excinfo: - workflow_setup["workflow_service"].delete_workflow( - session=workflow_setup["session"], - workflow_id=workflow_setup["workflow_id"], - tenant_id=workflow_setup["tenant_id"], - ) - - # Verify error message contains app name - assert "Cannot delete workflow that is currently in use by app" in str(excinfo.value) - - # Verify - workflow_setup["session"].delete.assert_not_called() - - -def test_delete_workflow_published_as_tool_error(workflow_setup): - # Setup mocks - from models.tools import WorkflowToolProvider - - # Mock the tool provider query - mock_tool_provider = MagicMock(spec=WorkflowToolProvider) - workflow_setup["session"].query.return_value.where.return_value.first.return_value = mock_tool_provider - - workflow_setup["session"].scalar = MagicMock( - side_effect=[workflow_setup["workflow"], None] - ) # Return workflow first, then None for app - - # Call the method and verify exception - with pytest.raises(WorkflowInUseError) as excinfo: - workflow_setup["workflow_service"].delete_workflow( - session=workflow_setup["session"], - workflow_id=workflow_setup["workflow_id"], - tenant_id=workflow_setup["tenant_id"], - ) - - # Verify error message - assert "Cannot delete workflow that is published as a tool" in str(excinfo.value) - - # Verify - workflow_setup["session"].delete.assert_not_called() From a71b7909fdd2542af2ed9aa9094cb7cf4b13a7e7 Mon Sep 17 00:00:00 2001 From: Desel72 Date: Mon, 23 Mar 2026 02:06:08 -0500 Subject: [PATCH 11/70] refactor: migrate conversation variable updater tests to testcontainers (#33903) --- .../test_conversation_variable_updater.py | 58 ++++++++++++++ .../test_conversation_variable_updater.py | 75 ------------------- 2 files changed, 58 insertions(+), 75 deletions(-) create mode 100644 api/tests/test_containers_integration_tests/services/test_conversation_variable_updater.py delete mode 100644 api/tests/unit_tests/services/test_conversation_variable_updater.py diff --git a/api/tests/test_containers_integration_tests/services/test_conversation_variable_updater.py b/api/tests/test_containers_integration_tests/services/test_conversation_variable_updater.py new file mode 100644 index 0000000000..42a2215896 --- /dev/null +++ b/api/tests/test_containers_integration_tests/services/test_conversation_variable_updater.py @@ -0,0 +1,58 @@ +"""Testcontainers integration tests for ConversationVariableUpdater.""" + +from uuid import uuid4 + +import pytest +from sqlalchemy.orm import sessionmaker + +from dify_graph.variables import StringVariable +from extensions.ext_database import db +from models.workflow import ConversationVariable +from services.conversation_variable_updater import ConversationVariableNotFoundError, ConversationVariableUpdater + + +class TestConversationVariableUpdater: + def _create_conversation_variable( + self, db_session_with_containers, *, conversation_id: str, variable: StringVariable, app_id: str | None = None + ) -> ConversationVariable: + row = ConversationVariable( + id=variable.id, + conversation_id=conversation_id, + app_id=app_id or str(uuid4()), + data=variable.model_dump_json(), + ) + db_session_with_containers.add(row) + db_session_with_containers.commit() + return row + + def test_should_update_conversation_variable_data_and_commit(self, db_session_with_containers): + conversation_id = str(uuid4()) + variable = StringVariable(id=str(uuid4()), name="topic", value="old value") + self._create_conversation_variable( + db_session_with_containers, conversation_id=conversation_id, variable=variable + ) + + updated_variable = StringVariable(id=variable.id, name="topic", value="new value") + updater = ConversationVariableUpdater(sessionmaker(bind=db.engine)) + + updater.update(conversation_id=conversation_id, variable=updated_variable) + + db_session_with_containers.expire_all() + row = db_session_with_containers.get(ConversationVariable, (variable.id, conversation_id)) + assert row is not None + assert row.data == updated_variable.model_dump_json() + + def test_should_raise_not_found_when_variable_missing(self, db_session_with_containers): + conversation_id = str(uuid4()) + variable = StringVariable(id=str(uuid4()), name="topic", value="value") + updater = ConversationVariableUpdater(sessionmaker(bind=db.engine)) + + with pytest.raises(ConversationVariableNotFoundError, match="conversation variable not found in the database"): + updater.update(conversation_id=conversation_id, variable=variable) + + def test_should_do_nothing_when_flush_is_called(self, db_session_with_containers): + updater = ConversationVariableUpdater(sessionmaker(bind=db.engine)) + + result = updater.flush() + + assert result is None diff --git a/api/tests/unit_tests/services/test_conversation_variable_updater.py b/api/tests/unit_tests/services/test_conversation_variable_updater.py deleted file mode 100644 index 20f7caa78e..0000000000 --- a/api/tests/unit_tests/services/test_conversation_variable_updater.py +++ /dev/null @@ -1,75 +0,0 @@ -from types import SimpleNamespace -from unittest.mock import MagicMock - -import pytest - -from dify_graph.variables import StringVariable -from services.conversation_variable_updater import ConversationVariableNotFoundError, ConversationVariableUpdater - - -class TestConversationVariableUpdater: - def test_should_update_conversation_variable_data_and_commit(self): - """Test update persists serialized variable data when the row exists.""" - conversation_id = "conv-123" - variable = StringVariable( - id="var-123", - name="topic", - value="new value", - ) - expected_json = variable.model_dump_json() - - row = SimpleNamespace(data="old value") - session = MagicMock() - session.scalar.return_value = row - - session_context = MagicMock() - session_context.__enter__.return_value = session - session_context.__exit__.return_value = None - - session_maker = MagicMock(return_value=session_context) - updater = ConversationVariableUpdater(session_maker) - - updater.update(conversation_id=conversation_id, variable=variable) - - session_maker.assert_called_once_with() - session.scalar.assert_called_once() - stmt = session.scalar.call_args.args[0] - compiled_params = stmt.compile().params - assert variable.id in compiled_params.values() - assert conversation_id in compiled_params.values() - assert row.data == expected_json - session.commit.assert_called_once() - - def test_should_raise_not_found_error_when_conversation_variable_missing(self): - """Test update raises ConversationVariableNotFoundError when no matching row exists.""" - conversation_id = "conv-404" - variable = StringVariable( - id="var-404", - name="topic", - value="value", - ) - - session = MagicMock() - session.scalar.return_value = None - - session_context = MagicMock() - session_context.__enter__.return_value = session - session_context.__exit__.return_value = None - - session_maker = MagicMock(return_value=session_context) - updater = ConversationVariableUpdater(session_maker) - - with pytest.raises(ConversationVariableNotFoundError, match="conversation variable not found in the database"): - updater.update(conversation_id=conversation_id, variable=variable) - - session.commit.assert_not_called() - - def test_should_do_nothing_when_flush_is_called(self): - """Test flush currently behaves as a no-op and returns None.""" - session_maker = MagicMock() - updater = ConversationVariableUpdater(session_maker) - - result = updater.flush() - - assert result is None - session_maker.assert_not_called() From 6014853d452f24cd69bb2ad81b13febb8578b0c0 Mon Sep 17 00:00:00 2001 From: Desel72 Date: Mon, 23 Mar 2026 02:07:51 -0500 Subject: [PATCH 12/70] test: migrate dataset permission tests to testcontainers (#33906) --- .../test_dataset_permission_service.py | 72 +++++ .../services/test_dataset_permission.py | 305 ------------------ 2 files changed, 72 insertions(+), 305 deletions(-) delete mode 100644 api/tests/unit_tests/services/test_dataset_permission.py diff --git a/api/tests/test_containers_integration_tests/services/test_dataset_permission_service.py b/api/tests/test_containers_integration_tests/services/test_dataset_permission_service.py index 975af3d428..55bfb64e18 100644 --- a/api/tests/test_containers_integration_tests/services/test_dataset_permission_service.py +++ b/api/tests/test_containers_integration_tests/services/test_dataset_permission_service.py @@ -397,6 +397,68 @@ class TestDatasetPermissionServiceClearPartialMemberList: class TestDatasetServiceCheckDatasetPermission: """Verify dataset access checks against persisted partial-member permissions.""" + def test_check_dataset_permission_different_tenant_should_fail(self, db_session_with_containers): + """Test that users from different tenants cannot access dataset.""" + owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER) + other_user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.EDITOR) + + dataset = DatasetPermissionTestDataFactory.create_dataset( + tenant.id, owner.id, permission=DatasetPermissionEnum.ALL_TEAM + ) + + with pytest.raises(NoPermissionError): + DatasetService.check_dataset_permission(dataset, other_user) + + def test_check_dataset_permission_owner_can_access_any_dataset(self, db_session_with_containers): + """Test that tenant owners can access any dataset regardless of permission level.""" + owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER) + creator, _ = DatasetPermissionTestDataFactory.create_account_with_tenant( + role=TenantAccountRole.NORMAL, tenant=tenant + ) + + dataset = DatasetPermissionTestDataFactory.create_dataset( + tenant.id, creator.id, permission=DatasetPermissionEnum.ONLY_ME + ) + + DatasetService.check_dataset_permission(dataset, owner) + + def test_check_dataset_permission_only_me_creator_can_access(self, db_session_with_containers): + """Test ONLY_ME permission allows only the dataset creator to access.""" + creator, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.EDITOR) + + dataset = DatasetPermissionTestDataFactory.create_dataset( + tenant.id, creator.id, permission=DatasetPermissionEnum.ONLY_ME + ) + + DatasetService.check_dataset_permission(dataset, creator) + + def test_check_dataset_permission_only_me_others_cannot_access(self, db_session_with_containers): + """Test ONLY_ME permission denies access to non-creators.""" + creator, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.NORMAL) + other, _ = DatasetPermissionTestDataFactory.create_account_with_tenant( + role=TenantAccountRole.NORMAL, tenant=tenant + ) + + dataset = DatasetPermissionTestDataFactory.create_dataset( + tenant.id, creator.id, permission=DatasetPermissionEnum.ONLY_ME + ) + + with pytest.raises(NoPermissionError): + DatasetService.check_dataset_permission(dataset, other) + + def test_check_dataset_permission_all_team_allows_access(self, db_session_with_containers): + """Test ALL_TEAM permission allows any team member to access the dataset.""" + creator, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.NORMAL) + member, _ = DatasetPermissionTestDataFactory.create_account_with_tenant( + role=TenantAccountRole.NORMAL, tenant=tenant + ) + + dataset = DatasetPermissionTestDataFactory.create_dataset( + tenant.id, creator.id, permission=DatasetPermissionEnum.ALL_TEAM + ) + + DatasetService.check_dataset_permission(dataset, member) + def test_check_dataset_permission_partial_members_with_permission_success(self, db_session_with_containers): """ Test that user with explicit permission can access partial_members dataset. @@ -443,6 +505,16 @@ class TestDatasetServiceCheckDatasetPermission: with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset"): DatasetService.check_dataset_permission(dataset, user) + def test_check_dataset_permission_partial_team_creator_can_access(self, db_session_with_containers): + """Test PARTIAL_TEAM permission allows creator to access without explicit permission.""" + creator, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.EDITOR) + + dataset = DatasetPermissionTestDataFactory.create_dataset( + tenant.id, creator.id, permission=DatasetPermissionEnum.PARTIAL_TEAM + ) + + DatasetService.check_dataset_permission(dataset, creator) + class TestDatasetServiceCheckDatasetOperatorPermission: """Verify operator permission checks against persisted partial-member permissions.""" diff --git a/api/tests/unit_tests/services/test_dataset_permission.py b/api/tests/unit_tests/services/test_dataset_permission.py deleted file mode 100644 index 4974d6c1ef..0000000000 --- a/api/tests/unit_tests/services/test_dataset_permission.py +++ /dev/null @@ -1,305 +0,0 @@ -from unittest.mock import Mock, patch - -import pytest - -from models.account import Account, TenantAccountRole -from models.dataset import Dataset, DatasetPermission, DatasetPermissionEnum -from services.dataset_service import DatasetService -from services.errors.account import NoPermissionError - - -class DatasetPermissionTestDataFactory: - """Factory class for creating test data and mock objects for dataset permission tests.""" - - @staticmethod - def create_dataset_mock( - dataset_id: str = "dataset-123", - tenant_id: str = "test-tenant-123", - created_by: str = "creator-456", - permission: DatasetPermissionEnum = DatasetPermissionEnum.ONLY_ME, - **kwargs, - ) -> Mock: - """Create a mock dataset with specified attributes.""" - dataset = Mock(spec=Dataset) - dataset.id = dataset_id - dataset.tenant_id = tenant_id - dataset.created_by = created_by - dataset.permission = permission - for key, value in kwargs.items(): - setattr(dataset, key, value) - return dataset - - @staticmethod - def create_user_mock( - user_id: str = "user-789", - tenant_id: str = "test-tenant-123", - role: TenantAccountRole = TenantAccountRole.NORMAL, - **kwargs, - ) -> Mock: - """Create a mock user with specified attributes.""" - user = Mock(spec=Account) - user.id = user_id - user.current_tenant_id = tenant_id - user.current_role = role - for key, value in kwargs.items(): - setattr(user, key, value) - return user - - @staticmethod - def create_dataset_permission_mock( - dataset_id: str = "dataset-123", - account_id: str = "user-789", - **kwargs, - ) -> Mock: - """Create a mock dataset permission record.""" - permission = Mock(spec=DatasetPermission) - permission.dataset_id = dataset_id - permission.account_id = account_id - for key, value in kwargs.items(): - setattr(permission, key, value) - return permission - - -class TestDatasetPermissionService: - """ - Comprehensive unit tests for DatasetService.check_dataset_permission method. - - This test suite covers all permission scenarios including: - - Cross-tenant access restrictions - - Owner privilege checks - - Different permission levels (ONLY_ME, ALL_TEAM, PARTIAL_TEAM) - - Explicit permission checks for PARTIAL_TEAM - - Error conditions and logging - """ - - @pytest.fixture - def mock_dataset_service_dependencies(self): - """Common mock setup for dataset service dependencies.""" - with patch("services.dataset_service.db.session") as mock_session: - yield { - "db_session": mock_session, - } - - @pytest.fixture - def mock_logging_dependencies(self): - """Mock setup for logging tests.""" - with patch("services.dataset_service.logger") as mock_logging: - yield { - "logging": mock_logging, - } - - def _assert_permission_check_passes(self, dataset: Mock, user: Mock): - """Helper method to verify that permission check passes without raising exceptions.""" - # Should not raise any exception - DatasetService.check_dataset_permission(dataset, user) - - def _assert_permission_check_fails( - self, dataset: Mock, user: Mock, expected_message: str = "You do not have permission to access this dataset." - ): - """Helper method to verify that permission check fails with expected error.""" - with pytest.raises(NoPermissionError, match=expected_message): - DatasetService.check_dataset_permission(dataset, user) - - def _assert_database_query_called(self, mock_session: Mock, dataset_id: str, account_id: str): - """Helper method to verify database query calls for permission checks.""" - mock_session.query().filter_by.assert_called_with(dataset_id=dataset_id, account_id=account_id) - - def _assert_database_query_not_called(self, mock_session: Mock): - """Helper method to verify that database query was not called.""" - mock_session.query.assert_not_called() - - # ==================== Cross-Tenant Access Tests ==================== - - def test_permission_check_different_tenant_should_fail(self): - """Test that users from different tenants cannot access dataset regardless of other permissions.""" - # Create dataset and user from different tenants - dataset = DatasetPermissionTestDataFactory.create_dataset_mock( - tenant_id="tenant-123", permission=DatasetPermissionEnum.ALL_TEAM - ) - user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="user-789", tenant_id="different-tenant-456", role=TenantAccountRole.EDITOR - ) - - # Should fail due to different tenant - self._assert_permission_check_fails(dataset, user) - - # ==================== Owner Privilege Tests ==================== - - def test_owner_can_access_any_dataset(self): - """Test that tenant owners can access any dataset regardless of permission level.""" - # Create dataset with restrictive permission - dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.ONLY_ME) - - # Create owner user - owner_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="owner-999", role=TenantAccountRole.OWNER - ) - - # Owner should have access regardless of dataset permission - self._assert_permission_check_passes(dataset, owner_user) - - # ==================== ONLY_ME Permission Tests ==================== - - def test_only_me_permission_creator_can_access(self): - """Test ONLY_ME permission allows only the dataset creator to access.""" - # Create dataset with ONLY_ME permission - dataset = DatasetPermissionTestDataFactory.create_dataset_mock( - created_by="creator-456", permission=DatasetPermissionEnum.ONLY_ME - ) - - # Create creator user - creator_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="creator-456", role=TenantAccountRole.EDITOR - ) - - # Creator should be able to access - self._assert_permission_check_passes(dataset, creator_user) - - def test_only_me_permission_others_cannot_access(self): - """Test ONLY_ME permission denies access to non-creators.""" - # Create dataset with ONLY_ME permission - dataset = DatasetPermissionTestDataFactory.create_dataset_mock( - created_by="creator-456", permission=DatasetPermissionEnum.ONLY_ME - ) - - # Create normal user (not the creator) - normal_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="normal-789", role=TenantAccountRole.NORMAL - ) - - # Non-creator should be denied access - self._assert_permission_check_fails(dataset, normal_user) - - # ==================== ALL_TEAM Permission Tests ==================== - - def test_all_team_permission_allows_access(self): - """Test ALL_TEAM permission allows any team member to access the dataset.""" - # Create dataset with ALL_TEAM permission - dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.ALL_TEAM) - - # Create different types of team members - normal_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="normal-789", role=TenantAccountRole.NORMAL - ) - editor_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="editor-456", role=TenantAccountRole.EDITOR - ) - - # All team members should have access - self._assert_permission_check_passes(dataset, normal_user) - self._assert_permission_check_passes(dataset, editor_user) - - # ==================== PARTIAL_TEAM Permission Tests ==================== - - def test_partial_team_permission_creator_can_access(self, mock_dataset_service_dependencies): - """Test PARTIAL_TEAM permission allows creator to access without database query.""" - # Create dataset with PARTIAL_TEAM permission - dataset = DatasetPermissionTestDataFactory.create_dataset_mock( - created_by="creator-456", permission=DatasetPermissionEnum.PARTIAL_TEAM - ) - - # Create creator user - creator_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="creator-456", role=TenantAccountRole.EDITOR - ) - - # Creator should have access without database query - self._assert_permission_check_passes(dataset, creator_user) - self._assert_database_query_not_called(mock_dataset_service_dependencies["db_session"]) - - def test_partial_team_permission_with_explicit_permission(self, mock_dataset_service_dependencies): - """Test PARTIAL_TEAM permission allows users with explicit permission records.""" - # Create dataset with PARTIAL_TEAM permission - dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.PARTIAL_TEAM) - - # Create normal user (not the creator) - normal_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="normal-789", role=TenantAccountRole.NORMAL - ) - - # Mock database query to return a permission record - mock_permission = DatasetPermissionTestDataFactory.create_dataset_permission_mock( - dataset_id=dataset.id, account_id=normal_user.id - ) - mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = mock_permission - - # User with explicit permission should have access - self._assert_permission_check_passes(dataset, normal_user) - self._assert_database_query_called(mock_dataset_service_dependencies["db_session"], dataset.id, normal_user.id) - - def test_partial_team_permission_without_explicit_permission(self, mock_dataset_service_dependencies): - """Test PARTIAL_TEAM permission denies users without explicit permission records.""" - # Create dataset with PARTIAL_TEAM permission - dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.PARTIAL_TEAM) - - # Create normal user (not the creator) - normal_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="normal-789", role=TenantAccountRole.NORMAL - ) - - # Mock database query to return None (no permission record) - mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = None - - # User without explicit permission should be denied access - self._assert_permission_check_fails(dataset, normal_user) - self._assert_database_query_called(mock_dataset_service_dependencies["db_session"], dataset.id, normal_user.id) - - def test_partial_team_permission_non_creator_without_permission_fails(self, mock_dataset_service_dependencies): - """Test that non-creators without explicit permission are denied access to PARTIAL_TEAM datasets.""" - # Create dataset with PARTIAL_TEAM permission - dataset = DatasetPermissionTestDataFactory.create_dataset_mock( - created_by="creator-456", permission=DatasetPermissionEnum.PARTIAL_TEAM - ) - - # Create a different user (not the creator) - other_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="other-user-123", role=TenantAccountRole.NORMAL - ) - - # Mock database query to return None (no permission record) - mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = None - - # Non-creator without explicit permission should be denied access - self._assert_permission_check_fails(dataset, other_user) - self._assert_database_query_called(mock_dataset_service_dependencies["db_session"], dataset.id, other_user.id) - - # ==================== Enum Usage Tests ==================== - - def test_partial_team_permission_uses_correct_enum(self): - """Test that the method correctly uses DatasetPermissionEnum.PARTIAL_TEAM instead of string literals.""" - # Create dataset with PARTIAL_TEAM permission using enum - dataset = DatasetPermissionTestDataFactory.create_dataset_mock( - created_by="creator-456", permission=DatasetPermissionEnum.PARTIAL_TEAM - ) - - # Create creator user - creator_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="creator-456", role=TenantAccountRole.EDITOR - ) - - # Creator should always have access regardless of permission level - self._assert_permission_check_passes(dataset, creator_user) - - # ==================== Logging Tests ==================== - - def test_permission_denied_logs_debug_message(self, mock_dataset_service_dependencies, mock_logging_dependencies): - """Test that permission denied events are properly logged for debugging purposes.""" - # Create dataset with PARTIAL_TEAM permission - dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.PARTIAL_TEAM) - - # Create normal user (not the creator) - normal_user = DatasetPermissionTestDataFactory.create_user_mock( - user_id="normal-789", role=TenantAccountRole.NORMAL - ) - - # Mock database query to return None (no permission record) - mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = None - - # Attempt permission check (should fail) - with pytest.raises(NoPermissionError): - DatasetService.check_dataset_permission(dataset, normal_user) - - # Verify debug message was logged with correct user and dataset information - mock_logging_dependencies["logging"].debug.assert_called_with( - "User %s does not have permission to access dataset %s", normal_user.id, dataset.id - ) From 368fc0bbe52d7294e618a5024b58a207895aa556 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:10:02 +0900 Subject: [PATCH 13/70] chore(deps): bump boto3 from 1.42.68 to 1.42.73 in /api in the storage group (#33871) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/pyproject.toml | 2 +- api/uv.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index fb71f3cd6c..ecc8718473 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -8,7 +8,7 @@ dependencies = [ "arize-phoenix-otel~=0.15.0", "azure-identity==1.25.3", "beautifulsoup4==4.14.3", - "boto3==1.42.68", + "boto3==1.42.73", "bs4~=0.0.1", "cachetools~=5.3.0", "celery~=5.6.2", diff --git a/api/uv.lock b/api/uv.lock index 952ec87273..40cbbfeb46 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -706,16 +706,16 @@ wheels = [ [[package]] name = "boto3" -version = "1.42.68" +version = "1.42.73" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/ae/60c642aa5413e560b671da825329f510b29a77274ed0f580bde77562294d/boto3-1.42.68.tar.gz", hash = "sha256:3f349f967ab38c23425626d130962bcb363e75f042734fe856ea8c5a00eef03c", size = 112761, upload-time = "2026-03-13T19:32:17.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/8b/d00575be514744ca4839e7d85bf4a8a3c7b6b4574433291e58d14c68ae09/boto3-1.42.73.tar.gz", hash = "sha256:d37b58d6cd452ca808dd6823ae19ca65b6244096c5125ef9052988b337298bae", size = 112775, upload-time = "2026-03-20T19:39:52.814Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/f6/dc6e993479dbb597d68223fbf61cb026511737696b15bd7d2a33e9b2c24f/boto3-1.42.68-py3-none-any.whl", hash = "sha256:dbff353eb7dc93cbddd7926ed24793e0174c04adbe88860dfa639568442e4962", size = 140556, upload-time = "2026-03-13T19:32:14.951Z" }, + { url = "https://files.pythonhosted.org/packages/aa/05/1fcf03d90abaa3d0b42a6bfd10231dd709493ecbacf794aa2eea5eae6841/boto3-1.42.73-py3-none-any.whl", hash = "sha256:1f81b79b873f130eeab14bb556417a7c66d38f3396b7f2fe3b958b3f9094f455", size = 140556, upload-time = "2026-03-20T19:39:50.298Z" }, ] [[package]] @@ -739,16 +739,16 @@ bedrock-runtime = [ [[package]] name = "botocore" -version = "1.42.68" +version = "1.42.73" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/22/87502d5fbbfa8189406a617b30b1e2a3dc0ab2669f7268e91b385c1c1c7a/botocore-1.42.68.tar.gz", hash = "sha256:3951c69e12ac871dda245f48dac5c7dd88ea1bfdd74a8879ec356cf2874b806a", size = 14994514, upload-time = "2026-03-13T19:32:03.577Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/23/0c88ca116ef63b1ae77c901cd5d2095d22a8dbde9e80df74545db4a061b4/botocore-1.42.73.tar.gz", hash = "sha256:575858641e4949aaf2af1ced145b8524529edf006d075877af6b82ff96ad854c", size = 15008008, upload-time = "2026-03-20T19:39:40.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/2a/1428f6594799780fe6ee845d8e6aeffafe026cd16a70c878684e2dcbbfc8/botocore-1.42.68-py3-none-any.whl", hash = "sha256:9df7da26374601f890e2f115bfa573d65bf15b25fe136bb3aac809f6145f52ab", size = 14668816, upload-time = "2026-03-13T19:31:58.572Z" }, + { url = "https://files.pythonhosted.org/packages/8e/65/971f3d55015f4d133a6ff3ad74cd39f4b8dd8f53f7775a3c2ad378ea5145/botocore-1.42.73-py3-none-any.whl", hash = "sha256:7b62e2a12f7a1b08eb7360eecd23bb16fe3b7ab7f5617cf91b25476c6f86a0fe", size = 14681861, upload-time = "2026-03-20T19:39:35.341Z" }, ] [[package]] @@ -1745,7 +1745,7 @@ requires-dist = [ { name = "azure-identity", specifier = "==1.25.3" }, { name = "beautifulsoup4", specifier = "==4.14.3" }, { name = "bleach", specifier = "~=6.2.0" }, - { name = "boto3", specifier = "==1.42.68" }, + { name = "boto3", specifier = "==1.42.73" }, { name = "bs4", specifier = "~=0.0.1" }, { name = "cachetools", specifier = "~=5.3.0" }, { name = "celery", specifier = "~=5.6.2" }, From 6604f8d50688600feb6403e80d691fa9b17ec5bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:10:41 +0900 Subject: [PATCH 14/70] chore(deps): bump litellm from 1.82.2 to 1.82.6 in /api in the llm group (#33870) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[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 ecc8718473..754e261d3e 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ "numpy~=1.26.4", "openpyxl~=3.1.5", "opik~=1.10.37", - "litellm==1.82.2", # Pinned to avoid madoka dependency issue + "litellm==1.82.6", # Pinned to avoid madoka dependency issue "opentelemetry-api==1.28.0", "opentelemetry-distro==0.49b0", "opentelemetry-exporter-otlp==1.28.0", diff --git a/api/uv.lock b/api/uv.lock index 40cbbfeb46..f2aa3569a7 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1776,7 +1776,7 @@ requires-dist = [ { name = "jsonschema", specifier = ">=4.25.1" }, { name = "langfuse", specifier = "~=2.51.3" }, { name = "langsmith", specifier = "~=0.7.16" }, - { name = "litellm", specifier = "==1.82.2" }, + { name = "litellm", specifier = "==1.82.6" }, { name = "markdown", specifier = "~=3.10.2" }, { name = "mlflow-skinny", specifier = ">=3.0.0" }, { name = "numpy", specifier = "~=1.26.4" }, @@ -3523,7 +3523,7 @@ wheels = [ [[package]] name = "litellm" -version = "1.82.2" +version = "1.82.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -3539,9 +3539,9 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/12/010a86643f12ac0b004032d5927c260094299a84ed38b5ed20a8f8c7e3c4/litellm-1.82.2.tar.gz", hash = "sha256:f5f4c4049f344a88bf80b2e421bb927807687c99624515d7ff4152d533ec9dcb", size = 17353218, upload-time = "2026-03-13T21:24:24.5Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/75/1c537aa458426a9127a92bc2273787b2f987f4e5044e21f01f2eed5244fd/litellm-1.82.6.tar.gz", hash = "sha256:2aa1c2da21fe940c33613aa447119674a3ad4d2ad5eb064e4d5ce5ee42420136", size = 17414147, upload-time = "2026-03-22T06:36:00.452Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/e4/87e3ca82a8bf6e6bfffb42a539a1350dd6ced1b7169397bd439ba56fde10/litellm-1.82.2-py3-none-any.whl", hash = "sha256:641ed024774fa3d5b4dd9347f0efb1e31fa422fba2a6500aabedee085d1194cb", size = 15524224, upload-time = "2026-03-13T21:24:21.288Z" }, + { url = "https://files.pythonhosted.org/packages/02/6c/5327667e6dbe9e98cbfbd4261c8e91386a52e38f41419575854248bbab6a/litellm-1.82.6-py3-none-any.whl", hash = "sha256:164a3ef3e19f309e3cabc199bef3d2045212712fefdfa25fc7f75884a5b5b205", size = 15591595, upload-time = "2026-03-22T06:35:56.795Z" }, ] [[package]] From 3f8f1fa003765d763d27a0c20ee525596072e81e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:11:32 +0900 Subject: [PATCH 15/70] chore(deps): bump google-api-python-client from 2.192.0 to 2.193.0 in /api in the google group (#33868) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[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 754e261d3e..ad9b6fc7ac 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "gevent~=25.9.1", "gmpy2~=2.3.0", "google-api-core>=2.19.1", - "google-api-python-client==2.192.0", + "google-api-python-client==2.193.0", "google-auth>=2.47.0", "google-auth-httplib2==0.3.0", "google-cloud-aiplatform>=1.123.0", diff --git a/api/uv.lock b/api/uv.lock index f2aa3569a7..cc7e5227c5 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1763,7 +1763,7 @@ requires-dist = [ { name = "gevent", specifier = "~=25.9.1" }, { name = "gmpy2", specifier = "~=2.3.0" }, { name = "google-api-core", specifier = ">=2.19.1" }, - { name = "google-api-python-client", specifier = "==2.192.0" }, + { name = "google-api-python-client", specifier = "==2.193.0" }, { name = "google-auth", specifier = ">=2.47.0" }, { name = "google-auth-httplib2", specifier = "==0.3.0" }, { name = "google-cloud-aiplatform", specifier = ">=1.123.0" }, @@ -2501,7 +2501,7 @@ grpc = [ [[package]] name = "google-api-python-client" -version = "2.192.0" +version = "2.193.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, @@ -2510,9 +2510,9 @@ dependencies = [ { name = "httplib2" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/d8/489052a40935e45b9b5b3d6accc14b041360c1507bdc659c2e1a19aaa3ff/google_api_python_client-2.192.0.tar.gz", hash = "sha256:d48cfa6078fadea788425481b007af33fe0ab6537b78f37da914fb6fc112eb27", size = 14209505, upload-time = "2026-03-05T15:17:01.598Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/f4/e14b6815d3b1885328dd209676a3a4c704882743ac94e18ef0093894f5c8/google_api_python_client-2.193.0.tar.gz", hash = "sha256:8f88d16e89d11341e0a8b199cafde0fb7e6b44260dffb88d451577cbd1bb5d33", size = 14281006, upload-time = "2026-03-17T18:25:29.415Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/76/ec4128f00fefb9011635ae2abc67d7dacd05c8559378f8f05f0c907c38d8/google_api_python_client-2.192.0-py3-none-any.whl", hash = "sha256:63a57d4457cd97df1d63eb89c5fda03c5a50588dcbc32c0115dd1433c08f4b62", size = 14783267, upload-time = "2026-03-05T15:16:58.804Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6d/fe75167797790a56d17799b75e1129bb93f7ff061efc7b36e9731bd4be2b/google_api_python_client-2.193.0-py3-none-any.whl", hash = "sha256:c42aa324b822109901cfecab5dc4fc3915d35a7b376835233c916c70610322db", size = 14856490, upload-time = "2026-03-17T18:25:26.608Z" }, ] [[package]] From 2809e4cc4022bdcedac77f606b5b386dd11d3d8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:12:23 +0900 Subject: [PATCH 16/70] chore(deps-dev): update pytest-cov requirement from ~=7.0.0 to ~=7.1.0 in /api in the dev group (#33872)d Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[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 ad9b6fc7ac..1efdb601ae 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -119,7 +119,7 @@ dev = [ "ruff~=0.15.5", "pytest~=9.0.2", "pytest-benchmark~=5.2.3", - "pytest-cov~=7.0.0", + "pytest-cov~=7.1.0", "pytest-env~=1.6.0", "pytest-mock~=3.15.1", "testcontainers~=4.14.1", diff --git a/api/uv.lock b/api/uv.lock index cc7e5227c5..142753f101 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1846,7 +1846,7 @@ dev = [ { name = "pyrefly", specifier = ">=0.55.0" }, { name = "pytest", specifier = "~=9.0.2" }, { name = "pytest-benchmark", specifier = "~=5.2.3" }, - { name = "pytest-cov", specifier = "~=7.0.0" }, + { name = "pytest-cov", specifier = "~=7.1.0" }, { name = "pytest-env", specifier = "~=1.6.0" }, { name = "pytest-mock", specifier = "~=3.15.1" }, { name = "pytest-timeout", specifier = ">=2.4.0" }, @@ -5523,16 +5523,16 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] From 33000d1c60322ec1a79ff7526547a698d710703f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:13:45 +0900 Subject: [PATCH 17/70] chore(deps): bump pydantic-extra-types from 2.11.0 to 2.11.1 in /api in the pydantic group (#33876) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/uv.lock b/api/uv.lock index 142753f101..5d9f4f1b9e 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -5275,15 +5275,15 @@ wheels = [ [[package]] name = "pydantic-extra-types" -version = "2.11.0" +version = "2.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/35/2fee58b1316a73e025728583d3b1447218a97e621933fc776fb8c0f2ebdd/pydantic_extra_types-2.11.0.tar.gz", hash = "sha256:4e9991959d045b75feb775683437a97991d02c138e00b59176571db9ce634f0e", size = 157226, upload-time = "2025-12-31T16:18:27.944Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/71/dba38ee2651f84f7842206adbd2233d8bbdb59fb85e9fa14232486a8c471/pydantic_extra_types-2.11.1.tar.gz", hash = "sha256:46792d2307383859e923d8fcefa82108b1a141f8a9c0198982b3832ab5ef1049", size = 172002, upload-time = "2026-03-16T08:08:03.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/17/fabd56da47096d240dd45ba627bead0333b0cf0ee8ada9bec579287dadf3/pydantic_extra_types-2.11.0-py3-none-any.whl", hash = "sha256:84b864d250a0fc62535b7ec591e36f2c5b4d1325fa0017eb8cda9aeb63b374a6", size = 74296, upload-time = "2025-12-31T16:18:26.38Z" }, + { url = "https://files.pythonhosted.org/packages/17/c1/3226e6d7f5a4f736f38ac11a6fbb262d701889802595cdb0f53a885ac2e0/pydantic_extra_types-2.11.1-py3-none-any.whl", hash = "sha256:1722ea2bddae5628ace25f2aa685b69978ef533123e5638cfbddb999e0100ec1", size = 79526, upload-time = "2026-03-16T08:08:02.533Z" }, ] [[package]] From 3c672703bc50d0c1ccbd25bf0819b07d8a28215f Mon Sep 17 00:00:00 2001 From: wangxiaolei Date: Mon, 23 Mar 2026 15:17:15 +0800 Subject: [PATCH 18/70] chore: remove log level reset (#33914) --- api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py b/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py index 7cb54b2c88..f54461e99a 100644 --- a/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py +++ b/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py @@ -181,10 +181,6 @@ class ArizePhoenixDataTrace(BaseTraceInstance): arize_phoenix_config: ArizeConfig | PhoenixConfig, ): super().__init__(arize_phoenix_config) - import logging - - logging.basicConfig() - logging.getLogger().setLevel(logging.DEBUG) self.arize_phoenix_config = arize_phoenix_config self.tracer, self.processor = setup_tracer(arize_phoenix_config) self.project = arize_phoenix_config.project From 82b094a2d50e4b71cfeec6fe889c2645fc5f7526 Mon Sep 17 00:00:00 2001 From: Desel72 Date: Mon, 23 Mar 2026 02:18:46 -0500 Subject: [PATCH 19/70] refactor: migrate attachment service tests to testcontainers (#33900) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../services/test_attachment_service.py | 80 +++++++++++++++++++ .../services/test_attachment_service.py | 73 ----------------- 2 files changed, 80 insertions(+), 73 deletions(-) create mode 100644 api/tests/test_containers_integration_tests/services/test_attachment_service.py delete mode 100644 api/tests/unit_tests/services/test_attachment_service.py diff --git a/api/tests/test_containers_integration_tests/services/test_attachment_service.py b/api/tests/test_containers_integration_tests/services/test_attachment_service.py new file mode 100644 index 0000000000..768a8baee2 --- /dev/null +++ b/api/tests/test_containers_integration_tests/services/test_attachment_service.py @@ -0,0 +1,80 @@ +"""Testcontainers integration tests for AttachmentService.""" + +import base64 +from datetime import UTC, datetime +from unittest.mock import patch +from uuid import uuid4 + +import pytest +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from werkzeug.exceptions import NotFound + +import services.attachment_service as attachment_service_module +from extensions.ext_database import db +from extensions.storage.storage_type import StorageType +from models.enums import CreatorUserRole +from models.model import UploadFile +from services.attachment_service import AttachmentService + + +class TestAttachmentService: + def _create_upload_file(self, db_session_with_containers, *, tenant_id: str | None = None) -> UploadFile: + upload_file = UploadFile( + tenant_id=tenant_id or str(uuid4()), + storage_type=StorageType.OPENDAL, + key=f"upload/{uuid4()}.txt", + name="test-file.txt", + size=100, + extension="txt", + mime_type="text/plain", + created_by_role=CreatorUserRole.ACCOUNT, + created_by=str(uuid4()), + created_at=datetime.now(UTC), + used=False, + ) + db_session_with_containers.add(upload_file) + db_session_with_containers.commit() + return upload_file + + def test_should_initialize_with_sessionmaker(self): + session_factory = sessionmaker() + + service = AttachmentService(session_factory=session_factory) + + assert service._session_maker is session_factory + + def test_should_initialize_with_engine(self): + engine = create_engine("sqlite:///:memory:") + + service = AttachmentService(session_factory=engine) + session = service._session_maker() + try: + assert session.bind == engine + finally: + session.close() + engine.dispose() + + @pytest.mark.parametrize("invalid_session_factory", [None, "not-a-session-factory", 1]) + def test_should_raise_assertion_error_for_invalid_session_factory(self, invalid_session_factory): + with pytest.raises(AssertionError, match="must be a sessionmaker or an Engine."): + AttachmentService(session_factory=invalid_session_factory) + + def test_should_return_base64_when_file_exists(self, db_session_with_containers): + upload_file = self._create_upload_file(db_session_with_containers) + service = AttachmentService(session_factory=sessionmaker(bind=db.engine)) + + with patch.object(attachment_service_module.storage, "load_once", return_value=b"binary-content") as mock_load: + result = service.get_file_base64(upload_file.id) + + assert result == base64.b64encode(b"binary-content").decode() + mock_load.assert_called_once_with(upload_file.key) + + def test_should_raise_not_found_when_file_missing(self, db_session_with_containers): + service = AttachmentService(session_factory=sessionmaker(bind=db.engine)) + + with patch.object(attachment_service_module.storage, "load_once") as mock_load: + with pytest.raises(NotFound, match="File not found"): + service.get_file_base64(str(uuid4())) + + mock_load.assert_not_called() diff --git a/api/tests/unit_tests/services/test_attachment_service.py b/api/tests/unit_tests/services/test_attachment_service.py deleted file mode 100644 index 88be20bc41..0000000000 --- a/api/tests/unit_tests/services/test_attachment_service.py +++ /dev/null @@ -1,73 +0,0 @@ -import base64 -from unittest.mock import MagicMock, patch - -import pytest -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from werkzeug.exceptions import NotFound - -import services.attachment_service as attachment_service_module -from models.model import UploadFile -from services.attachment_service import AttachmentService - - -class TestAttachmentService: - def test_should_initialize_with_sessionmaker_when_sessionmaker_is_provided(self): - """Test that AttachmentService keeps the provided sessionmaker instance.""" - session_factory = sessionmaker() - - service = AttachmentService(session_factory=session_factory) - - assert service._session_maker is session_factory - - def test_should_initialize_with_bound_sessionmaker_when_engine_is_provided(self): - """Test that AttachmentService builds a sessionmaker bound to the provided engine.""" - engine = create_engine("sqlite:///:memory:") - - service = AttachmentService(session_factory=engine) - session = service._session_maker() - try: - assert session.bind == engine - finally: - session.close() - engine.dispose() - - @pytest.mark.parametrize("invalid_session_factory", [None, "not-a-session-factory", 1]) - def test_should_raise_assertion_error_when_session_factory_type_is_invalid(self, invalid_session_factory): - """Test that invalid session_factory types are rejected.""" - with pytest.raises(AssertionError, match="must be a sessionmaker or an Engine."): - AttachmentService(session_factory=invalid_session_factory) - - def test_should_return_base64_encoded_blob_when_file_exists(self): - """Test that existing files are loaded from storage and returned as base64.""" - service = AttachmentService(session_factory=sessionmaker()) - upload_file = MagicMock(spec=UploadFile) - upload_file.key = "upload-file-key" - - session = MagicMock() - session.query.return_value.where.return_value.first.return_value = upload_file - service._session_maker = MagicMock(return_value=session) - - with patch.object(attachment_service_module.storage, "load_once", return_value=b"binary-content") as mock_load: - result = service.get_file_base64("file-123") - - assert result == base64.b64encode(b"binary-content").decode() - service._session_maker.assert_called_once_with(expire_on_commit=False) - session.query.assert_called_once_with(UploadFile) - mock_load.assert_called_once_with("upload-file-key") - - def test_should_raise_not_found_when_file_does_not_exist(self): - """Test that missing files raise NotFound and never call storage.""" - service = AttachmentService(session_factory=sessionmaker()) - - session = MagicMock() - session.query.return_value.where.return_value.first.return_value = None - service._session_maker = MagicMock(return_value=session) - - with patch.object(attachment_service_module.storage, "load_once") as mock_load: - with pytest.raises(NotFound, match="File not found"): - service.get_file_base64("missing-file") - - service._session_maker.assert_called_once_with(expire_on_commit=False) - session.query.assert_called_once_with(UploadFile) - mock_load.assert_not_called() From 25a83065d26fb703ead197601a31d659a2c79518 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:19:20 +0800 Subject: [PATCH 20/70] refactor(web): remove legacy data-source settings (#33905) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../__tests__/index.spec.tsx | 462 ------------------ .../data-source-notion/index.tsx | 103 ---- .../operate/__tests__/index.spec.tsx | 137 ------ .../data-source-notion/operate/index.tsx | 103 ---- .../__tests__/config-firecrawl-modal.spec.tsx | 204 -------- .../config-jina-reader-modal.spec.tsx | 179 ------- .../config-watercrawl-modal.spec.tsx | 204 -------- .../__tests__/index.spec.tsx | 251 ---------- .../config-firecrawl-modal.tsx | 165 ------- .../config-jina-reader-modal.tsx | 144 ------ .../config-watercrawl-modal.tsx | 165 ------- .../data-source-website/index.tsx | 137 ------ .../panel/__tests__/config-item.spec.tsx | 213 -------- .../panel/__tests__/index.spec.tsx | 226 --------- .../data-source-page/panel/config-item.tsx | 85 ---- .../data-source-page/panel/index.tsx | 151 ------ .../data-source-page/panel/style.module.css | 17 - .../data-source-page/panel/types.ts | 4 - web/eslint-suppressions.json | 63 --- 19 files changed, 3013 deletions(-) delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-notion/__tests__/index.spec.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-notion/operate/__tests__/index.spec.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-notion/operate/index.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-firecrawl-modal.spec.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-jina-reader-modal.spec.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-watercrawl-modal.spec.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/index.spec.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-website/config-watercrawl-modal.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/panel/__tests__/config-item.spec.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/panel/__tests__/index.spec.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/panel/config-item.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/panel/index.tsx delete mode 100644 web/app/components/header/account-setting/data-source-page/panel/style.module.css delete mode 100644 web/app/components/header/account-setting/data-source-page/panel/types.ts diff --git a/web/app/components/header/account-setting/data-source-page/data-source-notion/__tests__/index.spec.tsx b/web/app/components/header/account-setting/data-source-page/data-source-notion/__tests__/index.spec.tsx deleted file mode 100644 index dad82d81b9..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-notion/__tests__/index.spec.tsx +++ /dev/null @@ -1,462 +0,0 @@ -import type { UseQueryResult } from '@tanstack/react-query' -import type { AppContextValue } from '@/context/app-context' -import type { DataSourceNotion as TDataSourceNotion } from '@/models/common' -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' -import { useAppContext } from '@/context/app-context' -import { useDataSourceIntegrates, useInvalidDataSourceIntegrates, useNotionConnection } from '@/service/use-common' -import DataSourceNotion from '../index' - -/** - * DataSourceNotion Component Tests - * Using Unit approach with real Panel and sibling components to test Notion integration logic. - */ - -type MockQueryResult = UseQueryResult - -// Mock dependencies -vi.mock('@/context/app-context', () => ({ - useAppContext: vi.fn(), -})) - -vi.mock('@/service/common', () => ({ - syncDataSourceNotion: vi.fn(), - updateDataSourceNotionAction: vi.fn(), -})) - -vi.mock('@/service/use-common', () => ({ - useDataSourceIntegrates: vi.fn(), - useNotionConnection: vi.fn(), - useInvalidDataSourceIntegrates: vi.fn(), -})) - -describe('DataSourceNotion Component', () => { - const mockWorkspaces: TDataSourceNotion[] = [ - { - id: 'ws-1', - provider: 'notion', - is_bound: true, - source_info: { - workspace_name: 'Workspace 1', - workspace_icon: 'https://example.com/icon-1.png', - workspace_id: 'notion-ws-1', - total: 10, - pages: [], - }, - }, - ] - - const baseAppContext: AppContextValue = { - userProfile: { id: 'test-user-id', name: 'test-user', email: 'test@example.com', avatar: '', avatar_url: '', is_password_set: true }, - mutateUserProfile: vi.fn(), - currentWorkspace: { id: 'ws-id', name: 'Workspace', plan: 'basic', status: 'normal', created_at: 0, role: 'owner', providers: [], trial_credits: 0, trial_credits_used: 0, next_credit_reset_date: 0 }, - isCurrentWorkspaceManager: true, - isCurrentWorkspaceOwner: true, - isCurrentWorkspaceEditor: true, - isCurrentWorkspaceDatasetOperator: false, - mutateCurrentWorkspace: vi.fn(), - langGeniusVersionInfo: { current_version: '0.1.0', latest_version: '0.1.1', version: '0.1.1', release_date: '', release_notes: '', can_auto_update: false, current_env: 'test' }, - useSelector: vi.fn(), - isLoadingCurrentWorkspace: false, - isValidatingCurrentWorkspace: false, - } - - /* eslint-disable-next-line ts/no-explicit-any */ - const mockQuerySuccess = (data: T): MockQueryResult => ({ data, isSuccess: true, isError: false, isLoading: false, isPending: false, status: 'success', error: null, fetchStatus: 'idle' } as any) - /* eslint-disable-next-line ts/no-explicit-any */ - const mockQueryPending = (): MockQueryResult => ({ data: undefined, isSuccess: false, isError: false, isLoading: true, isPending: true, status: 'pending', error: null, fetchStatus: 'fetching' } as any) - - const originalLocation = window.location - - beforeEach(() => { - vi.clearAllMocks() - vi.mocked(useAppContext).mockReturnValue(baseAppContext) - vi.mocked(useDataSourceIntegrates).mockReturnValue(mockQuerySuccess({ data: [] })) - vi.mocked(useNotionConnection).mockReturnValue(mockQueryPending()) - vi.mocked(useInvalidDataSourceIntegrates).mockReturnValue(vi.fn()) - - const locationMock = { href: '', assign: vi.fn() } - Object.defineProperty(window, 'location', { value: locationMock, writable: true, configurable: true }) - - // Clear document body to avoid toast leaks between tests - document.body.innerHTML = '' - }) - - afterEach(() => { - Object.defineProperty(window, 'location', { value: originalLocation, writable: true, configurable: true }) - }) - - const getWorkspaceItem = (name: string) => { - const nameEl = screen.getByText(name) - return (nameEl.closest('div[class*="workspace-item"]') || nameEl.parentElement) as HTMLElement - } - - describe('Rendering', () => { - it('should render with no workspaces initially and call integration hook', () => { - // Act - render() - - // Assert - expect(screen.getByText('common.dataSource.notion.title')).toBeInTheDocument() - expect(screen.queryByText('common.dataSource.notion.connectedWorkspace')).not.toBeInTheDocument() - expect(useDataSourceIntegrates).toHaveBeenCalledWith({ initialData: undefined }) - }) - - it('should render with provided workspaces and pass initialData to hook', () => { - // Arrange - vi.mocked(useDataSourceIntegrates).mockReturnValue(mockQuerySuccess({ data: mockWorkspaces })) - - // Act - render() - - // Assert - expect(screen.getByText('common.dataSource.notion.connectedWorkspace')).toBeInTheDocument() - expect(screen.getByText('Workspace 1')).toBeInTheDocument() - expect(screen.getByText('common.dataSource.notion.connected')).toBeInTheDocument() - expect(screen.getByAltText('workspace icon')).toHaveAttribute('src', 'https://example.com/icon-1.png') - expect(useDataSourceIntegrates).toHaveBeenCalledWith({ initialData: { data: mockWorkspaces } }) - }) - - it('should handle workspaces prop being an empty array', () => { - // Act - render() - - // Assert - expect(screen.queryByText('common.dataSource.notion.connectedWorkspace')).not.toBeInTheDocument() - expect(useDataSourceIntegrates).toHaveBeenCalledWith({ initialData: { data: [] } }) - }) - - it('should handle optional workspaces configurations', () => { - // Branch: workspaces passed as undefined - const { rerender } = render() - expect(useDataSourceIntegrates).toHaveBeenCalledWith({ initialData: undefined }) - - // Branch: workspaces passed as null - /* eslint-disable-next-line ts/no-explicit-any */ - rerender() - expect(useDataSourceIntegrates).toHaveBeenCalledWith({ initialData: undefined }) - - // Branch: workspaces passed as [] - rerender() - expect(useDataSourceIntegrates).toHaveBeenCalledWith({ initialData: { data: [] } }) - }) - - it('should handle cases where integrates data is loading or broken', () => { - // Act (Loading) - const { rerender } = render() - vi.mocked(useDataSourceIntegrates).mockReturnValue(mockQueryPending()) - rerender() - // Assert - expect(screen.queryByText('common.dataSource.notion.connectedWorkspace')).not.toBeInTheDocument() - - // Act (Broken) - const brokenData = {} as { data: TDataSourceNotion[] } - vi.mocked(useDataSourceIntegrates).mockReturnValue(mockQuerySuccess(brokenData)) - rerender() - // Assert - expect(screen.queryByText('common.dataSource.notion.connectedWorkspace')).not.toBeInTheDocument() - }) - - it('should handle integrates being nullish', () => { - /* eslint-disable-next-line ts/no-explicit-any */ - vi.mocked(useDataSourceIntegrates).mockReturnValue({ data: undefined, isSuccess: true } as any) - render() - expect(screen.queryByText('common.dataSource.notion.connectedWorkspace')).not.toBeInTheDocument() - }) - - it('should handle integrates data being nullish', () => { - /* eslint-disable-next-line ts/no-explicit-any */ - vi.mocked(useDataSourceIntegrates).mockReturnValue({ data: { data: null }, isSuccess: true } as any) - render() - expect(screen.queryByText('common.dataSource.notion.connectedWorkspace')).not.toBeInTheDocument() - }) - - it('should handle integrates data being valid', () => { - /* eslint-disable-next-line ts/no-explicit-any */ - vi.mocked(useDataSourceIntegrates).mockReturnValue({ data: { data: [{ id: '1', is_bound: true, source_info: { workspace_name: 'W', workspace_icon: 'https://example.com/i.png', total: 1, pages: [] } }] }, isSuccess: true } as any) - render() - expect(screen.getByText('common.dataSource.notion.connectedWorkspace')).toBeInTheDocument() - }) - - it('should cover all possible falsy/nullish branches for integrates and workspaces', () => { - /* eslint-disable-next-line ts/no-explicit-any */ - const { rerender } = render() - - const integratesCases = [ - undefined, - null, - {}, - { data: null }, - { data: undefined }, - { data: [] }, - { data: [mockWorkspaces[0]] }, - { data: false }, - { data: 0 }, - { data: '' }, - 123, - 'string', - false, - ] - - integratesCases.forEach((val) => { - /* eslint-disable-next-line ts/no-explicit-any */ - vi.mocked(useDataSourceIntegrates).mockReturnValue({ data: val, isSuccess: true } as any) - /* eslint-disable-next-line ts/no-explicit-any */ - rerender() - }) - - expect(useDataSourceIntegrates).toHaveBeenCalled() - }) - }) - - describe('User Permissions', () => { - it('should pass readOnly as false when user is a manager', () => { - // Arrange - vi.mocked(useAppContext).mockReturnValue({ ...baseAppContext, isCurrentWorkspaceManager: true }) - - // Act - render() - - // Assert - expect(screen.getByText('common.dataSource.notion.title').closest('div')).not.toHaveClass('grayscale') - }) - - it('should pass readOnly as true when user is NOT a manager', () => { - // Arrange - vi.mocked(useAppContext).mockReturnValue({ ...baseAppContext, isCurrentWorkspaceManager: false }) - - // Act - render() - - // Assert - expect(screen.getByText('common.dataSource.connect')).toHaveClass('opacity-50', 'grayscale') - }) - }) - - describe('Configure and Auth Actions', () => { - it('should handle configure action when user is workspace manager', () => { - // Arrange - render() - - // Act - fireEvent.click(screen.getByText('common.dataSource.connect')) - - // Assert - expect(useNotionConnection).toHaveBeenCalledWith(true) - }) - - it('should block configure action when user is NOT workspace manager', () => { - // Arrange - vi.mocked(useAppContext).mockReturnValue({ ...baseAppContext, isCurrentWorkspaceManager: false }) - render() - - // Act - fireEvent.click(screen.getByText('common.dataSource.connect')) - - // Assert - expect(useNotionConnection).toHaveBeenCalledWith(false) - }) - - it('should redirect if auth URL is available when "Auth Again" is clicked', async () => { - // Arrange - vi.mocked(useDataSourceIntegrates).mockReturnValue(mockQuerySuccess({ data: mockWorkspaces })) - vi.mocked(useNotionConnection).mockReturnValue(mockQuerySuccess({ data: 'http://auth-url' })) - render() - - // Act - const workspaceItem = getWorkspaceItem('Workspace 1') - const actionBtn = within(workspaceItem).getByRole('button') - fireEvent.click(actionBtn) - const authAgainBtn = await screen.findByText('common.dataSource.notion.changeAuthorizedPages') - fireEvent.click(authAgainBtn) - - // Assert - expect(window.location.href).toBe('http://auth-url') - }) - - it('should trigger connection flow if URL is missing when "Auth Again" is clicked', async () => { - // Arrange - vi.mocked(useDataSourceIntegrates).mockReturnValue(mockQuerySuccess({ data: mockWorkspaces })) - render() - - // Act - const workspaceItem = getWorkspaceItem('Workspace 1') - const actionBtn = within(workspaceItem).getByRole('button') - fireEvent.click(actionBtn) - const authAgainBtn = await screen.findByText('common.dataSource.notion.changeAuthorizedPages') - fireEvent.click(authAgainBtn) - - // Assert - expect(useNotionConnection).toHaveBeenCalledWith(true) - }) - }) - - describe('Side Effects (Redirection and Toast)', () => { - it('should redirect automatically when connection data returns an http URL', async () => { - // Arrange - vi.mocked(useNotionConnection).mockReturnValue(mockQuerySuccess({ data: 'http://redirect-url' })) - - // Act - render() - - // Assert - await waitFor(() => { - expect(window.location.href).toBe('http://redirect-url') - }) - }) - - it('should show toast notification when connection data is "internal"', async () => { - // Arrange - vi.mocked(useNotionConnection).mockReturnValue(mockQuerySuccess({ data: 'internal' })) - - // Act - render() - - // Assert - expect(await screen.findByText('common.dataSource.notion.integratedAlert')).toBeInTheDocument() - }) - - it('should handle various data types and missing properties in connection data correctly', async () => { - // Arrange & Act (Unknown string) - const { rerender } = render() - vi.mocked(useNotionConnection).mockReturnValue(mockQuerySuccess({ data: 'unknown' })) - rerender() - // Assert - await waitFor(() => { - expect(window.location.href).toBe('') - expect(screen.queryByText('common.dataSource.notion.integratedAlert')).not.toBeInTheDocument() - }) - - // Act (Broken object) - /* eslint-disable-next-line ts/no-explicit-any */ - vi.mocked(useNotionConnection).mockReturnValue(mockQuerySuccess({} as any)) - rerender() - // Assert - await waitFor(() => { - expect(window.location.href).toBe('') - }) - - // Act (Non-string) - /* eslint-disable-next-line ts/no-explicit-any */ - vi.mocked(useNotionConnection).mockReturnValue(mockQuerySuccess({ data: 123 } as any)) - rerender() - // Assert - await waitFor(() => { - expect(window.location.href).toBe('') - }) - }) - - it('should redirect if data starts with "http" even if it is just "http"', async () => { - // Arrange - vi.mocked(useNotionConnection).mockReturnValue(mockQuerySuccess({ data: 'http' })) - - // Act - render() - - // Assert - await waitFor(() => { - expect(window.location.href).toBe('http') - }) - }) - - it('should skip side effect logic if connection data is an object but missing the "data" property', async () => { - // Arrange - /* eslint-disable-next-line ts/no-explicit-any */ - vi.mocked(useNotionConnection).mockReturnValue({} as any) - - // Act - render() - - // Assert - await waitFor(() => { - expect(window.location.href).toBe('') - }) - }) - - it('should skip side effect logic if data.data is falsy', async () => { - // Arrange - /* eslint-disable-next-line ts/no-explicit-any */ - vi.mocked(useNotionConnection).mockReturnValue({ data: { data: null } } as any) - - // Act - render() - - // Assert - await waitFor(() => { - expect(window.location.href).toBe('') - }) - }) - }) - - describe('Additional Action Edge Cases', () => { - it.each([ - undefined, - null, - {}, - { data: undefined }, - { data: null }, - { data: '' }, - { data: 0 }, - { data: false }, - { data: 'http' }, - { data: 'internal' }, - { data: 'unknown' }, - ])('should cover connection data branch: %s', async (val) => { - vi.mocked(useDataSourceIntegrates).mockReturnValue(mockQuerySuccess({ data: mockWorkspaces })) - /* eslint-disable-next-line ts/no-explicit-any */ - vi.mocked(useNotionConnection).mockReturnValue({ data: val, isSuccess: true } as any) - - render() - - // Trigger handleAuthAgain with these values - const workspaceItem = getWorkspaceItem('Workspace 1') - const actionBtn = within(workspaceItem).getByRole('button') - fireEvent.click(actionBtn) - const authAgainBtn = await screen.findByText('common.dataSource.notion.changeAuthorizedPages') - fireEvent.click(authAgainBtn) - - expect(useNotionConnection).toHaveBeenCalled() - }) - }) - - describe('Edge Cases in Workspace Data', () => { - it('should render correctly with missing source_info optional fields', async () => { - // Arrange - const workspaceWithMissingInfo: TDataSourceNotion = { - id: 'ws-2', - provider: 'notion', - is_bound: false, - source_info: { workspace_name: 'Workspace 2', workspace_id: 'notion-ws-2', workspace_icon: null, pages: [] }, - } - vi.mocked(useDataSourceIntegrates).mockReturnValue(mockQuerySuccess({ data: [workspaceWithMissingInfo] })) - - // Act - render() - - // Assert - expect(screen.getByText('Workspace 2')).toBeInTheDocument() - - const workspaceItem = getWorkspaceItem('Workspace 2') - const actionBtn = within(workspaceItem).getByRole('button') - fireEvent.click(actionBtn) - - expect(await screen.findByText('0 common.dataSource.notion.pagesAuthorized')).toBeInTheDocument() - }) - - it('should display inactive status correctly for unbound workspaces', () => { - // Arrange - const inactiveWS: TDataSourceNotion = { - id: 'ws-3', - provider: 'notion', - is_bound: false, - source_info: { workspace_name: 'Workspace 3', workspace_icon: 'https://example.com/icon-3.png', workspace_id: 'notion-ws-3', total: 5, pages: [] }, - } - vi.mocked(useDataSourceIntegrates).mockReturnValue(mockQuerySuccess({ data: [inactiveWS] })) - - // Act - render() - - // Assert - expect(screen.getByText('common.dataSource.notion.disconnected')).toBeInTheDocument() - }) - }) -}) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx deleted file mode 100644 index 0959383f29..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx +++ /dev/null @@ -1,103 +0,0 @@ -'use client' -import type { FC } from 'react' -import type { DataSourceNotion as TDataSourceNotion } from '@/models/common' -import { noop } from 'es-toolkit/function' -import * as React from 'react' -import { useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import NotionIcon from '@/app/components/base/notion-icon' -import Toast from '@/app/components/base/toast' -import { useAppContext } from '@/context/app-context' -import { useDataSourceIntegrates, useNotionConnection } from '@/service/use-common' -import Panel from '../panel' -import { DataSourceType } from '../panel/types' - -const Icon: FC<{ - src: string - name: string - className: string -}> = ({ src, name, className }) => { - return ( - - ) -} -type Props = { - workspaces?: TDataSourceNotion[] -} - -const DataSourceNotion: FC = ({ - workspaces, -}) => { - const { isCurrentWorkspaceManager } = useAppContext() - const [canConnectNotion, setCanConnectNotion] = useState(false) - const { data: integrates } = useDataSourceIntegrates({ - initialData: workspaces ? { data: workspaces } : undefined, - }) - const { data } = useNotionConnection(canConnectNotion) - const { t } = useTranslation() - - const resolvedWorkspaces = integrates?.data ?? [] - const connected = !!resolvedWorkspaces.length - - const handleConnectNotion = () => { - if (!isCurrentWorkspaceManager) - return - - setCanConnectNotion(true) - } - - const handleAuthAgain = () => { - if (data?.data) - window.location.href = data.data - else - setCanConnectNotion(true) - } - - useEffect(() => { - if (data && 'data' in data) { - if (data.data && typeof data.data === 'string' && data.data.startsWith('http')) { - window.location.href = data.data - } - else if (data.data === 'internal') { - Toast.notify({ - type: 'info', - message: t('dataSource.notion.integratedAlert', { ns: 'common' }), - }) - } - } - }, [data, t]) - - return ( - ({ - id: workspace.id, - logo: ({ className }: { className: string }) => ( - - ), - name: workspace.source_info.workspace_name, - isActive: workspace.is_bound, - notionConfig: { - total: workspace.source_info.total || 0, - }, - }))} - onRemove={noop} // handled in operation/index.tsx - notionActions={{ - onChangeAuthorizedPage: handleAuthAgain, - }} - /> - ) -} -export default React.memo(DataSourceNotion) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-notion/operate/__tests__/index.spec.tsx b/web/app/components/header/account-setting/data-source-page/data-source-notion/operate/__tests__/index.spec.tsx deleted file mode 100644 index f433b10020..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-notion/operate/__tests__/index.spec.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' -import { syncDataSourceNotion, updateDataSourceNotionAction } from '@/service/common' -import { useInvalidDataSourceIntegrates } from '@/service/use-common' -import Operate from '../index' - -/** - * Operate Component (Notion) Tests - * This component provides actions like Sync, Change Pages, and Remove for Notion data sources. - */ - -// Mock services and toast -vi.mock('@/service/common', () => ({ - syncDataSourceNotion: vi.fn(), - updateDataSourceNotionAction: vi.fn(), -})) - -vi.mock('@/service/use-common', () => ({ - useInvalidDataSourceIntegrates: vi.fn(), -})) - -describe('Operate Component (Notion)', () => { - const mockPayload = { - id: 'test-notion-id', - total: 5, - } - const mockOnAuthAgain = vi.fn() - const mockInvalidate = vi.fn() - - beforeEach(() => { - vi.clearAllMocks() - vi.mocked(useInvalidDataSourceIntegrates).mockReturnValue(mockInvalidate) - vi.mocked(syncDataSourceNotion).mockResolvedValue({ result: 'success' }) - vi.mocked(updateDataSourceNotionAction).mockResolvedValue({ result: 'success' }) - }) - - describe('Rendering', () => { - it('should render the menu button initially', () => { - // Act - const { container } = render() - - // Assert - const menuButton = within(container).getByRole('button') - expect(menuButton).toBeInTheDocument() - expect(menuButton).not.toHaveClass('bg-state-base-hover') - }) - - it('should open the menu and show all options when clicked', async () => { - // Arrange - const { container } = render() - const menuButton = within(container).getByRole('button') - - // Act - fireEvent.click(menuButton) - - // Assert - expect(await screen.findByText('common.dataSource.notion.changeAuthorizedPages')).toBeInTheDocument() - expect(screen.getByText('common.dataSource.notion.sync')).toBeInTheDocument() - expect(screen.getByText('common.dataSource.notion.remove')).toBeInTheDocument() - expect(screen.getByText(/5/)).toBeInTheDocument() - expect(screen.getByText(/common.dataSource.notion.pagesAuthorized/)).toBeInTheDocument() - expect(menuButton).toHaveClass('bg-state-base-hover') - }) - }) - - describe('Menu Actions', () => { - it('should call onAuthAgain when Change Authorized Pages is clicked', async () => { - // Arrange - const { container } = render() - fireEvent.click(within(container).getByRole('button')) - const option = await screen.findByText('common.dataSource.notion.changeAuthorizedPages') - - // Act - fireEvent.click(option) - - // Assert - expect(mockOnAuthAgain).toHaveBeenCalledTimes(1) - }) - - it('should call handleSync, show success toast, and invalidate cache when Sync is clicked', async () => { - // Arrange - const { container } = render() - fireEvent.click(within(container).getByRole('button')) - const syncBtn = await screen.findByText('common.dataSource.notion.sync') - - // Act - fireEvent.click(syncBtn) - - // Assert - await waitFor(() => { - expect(syncDataSourceNotion).toHaveBeenCalledWith({ - url: `/oauth/data-source/notion/${mockPayload.id}/sync`, - }) - }) - expect((await screen.findAllByText('common.api.success')).length).toBeGreaterThan(0) - expect(mockInvalidate).toHaveBeenCalledTimes(1) - }) - - it('should call handleRemove, show success toast, and invalidate cache when Remove is clicked', async () => { - // Arrange - const { container } = render() - fireEvent.click(within(container).getByRole('button')) - const removeBtn = await screen.findByText('common.dataSource.notion.remove') - - // Act - fireEvent.click(removeBtn) - - // Assert - await waitFor(() => { - expect(updateDataSourceNotionAction).toHaveBeenCalledWith({ - url: `/data-source/integrates/${mockPayload.id}/disable`, - }) - }) - expect((await screen.findAllByText('common.api.success')).length).toBeGreaterThan(0) - expect(mockInvalidate).toHaveBeenCalledTimes(1) - }) - }) - - describe('State Transitions', () => { - it('should toggle the open class on the button based on menu visibility', async () => { - // Arrange - const { container } = render() - const menuButton = within(container).getByRole('button') - - // Act (Open) - fireEvent.click(menuButton) - // Assert - expect(menuButton).toHaveClass('bg-state-base-hover') - - // Act (Close - click again) - fireEvent.click(menuButton) - // Assert - await waitFor(() => { - expect(menuButton).not.toHaveClass('bg-state-base-hover') - }) - }) - }) -}) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-notion/operate/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-notion/operate/index.tsx deleted file mode 100644 index 043eb3c846..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-notion/operate/index.tsx +++ /dev/null @@ -1,103 +0,0 @@ -'use client' -import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' -import { - RiDeleteBinLine, - RiLoopLeftLine, - RiMoreFill, - RiStickyNoteAddLine, -} from '@remixicon/react' -import { Fragment } from 'react' -import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' -import { syncDataSourceNotion, updateDataSourceNotionAction } from '@/service/common' -import { useInvalidDataSourceIntegrates } from '@/service/use-common' -import { cn } from '@/utils/classnames' - -type OperateProps = { - payload: { - id: string - total: number - } - onAuthAgain: () => void -} -export default function Operate({ - payload, - onAuthAgain, -}: OperateProps) { - const { t } = useTranslation() - const invalidateDataSourceIntegrates = useInvalidDataSourceIntegrates() - - const updateIntegrates = () => { - Toast.notify({ - type: 'success', - message: t('api.success', { ns: 'common' }), - }) - invalidateDataSourceIntegrates() - } - const handleSync = async () => { - await syncDataSourceNotion({ url: `/oauth/data-source/notion/${payload.id}/sync` }) - updateIntegrates() - } - const handleRemove = async () => { - await updateDataSourceNotionAction({ url: `/data-source/integrates/${payload.id}/disable` }) - updateIntegrates() - } - - return ( - - { - ({ open }) => ( - <> - - - - - -
- -
- -
-
{t('dataSource.notion.changeAuthorizedPages', { ns: 'common' })}
-
- {payload.total} - {' '} - {t('dataSource.notion.pagesAuthorized', { ns: 'common' })} -
-
-
-
- -
- -
{t('dataSource.notion.sync', { ns: 'common' })}
-
-
-
- -
-
- -
{t('dataSource.notion.remove', { ns: 'common' })}
-
-
-
-
-
- - ) - } -
- ) -} diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-firecrawl-modal.spec.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-firecrawl-modal.spec.tsx deleted file mode 100644 index dadda4a349..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-firecrawl-modal.spec.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import type { CommonResponse } from '@/models/common' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import userEvent from '@testing-library/user-event' - -import { createDataSourceApiKeyBinding } from '@/service/datasets' -import ConfigFirecrawlModal from '../config-firecrawl-modal' - -/** - * ConfigFirecrawlModal Component Tests - * Tests validation, save logic, and basic rendering for the Firecrawl configuration modal. - */ - -vi.mock('@/service/datasets', () => ({ - createDataSourceApiKeyBinding: vi.fn(), -})) - -describe('ConfigFirecrawlModal Component', () => { - const mockOnCancel = vi.fn() - const mockOnSaved = vi.fn() - - beforeEach(() => { - vi.clearAllMocks() - }) - - describe('Initial Rendering', () => { - it('should render the modal with all fields and buttons', () => { - // Act - render() - - // Assert - expect(screen.getByText('datasetCreation.firecrawl.configFirecrawl')).toBeInTheDocument() - expect(screen.getByPlaceholderText('datasetCreation.firecrawl.apiKeyPlaceholder')).toBeInTheDocument() - expect(screen.getByPlaceholderText('https://api.firecrawl.dev')).toBeInTheDocument() - expect(screen.getByRole('button', { name: /common\.operation\.save/i })).toBeInTheDocument() - expect(screen.getByRole('button', { name: /common\.operation\.cancel/i })).toBeInTheDocument() - expect(screen.getByRole('link', { name: /datasetCreation\.firecrawl\.getApiKeyLinkText/i })).toHaveAttribute('href', 'https://www.firecrawl.dev/account') - }) - }) - - describe('Form Interactions', () => { - it('should update state when input fields change', async () => { - // Arrange - render() - const apiKeyInput = screen.getByPlaceholderText('datasetCreation.firecrawl.apiKeyPlaceholder') - const baseUrlInput = screen.getByPlaceholderText('https://api.firecrawl.dev') - - // Act - fireEvent.change(apiKeyInput, { target: { value: 'firecrawl-key' } }) - fireEvent.change(baseUrlInput, { target: { value: 'https://custom.firecrawl.dev' } }) - - // Assert - expect(apiKeyInput).toHaveValue('firecrawl-key') - expect(baseUrlInput).toHaveValue('https://custom.firecrawl.dev') - }) - - it('should call onCancel when cancel button is clicked', async () => { - const user = userEvent.setup() - // Arrange - render() - - // Act - await user.click(screen.getByRole('button', { name: /common\.operation\.cancel/i })) - - // Assert - expect(mockOnCancel).toHaveBeenCalled() - }) - }) - - describe('Validation', () => { - it('should show error when saving without API Key', async () => { - const user = userEvent.setup() - // Arrange - render() - - // Act - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(screen.getByText('common.errorMsg.fieldRequired:{"field":"API Key"}')).toBeInTheDocument() - }) - expect(createDataSourceApiKeyBinding).not.toHaveBeenCalled() - }) - - it('should show error for invalid Base URL format', async () => { - const user = userEvent.setup() - // Arrange - render() - const baseUrlInput = screen.getByPlaceholderText('https://api.firecrawl.dev') - - // Act - await user.type(baseUrlInput, 'ftp://invalid-url.com') - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(screen.getByText('common.errorMsg.urlError')).toBeInTheDocument() - }) - expect(createDataSourceApiKeyBinding).not.toHaveBeenCalled() - }) - }) - - describe('Saving Logic', () => { - it('should save successfully with valid API Key and custom URL', async () => { - const user = userEvent.setup() - // Arrange - vi.mocked(createDataSourceApiKeyBinding).mockResolvedValue({ result: 'success' }) - render() - - // Act - await user.type(screen.getByPlaceholderText('datasetCreation.firecrawl.apiKeyPlaceholder'), 'valid-key') - await user.type(screen.getByPlaceholderText('https://api.firecrawl.dev'), 'http://my-firecrawl.com') - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(createDataSourceApiKeyBinding).toHaveBeenCalledWith({ - category: 'website', - provider: 'firecrawl', - credentials: { - auth_type: 'bearer', - config: { - api_key: 'valid-key', - base_url: 'http://my-firecrawl.com', - }, - }, - }) - }) - await waitFor(() => { - expect(screen.getByText('common.api.success')).toBeInTheDocument() - expect(mockOnSaved).toHaveBeenCalled() - }) - }) - - it('should use default Base URL if none is provided during save', async () => { - const user = userEvent.setup() - // Arrange - vi.mocked(createDataSourceApiKeyBinding).mockResolvedValue({ result: 'success' }) - render() - - // Act - await user.type(screen.getByPlaceholderText('datasetCreation.firecrawl.apiKeyPlaceholder'), 'test-key') - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(createDataSourceApiKeyBinding).toHaveBeenCalledWith(expect.objectContaining({ - credentials: expect.objectContaining({ - config: expect.objectContaining({ - base_url: 'https://api.firecrawl.dev', - }), - }), - })) - }) - }) - - it('should ignore multiple save clicks while saving is in progress', async () => { - const user = userEvent.setup() - // Arrange - let resolveSave: (value: CommonResponse) => void - const savePromise = new Promise((resolve) => { - resolveSave = resolve - }) - vi.mocked(createDataSourceApiKeyBinding).mockReturnValue(savePromise) - render() - await user.type(screen.getByPlaceholderText('datasetCreation.firecrawl.apiKeyPlaceholder'), 'test-key') - const saveBtn = screen.getByRole('button', { name: /common\.operation\.save/i }) - - // Act - await user.click(saveBtn) - await user.click(saveBtn) - - // Assert - expect(createDataSourceApiKeyBinding).toHaveBeenCalledTimes(1) - - // Cleanup - resolveSave!({ result: 'success' }) - await waitFor(() => expect(mockOnSaved).toHaveBeenCalledTimes(1)) - }) - - it('should accept base_url starting with https://', async () => { - const user = userEvent.setup() - // Arrange - vi.mocked(createDataSourceApiKeyBinding).mockResolvedValue({ result: 'success' }) - render() - - // Act - await user.type(screen.getByPlaceholderText('datasetCreation.firecrawl.apiKeyPlaceholder'), 'test-key') - await user.type(screen.getByPlaceholderText('https://api.firecrawl.dev'), 'https://secure-firecrawl.com') - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(createDataSourceApiKeyBinding).toHaveBeenCalledWith(expect.objectContaining({ - credentials: expect.objectContaining({ - config: expect.objectContaining({ - base_url: 'https://secure-firecrawl.com', - }), - }), - })) - }) - }) - }) -}) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-jina-reader-modal.spec.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-jina-reader-modal.spec.tsx deleted file mode 100644 index 26c53993c1..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-jina-reader-modal.spec.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react' -import userEvent from '@testing-library/user-event' - -import { DataSourceProvider } from '@/models/common' -import { createDataSourceApiKeyBinding } from '@/service/datasets' -import ConfigJinaReaderModal from '../config-jina-reader-modal' - -/** - * ConfigJinaReaderModal Component Tests - * Tests validation, save logic, and basic rendering for the Jina Reader configuration modal. - */ - -vi.mock('@/service/datasets', () => ({ - createDataSourceApiKeyBinding: vi.fn(), -})) - -describe('ConfigJinaReaderModal Component', () => { - const mockOnCancel = vi.fn() - const mockOnSaved = vi.fn() - - beforeEach(() => { - vi.clearAllMocks() - }) - - describe('Initial Rendering', () => { - it('should render the modal with API Key field and buttons', () => { - // Act - render() - - // Assert - expect(screen.getByText('datasetCreation.jinaReader.configJinaReader')).toBeInTheDocument() - expect(screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder')).toBeInTheDocument() - expect(screen.getByRole('button', { name: /common\.operation\.save/i })).toBeInTheDocument() - expect(screen.getByRole('button', { name: /common\.operation\.cancel/i })).toBeInTheDocument() - expect(screen.getByRole('link', { name: /datasetCreation\.jinaReader\.getApiKeyLinkText/i })).toHaveAttribute('href', 'https://jina.ai/reader/') - }) - }) - - describe('Form Interactions', () => { - it('should update state when API Key field changes', async () => { - const user = userEvent.setup() - // Arrange - render() - const apiKeyInput = screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder') - - // Act - await user.type(apiKeyInput, 'jina-test-key') - - // Assert - expect(apiKeyInput).toHaveValue('jina-test-key') - }) - - it('should call onCancel when cancel button is clicked', async () => { - const user = userEvent.setup() - // Arrange - render() - - // Act - await user.click(screen.getByRole('button', { name: /common\.operation\.cancel/i })) - - // Assert - expect(mockOnCancel).toHaveBeenCalled() - }) - }) - - describe('Validation', () => { - it('should show error when saving without API Key', async () => { - const user = userEvent.setup() - // Arrange - render() - - // Act - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(screen.getByText('common.errorMsg.fieldRequired:{"field":"API Key"}')).toBeInTheDocument() - }) - expect(createDataSourceApiKeyBinding).not.toHaveBeenCalled() - }) - }) - - describe('Saving Logic', () => { - it('should save successfully with valid API Key', async () => { - const user = userEvent.setup() - // Arrange - vi.mocked(createDataSourceApiKeyBinding).mockResolvedValue({ result: 'success' }) - render() - const apiKeyInput = screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder') - - // Act - await user.type(apiKeyInput, 'valid-jina-key') - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(createDataSourceApiKeyBinding).toHaveBeenCalledWith({ - category: 'website', - provider: DataSourceProvider.jinaReader, - credentials: { - auth_type: 'bearer', - config: { - api_key: 'valid-jina-key', - }, - }, - }) - }) - await waitFor(() => { - expect(screen.getByText('common.api.success')).toBeInTheDocument() - expect(mockOnSaved).toHaveBeenCalled() - }) - }) - - it('should ignore multiple save clicks while saving is in progress', async () => { - const user = userEvent.setup() - // Arrange - let resolveSave: (value: { result: 'success' }) => void - const savePromise = new Promise<{ result: 'success' }>((resolve) => { - resolveSave = resolve - }) - vi.mocked(createDataSourceApiKeyBinding).mockReturnValue(savePromise) - render() - await user.type(screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder'), 'test-key') - const saveBtn = screen.getByRole('button', { name: /common\.operation\.save/i }) - - // Act - await user.click(saveBtn) - await user.click(saveBtn) - - // Assert - expect(createDataSourceApiKeyBinding).toHaveBeenCalledTimes(1) - - // Cleanup - resolveSave!({ result: 'success' }) - await waitFor(() => expect(mockOnSaved).toHaveBeenCalledTimes(1)) - }) - - it('should show encryption info and external link in the modal', async () => { - render() - - // Verify PKCS1_OAEP link exists - const pkcsLink = screen.getByText('PKCS1_OAEP') - expect(pkcsLink.closest('a')).toHaveAttribute('href', 'https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html') - - // Verify the Jina Reader external link - const jinaLink = screen.getByRole('link', { name: /datasetCreation\.jinaReader\.getApiKeyLinkText/i }) - expect(jinaLink).toHaveAttribute('target', '_blank') - }) - - it('should return early when save is clicked while already saving (isSaving guard)', async () => { - const user = userEvent.setup() - // Arrange - a save that never resolves so isSaving stays true - let resolveFirst: (value: { result: 'success' }) => void - const neverResolves = new Promise<{ result: 'success' }>((resolve) => { - resolveFirst = resolve - }) - vi.mocked(createDataSourceApiKeyBinding).mockReturnValue(neverResolves) - render() - - const apiKeyInput = screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder') - await user.type(apiKeyInput, 'valid-key') - - const saveBtn = screen.getByRole('button', { name: /common\.operation\.save/i }) - // First click - starts saving, isSaving becomes true - await user.click(saveBtn) - expect(createDataSourceApiKeyBinding).toHaveBeenCalledTimes(1) - - // Second click using fireEvent bypasses disabled check - hits isSaving guard - const { fireEvent: fe } = await import('@testing-library/react') - fe.click(saveBtn) - // Still only called once because isSaving=true returns early - expect(createDataSourceApiKeyBinding).toHaveBeenCalledTimes(1) - - // Cleanup - resolveFirst!({ result: 'success' }) - await waitFor(() => expect(mockOnSaved).toHaveBeenCalled()) - }) - }) -}) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-watercrawl-modal.spec.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-watercrawl-modal.spec.tsx deleted file mode 100644 index 6c5961be54..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/config-watercrawl-modal.spec.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import type { CommonResponse } from '@/models/common' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import userEvent from '@testing-library/user-event' - -import { createDataSourceApiKeyBinding } from '@/service/datasets' -import ConfigWatercrawlModal from '../config-watercrawl-modal' - -/** - * ConfigWatercrawlModal Component Tests - * Tests validation, save logic, and basic rendering for the Watercrawl configuration modal. - */ - -vi.mock('@/service/datasets', () => ({ - createDataSourceApiKeyBinding: vi.fn(), -})) - -describe('ConfigWatercrawlModal Component', () => { - const mockOnCancel = vi.fn() - const mockOnSaved = vi.fn() - - beforeEach(() => { - vi.clearAllMocks() - }) - - describe('Initial Rendering', () => { - it('should render the modal with all fields and buttons', () => { - // Act - render() - - // Assert - expect(screen.getByText('datasetCreation.watercrawl.configWatercrawl')).toBeInTheDocument() - expect(screen.getByPlaceholderText('datasetCreation.watercrawl.apiKeyPlaceholder')).toBeInTheDocument() - expect(screen.getByPlaceholderText('https://app.watercrawl.dev')).toBeInTheDocument() - expect(screen.getByRole('button', { name: /common\.operation\.save/i })).toBeInTheDocument() - expect(screen.getByRole('button', { name: /common\.operation\.cancel/i })).toBeInTheDocument() - expect(screen.getByRole('link', { name: /datasetCreation\.watercrawl\.getApiKeyLinkText/i })).toHaveAttribute('href', 'https://app.watercrawl.dev/') - }) - }) - - describe('Form Interactions', () => { - it('should update state when input fields change', async () => { - // Arrange - render() - const apiKeyInput = screen.getByPlaceholderText('datasetCreation.watercrawl.apiKeyPlaceholder') - const baseUrlInput = screen.getByPlaceholderText('https://app.watercrawl.dev') - - // Act - fireEvent.change(apiKeyInput, { target: { value: 'water-key' } }) - fireEvent.change(baseUrlInput, { target: { value: 'https://custom.watercrawl.dev' } }) - - // Assert - expect(apiKeyInput).toHaveValue('water-key') - expect(baseUrlInput).toHaveValue('https://custom.watercrawl.dev') - }) - - it('should call onCancel when cancel button is clicked', async () => { - const user = userEvent.setup() - // Arrange - render() - - // Act - await user.click(screen.getByRole('button', { name: /common\.operation\.cancel/i })) - - // Assert - expect(mockOnCancel).toHaveBeenCalled() - }) - }) - - describe('Validation', () => { - it('should show error when saving without API Key', async () => { - const user = userEvent.setup() - // Arrange - render() - - // Act - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(screen.getByText('common.errorMsg.fieldRequired:{"field":"API Key"}')).toBeInTheDocument() - }) - expect(createDataSourceApiKeyBinding).not.toHaveBeenCalled() - }) - - it('should show error for invalid Base URL format', async () => { - const user = userEvent.setup() - // Arrange - render() - const baseUrlInput = screen.getByPlaceholderText('https://app.watercrawl.dev') - - // Act - await user.type(baseUrlInput, 'ftp://invalid-url.com') - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(screen.getByText('common.errorMsg.urlError')).toBeInTheDocument() - }) - expect(createDataSourceApiKeyBinding).not.toHaveBeenCalled() - }) - }) - - describe('Saving Logic', () => { - it('should save successfully with valid API Key and custom URL', async () => { - const user = userEvent.setup() - // Arrange - vi.mocked(createDataSourceApiKeyBinding).mockResolvedValue({ result: 'success' }) - render() - - // Act - await user.type(screen.getByPlaceholderText('datasetCreation.watercrawl.apiKeyPlaceholder'), 'valid-key') - await user.type(screen.getByPlaceholderText('https://app.watercrawl.dev'), 'http://my-watercrawl.com') - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(createDataSourceApiKeyBinding).toHaveBeenCalledWith({ - category: 'website', - provider: 'watercrawl', - credentials: { - auth_type: 'x-api-key', - config: { - api_key: 'valid-key', - base_url: 'http://my-watercrawl.com', - }, - }, - }) - }) - await waitFor(() => { - expect(screen.getByText('common.api.success')).toBeInTheDocument() - expect(mockOnSaved).toHaveBeenCalled() - }) - }) - - it('should use default Base URL if none is provided during save', async () => { - const user = userEvent.setup() - // Arrange - vi.mocked(createDataSourceApiKeyBinding).mockResolvedValue({ result: 'success' }) - render() - - // Act - await user.type(screen.getByPlaceholderText('datasetCreation.watercrawl.apiKeyPlaceholder'), 'test-api-key') - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(createDataSourceApiKeyBinding).toHaveBeenCalledWith(expect.objectContaining({ - credentials: expect.objectContaining({ - config: expect.objectContaining({ - base_url: 'https://app.watercrawl.dev', - }), - }), - })) - }) - }) - - it('should ignore multiple save clicks while saving is in progress', async () => { - const user = userEvent.setup() - // Arrange - let resolveSave: (value: CommonResponse) => void - const savePromise = new Promise((resolve) => { - resolveSave = resolve - }) - vi.mocked(createDataSourceApiKeyBinding).mockReturnValue(savePromise) - render() - await user.type(screen.getByPlaceholderText('datasetCreation.watercrawl.apiKeyPlaceholder'), 'test-api-key') - const saveBtn = screen.getByRole('button', { name: /common\.operation\.save/i }) - - // Act - await user.click(saveBtn) - await user.click(saveBtn) - - // Assert - expect(createDataSourceApiKeyBinding).toHaveBeenCalledTimes(1) - - // Cleanup - resolveSave!({ result: 'success' }) - await waitFor(() => expect(mockOnSaved).toHaveBeenCalledTimes(1)) - }) - - it('should accept base_url starting with https://', async () => { - const user = userEvent.setup() - // Arrange - vi.mocked(createDataSourceApiKeyBinding).mockResolvedValue({ result: 'success' }) - render() - - // Act - await user.type(screen.getByPlaceholderText('datasetCreation.watercrawl.apiKeyPlaceholder'), 'test-api-key') - await user.type(screen.getByPlaceholderText('https://app.watercrawl.dev'), 'https://secure-watercrawl.com') - await user.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(createDataSourceApiKeyBinding).toHaveBeenCalledWith(expect.objectContaining({ - credentials: expect.objectContaining({ - config: expect.objectContaining({ - base_url: 'https://secure-watercrawl.com', - }), - }), - })) - }) - }) - }) -}) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/index.spec.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/index.spec.tsx deleted file mode 100644 index 1e95cbd087..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/__tests__/index.spec.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import type { AppContextValue } from '@/context/app-context' -import type { CommonResponse } from '@/models/common' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' - -import { useAppContext } from '@/context/app-context' -import { DataSourceProvider } from '@/models/common' -import { fetchDataSources, removeDataSourceApiKeyBinding } from '@/service/datasets' -import DataSourceWebsite from '../index' - -/** - * DataSourceWebsite Component Tests - * Tests integration of multiple website scraping providers (Firecrawl, WaterCrawl, Jina Reader). - */ - -type DataSourcesResponse = CommonResponse & { - sources: Array<{ id: string, provider: DataSourceProvider }> -} - -// Mock App Context -vi.mock('@/context/app-context', () => ({ - useAppContext: vi.fn(), -})) - -// Mock Service calls -vi.mock('@/service/datasets', () => ({ - fetchDataSources: vi.fn(), - removeDataSourceApiKeyBinding: vi.fn(), - createDataSourceApiKeyBinding: vi.fn(), -})) - -describe('DataSourceWebsite Component', () => { - const mockSources = [ - { id: '1', provider: DataSourceProvider.fireCrawl }, - { id: '2', provider: DataSourceProvider.waterCrawl }, - { id: '3', provider: DataSourceProvider.jinaReader }, - ] - - beforeEach(() => { - vi.clearAllMocks() - vi.mocked(useAppContext).mockReturnValue({ isCurrentWorkspaceManager: true } as unknown as AppContextValue) - vi.mocked(fetchDataSources).mockResolvedValue({ result: 'success', sources: [] } as DataSourcesResponse) - }) - - // Helper to render and wait for initial fetch to complete - const renderAndWait = async (provider: DataSourceProvider) => { - const result = render() - await waitFor(() => expect(fetchDataSources).toHaveBeenCalled()) - return result - } - - describe('Data Initialization', () => { - it('should fetch data sources on mount and reflect configured status', async () => { - // Arrange - vi.mocked(fetchDataSources).mockResolvedValue({ result: 'success', sources: mockSources } as DataSourcesResponse) - - // Act - await renderAndWait(DataSourceProvider.fireCrawl) - - // Assert - expect(screen.getByText('common.dataSource.website.configuredCrawlers')).toBeInTheDocument() - }) - - it('should pass readOnly status based on workspace manager permissions', async () => { - // Arrange - vi.mocked(useAppContext).mockReturnValue({ isCurrentWorkspaceManager: false } as unknown as AppContextValue) - - // Act - await renderAndWait(DataSourceProvider.fireCrawl) - - // Assert - expect(screen.getByText('common.dataSource.configure')).toHaveClass('cursor-default') - }) - }) - - describe('Provider Specific Rendering', () => { - it('should render correct logo and name for Firecrawl', async () => { - // Arrange - vi.mocked(fetchDataSources).mockResolvedValue({ result: 'success', sources: [mockSources[0]] } as DataSourcesResponse) - - // Act - await renderAndWait(DataSourceProvider.fireCrawl) - - // Assert - expect(await screen.findByText('Firecrawl')).toBeInTheDocument() - expect(screen.getByText('🔥')).toBeInTheDocument() - }) - - it('should render correct logo and name for WaterCrawl', async () => { - // Arrange - vi.mocked(fetchDataSources).mockResolvedValue({ result: 'success', sources: [mockSources[1]] } as DataSourcesResponse) - - // Act - await renderAndWait(DataSourceProvider.waterCrawl) - - // Assert - const elements = await screen.findAllByText('WaterCrawl') - expect(elements.length).toBeGreaterThanOrEqual(1) - }) - - it('should render correct logo and name for Jina Reader', async () => { - // Arrange - vi.mocked(fetchDataSources).mockResolvedValue({ result: 'success', sources: [mockSources[2]] } as DataSourcesResponse) - - // Act - await renderAndWait(DataSourceProvider.jinaReader) - - // Assert - const elements = await screen.findAllByText('Jina Reader') - expect(elements.length).toBeGreaterThanOrEqual(1) - }) - }) - - describe('Modal Interactions', () => { - it('should manage opening and closing of configuration modals', async () => { - // Arrange - await renderAndWait(DataSourceProvider.fireCrawl) - - // Act (Open) - fireEvent.click(screen.getByText('common.dataSource.configure')) - // Assert - expect(screen.getByText('datasetCreation.firecrawl.configFirecrawl')).toBeInTheDocument() - - // Act (Cancel) - fireEvent.click(screen.getByRole('button', { name: /common\.operation\.cancel/i })) - // Assert - expect(screen.queryByText('datasetCreation.firecrawl.configFirecrawl')).not.toBeInTheDocument() - }) - - it('should re-fetch sources after saving configuration (Watercrawl)', async () => { - // Arrange - await renderAndWait(DataSourceProvider.waterCrawl) - fireEvent.click(screen.getByText('common.dataSource.configure')) - vi.mocked(fetchDataSources).mockClear() - - // Act - fireEvent.change(screen.getByPlaceholderText('datasetCreation.watercrawl.apiKeyPlaceholder'), { target: { value: 'test-key' } }) - fireEvent.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(fetchDataSources).toHaveBeenCalled() - expect(screen.queryByText('datasetCreation.watercrawl.configWatercrawl')).not.toBeInTheDocument() - }) - }) - - it('should re-fetch sources after saving configuration (Jina Reader)', async () => { - // Arrange - await renderAndWait(DataSourceProvider.jinaReader) - fireEvent.click(screen.getByText('common.dataSource.configure')) - vi.mocked(fetchDataSources).mockClear() - - // Act - fireEvent.change(screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder'), { target: { value: 'test-key' } }) - fireEvent.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(fetchDataSources).toHaveBeenCalled() - expect(screen.queryByText('datasetCreation.jinaReader.configJinaReader')).not.toBeInTheDocument() - }) - }) - }) - - describe('Management Actions', () => { - it('should handle successful data source removal with toast notification', async () => { - // Arrange - vi.mocked(fetchDataSources).mockResolvedValue({ result: 'success', sources: [mockSources[0]] } as DataSourcesResponse) - vi.mocked(removeDataSourceApiKeyBinding).mockResolvedValue({ result: 'success' } as CommonResponse) - await renderAndWait(DataSourceProvider.fireCrawl) - await waitFor(() => expect(screen.getByText('common.dataSource.website.configuredCrawlers')).toBeInTheDocument()) - - // Act - const removeBtn = screen.getByText('Firecrawl').parentElement?.querySelector('svg')?.parentElement - if (removeBtn) - fireEvent.click(removeBtn) - - // Assert - await waitFor(() => { - expect(removeDataSourceApiKeyBinding).toHaveBeenCalledWith('1') - expect(screen.getByText('common.api.remove')).toBeInTheDocument() - }) - expect(screen.queryByText('common.dataSource.website.configuredCrawlers')).not.toBeInTheDocument() - }) - - it('should skip removal API call if no data source ID is present', async () => { - // Arrange - await renderAndWait(DataSourceProvider.fireCrawl) - - // Act - const removeBtn = screen.queryByText('Firecrawl')?.parentElement?.querySelector('svg')?.parentElement - if (removeBtn) - fireEvent.click(removeBtn) - - // Assert - expect(removeDataSourceApiKeyBinding).not.toHaveBeenCalled() - }) - }) - - describe('Firecrawl Save Flow', () => { - it('should re-fetch sources after saving Firecrawl configuration', async () => { - // Arrange - await renderAndWait(DataSourceProvider.fireCrawl) - fireEvent.click(screen.getByText('common.dataSource.configure')) - expect(screen.getByText('datasetCreation.firecrawl.configFirecrawl')).toBeInTheDocument() - vi.mocked(fetchDataSources).mockClear() - - // Act - fill in required API key field and save - const apiKeyInput = screen.getByPlaceholderText('datasetCreation.firecrawl.apiKeyPlaceholder') - fireEvent.change(apiKeyInput, { target: { value: 'test-key' } }) - fireEvent.click(screen.getByRole('button', { name: /common\.operation\.save/i })) - - // Assert - await waitFor(() => { - expect(fetchDataSources).toHaveBeenCalled() - expect(screen.queryByText('datasetCreation.firecrawl.configFirecrawl')).not.toBeInTheDocument() - }) - }) - }) - - describe('Cancel Flow', () => { - it('should close watercrawl modal when cancel is clicked', async () => { - // Arrange - await renderAndWait(DataSourceProvider.waterCrawl) - fireEvent.click(screen.getByText('common.dataSource.configure')) - expect(screen.getByText('datasetCreation.watercrawl.configWatercrawl')).toBeInTheDocument() - - // Act - fireEvent.click(screen.getByRole('button', { name: /common\.operation\.cancel/i })) - - // Assert - modal closed - await waitFor(() => { - expect(screen.queryByText('datasetCreation.watercrawl.configWatercrawl')).not.toBeInTheDocument() - }) - }) - - it('should close jina reader modal when cancel is clicked', async () => { - // Arrange - await renderAndWait(DataSourceProvider.jinaReader) - fireEvent.click(screen.getByText('common.dataSource.configure')) - expect(screen.getByText('datasetCreation.jinaReader.configJinaReader')).toBeInTheDocument() - - // Act - fireEvent.click(screen.getByRole('button', { name: /common\.operation\.cancel/i })) - - // Assert - modal closed - await waitFor(() => { - expect(screen.queryByText('datasetCreation.jinaReader.configJinaReader')).not.toBeInTheDocument() - }) - }) - }) -}) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx deleted file mode 100644 index d7f15236a7..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx +++ /dev/null @@ -1,165 +0,0 @@ -'use client' -import type { FC } from 'react' -import type { FirecrawlConfig } from '@/models/common' -import * as React from 'react' -import { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' -import Button from '@/app/components/base/button' -import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' -import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' -import { - PortalToFollowElem, - PortalToFollowElemContent, -} from '@/app/components/base/portal-to-follow-elem' -import Toast from '@/app/components/base/toast' -import Field from '@/app/components/datasets/create/website/base/field' -import { createDataSourceApiKeyBinding } from '@/service/datasets' - -type Props = { - onCancel: () => void - onSaved: () => void -} - -const I18N_PREFIX = 'firecrawl' - -const DEFAULT_BASE_URL = 'https://api.firecrawl.dev' - -const ConfigFirecrawlModal: FC = ({ - onCancel, - onSaved, -}) => { - const { t } = useTranslation() - const [isSaving, setIsSaving] = useState(false) - const [config, setConfig] = useState({ - api_key: '', - base_url: '', - }) - - const handleConfigChange = useCallback((key: string) => { - return (value: string | number) => { - setConfig(prev => ({ ...prev, [key]: value as string })) - } - }, []) - - const handleSave = useCallback(async () => { - if (isSaving) - return - let errorMsg = '' - if (config.base_url && !((config.base_url.startsWith('http://') || config.base_url.startsWith('https://')))) - errorMsg = t('errorMsg.urlError', { ns: 'common' }) - if (!errorMsg) { - if (!config.api_key) { - errorMsg = t('errorMsg.fieldRequired', { - ns: 'common', - field: 'API Key', - }) - } - } - - if (errorMsg) { - Toast.notify({ - type: 'error', - message: errorMsg, - }) - return - } - const postData = { - category: 'website', - provider: 'firecrawl', - credentials: { - auth_type: 'bearer', - config: { - api_key: config.api_key, - base_url: config.base_url || DEFAULT_BASE_URL, - }, - }, - } - try { - setIsSaving(true) - await createDataSourceApiKeyBinding(postData) - Toast.notify({ - type: 'success', - message: t('api.success', { ns: 'common' }), - }) - } - finally { - setIsSaving(false) - } - - onSaved() - }, [config.api_key, config.base_url, onSaved, t, isSaving]) - - return ( - - -
-
-
-
-
{t(`${I18N_PREFIX}.configFirecrawl`, { ns: 'datasetCreation' })}
-
- -
- - -
-
- - {t(`${I18N_PREFIX}.getApiKeyLinkText`, { ns: 'datasetCreation' })} - - -
- - -
- -
-
-
-
- - {t('modelProvider.encrypted.front', { ns: 'common' })} - - PKCS1_OAEP - - {t('modelProvider.encrypted.back', { ns: 'common' })} -
-
-
-
-
-
- ) -} -export default React.memo(ConfigFirecrawlModal) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx deleted file mode 100644 index 2374ae6174..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx +++ /dev/null @@ -1,144 +0,0 @@ -'use client' -import type { FC } from 'react' -import * as React from 'react' -import { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' -import Button from '@/app/components/base/button' -import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' -import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' -import { - PortalToFollowElem, - PortalToFollowElemContent, -} from '@/app/components/base/portal-to-follow-elem' -import Toast from '@/app/components/base/toast' -import Field from '@/app/components/datasets/create/website/base/field' -import { DataSourceProvider } from '@/models/common' -import { createDataSourceApiKeyBinding } from '@/service/datasets' - -type Props = { - onCancel: () => void - onSaved: () => void -} - -const I18N_PREFIX = 'jinaReader' - -const ConfigJinaReaderModal: FC = ({ - onCancel, - onSaved, -}) => { - const { t } = useTranslation() - const [isSaving, setIsSaving] = useState(false) - const [apiKey, setApiKey] = useState('') - - const handleSave = useCallback(async () => { - if (isSaving) - return - let errorMsg = '' - if (!errorMsg) { - if (!apiKey) { - errorMsg = t('errorMsg.fieldRequired', { - ns: 'common', - field: 'API Key', - }) - } - } - - if (errorMsg) { - Toast.notify({ - type: 'error', - message: errorMsg, - }) - return - } - const postData = { - category: 'website', - provider: DataSourceProvider.jinaReader, - credentials: { - auth_type: 'bearer', - config: { - api_key: apiKey, - }, - }, - } - try { - setIsSaving(true) - await createDataSourceApiKeyBinding(postData) - Toast.notify({ - type: 'success', - message: t('api.success', { ns: 'common' }), - }) - } - finally { - setIsSaving(false) - } - - onSaved() - }, [apiKey, onSaved, t, isSaving]) - - return ( - - -
-
-
-
-
{t(`${I18N_PREFIX}.configJinaReader`, { ns: 'datasetCreation' })}
-
- -
- setApiKey(value as string)} - placeholder={t(`${I18N_PREFIX}.apiKeyPlaceholder`, { ns: 'datasetCreation' })!} - /> -
-
- - {t(`${I18N_PREFIX}.getApiKeyLinkText`, { ns: 'datasetCreation' })} - - -
- - -
- -
-
-
-
- - {t('modelProvider.encrypted.front', { ns: 'common' })} - - PKCS1_OAEP - - {t('modelProvider.encrypted.back', { ns: 'common' })} -
-
-
-
-
-
- ) -} -export default React.memo(ConfigJinaReaderModal) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/config-watercrawl-modal.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/config-watercrawl-modal.tsx deleted file mode 100644 index a9399f25cd..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/config-watercrawl-modal.tsx +++ /dev/null @@ -1,165 +0,0 @@ -'use client' -import type { FC } from 'react' -import type { WatercrawlConfig } from '@/models/common' -import * as React from 'react' -import { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' -import Button from '@/app/components/base/button' -import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' -import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' -import { - PortalToFollowElem, - PortalToFollowElemContent, -} from '@/app/components/base/portal-to-follow-elem' -import Toast from '@/app/components/base/toast' -import Field from '@/app/components/datasets/create/website/base/field' -import { createDataSourceApiKeyBinding } from '@/service/datasets' - -type Props = { - onCancel: () => void - onSaved: () => void -} - -const I18N_PREFIX = 'watercrawl' - -const DEFAULT_BASE_URL = 'https://app.watercrawl.dev' - -const ConfigWatercrawlModal: FC = ({ - onCancel, - onSaved, -}) => { - const { t } = useTranslation() - const [isSaving, setIsSaving] = useState(false) - const [config, setConfig] = useState({ - api_key: '', - base_url: '', - }) - - const handleConfigChange = useCallback((key: string) => { - return (value: string | number) => { - setConfig(prev => ({ ...prev, [key]: value as string })) - } - }, []) - - const handleSave = useCallback(async () => { - if (isSaving) - return - let errorMsg = '' - if (config.base_url && !((config.base_url.startsWith('http://') || config.base_url.startsWith('https://')))) - errorMsg = t('errorMsg.urlError', { ns: 'common' }) - if (!errorMsg) { - if (!config.api_key) { - errorMsg = t('errorMsg.fieldRequired', { - ns: 'common', - field: 'API Key', - }) - } - } - - if (errorMsg) { - Toast.notify({ - type: 'error', - message: errorMsg, - }) - return - } - const postData = { - category: 'website', - provider: 'watercrawl', - credentials: { - auth_type: 'x-api-key', - config: { - api_key: config.api_key, - base_url: config.base_url || DEFAULT_BASE_URL, - }, - }, - } - try { - setIsSaving(true) - await createDataSourceApiKeyBinding(postData) - Toast.notify({ - type: 'success', - message: t('api.success', { ns: 'common' }), - }) - } - finally { - setIsSaving(false) - } - - onSaved() - }, [config.api_key, config.base_url, onSaved, t, isSaving]) - - return ( - - -
-
-
-
-
{t(`${I18N_PREFIX}.configWatercrawl`, { ns: 'datasetCreation' })}
-
- -
- - -
-
- - {t(`${I18N_PREFIX}.getApiKeyLinkText`, { ns: 'datasetCreation' })} - - -
- - -
- -
-
-
-
- - {t('modelProvider.encrypted.front', { ns: 'common' })} - - PKCS1_OAEP - - {t('modelProvider.encrypted.back', { ns: 'common' })} -
-
-
-
-
-
- ) -} -export default React.memo(ConfigWatercrawlModal) diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx deleted file mode 100644 index 22bfb4950e..0000000000 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx +++ /dev/null @@ -1,137 +0,0 @@ -'use client' -import type { FC } from 'react' -import type { DataSourceItem } from '@/models/common' -import * as React from 'react' -import { useCallback, useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' -import s from '@/app/components/datasets/create/website/index.module.css' -import { useAppContext } from '@/context/app-context' -import { DataSourceProvider } from '@/models/common' -import { fetchDataSources, removeDataSourceApiKeyBinding } from '@/service/datasets' -import { cn } from '@/utils/classnames' -import Panel from '../panel' - -import { DataSourceType } from '../panel/types' -import ConfigFirecrawlModal from './config-firecrawl-modal' -import ConfigJinaReaderModal from './config-jina-reader-modal' -import ConfigWatercrawlModal from './config-watercrawl-modal' - -type Props = { - provider: DataSourceProvider -} - -const DataSourceWebsite: FC = ({ provider }) => { - const { t } = useTranslation() - const { isCurrentWorkspaceManager } = useAppContext() - const [sources, setSources] = useState([]) - const checkSetApiKey = useCallback(async () => { - const res = await fetchDataSources() as any - const list = res.sources - setSources(list) - }, []) - - useEffect(() => { - checkSetApiKey() - }, []) - - const [configTarget, setConfigTarget] = useState(null) - const showConfig = useCallback((provider: DataSourceProvider) => { - setConfigTarget(provider) - }, [setConfigTarget]) - - const hideConfig = useCallback(() => { - setConfigTarget(null) - }, [setConfigTarget]) - - const handleAdded = useCallback(() => { - checkSetApiKey() - hideConfig() - }, [checkSetApiKey, hideConfig]) - - const getIdByProvider = (provider: DataSourceProvider): string | undefined => { - const source = sources.find(item => item.provider === provider) - return source?.id - } - - const getProviderName = (provider: DataSourceProvider): string => { - if (provider === DataSourceProvider.fireCrawl) - return 'Firecrawl' - - if (provider === DataSourceProvider.waterCrawl) - return 'WaterCrawl' - - return 'Jina Reader' - } - - const handleRemove = useCallback((provider: DataSourceProvider) => { - return async () => { - const dataSourceId = getIdByProvider(provider) - if (dataSourceId) { - await removeDataSourceApiKeyBinding(dataSourceId) - setSources(sources.filter(item => item.provider !== provider)) - Toast.notify({ - type: 'success', - message: t('api.remove', { ns: 'common' }), - }) - } - } - }, [sources, t]) - - return ( - <> - item.provider === provider) !== undefined} - onConfigure={() => showConfig(provider)} - readOnly={!isCurrentWorkspaceManager} - configuredList={sources.filter(item => item.provider === provider).map(item => ({ - id: item.id, - logo: ({ className }: { className: string }) => { - if (item.provider === DataSourceProvider.fireCrawl) { - return ( -
- 🔥 -
- ) - } - - if (item.provider === DataSourceProvider.waterCrawl) { - return ( -
- -
- ) - } - return ( -
- -
- ) - }, - name: getProviderName(item.provider), - isActive: true, - }))} - onRemove={handleRemove(provider)} - /> - {configTarget === DataSourceProvider.fireCrawl && ( - - )} - {configTarget === DataSourceProvider.waterCrawl && ( - - )} - {configTarget === DataSourceProvider.jinaReader && ( - - )} - - - ) -} -export default React.memo(DataSourceWebsite) diff --git a/web/app/components/header/account-setting/data-source-page/panel/__tests__/config-item.spec.tsx b/web/app/components/header/account-setting/data-source-page/panel/__tests__/config-item.spec.tsx deleted file mode 100644 index 4ad49a8f8f..0000000000 --- a/web/app/components/header/account-setting/data-source-page/panel/__tests__/config-item.spec.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import type { ConfigItemType } from '../config-item' -import { fireEvent, render, screen } from '@testing-library/react' -import ConfigItem from '../config-item' -import { DataSourceType } from '../types' - -/** - * ConfigItem Component Tests - * Tests rendering of individual configuration items for Notion and Website data sources. - */ - -// Mock Operate component to isolate ConfigItem unit tests. -vi.mock('../../data-source-notion/operate', () => ({ - default: ({ onAuthAgain, payload }: { onAuthAgain: () => void, payload: { id: string, total: number } }) => ( -
- - {JSON.stringify(payload)} -
- ), -})) - -describe('ConfigItem Component', () => { - const mockOnRemove = vi.fn() - const mockOnChangeAuthorizedPage = vi.fn() - const MockLogo = (props: React.SVGProps) => - - const baseNotionPayload: ConfigItemType = { - id: 'notion-1', - logo: MockLogo, - name: 'Notion Workspace', - isActive: true, - notionConfig: { total: 5 }, - } - - const baseWebsitePayload: ConfigItemType = { - id: 'website-1', - logo: MockLogo, - name: 'My Website', - isActive: true, - } - - afterEach(() => { - vi.clearAllMocks() - }) - - describe('Notion Configuration', () => { - it('should render active Notion config item with connected status and operator', () => { - // Act - render( - , - ) - - // Assert - expect(screen.getByTestId('mock-logo')).toBeInTheDocument() - expect(screen.getByText('Notion Workspace')).toBeInTheDocument() - const statusText = screen.getByText('common.dataSource.notion.connected') - expect(statusText).toHaveClass('text-util-colors-green-green-600') - expect(screen.getByTestId('operate-payload')).toHaveTextContent(JSON.stringify({ id: 'notion-1', total: 5 })) - }) - - it('should render inactive Notion config item with disconnected status', () => { - // Arrange - const inactivePayload = { ...baseNotionPayload, isActive: false } - - // Act - render( - , - ) - - // Assert - const statusText = screen.getByText('common.dataSource.notion.disconnected') - expect(statusText).toHaveClass('text-util-colors-warning-warning-600') - }) - - it('should handle auth action through the Operate component', () => { - // Arrange - render( - , - ) - - // Act - fireEvent.click(screen.getByTestId('operate-auth-btn')) - - // Assert - expect(mockOnChangeAuthorizedPage).toHaveBeenCalled() - }) - - it('should fallback to 0 total if notionConfig is missing', () => { - // Arrange - const payloadNoConfig = { ...baseNotionPayload, notionConfig: undefined } - - // Act - render( - , - ) - - // Assert - expect(screen.getByTestId('operate-payload')).toHaveTextContent(JSON.stringify({ id: 'notion-1', total: 0 })) - }) - - it('should handle missing notionActions safely without crashing', () => { - // Arrange - render( - , - ) - - // Act & Assert - expect(() => fireEvent.click(screen.getByTestId('operate-auth-btn'))).not.toThrow() - }) - }) - - describe('Website Configuration', () => { - it('should render active Website config item and hide operator', () => { - // Act - render( - , - ) - - // Assert - expect(screen.getByText('common.dataSource.website.active')).toBeInTheDocument() - expect(screen.queryByTestId('mock-operate')).not.toBeInTheDocument() - }) - - it('should render inactive Website config item', () => { - // Arrange - const inactivePayload = { ...baseWebsitePayload, isActive: false } - - // Act - render( - , - ) - - // Assert - const statusText = screen.getByText('common.dataSource.website.inactive') - expect(statusText).toHaveClass('text-util-colors-warning-warning-600') - }) - - it('should show remove button and trigger onRemove when clicked (not read-only)', () => { - // Arrange - const { container } = render( - , - ) - - // Note: This selector is brittle but necessary since the delete button lacks - // accessible attributes (data-testid, aria-label). Ideally, the component should - // be updated to include proper accessibility attributes. - const deleteBtn = container.querySelector('div[class*="cursor-pointer"]') as HTMLElement - - // Act - fireEvent.click(deleteBtn) - - // Assert - expect(mockOnRemove).toHaveBeenCalled() - }) - - it('should hide remove button in read-only mode', () => { - // Arrange - const { container } = render( - , - ) - - // Assert - const deleteBtn = container.querySelector('div[class*="cursor-pointer"]') - expect(deleteBtn).not.toBeInTheDocument() - }) - }) -}) diff --git a/web/app/components/header/account-setting/data-source-page/panel/__tests__/index.spec.tsx b/web/app/components/header/account-setting/data-source-page/panel/__tests__/index.spec.tsx deleted file mode 100644 index d83cdb5360..0000000000 --- a/web/app/components/header/account-setting/data-source-page/panel/__tests__/index.spec.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import type { ConfigItemType } from '../config-item' -import { fireEvent, render, screen } from '@testing-library/react' -import { DataSourceProvider } from '@/models/common' -import Panel from '../index' -import { DataSourceType } from '../types' - -/** - * Panel Component Tests - * Tests layout, conditional rendering, and interactions for data source panels (Notion and Website). - */ - -vi.mock('../../data-source-notion/operate', () => ({ - default: () =>
, -})) - -describe('Panel Component', () => { - const onConfigure = vi.fn() - const onRemove = vi.fn() - const mockConfiguredList: ConfigItemType[] = [ - { id: '1', name: 'Item 1', isActive: true, logo: () => null }, - { id: '2', name: 'Item 2', isActive: false, logo: () => null }, - ] - - beforeEach(() => { - vi.clearAllMocks() - }) - - describe('Notion Panel Rendering', () => { - it('should render Notion panel when not configured and isSupportList is true', () => { - // Act - render( - , - ) - - // Assert - expect(screen.getByText('common.dataSource.notion.title')).toBeInTheDocument() - expect(screen.getByText('common.dataSource.notion.description')).toBeInTheDocument() - const connectBtn = screen.getByText('common.dataSource.connect') - expect(connectBtn).toBeInTheDocument() - - // Act - fireEvent.click(connectBtn) - // Assert - expect(onConfigure).toHaveBeenCalled() - }) - - it('should render Notion panel in readOnly mode when not configured', () => { - // Act - render( - , - ) - - // Assert - const connectBtn = screen.getByText('common.dataSource.connect') - expect(connectBtn).toHaveClass('cursor-default opacity-50 grayscale') - }) - - it('should render Notion panel when configured with list of items', () => { - // Act - render( - , - ) - - // Assert - expect(screen.getByRole('button', { name: 'common.dataSource.configure' })).toBeInTheDocument() - expect(screen.getByText('common.dataSource.notion.connectedWorkspace')).toBeInTheDocument() - expect(screen.getByText('Item 1')).toBeInTheDocument() - expect(screen.getByText('Item 2')).toBeInTheDocument() - }) - - it('should hide connect button for Notion if isSupportList is false', () => { - // Act - render( - , - ) - - // Assert - expect(screen.queryByText('common.dataSource.connect')).not.toBeInTheDocument() - }) - - it('should disable Notion configure button in readOnly mode (configured state)', () => { - // Act - render( - , - ) - - // Assert - const btn = screen.getByRole('button', { name: 'common.dataSource.configure' }) - expect(btn).toBeDisabled() - }) - }) - - describe('Website Panel Rendering', () => { - it('should show correct provider names and handle configuration when not configured', () => { - // Arrange - const { rerender } = render( - , - ) - - // Assert Firecrawl - expect(screen.getByText('🔥 Firecrawl')).toBeInTheDocument() - - // Rerender for WaterCrawl - rerender( - , - ) - expect(screen.getByText('WaterCrawl')).toBeInTheDocument() - - // Rerender for Jina Reader - rerender( - , - ) - expect(screen.getByText('Jina Reader')).toBeInTheDocument() - - // Act - const configBtn = screen.getByText('common.dataSource.configure') - fireEvent.click(configBtn) - // Assert - expect(onConfigure).toHaveBeenCalled() - }) - - it('should handle readOnly mode for Website configuration button', () => { - // Act - render( - , - ) - - // Assert - const configBtn = screen.getByText('common.dataSource.configure') - expect(configBtn).toHaveClass('cursor-default opacity-50 grayscale') - - // Act - fireEvent.click(configBtn) - // Assert - expect(onConfigure).not.toHaveBeenCalled() - }) - - it('should render Website panel correctly when configured with crawlers', () => { - // Act - render( - , - ) - - // Assert - expect(screen.getByText('common.dataSource.website.configuredCrawlers')).toBeInTheDocument() - expect(screen.getByText('Item 1')).toBeInTheDocument() - expect(screen.getByText('Item 2')).toBeInTheDocument() - }) - }) -}) diff --git a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx deleted file mode 100644 index f62c5e147d..0000000000 --- a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx +++ /dev/null @@ -1,85 +0,0 @@ -'use client' -import type { FC } from 'react' -import { - RiDeleteBinLine, -} from '@remixicon/react' -import { noop } from 'es-toolkit/function' -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { cn } from '@/utils/classnames' -import Indicator from '../../../indicator' -import Operate from '../data-source-notion/operate' -import s from './style.module.css' -import { DataSourceType } from './types' - -export type ConfigItemType = { - id: string - logo: any - name: string - isActive: boolean - notionConfig?: { - total: number - } -} - -type Props = { - type: DataSourceType - payload: ConfigItemType - onRemove: () => void - notionActions?: { - onChangeAuthorizedPage: () => void - } - readOnly: boolean -} - -const ConfigItem: FC = ({ - type, - payload, - onRemove, - notionActions, - readOnly, -}) => { - const { t } = useTranslation() - const isNotion = type === DataSourceType.notion - const isWebsite = type === DataSourceType.website - const onChangeAuthorizedPage = notionActions?.onChangeAuthorizedPage || noop - - return ( -
- -
{payload.name}
- { - payload.isActive - ? - : - } -
- { - payload.isActive - ? t(isNotion ? 'dataSource.notion.connected' : 'dataSource.website.active', { ns: 'common' }) - : t(isNotion ? 'dataSource.notion.disconnected' : 'dataSource.website.inactive', { ns: 'common' }) - } -
-
- {isNotion && ( - - )} - - { - isWebsite && !readOnly && ( -
- -
- ) - } - -
- ) -} -export default React.memo(ConfigItem) diff --git a/web/app/components/header/account-setting/data-source-page/panel/index.tsx b/web/app/components/header/account-setting/data-source-page/panel/index.tsx deleted file mode 100644 index 0909603ae8..0000000000 --- a/web/app/components/header/account-setting/data-source-page/panel/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -'use client' -import type { FC } from 'react' -import type { ConfigItemType } from './config-item' -import { RiAddLine } from '@remixicon/react' -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import Button from '@/app/components/base/button' - -import { DataSourceProvider } from '@/models/common' -import { cn } from '@/utils/classnames' -import ConfigItem from './config-item' -import s from './style.module.css' -import { DataSourceType } from './types' - -type Props = { - type: DataSourceType - provider?: DataSourceProvider - isConfigured: boolean - onConfigure: () => void - readOnly: boolean - isSupportList?: boolean - configuredList: ConfigItemType[] - onRemove: () => void - notionActions?: { - onChangeAuthorizedPage: () => void - } -} - -const Panel: FC = ({ - type, - provider, - isConfigured, - onConfigure, - readOnly, - configuredList, - isSupportList, - onRemove, - notionActions, -}) => { - const { t } = useTranslation() - const isNotion = type === DataSourceType.notion - const isWebsite = type === DataSourceType.website - - const getProviderName = (): string => { - if (provider === DataSourceProvider.fireCrawl) - return '🔥 Firecrawl' - if (provider === DataSourceProvider.waterCrawl) - return 'WaterCrawl' - return 'Jina Reader' - } - - return ( -
-
-
-
-
-
{t(`dataSource.${type}.title`, { ns: 'common' })}
- {isWebsite && ( -
- {t('dataSource.website.with', { ns: 'common' })} - {' '} - {getProviderName()} -
- )} -
- { - !isConfigured && ( -
- {t(`dataSource.${type}.description`, { ns: 'common' })} -
- ) - } -
- {isNotion && ( - <> - { - isConfigured - ? ( - - ) - : ( - <> - {isSupportList && ( -
- - {t('dataSource.connect', { ns: 'common' })} -
- )} - - ) - } - - )} - - {isWebsite && !isConfigured && ( -
- {t('dataSource.configure', { ns: 'common' })} -
- )} - -
- { - isConfigured && ( - <> -
-
- {isNotion ? t('dataSource.notion.connectedWorkspace', { ns: 'common' }) : t('dataSource.website.configuredCrawlers', { ns: 'common' })} -
-
-
-
- { - configuredList.map(item => ( - - )) - } -
- - ) - } -
- ) -} -export default React.memo(Panel) diff --git a/web/app/components/header/account-setting/data-source-page/panel/style.module.css b/web/app/components/header/account-setting/data-source-page/panel/style.module.css deleted file mode 100644 index ac9be02205..0000000000 --- a/web/app/components/header/account-setting/data-source-page/panel/style.module.css +++ /dev/null @@ -1,17 +0,0 @@ -.notion-icon { - background: #ffffff url(../../../assets/notion.svg) center center no-repeat; - background-size: 20px 20px; -} - -.website-icon { - background: #ffffff url(../../../../datasets/create/assets/web.svg) center center no-repeat; - background-size: 20px 20px; -} - -.workspace-item { - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); -} - -.workspace-item:last-of-type { - margin-bottom: 0; -} diff --git a/web/app/components/header/account-setting/data-source-page/panel/types.ts b/web/app/components/header/account-setting/data-source-page/panel/types.ts deleted file mode 100644 index 345bc10f81..0000000000 --- a/web/app/components/header/account-setting/data-source-page/panel/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum DataSourceType { - notion = 'notion', - website = 'website', -} diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index d0aa842e11..f4b95eee09 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -4739,69 +4739,6 @@ "count": 2 } }, - "app/components/header/account-setting/data-source-page/data-source-notion/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "app/components/header/account-setting/data-source-page/data-source-notion/operate/index.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 4 - } - }, - "app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx": { - "no-restricted-imports": { - "count": 2 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 1 - } - }, - "app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx": { - "no-restricted-imports": { - "count": 2 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 1 - } - }, - "app/components/header/account-setting/data-source-page/data-source-website/config-watercrawl-modal.tsx": { - "no-restricted-imports": { - "count": 2 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 1 - } - }, - "app/components/header/account-setting/data-source-page/data-source-website/index.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, - "app/components/header/account-setting/data-source-page/panel/config-item.tsx": { - "tailwindcss/enforce-consistent-class-order": { - "count": 2 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, - "app/components/header/account-setting/data-source-page/panel/index.tsx": { - "tailwindcss/enforce-consistent-class-order": { - "count": 3 - } - }, - "app/components/header/account-setting/data-source-page/panel/types.ts": { - "erasable-syntax-only/enums": { - "count": 1 - } - }, "app/components/header/account-setting/key-validator/declarations.ts": { "erasable-syntax-only/enums": { "count": 1 From 76a23deba76c79314be70907019920b4cf81a1bf Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:29:03 +0800 Subject: [PATCH 21/70] fix: crash when dataset icon_info is undefined in Knowledge Retrieval node (#33907) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> --- .../nodes/knowledge-retrieval/node.tsx | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx index 9f5fe1f31c..3fafdd018e 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx @@ -33,21 +33,29 @@ const Node: FC> = ({ return (
- {selectedDatasets.map(({ id, name, icon_info }) => ( -
- -
- {name} + {selectedDatasets.map(({ id, name, icon_info }) => { + const iconInfo = icon_info || { + icon: '📙', + icon_type: 'emoji' as const, + icon_background: '#FFF4ED', + icon_url: '', + } + return ( +
+ +
+ {name} +
-
- ))} + ) + })}
) From 4ab7ba4f2e87d92a602c63c89037535fb83d9d89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:31:23 +0900 Subject: [PATCH 22/70] chore(deps): bump the llm group across 1 directory with 2 updates (#33916) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/uv.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/uv.lock b/api/uv.lock index 5d9f4f1b9e..cb4a330962 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -3460,7 +3460,7 @@ wheels = [ [[package]] name = "langsmith" -version = "0.7.17" +version = "0.7.22" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -3473,9 +3473,9 @@ dependencies = [ { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/79/81041dde07a974e728db7def23c1c7255950b8874102925cc77093bc847d/langsmith-0.7.17.tar.gz", hash = "sha256:6c1b0c2863cdd6636d2a58b8d5b1b80060703d98cac2593f4233e09ac25b5a9d", size = 1132228, upload-time = "2026-03-12T20:41:10.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/2a/2d5e6c67396fd228670af278c4da7bd6db2b8d11deaf6f108490b6d3f561/langsmith-0.7.22.tar.gz", hash = "sha256:35bfe795d648b069958280760564632fd28ebc9921c04f3e209c0db6a6c7dc04", size = 1134923, upload-time = "2026-03-19T22:45:23.492Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/31/62689d57f4d25792bd6a3c05c868771899481be2f3e31f9e71d31e1ac4ab/langsmith-0.7.17-py3-none-any.whl", hash = "sha256:cbec10460cb6c6ecc94c18c807be88a9984838144ae6c4693c9f859f378d7d02", size = 359147, upload-time = "2026-03-12T20:41:08.758Z" }, + { url = "https://files.pythonhosted.org/packages/1a/94/1f5d72655ab6534129540843776c40eff757387b88e798d8b3bf7e313fd4/langsmith-0.7.22-py3-none-any.whl", hash = "sha256:6e9d5148314d74e86748cb9d3898632cad0320c9323d95f70f969e5bc078eee4", size = 359927, upload-time = "2026-03-19T22:45:21.603Z" }, ] [[package]] @@ -4538,7 +4538,7 @@ wheels = [ [[package]] name = "opik" -version = "1.10.39" +version = "1.10.45" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3-stubs", extra = ["bedrock-runtime"] }, @@ -4557,9 +4557,9 @@ dependencies = [ { name = "tqdm" }, { name = "uuid6" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/0f/b1e00a18cac16b4f36bf6cecc2de962fda810a9416d1159c48f46b81f5ec/opik-1.10.39.tar.gz", hash = "sha256:4d808eb2137070fc5d92a3bed3c3100d9cccfb35f4f0b71ea9990733f293dbb2", size = 780312, upload-time = "2026-03-12T14:08:25.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/17/edea6308347cec62e6828de7c573c596559c502b54fa4f0c88a52e2e81f5/opik-1.10.45.tar.gz", hash = "sha256:d8d8627ba03d12def46965e03d58f611daaf5cf878b3d087c53fe1159788c140", size = 789876, upload-time = "2026-03-20T11:35:12.457Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/24/0f4404907a98b4aec4508504570a78a61a3a8b5e451c67326632695ba8e6/opik-1.10.39-py3-none-any.whl", hash = "sha256:a72d735b9afac62e5262294b2f704aca89ec31f5c9beda17504815f7423870c3", size = 1317833, upload-time = "2026-03-12T14:08:23.954Z" }, + { url = "https://files.pythonhosted.org/packages/b7/17/150e9eecfa28cb23f7a0bfe83ae1486a11022b97fe6d12328b455784658d/opik-1.10.45-py3-none-any.whl", hash = "sha256:e8050d9e5e0d92ff587f156eacbdd02099897f39cfe79a98380b6c8ae9906b95", size = 1337714, upload-time = "2026-03-20T11:35:10.237Z" }, ] [[package]] From df69997d8e93c4d9f0c73b2475e6b0b972058149 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:32:05 +0900 Subject: [PATCH 23/70] chore(deps): bump google-cloud-aiplatform from 1.141.0 to 1.142.0 in /api in the google group across 1 directory (#33917) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/uv.lock b/api/uv.lock index cb4a330962..74a0a9307b 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -2548,7 +2548,7 @@ wheels = [ [[package]] name = "google-cloud-aiplatform" -version = "1.141.0" +version = "1.142.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docstring-parser" }, @@ -2564,9 +2564,9 @@ dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/dc/1209c7aab43bd7233cf631165a3b1b4284d22fc7fe7387c66228d07868ab/google_cloud_aiplatform-1.141.0.tar.gz", hash = "sha256:e3b1cdb28865dd862aac9c685dfc5ac076488705aba0a5354016efadcddd59c6", size = 10152688, upload-time = "2026-03-10T22:20:08.692Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/0d/3063a0512d60cf18854a279e00ccb796429545464345ef821cf77cb93d05/google_cloud_aiplatform-1.142.0.tar.gz", hash = "sha256:87b49e002703dc14885093e9b264587db84222bef5f70f5a442d03f41beecdd1", size = 10207993, upload-time = "2026-03-20T22:49:13.797Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/fc/428af69a69ff2e477e7f5e12d227b31fe5790f1a8234aacd54297f49c836/google_cloud_aiplatform-1.141.0-py2.py3-none-any.whl", hash = "sha256:6bd25b4d514c40b8181ca703e1b313ad6d0454ab8006fc9907fb3e9f672f31d1", size = 8358409, upload-time = "2026-03-10T22:20:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/59/8b/f29646d3fa940f0e38cfcc12137f4851856b50d7486a3c05103ebc78d82d/google_cloud_aiplatform-1.142.0-py2.py3-none-any.whl", hash = "sha256:17c91db9b613cbbafb2c36335b123686aeb2b4b8448be5134b565ae07165a39a", size = 8388991, upload-time = "2026-03-20T22:49:10.334Z" }, ] [[package]] From a942d4c92669a8d7a3c5de147c5ed477fd00d8bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:33:31 +0900 Subject: [PATCH 24/70] chore(deps): bump the python-packages group in /api with 4 updates (#33873) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/pyproject.toml | 8 ++++---- api/uv.lock | 32 ++++++++++++++++---------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index 1efdb601ae..a16e1ed934 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -72,10 +72,10 @@ dependencies = [ "pyyaml~=6.0.1", "readabilipy~=0.3.0", "redis[hiredis]~=7.3.0", - "resend~=2.23.0", - "sentry-sdk[flask]~=2.54.0", + "resend~=2.26.0", + "sentry-sdk[flask]~=2.55.0", "sqlalchemy~=2.0.29", - "starlette==0.52.1", + "starlette==1.0.0", "tiktoken~=0.12.0", "transformers~=5.3.0", "unstructured[docx,epub,md,ppt,pptx]~=0.21.5", @@ -92,7 +92,7 @@ dependencies = [ "apscheduler>=3.11.0", "weave>=0.52.16", "fastopenapi[flask]>=0.7.0", - "bleach~=6.2.0", + "bleach~=6.3.0", ] # 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 74a0a9307b..dbda8941f4 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -660,14 +660,14 @@ wheels = [ [[package]] name = "bleach" -version = "6.2.0" +version = "6.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083, upload-time = "2024-10-29T18:30:40.477Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406, upload-time = "2024-10-29T18:30:38.186Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, ] [[package]] @@ -1744,7 +1744,7 @@ requires-dist = [ { name = "arize-phoenix-otel", specifier = "~=0.15.0" }, { name = "azure-identity", specifier = "==1.25.3" }, { name = "beautifulsoup4", specifier = "==4.14.3" }, - { name = "bleach", specifier = "~=6.2.0" }, + { name = "bleach", specifier = "~=6.3.0" }, { name = "boto3", specifier = "==1.42.73" }, { name = "bs4", specifier = "~=0.0.1" }, { name = "cachetools", specifier = "~=5.3.0" }, @@ -1815,12 +1815,12 @@ requires-dist = [ { name = "pyyaml", specifier = "~=6.0.1" }, { name = "readabilipy", specifier = "~=0.3.0" }, { name = "redis", extras = ["hiredis"], specifier = "~=7.3.0" }, - { name = "resend", specifier = "~=2.23.0" }, + { name = "resend", specifier = "~=2.26.0" }, { name = "sendgrid", specifier = "~=6.12.3" }, - { name = "sentry-sdk", extras = ["flask"], specifier = "~=2.54.0" }, + { name = "sentry-sdk", extras = ["flask"], specifier = "~=2.55.0" }, { name = "sqlalchemy", specifier = "~=2.0.29" }, { name = "sseclient-py", specifier = "~=1.9.0" }, - { name = "starlette", specifier = "==0.52.1" }, + { name = "starlette", specifier = "==1.0.0" }, { name = "tiktoken", specifier = "~=0.12.0" }, { name = "transformers", specifier = "~=5.3.0" }, { name = "unstructured", extras = ["docx", "epub", "md", "ppt", "pptx"], specifier = "~=0.21.5" }, @@ -5968,15 +5968,15 @@ wheels = [ [[package]] name = "resend" -version = "2.23.0" +version = "2.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/a3/20003e7d14604fef778bd30c69604df3560a657a95a5c29a9688610759b6/resend-2.23.0.tar.gz", hash = "sha256:df613827dcc40eb1c9de2e5ff600cd4081b89b206537dec8067af1a5016d23c7", size = 31416, upload-time = "2026-02-23T19:01:57.603Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/ff/6a4e5e758fc2145c6a7d8563934d8ee24bf96a0212d7ec7d1af1f155bb74/resend-2.26.0.tar.gz", hash = "sha256:957a6a59dc597ce27fbd6d5383220dd9cc497fab99d4f3d775c8a42a449a569e", size = 36238, upload-time = "2026-03-20T22:49:09.728Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/35/64df775b8cd95e89798fd7b1b7fcafa975b6b09f559c10c0650e65b33580/resend-2.23.0-py2.py3-none-any.whl", hash = "sha256:eca6d28a1ffd36c1fc489fa83cb6b511f384792c9f07465f7c92d96c8b4d5636", size = 52599, upload-time = "2026-02-23T19:01:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/16/c2/f88d3299d97aa1d36a923d0846fe185fcf5355ca898c954b2e5a79f090b5/resend-2.26.0-py2.py3-none-any.whl", hash = "sha256:5e25a804a84a68df504f2ade5369ac37e0139e37788a1f20b66c88696595b4bc", size = 57699, upload-time = "2026-03-20T22:49:08.354Z" }, ] [[package]] @@ -6142,15 +6142,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.54.0" +version = "2.55.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/e9/2e3a46c304e7fa21eaa70612f60354e32699c7102eb961f67448e222ad7c/sentry_sdk-2.54.0.tar.gz", hash = "sha256:2620c2575128d009b11b20f7feb81e4e4e8ae08ec1d36cbc845705060b45cc1b", size = 413813, upload-time = "2026-03-02T15:12:41.355Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/b8/285293dc60fc198fffc3fcdbc7c6d4e646e0f74e61461c355d40faa64ceb/sentry_sdk-2.55.0.tar.gz", hash = "sha256:3774c4d8820720ca4101548131b9c162f4c9426eb7f4d24aca453012a7470f69", size = 424505, upload-time = "2026-03-17T14:15:51.707Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/39/be412cc86bc6247b8f69e9383d7950711bd86f8d0a4a4b0fe8fad685bc21/sentry_sdk-2.54.0-py2.py3-none-any.whl", hash = "sha256:fd74e0e281dcda63afff095d23ebcd6e97006102cdc8e78a29f19ecdf796a0de", size = 439198, upload-time = "2026-03-02T15:12:39.546Z" }, + { url = "https://files.pythonhosted.org/packages/9a/66/20465097782d7e1e742d846407ea7262d338c6e876ddddad38ca8907b38f/sentry_sdk-2.55.0-py2.py3-none-any.whl", hash = "sha256:97026981cb15699394474a196b88503a393cbc58d182ece0d3abe12b9bd978d4", size = 449284, upload-time = "2026-03-17T14:15:49.604Z" }, ] [package.optional-dependencies] @@ -6386,15 +6386,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.52.1" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, ] [[package]] From 02e13e6d059af6ce200412c998f8fd950db2511e Mon Sep 17 00:00:00 2001 From: Renzo <170978465+RenzoMXD@users.noreply.github.com> Date: Mon, 23 Mar 2026 08:38:04 +0100 Subject: [PATCH 25/70] refactor: select in console app message controller (#33893) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/controllers/console/app/message.py | 40 ++++++++++--------- .../controllers/console/app/test_message.py | 20 +++++----- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/api/controllers/console/app/message.py b/api/controllers/console/app/message.py index 4fb73f61f3..736e7dbe17 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -4,7 +4,7 @@ from typing import Literal from flask import request from flask_restx import Resource, fields, marshal_with from pydantic import BaseModel, Field, field_validator -from sqlalchemy import exists, select +from sqlalchemy import exists, func, select from werkzeug.exceptions import InternalServerError, NotFound from controllers.common.schema import register_schema_models @@ -244,27 +244,25 @@ class ChatMessageListApi(Resource): def get(self, app_model): args = ChatMessagesQuery.model_validate(request.args.to_dict()) - conversation = ( - db.session.query(Conversation) + conversation = db.session.scalar( + select(Conversation) .where(Conversation.id == args.conversation_id, Conversation.app_id == app_model.id) - .first() + .limit(1) ) if not conversation: raise NotFound("Conversation Not Exists.") if args.first_id: - first_message = ( - db.session.query(Message) - .where(Message.conversation_id == conversation.id, Message.id == args.first_id) - .first() + first_message = db.session.scalar( + select(Message).where(Message.conversation_id == conversation.id, Message.id == args.first_id).limit(1) ) if not first_message: raise NotFound("First message not found") - history_messages = ( - db.session.query(Message) + history_messages = db.session.scalars( + select(Message) .where( Message.conversation_id == conversation.id, Message.created_at < first_message.created_at, @@ -272,16 +270,14 @@ class ChatMessageListApi(Resource): ) .order_by(Message.created_at.desc()) .limit(args.limit) - .all() - ) + ).all() else: - history_messages = ( - db.session.query(Message) + history_messages = db.session.scalars( + select(Message) .where(Message.conversation_id == conversation.id) .order_by(Message.created_at.desc()) .limit(args.limit) - .all() - ) + ).all() # Initialize has_more based on whether we have a full page if len(history_messages) == args.limit: @@ -326,7 +322,9 @@ class MessageFeedbackApi(Resource): message_id = str(args.message_id) - message = db.session.query(Message).where(Message.id == message_id, Message.app_id == app_model.id).first() + message = db.session.scalar( + select(Message).where(Message.id == message_id, Message.app_id == app_model.id).limit(1) + ) if not message: raise NotFound("Message Not Exists.") @@ -375,7 +373,9 @@ class MessageAnnotationCountApi(Resource): @login_required @account_initialization_required def get(self, app_model): - count = db.session.query(MessageAnnotation).where(MessageAnnotation.app_id == app_model.id).count() + count = db.session.scalar( + select(func.count(MessageAnnotation.id)).where(MessageAnnotation.app_id == app_model.id) + ) return {"count": count} @@ -479,7 +479,9 @@ class MessageApi(Resource): def get(self, app_model, message_id: str): message_id = str(message_id) - message = db.session.query(Message).where(Message.id == message_id, Message.app_id == app_model.id).first() + message = db.session.scalar( + select(Message).where(Message.id == message_id, Message.app_id == app_model.id).limit(1) + ) if not message: raise NotFound("Message Not Exists.") diff --git a/api/tests/unit_tests/controllers/console/app/test_message.py b/api/tests/unit_tests/controllers/console/app/test_message.py index 3ffa53b6db..e6dfc0d3bd 100644 --- a/api/tests/unit_tests/controllers/console/app/test_message.py +++ b/api/tests/unit_tests/controllers/console/app/test_message.py @@ -170,7 +170,7 @@ class TestMessageEndpoints: mock_app_model, qs={"conversation_id": "123e4567-e89b-12d3-a456-426614174000"}, ) as (api, mock_db, v_args): - mock_db.data_query.where.return_value.first.return_value = None + mock_db.session.scalar.return_value = None with pytest.raises(NotFound): api.get(**v_args) @@ -198,11 +198,11 @@ class TestMessageEndpoints: mock_msg.message = {} mock_msg.message_metadata_dict = {} - # mock returns - q_mock = mock_db.data_query - q_mock.where.return_value.first.side_effect = [mock_conv] - q_mock.where.return_value.order_by.return_value.limit.return_value.all.return_value = [mock_msg] - mock_db.session.scalar.return_value = False + # scalar() is called twice: first for conversation lookup, second for has_more check + mock_db.session.scalar.side_effect = [mock_conv, False] + scalars_result = MagicMock() + scalars_result.all.return_value = [mock_msg] + mock_db.session.scalars.return_value = scalars_result resp = api.get(**v_args) assert resp["limit"] == 1 @@ -219,7 +219,7 @@ class TestMessageEndpoints: mock_app_model, payload={"message_id": "123e4567-e89b-12d3-a456-426614174000"}, ) as (api, mock_db, v_args): - mock_db.data_query.where.return_value.first.return_value = None + mock_db.session.scalar.return_value = None with pytest.raises(NotFound): api.post(**v_args) @@ -231,7 +231,7 @@ class TestMessageEndpoints: ) as (api, mock_db, v_args): mock_msg = MagicMock() mock_msg.admin_feedback = None - mock_db.data_query.where.return_value.first.return_value = mock_msg + mock_db.session.scalar.return_value = mock_msg resp = api.post(**v_args) assert resp == {"result": "success"} @@ -240,7 +240,7 @@ class TestMessageEndpoints: with setup_test_context( app, MessageAnnotationCountApi, "/apps/app_123/annotations/count", "GET", mock_account, mock_app_model ) as (api, mock_db, v_args): - mock_db.data_query.where.return_value.count.return_value = 5 + mock_db.session.scalar.return_value = 5 resp = api.get(**v_args) assert resp == {"count": 5} @@ -314,7 +314,7 @@ class TestMessageEndpoints: mock_msg.message = {} mock_msg.message_metadata_dict = {} - mock_db.data_query.where.return_value.first.return_value = mock_msg + mock_db.session.scalar.return_value = mock_msg resp = api.get(**v_args) assert resp["id"] == "msg_123" From e5e8c0711c2ed6362f4ad18b075c6053293c40c8 Mon Sep 17 00:00:00 2001 From: Mahmoud Hamdy <148990144+mahmoodhamdi@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:56:00 +0200 Subject: [PATCH 26/70] refactor: rewrite docker/dify-env-sync.sh in Python for better maintainability (#33466) Co-authored-by: 99 --- docker/dify-env-sync.py | 440 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100755 docker/dify-env-sync.py diff --git a/docker/dify-env-sync.py b/docker/dify-env-sync.py new file mode 100755 index 0000000000..d7c762748c --- /dev/null +++ b/docker/dify-env-sync.py @@ -0,0 +1,440 @@ +#!/usr/bin/env python3 + +# ================================================================ +# Dify Environment Variables Synchronization Script +# +# Features: +# - Synchronize latest settings from .env.example to .env +# - Preserve custom settings in existing .env +# - Add new environment variables +# - Detect removed environment variables +# - Create backup files +# ================================================================ + +import argparse +import re +import shutil +import sys +from datetime import datetime +from pathlib import Path + +# ANSI color codes +RED = "\033[0;31m" +GREEN = "\033[0;32m" +YELLOW = "\033[1;33m" +BLUE = "\033[0;34m" +NC = "\033[0m" # No Color + + +def supports_color() -> bool: + """Return True if the terminal supports ANSI color codes.""" + return hasattr(sys.stdout, "isatty") and sys.stdout.isatty() + + +def log_info(message: str) -> None: + """Print an informational message in blue.""" + if supports_color(): + print(f"{BLUE}[INFO]{NC} {message}") + else: + print(f"[INFO] {message}") + + +def log_success(message: str) -> None: + """Print a success message in green.""" + if supports_color(): + print(f"{GREEN}[SUCCESS]{NC} {message}") + else: + print(f"[SUCCESS] {message}") + + +def log_warning(message: str) -> None: + """Print a warning message in yellow to stderr.""" + if supports_color(): + print(f"{YELLOW}[WARNING]{NC} {message}", file=sys.stderr) + else: + print(f"[WARNING] {message}", file=sys.stderr) + + +def log_error(message: str) -> None: + """Print an error message in red to stderr.""" + if supports_color(): + print(f"{RED}[ERROR]{NC} {message}", file=sys.stderr) + else: + print(f"[ERROR] {message}", file=sys.stderr) + + +def parse_env_file(path: Path) -> dict[str, str]: + """Parse an .env-style file and return a mapping of key to raw value. + + Lines that are blank or start with '#' (after optional whitespace) are + skipped. Only lines containing '=' are considered variable definitions. + + Args: + path: Path to the .env file to parse. + + Returns: + Ordered dict mapping variable name to its value string. + """ + variables: dict[str, str] = {} + with path.open(encoding="utf-8") as fh: + for line in fh: + line = line.rstrip("\n") + # Skip blank lines and comment lines + stripped = line.strip() + if not stripped or stripped.startswith("#"): + continue + if "=" not in line: + continue + key, _, value = line.partition("=") + key = key.strip() + if key: + variables[key] = value.strip() + return variables + + +def check_files(work_dir: Path) -> None: + """Verify required files exist; create .env from .env.example if absent. + + Args: + work_dir: Directory that must contain .env.example (and optionally .env). + + Raises: + SystemExit: If .env.example does not exist. + """ + log_info("Checking required files...") + + example_file = work_dir / ".env.example" + env_file = work_dir / ".env" + + if not example_file.exists(): + log_error(".env.example file not found") + sys.exit(1) + + if not env_file.exists(): + log_warning(".env file does not exist. Creating from .env.example.") + shutil.copy2(example_file, env_file) + log_success(".env file created") + + log_success("Required files verified") + + +def create_backup(work_dir: Path) -> None: + """Create a timestamped backup of the current .env file. + + Backups are placed in ``/env-backup/`` with the filename + ``.env.backup_``. + + Args: + work_dir: Directory containing the .env file to back up. + """ + env_file = work_dir / ".env" + if not env_file.exists(): + return + + backup_dir = work_dir / "env-backup" + if not backup_dir.exists(): + backup_dir.mkdir(parents=True) + log_info(f"Created backup directory: {backup_dir}") + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_file = backup_dir / f".env.backup_{timestamp}" + shutil.copy2(env_file, backup_file) + log_success(f"Backed up existing .env to {backup_file}") + + +def analyze_value_change(current: str, recommended: str) -> str | None: + """Analyse what kind of change occurred between two env values. + + Args: + current: Value currently set in .env. + recommended: Value present in .env.example. + + Returns: + A human-readable description string, or None when no analysis applies. + """ + use_colors = supports_color() + + def colorize(color: str, text: str) -> str: + return f"{color}{text}{NC}" if use_colors else text + + if not current and recommended: + return colorize(RED, " -> Setting from empty to recommended value") + if current and not recommended: + return colorize(RED, " -> Recommended value changed to empty") + + # Numeric comparison + if re.fullmatch(r"\d+", current) and re.fullmatch(r"\d+", recommended): + cur_int, rec_int = int(current), int(recommended) + if cur_int < rec_int: + return colorize(BLUE, f" -> Numeric increase ({current} < {recommended})") + if cur_int > rec_int: + return colorize(YELLOW, f" -> Numeric decrease ({current} > {recommended})") + return None + + # Boolean comparison + if current.lower() in {"true", "false"} and recommended.lower() in {"true", "false"}: + if current.lower() != recommended.lower(): + return colorize(BLUE, f" -> Boolean value change ({current} -> {recommended})") + return None + + # URL / endpoint + if current.startswith(("http://", "https://")) or recommended.startswith(("http://", "https://")): + return colorize(BLUE, " -> URL/endpoint change") + + # File path + if current.startswith("/") or recommended.startswith("/"): + return colorize(BLUE, " -> File path change") + + # String length + if len(current) != len(recommended): + return colorize(YELLOW, f" -> String length change ({len(current)} -> {len(recommended)} characters)") + + return None + + +def detect_differences(env_vars: dict[str, str], example_vars: dict[str, str]) -> dict[str, tuple[str, str]]: + """Find variables whose values differ between .env and .env.example. + + Only variables present in *both* files are compared; new or removed + variables are handled by separate functions. + + Args: + env_vars: Parsed key/value pairs from .env. + example_vars: Parsed key/value pairs from .env.example. + + Returns: + Mapping of key -> (env_value, example_value) for every key whose + values differ. + """ + log_info("Detecting differences between .env and .env.example...") + + diffs: dict[str, tuple[str, str]] = {} + for key, example_value in example_vars.items(): + if key in env_vars and env_vars[key] != example_value: + diffs[key] = (env_vars[key], example_value) + + if diffs: + log_success(f"Detected differences in {len(diffs)} environment variables") + show_differences_detail(diffs) + else: + log_info("No differences detected") + + return diffs + + +def show_differences_detail(diffs: dict[str, tuple[str, str]]) -> None: + """Print a formatted table of differing environment variables. + + Args: + diffs: Mapping of key -> (current_value, recommended_value). + """ + use_colors = supports_color() + + log_info("") + log_info("=== Environment Variable Differences ===") + + if not diffs: + log_info("No differences to display") + return + + for count, (key, (env_value, example_value)) in enumerate(diffs.items(), start=1): + print() + if use_colors: + print(f"{YELLOW}[{count}] {key}{NC}") + print(f" {GREEN}.env (current){NC} : {env_value}") + print(f" {BLUE}.env.example (recommended){NC} : {example_value}") + else: + print(f"[{count}] {key}") + print(f" .env (current) : {env_value}") + print(f" .env.example (recommended) : {example_value}") + + analysis = analyze_value_change(env_value, example_value) + if analysis: + print(analysis) + + print() + log_info("=== Difference Analysis Complete ===") + log_info("Note: Consider changing to the recommended values above.") + log_info("Current implementation preserves .env values.") + print() + + +def detect_removed_variables(env_vars: dict[str, str], example_vars: dict[str, str]) -> list[str]: + """Identify variables present in .env but absent from .env.example. + + Args: + env_vars: Parsed key/value pairs from .env. + example_vars: Parsed key/value pairs from .env.example. + + Returns: + Sorted list of variable names that no longer appear in .env.example. + """ + log_info("Detecting removed environment variables...") + + removed = sorted(set(env_vars) - set(example_vars)) + + if removed: + log_warning("The following environment variables have been removed from .env.example:") + for var in removed: + log_warning(f" - {var}") + log_warning("Consider manually removing these variables from .env") + else: + log_success("No removed environment variables found") + + return removed + + +def sync_env_file(work_dir: Path, env_vars: dict[str, str], diffs: dict[str, tuple[str, str]]) -> None: + """Rewrite .env based on .env.example while preserving custom values. + + The output file follows the exact line structure of .env.example + (preserving comments, blank lines, and ordering). For every variable + that exists in .env with a different value from the example, the + current .env value is kept. Variables that are new in .env.example + (not present in .env at all) are added with the example's default. + + Args: + work_dir: Directory containing .env and .env.example. + env_vars: Parsed key/value pairs from the original .env. + diffs: Keys whose .env values differ from .env.example (to preserve). + """ + log_info("Starting partial synchronization of .env file...") + + example_file = work_dir / ".env.example" + new_env_file = work_dir / ".env.new" + + # Keys whose current .env value should override the example default + preserved_keys: set[str] = set(diffs.keys()) + + preserved_count = 0 + updated_count = 0 + + env_var_pattern = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)\s*=") + + with example_file.open(encoding="utf-8") as src, new_env_file.open("w", encoding="utf-8") as dst: + for line in src: + raw_line = line.rstrip("\n") + match = env_var_pattern.match(raw_line) + if match: + key = match.group(1) + if key in preserved_keys: + # Write the preserved value from .env + dst.write(f"{key}={env_vars[key]}\n") + log_info(f" Preserved: {key} (.env value)") + preserved_count += 1 + else: + # Use the example value (covers new vars and unchanged ones) + dst.write(line if line.endswith("\n") else raw_line + "\n") + updated_count += 1 + else: + # Blank line, comment, or non-variable line — keep as-is + dst.write(line if line.endswith("\n") else raw_line + "\n") + + # Atomically replace the original .env + try: + new_env_file.replace(work_dir / ".env") + except OSError as exc: + log_error(f"Failed to replace .env file: {exc}") + new_env_file.unlink(missing_ok=True) + sys.exit(1) + + log_success("Successfully created new .env file") + log_success("Partial synchronization of .env file completed") + log_info(f" Preserved .env values: {preserved_count}") + log_info(f" Updated to .env.example values: {updated_count}") + + +def show_statistics(work_dir: Path) -> None: + """Print a summary of variable counts from both env files. + + Args: + work_dir: Directory containing .env and .env.example. + """ + log_info("Synchronization statistics:") + + example_file = work_dir / ".env.example" + env_file = work_dir / ".env" + + example_count = len(parse_env_file(example_file)) if example_file.exists() else 0 + env_count = len(parse_env_file(env_file)) if env_file.exists() else 0 + + log_info(f" .env.example environment variables: {example_count}") + log_info(f" .env environment variables: {env_count}") + + +def build_arg_parser() -> argparse.ArgumentParser: + """Build and return the CLI argument parser. + + Returns: + Configured ArgumentParser instance. + """ + parser = argparse.ArgumentParser( + prog="dify-env-sync", + description=( + "Synchronize .env with .env.example: add new variables, " + "preserve custom values, and report removed variables." + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=( + "Examples:\n" + " # Run from the docker/ directory (default)\n" + " python dify-env-sync.py\n\n" + " # Specify a custom working directory\n" + " python dify-env-sync.py --dir /path/to/docker\n" + ), + ) + parser.add_argument( + "--dir", + metavar="DIRECTORY", + default=".", + help="Working directory containing .env and .env.example (default: current directory)", + ) + parser.add_argument( + "--no-backup", + action="store_true", + default=False, + help="Skip creating a timestamped backup of the existing .env file", + ) + return parser + + +def main() -> None: + """Orchestrate the complete environment variable synchronization process.""" + parser = build_arg_parser() + args = parser.parse_args() + + work_dir = Path(args.dir).resolve() + + log_info("=== Dify Environment Variables Synchronization Script ===") + log_info(f"Execution started: {datetime.now()}") + log_info(f"Working directory: {work_dir}") + + # 1. Verify prerequisites + check_files(work_dir) + + # 2. Backup existing .env + if not args.no_backup: + create_backup(work_dir) + + # 3. Parse both files + env_vars = parse_env_file(work_dir / ".env") + example_vars = parse_env_file(work_dir / ".env.example") + + # 4. Report differences (values that changed in the example) + diffs = detect_differences(env_vars, example_vars) + + # 5. Report variables removed from the example + detect_removed_variables(env_vars, example_vars) + + # 6. Rewrite .env + sync_env_file(work_dir, env_vars, diffs) + + # 7. Print summary statistics + show_statistics(work_dir) + + log_success("=== Synchronization process completed successfully ===") + log_info(f"Execution finished: {datetime.now()}") + + +if __name__ == "__main__": + main() From 93369352959ee1a17949666e32879ba4a2f9dba1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:57:17 +0900 Subject: [PATCH 27/70] chore(deps-dev): bump the storage group across 1 directory with 2 updates (#33915) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/uv.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/uv.lock b/api/uv.lock index dbda8941f4..38522082b4 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -582,16 +582,16 @@ wheels = [ [[package]] name = "bce-python-sdk" -version = "0.9.63" +version = "0.9.64" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "future" }, { name = "pycryptodome" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/ab/4c2927b01a97562af6a296b722eee79658335795f341a395a12742d5e1a3/bce_python_sdk-0.9.63.tar.gz", hash = "sha256:0c80bc3ac128a0a144bae3b8dff1f397f42c30b36f7677e3a39d8df8e77b1088", size = 284419, upload-time = "2026-03-06T14:54:06.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/047e9c1a6c97e0cd4d93a6490abd8fbc2ccd13569462fc0228699edc08bc/bce_python_sdk-0.9.64.tar.gz", hash = "sha256:901bf787c26ad35855a80d65e58d7584c8541f7f0f2af20847830e572e5b622e", size = 287125, upload-time = "2026-03-17T11:24:29.345Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/a4/501e978776c7060aa8ba77e68536597e754d938bcdbe1826618acebfbddf/bce_python_sdk-0.9.63-py3-none-any.whl", hash = "sha256:ec66eee8807c6aa4036412592da7e8c9e2cd7fdec494190986288ac2195d8276", size = 400305, upload-time = "2026-03-06T14:53:52.887Z" }, + { url = "https://files.pythonhosted.org/packages/48/7f/dd289582f37ab4effea47b2a8503880db4781ca0fc8e0a8ed5ff493359e5/bce_python_sdk-0.9.64-py3-none-any.whl", hash = "sha256:eaad97e4f0e7d613ae978da3cdc5294e9f724ffca2735f79820037fa1317cd6d", size = 402233, upload-time = "2026-03-17T11:24:24.673Z" }, ] [[package]] @@ -2619,7 +2619,7 @@ wheels = [ [[package]] name = "google-cloud-storage" -version = "3.9.0" +version = "3.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, @@ -2629,9 +2629,9 @@ dependencies = [ { name = "google-resumable-media" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/b1/4f0798e88285b50dfc60ed3a7de071def538b358db2da468c2e0deecbb40/google_cloud_storage-3.9.0.tar.gz", hash = "sha256:f2d8ca7db2f652be757e92573b2196e10fbc09649b5c016f8b422ad593c641cc", size = 17298544, upload-time = "2026-02-02T13:36:34.119Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/e3/747759eebc72e420c25903d6bc231d0ceb110b66ac7e6ee3f350417152cd/google_cloud_storage-3.10.0.tar.gz", hash = "sha256:1aeebf097c27d718d84077059a28d7e87f136f3700212215f1ceeae1d1c5d504", size = 17309829, upload-time = "2026-03-18T15:54:11.875Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/0b/816a6ae3c9fd096937d2e5f9670558908811d57d59ddf69dd4b83b326fd1/google_cloud_storage-3.9.0-py3-none-any.whl", hash = "sha256:2dce75a9e8b3387078cbbdad44757d410ecdb916101f8ba308abf202b6968066", size = 321324, upload-time = "2026-02-02T13:36:32.271Z" }, + { url = "https://files.pythonhosted.org/packages/29/e2/d58442f4daee5babd9255cf492a1f3d114357164072f8339a22a3ad460a2/google_cloud_storage-3.10.0-py3-none-any.whl", hash = "sha256:0072e7783b201e45af78fd9779894cdb6bec2bf922ee932f3fcc16f8bce9b9a3", size = 324382, upload-time = "2026-03-18T15:54:10.091Z" }, ] [[package]] From d7cafc629642a316eae0e18b1dc7ec39b94fc406 Mon Sep 17 00:00:00 2001 From: Bowen Liang Date: Mon, 23 Mar 2026 16:22:33 +0800 Subject: [PATCH 28/70] chore(dep): move hono and @hono/node-server to devDependencies (#33742) --- web/package.json | 4 ++-- web/pnpm-lock.yaml | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/package.json b/web/package.json index fdff69acb6..7d82b6afde 100644 --- a/web/package.json +++ b/web/package.json @@ -66,7 +66,6 @@ "@formatjs/intl-localematcher": "0.8.2", "@headlessui/react": "2.2.9", "@heroicons/react": "2.2.0", - "@hono/node-server": "1.19.11", "@lexical/code": "0.42.0", "@lexical/link": "0.42.0", "@lexical/list": "0.42.0", @@ -108,7 +107,6 @@ "es-toolkit": "1.45.1", "fast-deep-equal": "3.1.3", "foxact": "0.3.0", - "hono": "4.12.8", "html-entities": "2.6.0", "html-to-image": "1.11.13", "i18next": "25.10.4", @@ -170,6 +168,7 @@ "@chromatic-com/storybook": "5.0.2", "@egoist/tailwindcss-icons": "1.9.2", "@eslint-react/eslint-plugin": "3.0.0", + "@hono/node-server": "1.19.11", "@iconify-json/heroicons": "1.2.3", "@iconify-json/ri": "1.2.10", "@mdx-js/loader": "3.1.1", @@ -224,6 +223,7 @@ "eslint-plugin-react-refresh": "0.5.2", "eslint-plugin-sonarjs": "4.0.2", "eslint-plugin-storybook": "10.3.1", + "hono": "4.12.8", "husky": "9.1.7", "iconify-import-svg": "0.1.2", "jsdom": "29.0.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index f72b889788..f0a3054947 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -85,9 +85,6 @@ importers: '@heroicons/react': specifier: 2.2.0 version: 2.2.0(react@19.2.4) - '@hono/node-server': - specifier: 1.19.11 - version: 1.19.11(hono@4.12.8) '@lexical/code': specifier: npm:lexical-code-no-prism@0.41.0 version: lexical-code-no-prism@0.41.0(@lexical/utils@0.42.0)(lexical@0.42.0) @@ -211,9 +208,6 @@ importers: foxact: specifier: 0.3.0 version: 0.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - hono: - specifier: 4.12.8 - version: 4.12.8 html-entities: specifier: 2.6.0 version: 2.6.0 @@ -392,6 +386,9 @@ importers: '@eslint-react/eslint-plugin': specifier: 3.0.0 version: 3.0.0(eslint@10.1.0(jiti@1.21.7))(typescript@5.9.3) + '@hono/node-server': + specifier: 1.19.11 + version: 1.19.11(hono@4.12.8) '@iconify-json/heroicons': specifier: 1.2.3 version: 1.2.3 @@ -554,6 +551,9 @@ importers: eslint-plugin-storybook: specifier: 10.3.1 version: 10.3.1(eslint@10.1.0(jiti@1.21.7))(storybook@10.3.1(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + hono: + specifier: 4.12.8 + version: 4.12.8 husky: specifier: 9.1.7 version: 9.1.7 From 407f5f0cde1ddb838b23bdb3fdf6f10f43e0b9cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:25:44 +0900 Subject: [PATCH 29/70] chore(deps-dev): bump alibabacloud-gpdb20160503 from 3.8.3 to 5.1.0 in /api in the vdb group (#33879) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/pyproject.toml | 2 +- api/uv.lock | 90 ++++------------------------------------------ 2 files changed, 8 insertions(+), 84 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index a16e1ed934..d4d0ebcf7f 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -203,7 +203,7 @@ tools = ["cloudscraper~=1.2.71", "nltk~=3.9.1"] # Required by vector store clients ############################################################ vdb = [ - "alibabacloud_gpdb20160503~=3.8.0", + "alibabacloud_gpdb20160503~=5.1.0", "alibabacloud_tea_openapi~=0.4.3", "chromadb==0.5.20", "clickhouse-connect~=0.14.1", diff --git a/api/uv.lock b/api/uv.lock index 38522082b4..2d1d9fb1d6 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -169,12 +169,6 @@ version = "1.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a0/87/1d7019d23891897cb076b2f7e3c81ab3c2ba91de3bb067196f675d60d34c/alibabacloud-credentials-api-1.0.0.tar.gz", hash = "sha256:8c340038d904f0218d7214a8f4088c31912bfcf279af2cbc7d9be4897a97dd2f", size = 2330, upload-time = "2025-01-13T05:53:04.931Z" } -[[package]] -name = "alibabacloud-endpoint-util" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/7d/8cc92a95c920e344835b005af6ea45a0db98763ad6ad19299d26892e6c8d/alibabacloud_endpoint_util-0.0.4.tar.gz", hash = "sha256:a593eb8ddd8168d5dc2216cd33111b144f9189fcd6e9ca20e48f358a739bbf90", size = 2813, upload-time = "2025-06-12T07:20:52.572Z" } - [[package]] name = "alibabacloud-gateway-spi" version = "0.0.3" @@ -186,69 +180,17 @@ sdist = { url = "https://files.pythonhosted.org/packages/ab/98/d7111245f17935bf7 [[package]] name = "alibabacloud-gpdb20160503" -version = "3.8.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alibabacloud-endpoint-util" }, - { name = "alibabacloud-openapi-util" }, - { name = "alibabacloud-openplatform20191219" }, - { name = "alibabacloud-oss-sdk" }, - { name = "alibabacloud-oss-util" }, - { name = "alibabacloud-tea-fileform" }, - { name = "alibabacloud-tea-openapi" }, - { name = "alibabacloud-tea-util" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/15/6a/cc72e744e95c8f37fa6a84e66ae0b9b57a13ee97a0ef03d94c7127c31d75/alibabacloud_gpdb20160503-3.8.3.tar.gz", hash = "sha256:4dfcc0d9cff5a921d529d76f4bf97e2ceb9dc2fa53f00ab055f08509423d8e30", size = 155092, upload-time = "2024-07-18T17:09:42.438Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/36/bce41704b3bf59d607590ec73a42a254c5dea27c0f707aee11d20512a200/alibabacloud_gpdb20160503-3.8.3-py3-none-any.whl", hash = "sha256:06e1c46ce5e4e9d1bcae76e76e51034196c625799d06b2efec8d46a7df323fe8", size = 156097, upload-time = "2024-07-18T17:09:40.414Z" }, -] - -[[package]] -name = "alibabacloud-openapi-util" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alibabacloud-tea-util" }, - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f6/50/5f41ab550d7874c623f6e992758429802c4b52a6804db437017e5387de33/alibabacloud_openapi_util-0.2.2.tar.gz", hash = "sha256:ebbc3906f554cb4bf8f513e43e8a33e8b6a3d4a0ef13617a0e14c3dda8ef52a8", size = 7201, upload-time = "2023-10-23T07:44:18.523Z" } - -[[package]] -name = "alibabacloud-openplatform20191219" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alibabacloud-endpoint-util" }, - { name = "alibabacloud-openapi-util" }, - { name = "alibabacloud-tea-openapi" }, - { name = "alibabacloud-tea-util" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4f/bf/f7fa2f3657ed352870f442434cb2f27b7f70dcd52a544a1f3998eeaf6d71/alibabacloud_openplatform20191219-2.0.0.tar.gz", hash = "sha256:e67f4c337b7542538746592c6a474bd4ae3a9edccdf62e11a32ca61fad3c9020", size = 5038, upload-time = "2022-09-21T06:16:10.683Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/e5/18c75213551eeca9db1f6b41ddcc0bd87b5b6508c75a67f05cd8671847b4/alibabacloud_openplatform20191219-2.0.0-py3-none-any.whl", hash = "sha256:873821c45bca72a6c6ec7a906c9cb21554c122e88893bbac3986934dab30dd36", size = 5204, upload-time = "2022-09-21T06:16:07.844Z" }, -] - -[[package]] -name = "alibabacloud-oss-sdk" -version = "0.1.1" +version = "5.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alibabacloud-credentials" }, - { name = "alibabacloud-oss-util" }, - { name = "alibabacloud-tea-fileform" }, - { name = "alibabacloud-tea-util" }, - { name = "alibabacloud-tea-xml" }, + { name = "alibabacloud-tea-openapi" }, + { name = "darabonba-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/d1/f442dd026908fcf55340ca694bb1d027aa91e119e76ae2fbea62f2bde4f4/alibabacloud_oss_sdk-0.1.1.tar.gz", hash = "sha256:f51a368020d0964fcc0978f96736006f49f5ab6a4a4bf4f0b8549e2c659e7358", size = 46434, upload-time = "2025-04-22T12:40:41.717Z" } - -[[package]] -name = "alibabacloud-oss-util" -version = "0.0.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alibabacloud-tea" }, +sdist = { url = "https://files.pythonhosted.org/packages/b3/36/69333c7fb7fb5267f338371b14fdd8dbdd503717c97bbc7a6419d155ab4c/alibabacloud_gpdb20160503-5.1.0.tar.gz", hash = "sha256:086ec6d5e39b64f54d0e44bb3fd4fde1a4822a53eb9f6ff7464dff7d19b07b63", size = 295641, upload-time = "2026-03-19T10:09:02.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/7f/a91a2f9ad97c92fa9a6981587ea0ff789240cea05b17b17b7c244e5bac64/alibabacloud_gpdb20160503-5.1.0-py3-none-any.whl", hash = "sha256:580e4579285a54c7f04570782e0f60423a1997568684187fe88e4110acfb640e", size = 848784, upload-time = "2026-03-19T10:09:00.72Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/7c/d7e812b9968247a302573daebcfef95d0f9a718f7b4bfcca8d3d83e266be/alibabacloud_oss_util-0.0.6.tar.gz", hash = "sha256:d3ecec36632434bd509a113e8cf327dc23e830ac8d9dd6949926f4e334c8b5d6", size = 10008, upload-time = "2021-04-28T09:25:04.056Z" } [[package]] name = "alibabacloud-tea" @@ -260,15 +202,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9a/7d/b22cb9a0d4f396ee0f3f9d7f26b76b9ed93d4101add7867a2c87ed2534f5/alibabacloud-tea-0.4.3.tar.gz", hash = "sha256:ec8053d0aa8d43ebe1deb632d5c5404339b39ec9a18a0707d57765838418504a", size = 8785, upload-time = "2025-03-24T07:34:42.958Z" } -[[package]] -name = "alibabacloud-tea-fileform" -version = "0.0.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alibabacloud-tea" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/8a/ef8ddf5ee0350984cad2749414b420369fe943e15e6d96b79be45367630e/alibabacloud_tea_fileform-0.0.5.tar.gz", hash = "sha256:fd00a8c9d85e785a7655059e9651f9e91784678881831f60589172387b968ee8", size = 3961, upload-time = "2021-04-28T09:22:54.56Z" } - [[package]] name = "alibabacloud-tea-openapi" version = "0.4.3" @@ -297,15 +230,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/72/9e/c394b4e2104766fb28a1e44e3ed36e4c7773b4d05c868e482be99d5635c9/alibabacloud_tea_util-0.3.14-py3-none-any.whl", hash = "sha256:10d3e5c340d8f7ec69dd27345eb2fc5a1dab07875742525edf07bbe86db93bfe", size = 6697, upload-time = "2025-11-19T06:01:07.355Z" }, ] -[[package]] -name = "alibabacloud-tea-xml" -version = "0.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alibabacloud-tea" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/32/eb/5e82e419c3061823f3feae9b5681588762929dc4da0176667297c2784c1a/alibabacloud_tea_xml-0.0.3.tar.gz", hash = "sha256:979cb51fadf43de77f41c69fc69c12529728919f849723eb0cd24eb7b048a90c", size = 3466, upload-time = "2025-07-01T08:04:55.144Z" } - [[package]] name = "aliyun-log-python-sdk" version = "0.9.37" @@ -1912,7 +1836,7 @@ tools = [ { name = "nltk", specifier = "~=3.9.1" }, ] vdb = [ - { name = "alibabacloud-gpdb20160503", specifier = "~=3.8.0" }, + { name = "alibabacloud-gpdb20160503", specifier = "~=5.1.0" }, { name = "alibabacloud-tea-openapi", specifier = "~=0.4.3" }, { name = "chromadb", specifier = "==0.5.20" }, { name = "clickhouse-connect", specifier = "~=0.14.1" }, From edb261bc900e2be8a06a4f37d1607443244f4906 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:26:47 +0900 Subject: [PATCH 30/70] chore(deps-dev): bump the dev group across 1 directory with 12 updates (#33919) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- api/uv.lock | 164 ++++++++++++++++++++++++++-------------------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/api/uv.lock b/api/uv.lock index 2d1d9fb1d6..30c5b851bc 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -494,14 +494,14 @@ wheels = [ [[package]] name = "basedpyright" -version = "1.38.2" +version = "1.38.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodejs-wheel-binaries" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/a3/20aa7c4e83f2f614e0036300f3c352775dede0655c66814da16c37b661a9/basedpyright-1.38.2.tar.gz", hash = "sha256:b433b2b8ba745ed7520cdc79a29a03682f3fb00346d272ece5944e9e5e5daa92", size = 25277019, upload-time = "2026-02-26T11:18:43.594Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/58/7abba2c743571a42b2548f07aee556ebc1e4d0bc2b277aeba1ee6c83b0af/basedpyright-1.38.3.tar.gz", hash = "sha256:9725419786afbfad8a9539527f162da02d462afad440b0412fdb3f3cdf179b90", size = 25277430, upload-time = "2026-03-17T13:10:41.526Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/12/736cab83626fea3fe65cdafb3ef3d2ee9480c56723f2fd33921537289a5e/basedpyright-1.38.2-py3-none-any.whl", hash = "sha256:153481d37fd19f9e3adedc8629d1d071b10c5f5e49321fb026b74444b7c70e24", size = 12312475, upload-time = "2026-02-26T11:18:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e3/3ebb5c23bd3abb5fc2053b8a06a889aa5c1cf8cff738c78cb6c1957e90cd/basedpyright-1.38.3-py3-none-any.whl", hash = "sha256:1f15c2e489c67d6c5e896c24b6a63251195c04223a55e4568b8f8e8ed49ca830", size = 12313363, upload-time = "2026-03-17T13:10:47.344Z" }, ] [[package]] @@ -644,16 +644,16 @@ wheels = [ [[package]] name = "boto3-stubs" -version = "1.42.68" +version = "1.42.73" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore-stubs" }, { name = "types-s3transfer" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/8c/dd4b0c95ff008bed5a35ab411452ece121b355539d2a0b6dcd62a0c47be5/boto3_stubs-1.42.68.tar.gz", hash = "sha256:96ad1020735619483fb9b4da7a5e694b460bf2e18f84a34d5d175d0ffe8c4653", size = 101372, upload-time = "2026-03-13T19:49:54.867Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/c3/fcc47102c63278af25ad57c93d97dc393f4dbc54c0117a29c78f2b96ec1e/boto3_stubs-1.42.73.tar.gz", hash = "sha256:36f625769b5505c4bc627f16244b98de9e10dae3ac36f1aa0f0ebe2f201dc138", size = 101373, upload-time = "2026-03-20T19:59:51.463Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/15/3ca5848917214a168134512a5b45f856a56e913659888947a052e02031b5/boto3_stubs-1.42.68-py3-none-any.whl", hash = "sha256:ed7f98334ef7b2377fa8532190e63dc2c6d1dc895e3d7cb3d6d1c83771b81bf6", size = 70011, upload-time = "2026-03-13T19:49:42.801Z" }, + { url = "https://files.pythonhosted.org/packages/4b/57/d570ba61a2a0c7fe0c8667e41269a0480293cb53e1786d6661a2bd827fc5/boto3_stubs-1.42.73-py3-none-any.whl", hash = "sha256:bd658429069d8215247fc3abc003220cd875c24ab6eda7b3405090408afaacdf", size = 70009, upload-time = "2026-03-20T19:59:43.786Z" }, ] [package.optional-dependencies] @@ -1214,41 +1214,41 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.4" +version = "7.13.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, - { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, - { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, - { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, - { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, - { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, - { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, - { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [package.optional-dependencies] @@ -5981,27 +5981,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.6" +version = "0.15.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, - { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, - { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, - { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, - { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, - { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, - { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, - { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, - { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, - { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, - { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, + { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] [[package]] @@ -6040,14 +6040,14 @@ wheels = [ [[package]] name = "scipy-stubs" -version = "1.17.1.2" +version = "1.17.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "optype", extra = ["numpy"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/ab/43f681ffba42f363b7ed6b767fd215d1e26006578214ff8330586a11bf95/scipy_stubs-1.17.1.2.tar.gz", hash = "sha256:2ecadc8c87a3b61aaf7379d6d6b10f1038a829c53b9efe5b174fb97fc8b52237", size = 388354, upload-time = "2026-03-15T22:33:20.449Z" } +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" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/0b/ec4fe720c1202d9df729a3e9d9b7e4d2da9f6e7f28bd2877b7d0769f4f75/scipy_stubs-1.17.1.2-py3-none-any.whl", hash = "sha256:f19e8f5273dbe3b7ee6a9554678c3973b9695fa66b91f29206d00830a1536c06", size = 594377, upload-time = "2026-03-15T22:33:18.684Z" }, + { 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" }, ] [[package]] @@ -6727,11 +6727,11 @@ wheels = [ [[package]] name = "types-cachetools" -version = "6.2.0.20251022" +version = "6.2.0.20260317" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/a8/f9bcc7f1be63af43ef0170a773e2d88817bcc7c9d8769f2228c802826efe/types_cachetools-6.2.0.20251022.tar.gz", hash = "sha256:f1d3c736f0f741e89ec10f0e1b0138625023e21eb33603a930c149e0318c0cef", size = 9608, upload-time = "2025-10-22T03:03:58.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/7f/16a4d8344c28193a5a74358028c2d2f753f0d9658dd98b9e1967c50045a2/types_cachetools-6.2.0.20260317.tar.gz", hash = "sha256:6d91855bcc944665897c125e720aa3c80aace929b77a64e796343701df4f61c6", size = 9812, upload-time = "2026-03-17T04:06:32.007Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/2d/8d821ed80f6c2c5b427f650bf4dc25b80676ed63d03388e4b637d2557107/types_cachetools-6.2.0.20251022-py3-none-any.whl", hash = "sha256:698eb17b8f16b661b90624708b6915f33dbac2d185db499ed57e4997e7962cad", size = 9341, upload-time = "2025-10-22T03:03:57.036Z" }, + { url = "https://files.pythonhosted.org/packages/17/9a/b00b23054934c4d569c19f7278c4fb32746cd36a64a175a216d3073a4713/types_cachetools-6.2.0.20260317-py3-none-any.whl", hash = "sha256:92fa9bc50e4629e31fca67ceb3fb1de71791e314fa16c0a0d2728724dc222c8b", size = 9346, upload-time = "2026-03-17T04:06:31.184Z" }, ] [[package]] @@ -6775,11 +6775,11 @@ wheels = [ [[package]] name = "types-docutils" -version = "0.22.3.20260316" +version = "0.22.3.20260322" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/27/a7f16b3a2fad0a4ddd85a668319f9a1d0311c4bd9578894f6471c7e6c788/types_docutils-0.22.3.20260316.tar.gz", hash = "sha256:8ef27d565b9831ff094fe2eac75337a74151013e2d21ecabd445c2955f891564", size = 57263, upload-time = "2026-03-16T04:29:12.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/bb/243a87fc1605a4a94c2c343d6dbddbf0d7ef7c0b9550f360b8cda8e82c39/types_docutils-0.22.3.20260322.tar.gz", hash = "sha256:e2450bb997283c3141ec5db3e436b91f0aa26efe35eb9165178ca976ccb4930b", size = 57311, upload-time = "2026-03-22T04:08:44.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/60/c1f22b7cfc4837d5419e5a2d8702c7d65f03343f866364b71cccd8a73b79/types_docutils-0.22.3.20260316-py3-none-any.whl", hash = "sha256:083c7091b8072c242998ec51da1bf1492f0332387da81c3b085efbf5ca754c7d", size = 91968, upload-time = "2026-03-16T04:29:11.114Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4a/22c090cd4615a16917dff817cbe7c5956da376c961e024c241cd962d2c3d/types_docutils-0.22.3.20260322-py3-none-any.whl", hash = "sha256:681d4510ce9b80a0c6a593f0f9843d81f8caa786db7b39ba04d9fd5480ac4442", size = 91978, upload-time = "2026-03-22T04:08:43.117Z" }, ] [[package]] @@ -6809,15 +6809,15 @@ wheels = [ [[package]] name = "types-gevent" -version = "25.9.0.20251228" +version = "25.9.0.20260322" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-greenlet" }, { name = "types-psutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/85/c5043c4472f82c8ee3d9e0673eb4093c7d16770a26541a137a53a1d096f6/types_gevent-25.9.0.20251228.tar.gz", hash = "sha256:423ef9891d25c5a3af236c3e9aace4c444c86ff773fe13ef22731bc61d59abef", size = 38063, upload-time = "2025-12-28T03:28:28.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/f0/14a99ddcaa69b559fa7cec8c9de880b792bebb0b848ae865d94ea9058533/types_gevent-25.9.0.20260322.tar.gz", hash = "sha256:91257920845762f09753c08aa20fad1743ac13d2de8bcf23f4b8fe967d803732", size = 38241, upload-time = "2026-03-22T04:08:55.213Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/b7/a2d6b652ab5a26318b68cafd58c46fafb9b15c5313d2d76a70b838febb4b/types_gevent-25.9.0.20251228-py3-none-any.whl", hash = "sha256:e2e225af4fface9241c16044983eb2fc3993f2d13d801f55c2932848649b7f2f", size = 55486, upload-time = "2025-12-28T03:28:27.382Z" }, + { url = "https://files.pythonhosted.org/packages/89/0f/964440b57eb4ddb4aca03479a4093852e1ce79010d1c5967234e6f5d6bd9/types_gevent-25.9.0.20260322-py3-none-any.whl", hash = "sha256:21b3c269b3a20ecb0e4668289c63b97d21694d84a004ab059c1e32ab970eacc2", size = 55500, upload-time = "2026-03-22T04:08:54.103Z" }, ] [[package]] @@ -6900,11 +6900,11 @@ wheels = [ [[package]] name = "types-openpyxl" -version = "3.1.5.20260316" +version = "3.1.5.20260322" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/38/32f8ee633dd66ca6d52b8853b9fd45dc3869490195a6ed435d5c868b9c2d/types_openpyxl-3.1.5.20260316.tar.gz", hash = "sha256:081dda9427ea1141e5649e3dcf630e7013a4cf254a5862a7e0a3f53c123b7ceb", size = 101318, upload-time = "2026-03-16T04:29:05.004Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/bf/15240de4d68192d2a1f385ef2f6f1ecb29b85d2f3791dd2e2d5b980be30f/types_openpyxl-3.1.5.20260322.tar.gz", hash = "sha256:a61d66ebe1e49697853c6db8e0929e1cda2c96755e71fb676ed7fc48dfdcf697", size = 101325, upload-time = "2026-03-22T04:08:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/df/b87ae6226ed7cc84b9e43119c489c7f053a9a25e209e0ebb5d84bc36fa37/types_openpyxl-3.1.5.20260316-py3-none-any.whl", hash = "sha256:38e7e125df520fb7eb72cb1129c9f024eb99ef9564aad2c27f68f080c26bcf2d", size = 166084, upload-time = "2026-03-16T04:29:03.657Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/c14191b30bcb266365b124b2bb4e67ecd68425a78ba77ee026f33667daa9/types_openpyxl-3.1.5.20260322-py3-none-any.whl", hash = "sha256:2f515f0b0bbfb04bfb587de34f7522d90b5151a8da7bbbd11ecec4ca40f64238", size = 166102, upload-time = "2026-03-22T04:08:39.174Z" }, ] [[package]] @@ -6979,11 +6979,11 @@ wheels = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20260305" +version = "2.9.0.20260323" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/c7/025c624f347e10476b439a6619a95f1d200250ea88e7ccea6e09e48a7544/types_python_dateutil-2.9.0.20260305.tar.gz", hash = "sha256:389717c9f64d8f769f36d55a01873915b37e97e52ce21928198d210fbd393c8b", size = 16885, upload-time = "2026-03-05T04:00:47.409Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/02/f72df9ef5ffc4f959b83cb80c8aa03eb8718a43e563ecd99ccffe265fa89/types_python_dateutil-2.9.0.20260323.tar.gz", hash = "sha256:a107aef5841db41ace381dbbbd7e4945220fc940f7a72172a0be5a92d9ab7164", size = 16897, upload-time = "2026-03-23T04:15:14.829Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/77/8c0d1ec97f0d9707ad3d8fa270ab8964e7b31b076d2f641c94987395cc75/types_python_dateutil-2.9.0.20260305-py3-none-any.whl", hash = "sha256:a3be9ca444d38cadabd756cfbb29780d8b338ae2a3020e73c266a83cc3025dd7", size = 18419, upload-time = "2026-03-05T04:00:46.392Z" }, + { url = "https://files.pythonhosted.org/packages/92/c1/b661838b97453e699a215451f2e22cee750eaaf4ea4619b34bdaf01221a4/types_python_dateutil-2.9.0.20260323-py3-none-any.whl", hash = "sha256:a23a50a07f6eb87e729d4cb0c2eb511c81761eeb3f505db2c1413be94aae8335", size = 18433, upload-time = "2026-03-23T04:15:13.683Z" }, ] [[package]] @@ -6997,11 +6997,11 @@ wheels = [ [[package]] name = "types-pywin32" -version = "311.0.0.20260316" +version = "311.0.0.20260323" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/17/a8/b4652002a854fcfe5d272872a0ae2d5df0e9dc482e1a6dfb5e97b905b76f/types_pywin32-311.0.0.20260316.tar.gz", hash = "sha256:c136fa489fe6279a13bca167b750414e18d657169b7cf398025856dc363004e8", size = 329956, upload-time = "2026-03-16T04:28:57.366Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/cc/f03ddb7412ac2fc2238358b617c2d5919ba96812dff8d3081f3b2754bb83/types_pywin32-311.0.0.20260323.tar.gz", hash = "sha256:2e8dc6a59fedccbc51b241651ce1e8aa58488934f517debf23a9c6d0ff329b4b", size = 332263, upload-time = "2026-03-23T04:15:20.004Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/83/704698d93788cf1c2f5e236eae2b37f1b2152ef84dc66b4b83f6c7487b76/types_pywin32-311.0.0.20260316-py3-none-any.whl", hash = "sha256:abb643d50012386d697af49384cc0e6e475eab76b0ca2a7f93d480d0862b3692", size = 392959, upload-time = "2026-03-16T04:28:56.104Z" }, + { url = "https://files.pythonhosted.org/packages/dc/82/d786d5d8b846e3cbe1ee52da8945560b111c789b42c3771b2129b312ab94/types_pywin32-311.0.0.20260323-py3-none-any.whl", hash = "sha256:2f2b03fc72ae77ccbb0ee258da0f181c3a38bd8602f6e332e42587b3b0d5f095", size = 395435, upload-time = "2026-03-23T04:15:18.76Z" }, ] [[package]] @@ -7097,16 +7097,16 @@ wheels = [ [[package]] name = "types-tensorflow" -version = "2.18.0.20260224" +version = "2.18.0.20260322" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "types-protobuf" }, { name = "types-requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/cb/4914c2fbc1cf8a8d1ef2a7c727bb6f694879be85edeee880a0c88e696af8/types_tensorflow-2.18.0.20260224.tar.gz", hash = "sha256:9b0ccc91c79c88791e43d3f80d6c879748fa0361409c5ff23c7ffe3709be00f2", size = 258786, upload-time = "2026-02-24T04:06:45.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/cb/81dfaa2680031a6e087bcdfaf1c0556371098e229aee541e21c81a381065/types_tensorflow-2.18.0.20260322.tar.gz", hash = "sha256:135dc6ca06cc647a002e1bca5c5c99516fde51efd08e46c48a9b1916fc5df07f", size = 259030, upload-time = "2026-03-22T04:09:14.069Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/1d/a1c3c60f0eb1a204500dbdc66e3d18aafabc86ad07a8eca71ea05bc8c5a8/types_tensorflow-2.18.0.20260224-py3-none-any.whl", hash = "sha256:6a25f5f41f3e06f28c1f65c6e09f484d4ba0031d6d8df83a39df9d890245eefc", size = 329746, upload-time = "2026-02-24T04:06:44.4Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0c/a178061450b640e53577e2c423ad22bf5d3f692f6bfeeb12156d02b531ef/types_tensorflow-2.18.0.20260322-py3-none-any.whl", hash = "sha256:d8776b6daacdb279e64f105f9dcbc0b8e3544b9a2f2eb71ec6ea5955081f65e6", size = 329771, upload-time = "2026-03-22T04:09:12.844Z" }, ] [[package]] From dc1a68661ce239feb2490614280605f39759ace5 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:31:41 +0800 Subject: [PATCH 31/70] refactor(web): migrate members invite overlays to base ui (#33922) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../api-based-extension-page/modal.tsx | 1 + .../api-based-extension-page/selector.tsx | 2 +- .../data-source-page-new/configure.tsx | 4 +- .../data-source-page-new/operator.tsx | 12 +- .../invite-modal/__tests__/index.spec.tsx | 68 +++--- .../invite-modal/index.module.css | 12 -- .../members-page/invite-modal/index.tsx | 41 ++-- .../invite-modal/role-selector.tsx | 199 +++++++++--------- .../members-page/invited-modal/index.tsx | 55 +++-- .../invited-modal/invitation-link.tsx | 32 +-- .../members-page/operation/index.tsx | 2 +- .../transfer-ownership-modal/index.tsx | 1 + .../member-selector.tsx | 2 +- .../model-auth/add-custom-model.tsx | 6 +- .../model-auth/authorized/index.tsx | 6 +- .../model-auth/credential-selector.tsx | 10 +- .../model-load-balancing-modal.tsx | 1 + web/eslint-suppressions.json | 33 --- 18 files changed, 223 insertions(+), 264 deletions(-) delete mode 100644 web/app/components/header/account-setting/members-page/invite-modal/index.module.css diff --git a/web/app/components/header/account-setting/api-based-extension-page/modal.tsx b/web/app/components/header/account-setting/api-based-extension-page/modal.tsx index efe6c46dcc..5f1492f14a 100644 --- a/web/app/components/header/account-setting/api-based-extension-page/modal.tsx +++ b/web/app/components/header/account-setting/api-based-extension-page/modal.tsx @@ -78,6 +78,7 @@ const ApiBasedExtensionModal: FC = ({
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 38acb73154..62052aece6 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 @@ -69,7 +69,7 @@ const ApiBasedExtensionSelector: FC = ({ ) } - +
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 a3dba783e1..484338d333 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 @@ -84,7 +84,7 @@ const Configure = ({ {t('dataSource.configure', { ns: 'common' })} - +
{ !!canOAuth && ( @@ -104,7 +104,7 @@ const Configure = ({ } { !!canApiKey && !!canOAuth && ( -
+
OR
diff --git a/web/app/components/header/account-setting/data-source-page-new/operator.tsx b/web/app/components/header/account-setting/data-source-page-new/operator.tsx index 14bdee4fd0..c5b2a948de 100644 --- a/web/app/components/header/account-setting/data-source-page-new/operator.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/operator.tsx @@ -39,7 +39,7 @@ const Operator = ({ text: (
-
{t('auth.setDefault', { ns: 'plugin' })}
+
{t('auth.setDefault', { ns: 'plugin' })}
), }, @@ -51,7 +51,7 @@ const Operator = ({ text: (
-
{t('operation.rename', { ns: 'common' })}
+
{t('operation.rename', { ns: 'common' })}
), }, @@ -66,7 +66,7 @@ const Operator = ({ text: (
-
{t('operation.edit', { ns: 'common' })}
+
{t('operation.edit', { ns: 'common' })}
), }, @@ -81,7 +81,7 @@ const Operator = ({ text: (
-
{t('dataSource.notion.changeAuthorizedPages', { ns: 'common' })}
+
{t('dataSource.notion.changeAuthorizedPages', { ns: 'common' })}
), }, @@ -98,7 +98,7 @@ const Operator = ({ text: (
-
+
{t('operation.remove', { ns: 'common' })}
@@ -122,7 +122,7 @@ const Operator = ({ items={items} secondItems={secondItems} onSelect={handleSelect} - popupClassName="z-[61]" + popupClassName="z-[1002]" triggerProps={{ size: 'l', }} diff --git a/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx b/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx index d2aeca1b6c..7de1fbeccb 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx @@ -2,11 +2,15 @@ import type { InvitationResponse } from '@/models/common' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { vi } from 'vitest' -import { ToastContext } from '@/app/components/base/toast/context' +import { toast } from '@/app/components/base/ui/toast' import { useProviderContextSelector } from '@/context/provider-context' import { inviteMember } from '@/service/common' import InviteModal from '../index' +const { mockToastError } = vi.hoisted(() => ({ + mockToastError: vi.fn(), +})) + vi.mock('@/context/provider-context', () => ({ useProviderContextSelector: vi.fn(), useProviderContext: vi.fn(() => ({ @@ -14,6 +18,11 @@ vi.mock('@/context/provider-context', () => ({ })), })) vi.mock('@/service/common') +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + error: mockToastError, + }, +})) vi.mock('@/context/i18n', () => ({ useLocale: () => 'en-US', })) @@ -37,7 +46,6 @@ describe('InviteModal', () => { const mockOnCancel = vi.fn() const mockOnSend = vi.fn() const mockRefreshLicenseLimit = vi.fn() - const mockNotify = vi.fn() beforeEach(() => { vi.clearAllMocks() @@ -49,10 +57,11 @@ describe('InviteModal', () => { }) const renderModal = (isEmailSetup = true) => render( - - - , + , ) + const fillEmails = (value: string) => { + fireEvent.change(screen.getByTestId('mock-email-input'), { target: { value } }) + } it('should render invite modal content', async () => { renderModal() @@ -68,12 +77,8 @@ describe('InviteModal', () => { }) it('should enable send button after entering an email', async () => { - const user = userEvent.setup() - renderModal() - - const input = screen.getByTestId('mock-email-input') - await user.type(input, 'user@example.com') + fillEmails('user@example.com') expect(screen.getByRole('button', { name: /members\.sendInvite/i })).toBeEnabled() }) @@ -84,7 +89,7 @@ describe('InviteModal', () => { renderModal() - await user.type(screen.getByTestId('mock-email-input'), 'user@example.com') + fillEmails('user@example.com') await user.click(screen.getByRole('button', { name: /members\.sendInvite/i })) await waitFor(() => { @@ -103,8 +108,7 @@ describe('InviteModal', () => { renderModal() - const input = screen.getByTestId('mock-email-input') - await user.type(input, 'user@example.com') + fillEmails('user@example.com') await user.click(screen.getByRole('button', { name: /members\.sendInvite/i })) await waitFor(() => { @@ -116,8 +120,6 @@ describe('InviteModal', () => { }) it('should keep send button disabled when license limit is exceeded', async () => { - const user = userEvent.setup() - vi.mocked(useProviderContextSelector).mockImplementation(selector => selector({ licenseLimit: { workspace_members: { size: 10, limit: 10 } }, refreshLicenseLimit: mockRefreshLicenseLimit, @@ -125,8 +127,7 @@ describe('InviteModal', () => { renderModal() - const input = screen.getByTestId('mock-email-input') - await user.type(input, 'user@example.com') + fillEmails('user@example.com') expect(screen.getByRole('button', { name: /members\.sendInvite/i })).toBeDisabled() }) @@ -144,15 +145,11 @@ describe('InviteModal', () => { const user = userEvent.setup() renderModal() - const input = screen.getByTestId('mock-email-input') // Use an email that passes basic validation but fails our strict regex (needs 2+ char TLD) - await user.type(input, 'invalid@email.c') + fillEmails('invalid@email.c') await user.click(screen.getByRole('button', { name: /members\.sendInvite/i })) - expect(mockNotify).toHaveBeenCalledWith({ - type: 'error', - message: 'common.members.emailInvalid', - }) + expect(toast.error).toHaveBeenCalledWith('common.members.emailInvalid') expect(inviteMember).not.toHaveBeenCalled() }) @@ -160,8 +157,7 @@ describe('InviteModal', () => { const user = userEvent.setup() renderModal() - const input = screen.getByTestId('mock-email-input') - await user.type(input, 'user@example.com') + fillEmails('user@example.com') expect(screen.getByText('user@example.com')).toBeInTheDocument() @@ -203,7 +199,7 @@ describe('InviteModal', () => { renderModal() - await user.type(screen.getByTestId('mock-email-input'), 'user@example.com') + fillEmails('user@example.com') await user.click(screen.getByRole('button', { name: /members\.sendInvite/i })) await waitFor(() => { @@ -214,8 +210,6 @@ describe('InviteModal', () => { }) it('should show destructive text color when used size exceeds limit', async () => { - const user = userEvent.setup() - vi.mocked(useProviderContextSelector).mockImplementation(selector => selector({ licenseLimit: { workspace_members: { size: 10, limit: 10 } }, refreshLicenseLimit: mockRefreshLicenseLimit, @@ -223,8 +217,7 @@ describe('InviteModal', () => { renderModal() - const input = screen.getByTestId('mock-email-input') - await user.type(input, 'user@example.com') + fillEmails('user@example.com') // usedSize = 10 + 1 = 11 > limit 10 → destructive color const counter = screen.getByText('11') @@ -241,8 +234,7 @@ describe('InviteModal', () => { renderModal() - const input = screen.getByTestId('mock-email-input') - await user.type(input, 'user@example.com') + fillEmails('user@example.com') const sendBtn = screen.getByRole('button', { name: /members\.sendInvite/i }) @@ -264,8 +256,6 @@ describe('InviteModal', () => { }) it('should show destructive color and disable send button when limit is exactly met with one email', async () => { - const user = userEvent.setup() - // size=10, limit=10 - adding 1 email makes usedSize=11 > limit=10 vi.mocked(useProviderContextSelector).mockImplementation(selector => selector({ licenseLimit: { workspace_members: { size: 10, limit: 10 } }, @@ -274,8 +264,7 @@ describe('InviteModal', () => { renderModal() - const input = screen.getByTestId('mock-email-input') - await user.type(input, 'user@example.com') + fillEmails('user@example.com') // isLimitExceeded=true → button is disabled, cannot submit const sendBtn = screen.getByRole('button', { name: /members\.sendInvite/i }) @@ -293,8 +282,7 @@ describe('InviteModal', () => { renderModal() - const input = screen.getByTestId('mock-email-input') - await user.type(input, 'user@example.com') + fillEmails('user@example.com') const sendBtn = screen.getByRole('button', { name: /members\.sendInvite/i }) @@ -320,11 +308,9 @@ describe('InviteModal', () => { refreshLicenseLimit: mockRefreshLicenseLimit, } as unknown as Parameters[0])) - const user = userEvent.setup() renderModal() - const input = screen.getByTestId('mock-email-input') - await user.type(input, 'user@example.com') + fillEmails('user@example.com') // isLimited=false → no destructive color const counter = screen.getByText('1') diff --git a/web/app/components/header/account-setting/members-page/invite-modal/index.module.css b/web/app/components/header/account-setting/members-page/invite-modal/index.module.css deleted file mode 100644 index fbaa1187bd..0000000000 --- a/web/app/components/header/account-setting/members-page/invite-modal/index.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.modal { - padding: 24px 32px !important; - width: 400px !important; -} - -.emailsInput { - background-color: rgb(243 244 246 / var(--tw-bg-opacity)) !important; -} - -.emailBackground { - background-color: white !important; -} diff --git a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx index 8e4e47e0b8..9b4e9fccdc 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx @@ -2,20 +2,17 @@ import type { RoleKey } from './role-selector' import type { InvitationResult } from '@/models/common' import { useBoolean } from 'ahooks' -import { noop } from 'es-toolkit/function' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { ReactMultiEmail } from 'react-multi-email' -import { useContext } from 'use-context-selector' import Button from '@/app/components/base/button' -import Modal from '@/app/components/base/modal' -import { ToastContext } from '@/app/components/base/toast/context' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' +import { toast } from '@/app/components/base/ui/toast' import { emailRegex } from '@/config' import { useLocale } from '@/context/i18n' import { useProviderContextSelector } from '@/context/provider-context' import { inviteMember } from '@/service/common' import { cn } from '@/utils/classnames' -import s from './index.module.css' import RoleSelector from './role-selector' import 'react-multi-email/dist/style.css' @@ -34,7 +31,6 @@ const InviteModal = ({ const licenseLimit = useProviderContextSelector(s => s.licenseLimit) const refreshLicenseLimit = useProviderContextSelector(s => s.refreshLicenseLimit) const [emails, setEmails] = useState([]) - const { notify } = useContext(ToastContext) const [isLimited, setIsLimited] = useState(false) const [isLimitExceeded, setIsLimitExceeded] = useState(false) const [usedSize, setUsedSize] = useState(licenseLimit.workspace_members.size ?? 0) @@ -74,21 +70,28 @@ const InviteModal = ({ catch { } } else { - notify({ type: 'error', message: t('members.emailInvalid', { ns: 'common' }) }) + toast.error(t('members.emailInvalid', { ns: 'common' })) } setIsSubmitted() - }, [isLimitExceeded, emails, role, locale, onCancel, onSend, notify, t, isSubmitting, refreshLicenseLimit, setIsSubmitted, setIsSubmitting]) + }, [isLimitExceeded, emails, role, locale, onCancel, onSend, t, isSubmitting, refreshLicenseLimit, setIsSubmitted, setIsSubmitting]) return ( -
- -
-
{t('members.inviteTeamMember', { ns: 'common' })}
-
+ { + if (!open) + onCancel() + }} + > + + +
+ + {t('members.inviteTeamMember', { ns: 'common' })} +
{t('members.inviteTeamMemberTip', { ns: 'common' })}
{!isEmailSetup && ( @@ -152,8 +155,8 @@ const InviteModal = ({ {t('members.sendInvite', { ns: 'common' })}
- -
+ + ) } diff --git a/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx b/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx index e258884b0f..6383b203d9 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx @@ -1,11 +1,10 @@ import * as React from 'react' -import { useState } from 'react' import { useTranslation } from 'react-i18next' import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' + Popover, + PopoverContent, + PopoverTrigger, +} from '@/app/components/base/ui/popover' import { useProviderContext } from '@/context/provider-context' import { cn } from '@/utils/classnames' @@ -25,115 +24,111 @@ export type RoleSelectorProps = { const RoleSelector = ({ value, onChange }: RoleSelectorProps) => { const { t } = useTranslation() - const [open, setOpen] = useState(false) const { datasetOperatorEnabled } = useProviderContext() + const [open, setOpen] = React.useState(false) return ( - -
- setOpen(v => !v)} - className="block" - > + +
{t('members.invitedAsRole', { ns: 'common', role: t(roleI18nKeyMap[value], { ns: 'common' }) })}
+
+ + +
{ + onChange('normal') + setOpen(false) + }} > -
{t('members.invitedAsRole', { ns: 'common', role: t(roleI18nKeyMap[value], { ns: 'common' }) })}
-
-
- - -
-
-
{ - onChange('normal') - setOpen(false) - }} - > -
-
{t('members.normal', { ns: 'common' })}
-
{t('members.normalTip', { ns: 'common' })}
- {value === 'normal' && ( -
- )} -
-
-
{ - onChange('editor') - setOpen(false) - }} - > -
-
{t('members.editor', { ns: 'common' })}
-
{t('members.editorTip', { ns: 'common' })}
- {value === 'editor' && ( -
- )} -
-
-
{ - onChange('admin') - setOpen(false) - }} - > -
-
{t('members.admin', { ns: 'common' })}
-
{t('members.adminTip', { ns: 'common' })}
- {value === 'admin' && ( -
- )} -
-
- {datasetOperatorEnabled && ( +
+
{t('members.normal', { ns: 'common' })}
+
{t('members.normalTip', { ns: 'common' })}
+ {value === 'normal' && (
{ - onChange('dataset_operator') - setOpen(false) - }} - > -
-
{t('members.datasetOperator', { ns: 'common' })}
-
{t('members.datasetOperatorTip', { ns: 'common' })}
- {value === 'dataset_operator' && ( -
- )} -
-
+ data-testid="role-option-check" + className="i-custom-vender-line-general-check absolute left-0 top-0.5 h-4 w-4 text-text-accent" + /> )}
- -
- +
{ + onChange('editor') + setOpen(false) + }} + > +
+
{t('members.editor', { ns: 'common' })}
+
{t('members.editorTip', { ns: 'common' })}
+ {value === 'editor' && ( +
+ )} +
+
+
{ + onChange('admin') + setOpen(false) + }} + > +
+
{t('members.admin', { ns: 'common' })}
+
{t('members.adminTip', { ns: 'common' })}
+ {value === 'admin' && ( +
+ )} +
+
+ {datasetOperatorEnabled && ( +
{ + onChange('dataset_operator') + setOpen(false) + }} + > +
+
{t('members.datasetOperator', { ns: 'common' })}
+
{t('members.datasetOperatorTip', { ns: 'common' })}
+ {value === 'dataset_operator' && ( +
+ )} +
+
+ )} +
+ + ) } diff --git a/web/app/components/header/account-setting/members-page/invited-modal/index.tsx b/web/app/components/header/account-setting/members-page/invited-modal/index.tsx index 389db4a42d..dbabb384a2 100644 --- a/web/app/components/header/account-setting/members-page/invited-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/invited-modal/index.tsx @@ -1,15 +1,10 @@ import type { InvitationResult } from '@/models/common' -import { XMarkIcon } from '@heroicons/react/24/outline' -import { CheckCircleIcon } from '@heroicons/react/24/solid' -import { RiQuestionLine } from '@remixicon/react' -import { noop } from 'es-toolkit/function' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import Modal from '@/app/components/base/modal' -import Tooltip from '@/app/components/base/tooltip' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' import { IS_CE_EDITION } from '@/config' -import s from './index.module.css' import InvitationLink from './invitation-link' export type SuccessInvitationResult = Extract @@ -29,8 +24,18 @@ const InvitedModal = ({ const failedInvitationResults = useMemo(() => invitationResults?.filter(item => item.status !== 'success') as FailedInvitationResult[], [invitationResults]) return ( -
- + { + if (!open) + onCancel() + }} + > + +
- +
-
-
{t('members.invitationSent', { ns: 'common' })}
+ {t('members.invitationSent', { ns: 'common' })} {!IS_CE_EDITION && (
{t('members.invitationSentTip', { ns: 'common' })}
)} @@ -54,7 +58,7 @@ const InvitedModal = ({ !!successInvitationResults.length && ( <> -
{t('members.invitationLink', { ns: 'common' })}
+
{t('members.invitationLink', { ns: 'common' })}
{successInvitationResults.map(item => )} @@ -64,18 +68,23 @@ const InvitedModal = ({ !!failedInvitationResults.length && ( <> -
{t('members.failedInvitationEmails', { ns: 'common' })}
+
{t('members.failedInvitationEmails', { ns: 'common' })}
{ failedInvitationResults.map(item => (
- -
- {item.email} - -
+ + + {item.email} +
+
+ )} + /> + + {item.message} +
), @@ -97,8 +106,8 @@ const InvitedModal = ({ {t('members.ok', { ns: 'common' })}
- -
+
+
) } diff --git a/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx b/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx index 8f55660fd8..0c5874c4dc 100644 --- a/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx +++ b/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx @@ -4,7 +4,7 @@ import copy from 'copy-to-clipboard' import { t } from 'i18next' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' -import Tooltip from '@/app/components/base/tooltip' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' import s from './index.module.css' type IInvitationLinkProps = { @@ -38,20 +38,28 @@ const InvitationLink = ({
- -
{value.url}
+ + {value.url}
} + /> + + {isCopied ? t('copied', { ns: 'appApi' }) : t('copy', { ns: 'appApi' })} +
- -
-
-
-
+ + +
+
+
+ )} + /> + + {isCopied ? t('copied', { ns: 'appApi' }) : t('copy', { ns: 'appApi' })} +
diff --git a/web/app/components/header/account-setting/members-page/operation/index.tsx b/web/app/components/header/account-setting/members-page/operation/index.tsx index 35c4676d5f..e2b14b9078 100644 --- a/web/app/components/header/account-setting/members-page/operation/index.tsx +++ b/web/app/components/header/account-setting/members-page/operation/index.tsx @@ -102,7 +102,7 @@ const Operation = ({
- +
{ diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx index 099a146866..6a2af9ffdb 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx @@ -141,6 +141,7 @@ const TransferOwnershipModal = ({ onClose, show }: Props) => {
= ({
- +
{renderTrigger(open)} - +
{ @@ -136,7 +136,7 @@ const AddCustomModel = ({ modelName={model.model} />
{model.model} @@ -148,7 +148,7 @@ const AddCustomModel = ({ { !notAllowCustomCredential && (
{ handleOpenModalForAddNewCustomModel() setOpen(false) 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 e2f859b09d..15101a6542 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 @@ -164,7 +164,7 @@ const Authorized = ({ > {renderTrigger(mergedIsOpen)} - +
{ popupTitle && ( -
+
{popupTitle}
) @@ -218,7 +218,7 @@ const Authorized = ({ } : undefined, )} - className="system-xs-medium flex h-[40px] cursor-pointer items-center px-3 text-text-accent-light-mode-only" + className="flex h-[40px] cursor-pointer items-center px-3 text-text-accent-light-mode-only system-xs-medium" > {t('modelProvider.auth.addModelCredential', { ns: 'common' })} diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx index 52513e7aeb..dd1d8e6eb9 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx @@ -53,14 +53,14 @@ const CredentialSelector = ({ triggerPopupSameWidth > !disabled && setOpen(v => !v)}> -
+
{ selectedCredential && (
{ !selectedCredential.addNewCredential && } -
{selectedCredential.credential_name}
+
{selectedCredential.credential_name}
{ selectedCredential.from_enterprise && ( Enterprise @@ -71,13 +71,13 @@ const CredentialSelector = ({ } { !selectedCredential && ( -
{t('modelProvider.auth.selectModelCredential', { ns: 'common' })}
+
{t('modelProvider.auth.selectModelCredential', { ns: 'common' })}
) }
- +
{ @@ -98,7 +98,7 @@ const CredentialSelector = ({ { !notAllowAddNewCredential && (
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx index 13fb974728..d0f7ac7e53 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx @@ -244,6 +244,7 @@ const ModelLoadBalancingModal = ({ diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index f4b95eee09..1afbb8ec5a 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -4700,9 +4700,6 @@ "app/components/header/account-setting/data-source-page-new/configure.tsx": { "no-restricted-imports": { "count": 1 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 1 } }, "app/components/header/account-setting/data-source-page-new/hooks/use-marketplace-all-plugins.ts": { @@ -4729,9 +4726,6 @@ "app/components/header/account-setting/data-source-page-new/operator.tsx": { "no-restricted-imports": { "count": 2 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 5 } }, "app/components/header/account-setting/data-source-page-new/types.ts": { @@ -4758,28 +4752,10 @@ } }, "app/components/header/account-setting/members-page/invite-modal/index.tsx": { - "no-restricted-imports": { - "count": 2 - }, "react/set-state-in-effect": { "count": 3 } }, - "app/components/header/account-setting/members-page/invite-modal/role-selector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "app/components/header/account-setting/members-page/invited-modal/index.tsx": { - "no-restricted-imports": { - "count": 2 - } - }, - "app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "app/components/header/account-setting/members-page/operation/index.tsx": { "no-restricted-imports": { "count": 2 @@ -4833,9 +4809,6 @@ "app/components/header/account-setting/model-provider-page/model-auth/add-custom-model.tsx": { "no-restricted-imports": { "count": 2 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 2 } }, "app/components/header/account-setting/model-provider-page/model-auth/authorized/authorized-item.tsx": { @@ -4847,9 +4820,6 @@ "no-restricted-imports": { "count": 3 }, - "tailwindcss/enforce-consistent-class-order": { - "count": 2 - }, "ts/no-explicit-any": { "count": 2 } @@ -4867,9 +4837,6 @@ "app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx": { "no-restricted-imports": { "count": 1 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 4 } }, "app/components/header/account-setting/model-provider-page/model-auth/hooks/use-auth.ts": { From abda8590756c23b6ba971818a9bc7ef3f9e478d5 Mon Sep 17 00:00:00 2001 From: Desel72 Date: Mon, 23 Mar 2026 03:32:11 -0500 Subject: [PATCH 32/70] refactor: migrate execution extra content repository tests from mocks to testcontainers (#33852) --- api/models/execution_extra_content.py | 4 +- ...test_execution_extra_content_repository.py | 27 -- ...hemy_execution_extra_content_repository.py | 407 ++++++++++++++++++ ...hemy_execution_extra_content_repository.py | 180 -------- 4 files changed, 409 insertions(+), 209 deletions(-) delete mode 100644 api/tests/test_containers_integration_tests/repositories/test_execution_extra_content_repository.py create mode 100644 api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py delete mode 100644 api/tests/unit_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py diff --git a/api/models/execution_extra_content.py b/api/models/execution_extra_content.py index d0bd34efec..b2d09a7732 100644 --- a/api/models/execution_extra_content.py +++ b/api/models/execution_extra_content.py @@ -66,8 +66,8 @@ class HumanInputContent(ExecutionExtraContent): form_id: Mapped[str] = mapped_column(StringUUID, nullable=True) @classmethod - def new(cls, form_id: str, message_id: str | None) -> "HumanInputContent": - return cls(form_id=form_id, message_id=message_id) + def new(cls, *, workflow_run_id: str, form_id: str, message_id: str | None) -> "HumanInputContent": + return cls(workflow_run_id=workflow_run_id, form_id=form_id, message_id=message_id) form: Mapped["HumanInputForm"] = relationship( "HumanInputForm", diff --git a/api/tests/test_containers_integration_tests/repositories/test_execution_extra_content_repository.py b/api/tests/test_containers_integration_tests/repositories/test_execution_extra_content_repository.py deleted file mode 100644 index c9058626d1..0000000000 --- a/api/tests/test_containers_integration_tests/repositories/test_execution_extra_content_repository.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import annotations - -from sqlalchemy.orm import sessionmaker - -from extensions.ext_database import db -from repositories.sqlalchemy_execution_extra_content_repository import SQLAlchemyExecutionExtraContentRepository -from tests.test_containers_integration_tests.helpers.execution_extra_content import ( - create_human_input_message_fixture, -) - - -def test_get_by_message_ids_returns_human_input_content(db_session_with_containers): - fixture = create_human_input_message_fixture(db_session_with_containers) - repository = SQLAlchemyExecutionExtraContentRepository( - session_maker=sessionmaker(bind=db.engine, expire_on_commit=False) - ) - - results = repository.get_by_message_ids([fixture.message.id]) - - assert len(results) == 1 - assert len(results[0]) == 1 - content = results[0][0] - assert content.submitted is True - assert content.form_submission_data is not None - assert content.form_submission_data.action_id == fixture.action_id - assert content.form_submission_data.action_text == fixture.action_text - assert content.form_submission_data.rendered_content == fixture.form.rendered_content diff --git a/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py b/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py new file mode 100644 index 0000000000..ed998c9ed0 --- /dev/null +++ b/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py @@ -0,0 +1,407 @@ +"""Integration tests for SQLAlchemyExecutionExtraContentRepository using Testcontainers. + +Part of #32454 — replaces the mock-based unit tests with real database interactions. +""" + +from __future__ import annotations + +from collections.abc import Generator +from dataclasses import dataclass +from datetime import datetime, timedelta +from decimal import Decimal +from uuid import uuid4 + +import pytest +from sqlalchemy import Engine, delete, select +from sqlalchemy.orm import Session, sessionmaker + +from dify_graph.nodes.human_input.entities import FormDefinition, UserAction +from dify_graph.nodes.human_input.enums import HumanInputFormStatus +from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole +from models.enums import ConversationFromSource, InvokeFrom +from models.execution_extra_content import ExecutionExtraContent, HumanInputContent +from models.human_input import ( + ConsoleRecipientPayload, + HumanInputDelivery, + HumanInputForm, + HumanInputFormRecipient, + RecipientType, +) +from models.model import App, Conversation, Message +from repositories.sqlalchemy_execution_extra_content_repository import SQLAlchemyExecutionExtraContentRepository + + +@dataclass +class _TestScope: + """Per-test data scope used to isolate DB rows. + + IDs are populated after flushing the base entities to the database. + """ + + tenant_id: str = "" + app_id: str = "" + user_id: str = "" + + +def _cleanup_scope_data(session: Session, scope: _TestScope) -> None: + """Remove test-created DB rows for a test scope.""" + form_ids_subquery = select(HumanInputForm.id).where( + HumanInputForm.tenant_id == scope.tenant_id, + ) + session.execute(delete(HumanInputFormRecipient).where(HumanInputFormRecipient.form_id.in_(form_ids_subquery))) + session.execute(delete(HumanInputDelivery).where(HumanInputDelivery.form_id.in_(form_ids_subquery))) + session.execute( + delete(ExecutionExtraContent).where( + ExecutionExtraContent.workflow_run_id.in_( + select(HumanInputForm.workflow_run_id).where(HumanInputForm.tenant_id == scope.tenant_id) + ) + ) + ) + session.execute(delete(HumanInputForm).where(HumanInputForm.tenant_id == scope.tenant_id)) + session.execute(delete(Message).where(Message.app_id == scope.app_id)) + session.execute(delete(Conversation).where(Conversation.app_id == scope.app_id)) + session.execute(delete(App).where(App.id == scope.app_id)) + session.execute(delete(TenantAccountJoin).where(TenantAccountJoin.tenant_id == scope.tenant_id)) + session.execute(delete(Account).where(Account.id == scope.user_id)) + session.execute(delete(Tenant).where(Tenant.id == scope.tenant_id)) + session.commit() + + +def _seed_base_entities(session: Session, scope: _TestScope) -> None: + """Create the base tenant, account, and app needed by tests.""" + tenant = Tenant(name="Test Tenant") + session.add(tenant) + session.flush() + scope.tenant_id = tenant.id + + account = Account( + name="Test Account", + email=f"test_{uuid4()}@example.com", + password="hashed-password", + password_salt="salt", + interface_language="en-US", + timezone="UTC", + ) + session.add(account) + session.flush() + scope.user_id = account.id + + tenant_join = TenantAccountJoin( + tenant_id=scope.tenant_id, + account_id=scope.user_id, + role=TenantAccountRole.OWNER, + current=True, + ) + session.add(tenant_join) + + app = App( + tenant_id=scope.tenant_id, + name="Test App", + description="", + mode="chat", + icon_type="emoji", + icon="bot", + icon_background="#FFFFFF", + enable_site=False, + enable_api=True, + api_rpm=100, + api_rph=100, + is_demo=False, + is_public=False, + is_universal=False, + created_by=scope.user_id, + updated_by=scope.user_id, + ) + session.add(app) + session.flush() + scope.app_id = app.id + + +def _create_conversation(session: Session, scope: _TestScope) -> Conversation: + conversation = Conversation( + app_id=scope.app_id, + mode="chat", + name="Test Conversation", + summary="", + introduction="", + system_instruction="", + status="normal", + invoke_from=InvokeFrom.EXPLORE, + from_source=ConversationFromSource.CONSOLE, + from_account_id=scope.user_id, + from_end_user_id=None, + ) + conversation.inputs = {} + session.add(conversation) + session.flush() + return conversation + + +def _create_message( + session: Session, + scope: _TestScope, + conversation_id: str, + workflow_run_id: str, +) -> Message: + message = Message( + app_id=scope.app_id, + conversation_id=conversation_id, + inputs={}, + query="test query", + message={"messages": []}, + answer="test answer", + message_tokens=50, + message_unit_price=Decimal("0.001"), + answer_tokens=80, + answer_unit_price=Decimal("0.001"), + provider_response_latency=0.5, + currency="USD", + from_source=ConversationFromSource.CONSOLE, + from_account_id=scope.user_id, + workflow_run_id=workflow_run_id, + ) + session.add(message) + session.flush() + return message + + +def _create_submitted_form( + session: Session, + scope: _TestScope, + *, + workflow_run_id: str, + action_id: str = "approve", + action_title: str = "Approve", + node_title: str = "Approval", +) -> HumanInputForm: + expiration_time = datetime.utcnow() + timedelta(days=1) + form_definition = FormDefinition( + form_content="content", + inputs=[], + user_actions=[UserAction(id=action_id, title=action_title)], + rendered_content="rendered", + expiration_time=expiration_time, + node_title=node_title, + display_in_ui=True, + ) + form = HumanInputForm( + tenant_id=scope.tenant_id, + app_id=scope.app_id, + workflow_run_id=workflow_run_id, + node_id="node-id", + form_definition=form_definition.model_dump_json(), + rendered_content=f"Rendered {action_title}", + status=HumanInputFormStatus.SUBMITTED, + expiration_time=expiration_time, + selected_action_id=action_id, + ) + session.add(form) + session.flush() + return form + + +def _create_waiting_form( + session: Session, + scope: _TestScope, + *, + workflow_run_id: str, + default_values: dict | None = None, +) -> HumanInputForm: + expiration_time = datetime.utcnow() + timedelta(days=1) + form_definition = FormDefinition( + form_content="content", + inputs=[], + user_actions=[UserAction(id="approve", title="Approve")], + rendered_content="rendered", + expiration_time=expiration_time, + default_values=default_values or {"name": "John"}, + node_title="Approval", + display_in_ui=True, + ) + form = HumanInputForm( + tenant_id=scope.tenant_id, + app_id=scope.app_id, + workflow_run_id=workflow_run_id, + node_id="node-id", + form_definition=form_definition.model_dump_json(), + rendered_content="Rendered block", + status=HumanInputFormStatus.WAITING, + expiration_time=expiration_time, + ) + session.add(form) + session.flush() + return form + + +def _create_human_input_content( + session: Session, + *, + workflow_run_id: str, + message_id: str, + form_id: str, +) -> HumanInputContent: + content = HumanInputContent.new( + workflow_run_id=workflow_run_id, + message_id=message_id, + form_id=form_id, + ) + session.add(content) + return content + + +def _create_recipient( + session: Session, + *, + form_id: str, + delivery_id: str, + recipient_type: RecipientType = RecipientType.CONSOLE, + access_token: str = "token-1", +) -> HumanInputFormRecipient: + payload = ConsoleRecipientPayload(account_id=None) + recipient = HumanInputFormRecipient( + form_id=form_id, + delivery_id=delivery_id, + recipient_type=recipient_type, + recipient_payload=payload.model_dump_json(), + access_token=access_token, + ) + session.add(recipient) + return recipient + + +def _create_delivery(session: Session, *, form_id: str) -> HumanInputDelivery: + from dify_graph.nodes.human_input.enums import DeliveryMethodType + from models.human_input import ConsoleDeliveryPayload + + delivery = HumanInputDelivery( + form_id=form_id, + delivery_method_type=DeliveryMethodType.WEBAPP, + channel_payload=ConsoleDeliveryPayload().model_dump_json(), + ) + session.add(delivery) + session.flush() + return delivery + + +@pytest.fixture +def repository(db_session_with_containers: Session) -> SQLAlchemyExecutionExtraContentRepository: + """Build a repository backed by the testcontainers database engine.""" + engine = db_session_with_containers.get_bind() + assert isinstance(engine, Engine) + return SQLAlchemyExecutionExtraContentRepository(sessionmaker(bind=engine, expire_on_commit=False)) + + +@pytest.fixture +def test_scope(db_session_with_containers: Session) -> Generator[_TestScope]: + """Provide an isolated scope and clean related data after each test.""" + scope = _TestScope() + _seed_base_entities(db_session_with_containers, scope) + db_session_with_containers.commit() + yield scope + _cleanup_scope_data(db_session_with_containers, scope) + + +class TestGetByMessageIds: + """Tests for SQLAlchemyExecutionExtraContentRepository.get_by_message_ids.""" + + def test_groups_contents_by_message( + self, + db_session_with_containers: Session, + repository: SQLAlchemyExecutionExtraContentRepository, + test_scope: _TestScope, + ) -> None: + """Submitted forms are correctly mapped and grouped by message ID.""" + workflow_run_id = str(uuid4()) + conversation = _create_conversation(db_session_with_containers, test_scope) + msg1 = _create_message(db_session_with_containers, test_scope, conversation.id, workflow_run_id) + msg2 = _create_message(db_session_with_containers, test_scope, conversation.id, workflow_run_id) + + form = _create_submitted_form( + db_session_with_containers, + test_scope, + workflow_run_id=workflow_run_id, + action_id="approve", + action_title="Approve", + ) + _create_human_input_content( + db_session_with_containers, + workflow_run_id=workflow_run_id, + message_id=msg1.id, + form_id=form.id, + ) + db_session_with_containers.commit() + + result = repository.get_by_message_ids([msg1.id, msg2.id]) + + assert len(result) == 2 + # msg1 has one submitted content + assert len(result[0]) == 1 + content = result[0][0] + assert content.submitted is True + assert content.workflow_run_id == workflow_run_id + assert content.form_submission_data is not None + assert content.form_submission_data.action_id == "approve" + assert content.form_submission_data.action_text == "Approve" + assert content.form_submission_data.rendered_content == "Rendered Approve" + assert content.form_submission_data.node_id == "node-id" + assert content.form_submission_data.node_title == "Approval" + # msg2 has no content + assert result[1] == [] + + def test_returns_unsubmitted_form_definition( + self, + db_session_with_containers: Session, + repository: SQLAlchemyExecutionExtraContentRepository, + test_scope: _TestScope, + ) -> None: + """Waiting forms return full form_definition with resolved token and defaults.""" + workflow_run_id = str(uuid4()) + conversation = _create_conversation(db_session_with_containers, test_scope) + msg = _create_message(db_session_with_containers, test_scope, conversation.id, workflow_run_id) + + form = _create_waiting_form( + db_session_with_containers, + test_scope, + workflow_run_id=workflow_run_id, + default_values={"name": "John"}, + ) + delivery = _create_delivery(db_session_with_containers, form_id=form.id) + _create_recipient( + db_session_with_containers, + form_id=form.id, + delivery_id=delivery.id, + access_token="token-1", + ) + _create_human_input_content( + db_session_with_containers, + workflow_run_id=workflow_run_id, + message_id=msg.id, + form_id=form.id, + ) + db_session_with_containers.commit() + + result = repository.get_by_message_ids([msg.id]) + + assert len(result) == 1 + assert len(result[0]) == 1 + domain_content = result[0][0] + assert domain_content.submitted is False + assert domain_content.workflow_run_id == workflow_run_id + assert domain_content.form_definition is not None + form_def = domain_content.form_definition + assert form_def.form_id == form.id + assert form_def.node_id == "node-id" + assert form_def.node_title == "Approval" + assert form_def.form_content == "Rendered block" + assert form_def.display_in_ui is True + assert form_def.form_token == "token-1" + assert form_def.resolved_default_values == {"name": "John"} + assert form_def.expiration_time == int(form.expiration_time.timestamp()) + + def test_empty_message_ids_returns_empty_list( + self, + repository: SQLAlchemyExecutionExtraContentRepository, + ) -> None: + """Passing no message IDs returns an empty list without hitting the DB.""" + result = repository.get_by_message_ids([]) + assert result == [] diff --git a/api/tests/unit_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py b/api/tests/unit_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py deleted file mode 100644 index 8daf91c538..0000000000 --- a/api/tests/unit_tests/repositories/test_sqlalchemy_execution_extra_content_repository.py +++ /dev/null @@ -1,180 +0,0 @@ -from __future__ import annotations - -from collections.abc import Sequence -from dataclasses import dataclass -from datetime import UTC, datetime, timedelta - -from core.entities.execution_extra_content import HumanInputContent as HumanInputContentDomain -from core.entities.execution_extra_content import HumanInputFormSubmissionData -from dify_graph.nodes.human_input.entities import ( - FormDefinition, - UserAction, -) -from dify_graph.nodes.human_input.enums import HumanInputFormStatus -from models.execution_extra_content import HumanInputContent as HumanInputContentModel -from models.human_input import ConsoleRecipientPayload, HumanInputForm, HumanInputFormRecipient, RecipientType -from repositories.sqlalchemy_execution_extra_content_repository import SQLAlchemyExecutionExtraContentRepository - - -class _FakeScalarResult: - def __init__(self, values: Sequence[HumanInputContentModel]): - self._values = list(values) - - def all(self) -> list[HumanInputContentModel]: - return list(self._values) - - -class _FakeSession: - def __init__(self, values: Sequence[Sequence[object]]): - self._values = list(values) - - def scalars(self, _stmt): - if not self._values: - return _FakeScalarResult([]) - return _FakeScalarResult(self._values.pop(0)) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc, tb): - return False - - -@dataclass -class _FakeSessionMaker: - session: _FakeSession - - def __call__(self) -> _FakeSession: - return self.session - - -def _build_form(action_id: str, action_title: str, rendered_content: str) -> HumanInputForm: - expiration_time = datetime.now(UTC) + timedelta(days=1) - definition = FormDefinition( - form_content="content", - inputs=[], - user_actions=[UserAction(id=action_id, title=action_title)], - rendered_content="rendered", - expiration_time=expiration_time, - node_title="Approval", - display_in_ui=True, - ) - form = HumanInputForm( - id=f"form-{action_id}", - tenant_id="tenant-id", - app_id="app-id", - workflow_run_id="workflow-run", - node_id="node-id", - form_definition=definition.model_dump_json(), - rendered_content=rendered_content, - status=HumanInputFormStatus.SUBMITTED, - expiration_time=expiration_time, - ) - form.selected_action_id = action_id - return form - - -def _build_content(message_id: str, action_id: str, action_title: str) -> HumanInputContentModel: - form = _build_form( - action_id=action_id, - action_title=action_title, - rendered_content=f"Rendered {action_title}", - ) - content = HumanInputContentModel( - id=f"content-{message_id}", - form_id=form.id, - message_id=message_id, - workflow_run_id=form.workflow_run_id, - ) - content.form = form - return content - - -def test_get_by_message_ids_groups_contents_by_message() -> None: - message_ids = ["msg-1", "msg-2"] - contents = [_build_content("msg-1", "approve", "Approve")] - repository = SQLAlchemyExecutionExtraContentRepository( - session_maker=_FakeSessionMaker(session=_FakeSession(values=[contents, []])) - ) - - result = repository.get_by_message_ids(message_ids) - - assert len(result) == 2 - assert [content.model_dump(mode="json", exclude_none=True) for content in result[0]] == [ - HumanInputContentDomain( - workflow_run_id="workflow-run", - submitted=True, - form_submission_data=HumanInputFormSubmissionData( - node_id="node-id", - node_title="Approval", - rendered_content="Rendered Approve", - action_id="approve", - action_text="Approve", - ), - ).model_dump(mode="json", exclude_none=True) - ] - assert result[1] == [] - - -def test_get_by_message_ids_returns_unsubmitted_form_definition() -> None: - expiration_time = datetime.now(UTC) + timedelta(days=1) - definition = FormDefinition( - form_content="content", - inputs=[], - user_actions=[UserAction(id="approve", title="Approve")], - rendered_content="rendered", - expiration_time=expiration_time, - default_values={"name": "John"}, - node_title="Approval", - display_in_ui=True, - ) - form = HumanInputForm( - id="form-1", - tenant_id="tenant-id", - app_id="app-id", - workflow_run_id="workflow-run", - node_id="node-id", - form_definition=definition.model_dump_json(), - rendered_content="Rendered block", - status=HumanInputFormStatus.WAITING, - expiration_time=expiration_time, - ) - content = HumanInputContentModel( - id="content-msg-1", - form_id=form.id, - message_id="msg-1", - workflow_run_id=form.workflow_run_id, - ) - content.form = form - - recipient = HumanInputFormRecipient( - form_id=form.id, - delivery_id="delivery-1", - recipient_type=RecipientType.CONSOLE, - recipient_payload=ConsoleRecipientPayload(account_id=None).model_dump_json(), - access_token="token-1", - ) - - repository = SQLAlchemyExecutionExtraContentRepository( - session_maker=_FakeSessionMaker(session=_FakeSession(values=[[content], [recipient]])) - ) - - result = repository.get_by_message_ids(["msg-1"]) - - assert len(result) == 1 - assert len(result[0]) == 1 - domain_content = result[0][0] - assert domain_content.submitted is False - assert domain_content.workflow_run_id == "workflow-run" - assert domain_content.form_definition is not None - assert domain_content.form_definition.expiration_time == int(form.expiration_time.timestamp()) - assert domain_content.form_definition is not None - form_definition = domain_content.form_definition - assert form_definition.form_id == "form-1" - assert form_definition.node_id == "node-id" - assert form_definition.node_title == "Approval" - assert form_definition.form_content == "Rendered block" - assert form_definition.display_in_ui is True - assert form_definition.form_token == "token-1" - assert form_definition.resolved_default_values == {"name": "John"} - assert form_definition.expiration_time == int(form.expiration_time.timestamp()) From fdc880bc6725b82d8beb256bfcc27f90b8fc97fc Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Mon, 23 Mar 2026 16:37:03 +0800 Subject: [PATCH 33/70] test(workflow): add unit tests for workflow components (#33910) Co-authored-by: CodingOnStar --- .../__tests__/ExternalApiSelection.spec.tsx | 2 +- .../header/nav/__tests__/index.spec.tsx | 29 +- .../__tests__/tool-picker.spec.tsx | 532 +++++++++++ .../__tests__/provider.spec.tsx | 91 ++ .../header/__tests__/header-layouts.spec.tsx | 308 +++++++ .../workflow/header/__tests__/index.spec.tsx | 106 +++ .../hooks-store/__tests__/provider.spec.tsx | 73 ++ .../workflow/nodes/__tests__/index.spec.tsx | 107 +++ .../__tests__/file-support.spec.tsx | 226 +++++ .../error-handle/__tests__/index.spec.tsx | 250 +++++ .../input-field/__tests__/index.spec.tsx | 8 + .../layout/__tests__/index.spec.tsx | 57 +- .../__tests__/index.spec.tsx | 114 +++ .../__tests__/placeholder.spec.tsx | 78 ++ .../panel-operator/__tests__/details.spec.tsx | 268 ++++++ .../__tests__/index.spec.tsx | 52 ++ .../assigned-var-reference-popup.spec.tsx | 72 ++ .../variable-label/__tests__/index.spec.tsx | 98 +- .../agent/__tests__/integration.spec.tsx | 340 +++++++ .../assigner/__tests__/integration.spec.tsx | 514 +++++++++++ .../code/__tests__/dependency-picker.spec.tsx | 39 + .../__tests__/integration.spec.tsx | 204 +++++ .../nodes/http/__tests__/integration.spec.tsx | 705 +++++++++++++++ .../if-else/__tests__/integration.spec.tsx | 430 +++++++++ .../iteration/__tests__/integration.spec.tsx | 266 ++++++ .../__tests__/integration.spec.tsx | 615 +++++++++++++ .../__tests__/integration.spec.tsx | 309 +++++++ .../nodes/llm/__tests__/panel.spec.tsx | 105 +-- .../nodes/loop/__tests__/integration.spec.tsx | 665 ++++++++++++++ .../__tests__/integration.spec.tsx | 851 ++++++++++++++++++ .../__tests__/integration.spec.tsx | 385 ++++++++ .../__tests__/integration.spec.tsx | 224 +++++ .../__tests__/input-var-list.spec.tsx | 513 +++++++++++ .../trigger-schedule/__tests__/panel.spec.tsx | 266 ++++++ .../components/__tests__/integration.spec.tsx | 151 ++++ .../__tests__/integration.spec.tsx | 537 +++++++++++ .../__tests__/human-input-form-list.spec.tsx | 162 ++++ .../workflow/panel/__tests__/index.spec.tsx | 311 +++++-- .../panel/__tests__/workflow-preview.spec.tsx | 354 ++++++++ .../__tests__/integration.spec.tsx | 176 ++++ .../workflow/panel/chat-record/user-input.tsx | 17 +- .../__tests__/index.spec.tsx | 262 ++++++ .../components/__tests__/integration.spec.tsx | 282 ++++++ .../__tests__/components.spec.tsx | 610 +++++++++++++ .../env-panel/__tests__/integration.spec.tsx | 267 ++++++ .../__tests__/index.spec.tsx | 55 ++ .../__tests__/index.spec.tsx | 68 ++ .../run/__tests__/loop-result-panel.spec.tsx | 116 +++ .../agent-log/__tests__/integration.spec.tsx | 101 +++ .../run/agent-log/agent-log-nav-more.tsx | 2 +- .../__tests__/integration.spec.tsx | 70 ++ .../__tests__/retry-result-panel.spec.tsx | 75 ++ .../simple-node/__tests__/index.spec.tsx | 138 +++ web/eslint-suppressions.json | 2 +- 54 files changed, 12469 insertions(+), 189 deletions(-) create mode 100644 web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx create mode 100644 web/app/components/workflow/datasets-detail-store/__tests__/provider.spec.tsx create mode 100644 web/app/components/workflow/header/__tests__/header-layouts.spec.tsx create mode 100644 web/app/components/workflow/header/__tests__/index.spec.tsx create mode 100644 web/app/components/workflow/hooks-store/__tests__/provider.spec.tsx create mode 100644 web/app/components/workflow/nodes/__tests__/index.spec.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/__tests__/file-support.spec.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/__tests__/index.spec.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/__tests__/index.spec.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/__tests__/placeholder.spec.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/panel-operator/__tests__/details.spec.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/support-var-input/__tests__/index.spec.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/variable/__tests__/assigned-var-reference-popup.spec.tsx create mode 100644 web/app/components/workflow/nodes/agent/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/assigner/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/code/__tests__/dependency-picker.spec.tsx create mode 100644 web/app/components/workflow/nodes/document-extractor/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/http/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/if-else/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/iteration/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/knowledge-retrieval/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/list-operator/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/loop/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/parameter-extractor/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/question-classifier/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/template-transform/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/tool/components/__tests__/input-var-list.spec.tsx create mode 100644 web/app/components/workflow/nodes/trigger-schedule/__tests__/panel.spec.tsx create mode 100644 web/app/components/workflow/nodes/trigger-schedule/components/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/nodes/variable-assigner/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/panel/__tests__/human-input-form-list.spec.tsx create mode 100644 web/app/components/workflow/panel/__tests__/workflow-preview.spec.tsx create mode 100644 web/app/components/workflow/panel/chat-record/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/panel/chat-variable-panel/__tests__/index.spec.tsx create mode 100644 web/app/components/workflow/panel/chat-variable-panel/components/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/panel/debug-and-preview/__tests__/components.spec.tsx create mode 100644 web/app/components/workflow/panel/env-panel/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/panel/global-variable-panel/__tests__/index.spec.tsx create mode 100644 web/app/components/workflow/plugin-dependency/__tests__/index.spec.tsx create mode 100644 web/app/components/workflow/run/__tests__/loop-result-panel.spec.tsx create mode 100644 web/app/components/workflow/run/agent-log/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/run/iteration-log/__tests__/integration.spec.tsx create mode 100644 web/app/components/workflow/run/retry-log/__tests__/retry-result-panel.spec.tsx create mode 100644 web/app/components/workflow/simple-node/__tests__/index.spec.tsx diff --git a/web/app/components/datasets/external-knowledge-base/create/__tests__/ExternalApiSelection.spec.tsx b/web/app/components/datasets/external-knowledge-base/create/__tests__/ExternalApiSelection.spec.tsx index 97934f36e1..8d055606b8 100644 --- a/web/app/components/datasets/external-knowledge-base/create/__tests__/ExternalApiSelection.spec.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/__tests__/ExternalApiSelection.spec.tsx @@ -35,7 +35,7 @@ vi.mock('../ExternalApiSelect', () => ({ {value} {items.length} {items.map((item: MockSelectItem) => ( - ))} diff --git a/web/app/components/header/nav/__tests__/index.spec.tsx b/web/app/components/header/nav/__tests__/index.spec.tsx index 6ee8a7a924..3dce8375b3 100644 --- a/web/app/components/header/nav/__tests__/index.spec.tsx +++ b/web/app/components/header/nav/__tests__/index.spec.tsx @@ -8,6 +8,7 @@ import { waitFor, } from '@testing-library/react' import * as React from 'react' +import { use } from 'react' import { vi } from 'vitest' import { useStore as useAppStore } from '@/app/components/app/store' import { useAppContext } from '@/context/app-context' @@ -23,14 +24,14 @@ vi.mock('@headlessui/react', () => { const [open, setOpen] = React.useState(false) const value = React.useMemo(() => ({ open, setOpen }), [open]) return ( - + {typeof children === 'function' ? children({ open }) : children} - + ) } const MenuButton = ({ onClick, children, ...props }: { onClick?: () => void, children?: React.ReactNode }) => { - const context = React.useContext(MenuContext) + const context = use(MenuContext) const handleClick = () => { context?.setOpen(!context.open) onClick?.() @@ -43,7 +44,7 @@ vi.mock('@headlessui/react', () => { } const MenuItems = ({ as: Component = 'div', role, children, ...props }: { as?: React.ElementType, role?: string, children: React.ReactNode }) => { - const context = React.useContext(MenuContext) + const context = use(MenuContext) if (!context?.open) return null return ( @@ -84,6 +85,26 @@ vi.mock('@/context/app-context', () => ({ useAppContext: vi.fn(), })) +vi.mock('@/next/link', () => ({ + default: ({ + href, + children, + onClick, + ...props + }: React.AnchorHTMLAttributes & { href: string, children?: React.ReactNode }) => ( + { + event.preventDefault() + onClick?.(event) + }} + {...props} + > + {children} + + ), +})) + describe('Nav Component', () => { const mockSetAppDetail = vi.fn() const mockOnCreate = vi.fn() 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 new file mode 100644 index 0000000000..47ad2fad02 --- /dev/null +++ b/web/app/components/workflow/block-selector/__tests__/tool-picker.spec.tsx @@ -0,0 +1,532 @@ +import type { ToolWithProvider } from '../../types' +import type { ToolValue } from '../types' +import type { Plugin } from '@/app/components/plugins/types' +import type { Tool } from '@/app/components/tools/types' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { useTags } from '@/app/components/plugins/hooks' +import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks' +import { PluginCategoryEnum } from '@/app/components/plugins/types' +import { CollectionType } from '@/app/components/tools/types' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useGetLanguage } from '@/context/i18n' +import useTheme from '@/hooks/use-theme' +import { createCustomCollection } from '@/service/tools' +import { useFeaturedToolsRecommendations } from '@/service/use-plugins' +import { + useAllBuiltInTools, + useAllCustomTools, + useAllMCPTools, + useAllWorkflowTools, + useInvalidateAllBuiltInTools, + useInvalidateAllCustomTools, + useInvalidateAllMCPTools, + useInvalidateAllWorkflowTools, +} from '@/service/use-tools' +import { Theme } from '@/types/app' +import { defaultSystemFeatures } from '@/types/feature' +import ToolPicker from '../tool-picker' + +const mockNotify = vi.fn() +const mockSetSystemFeatures = vi.fn() +const mockInvalidateBuiltInTools = vi.fn() +const mockInvalidateCustomTools = vi.fn() +const mockInvalidateWorkflowTools = vi.fn() +const mockInvalidateMcpTools = vi.fn() +const mockCreateCustomCollection = vi.mocked(createCustomCollection) +const mockInstallPackageFromMarketPlace = vi.fn() +const mockCheckInstalled = vi.fn() +const mockRefreshPluginList = vi.fn() + +const mockUseGlobalPublicStore = vi.mocked(useGlobalPublicStore) +const mockUseGetLanguage = vi.mocked(useGetLanguage) +const mockUseTheme = vi.mocked(useTheme) +const mockUseTags = vi.mocked(useTags) +const mockUseMarketplacePlugins = vi.mocked(useMarketplacePlugins) +const mockUseAllBuiltInTools = vi.mocked(useAllBuiltInTools) +const mockUseAllCustomTools = vi.mocked(useAllCustomTools) +const mockUseAllWorkflowTools = vi.mocked(useAllWorkflowTools) +const mockUseAllMCPTools = vi.mocked(useAllMCPTools) +const mockUseInvalidateAllBuiltInTools = vi.mocked(useInvalidateAllBuiltInTools) +const mockUseInvalidateAllCustomTools = vi.mocked(useInvalidateAllCustomTools) +const mockUseInvalidateAllWorkflowTools = vi.mocked(useInvalidateAllWorkflowTools) +const mockUseInvalidateAllMCPTools = vi.mocked(useInvalidateAllMCPTools) +const mockUseFeaturedToolsRecommendations = vi.mocked(useFeaturedToolsRecommendations) + +vi.mock('@/context/global-public-context', () => ({ + useGlobalPublicStore: vi.fn(), +})) + +vi.mock('@/context/i18n', () => ({ + useGetLanguage: vi.fn(), +})) + +vi.mock('@/hooks/use-theme', () => ({ + default: vi.fn(), +})) + +vi.mock('@/app/components/plugins/hooks', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useTags: vi.fn(), + } +}) + +vi.mock('@/app/components/plugins/marketplace/hooks', () => ({ + useMarketplacePlugins: vi.fn(), +})) + +vi.mock('@/service/tools', () => ({ + createCustomCollection: vi.fn(), +})) + +vi.mock('@/service/use-plugins', () => ({ + useFeaturedToolsRecommendations: vi.fn(), + useDownloadPlugin: vi.fn(() => ({ + data: undefined, + isLoading: false, + })), + useInstallPackageFromMarketPlace: () => ({ + mutateAsync: mockInstallPackageFromMarketPlace, + isPending: false, + }), + usePluginDeclarationFromMarketPlace: () => ({ + data: undefined, + }), + usePluginTaskList: () => ({ + handleRefetch: vi.fn(), + }), + useUpdatePackageFromMarketPlace: () => ({ + mutateAsync: vi.fn(), + }), +})) + +vi.mock('@/service/use-tools', () => ({ + useAllBuiltInTools: vi.fn(), + useAllCustomTools: vi.fn(), + useAllWorkflowTools: vi.fn(), + useAllMCPTools: vi.fn(), + useInvalidateAllBuiltInTools: vi.fn(), + useInvalidateAllCustomTools: vi.fn(), + useInvalidateAllWorkflowTools: vi.fn(), + useInvalidateAllMCPTools: vi.fn(), +})) + +vi.mock('@/app/components/base/toast', () => ({ + default: { + notify: (payload: unknown) => mockNotify(payload), + }, +})) + +vi.mock('@/app/components/base/amplitude', () => ({ + trackEvent: vi.fn(), +})) + +vi.mock('next-themes', () => ({ + useTheme: () => ({ theme: Theme.light }), +})) + +vi.mock('@/app/components/tools/edit-custom-collection-modal', () => ({ + default: ({ + onAdd, + onHide, + }: { + onAdd: (payload: { name: string }) => Promise + onHide: () => void + }) => ( +
+ + +
+ ), +})) + +vi.mock('@/app/components/plugins/install-plugin/hooks/use-check-installed', () => ({ + default: () => mockCheckInstalled(), +})) + +vi.mock('@/app/components/plugins/install-plugin/hooks/use-install-plugin-limit', () => ({ + default: () => ({ + canInstall: true, + }), +})) + +vi.mock('@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list', () => ({ + default: () => ({ + refreshPluginList: mockRefreshPluginList, + }), +})) + +vi.mock('@/app/components/plugins/install-plugin/base/check-task-status', () => ({ + default: () => ({ + check: vi.fn().mockResolvedValue({ status: 'success' }), + stop: vi.fn(), + }), +})) + +vi.mock('@/app/components/plugins/install-plugin/install-from-marketplace', () => ({ + default: ({ + onSuccess, + onClose, + }: { + onSuccess: () => void | Promise + onClose: () => void + }) => ( +
+ + +
+ ), +})) + +vi.mock('@/utils/var', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + getMarketplaceUrl: () => 'https://marketplace.test/tools', + } +}) + +const createTool = ( + name: string, + label: string, + description = `${label} description`, +): Tool => ({ + name, + author: 'author', + label: { + en_US: label, + zh_Hans: label, + }, + description: { + en_US: description, + zh_Hans: description, + }, + parameters: [], + labels: [], + output_schema: {}, +}) + +const createToolProvider = ( + overrides: Partial = {}, +): ToolWithProvider => ({ + id: 'provider-1', + name: 'provider-one', + author: 'Provider Author', + description: { + en_US: 'Provider description', + zh_Hans: 'Provider description', + }, + icon: 'icon', + icon_dark: 'icon-dark', + label: { + en_US: 'Provider One', + zh_Hans: 'Provider One', + }, + type: CollectionType.builtIn, + team_credentials: {}, + is_team_authorization: false, + allow_delete: false, + labels: [], + plugin_id: 'plugin-1', + tools: [createTool('tool-a', 'Tool A')], + meta: { version: '1.0.0' } as ToolWithProvider['meta'], + plugin_unique_identifier: 'plugin-1@1.0.0', + ...overrides, +}) + +const createToolValue = (overrides: Partial = {}): ToolValue => ({ + provider_name: 'provider-a', + tool_name: 'tool-a', + tool_label: 'Tool A', + ...overrides, +}) + +const createPlugin = (overrides: Partial = {}): Plugin => ({ + type: 'plugin', + org: 'org', + author: 'author', + name: 'Plugin One', + plugin_id: 'plugin-1', + version: '1.0.0', + latest_version: '1.0.0', + latest_package_identifier: 'plugin-1@1.0.0', + icon: 'icon', + verified: true, + label: { en_US: 'Plugin One' }, + brief: { en_US: 'Brief' }, + description: { en_US: 'Plugin description' }, + introduction: 'Intro', + repository: 'https://example.com', + category: PluginCategoryEnum.tool, + install_count: 0, + endpoint: { settings: [] }, + tags: [{ name: 'tag-a' }], + badges: [], + verification: { authorized_category: 'community' }, + from: 'marketplace', + ...overrides, +}) + +const builtInTools = [ + createToolProvider({ + id: 'built-in-1', + name: 'built-in-provider', + label: { en_US: 'Built-in Provider', zh_Hans: 'Built-in Provider' }, + tools: [createTool('built-in-tool', 'Built-in Tool')], + }), +] + +const customTools = [ + createToolProvider({ + id: 'custom-1', + name: 'custom-provider', + label: { en_US: 'Custom Provider', zh_Hans: 'Custom Provider' }, + type: CollectionType.custom, + tools: [createTool('weather-tool', 'Weather Tool')], + }), +] + +const workflowTools = [ + createToolProvider({ + id: 'workflow-1', + name: 'workflow-provider', + label: { en_US: 'Workflow Provider', zh_Hans: 'Workflow Provider' }, + type: CollectionType.workflow, + tools: [createTool('workflow-tool', 'Workflow Tool')], + }), +] + +const mcpTools = [ + createToolProvider({ + id: 'mcp-1', + name: 'mcp-provider', + label: { en_US: 'MCP Provider', zh_Hans: 'MCP Provider' }, + type: CollectionType.mcp, + tools: [createTool('mcp-tool', 'MCP Tool')], + }), +] + +const renderToolPicker = (props: Partial> = {}) => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + return render( + + open-picker} + isShow={false} + onShowChange={vi.fn()} + onSelect={vi.fn()} + onSelectMultiple={vi.fn()} + selectedTools={[createToolValue()]} + {...props} + /> + , + ) +} + +describe('ToolPicker', () => { + beforeEach(() => { + vi.clearAllMocks() + + mockUseGlobalPublicStore.mockImplementation(selector => selector({ + systemFeatures: { + ...defaultSystemFeatures, + enable_marketplace: true, + }, + setSystemFeatures: mockSetSystemFeatures, + })) + mockUseGetLanguage.mockReturnValue('en_US') + mockUseTheme.mockReturnValue({ theme: Theme.light } as ReturnType) + mockUseTags.mockReturnValue({ + tags: [{ name: 'weather', label: 'Weather' }], + tagsMap: { weather: { name: 'weather', label: 'Weather' } }, + getTagLabel: (name: string) => name, + }) + mockUseMarketplacePlugins.mockReturnValue({ + plugins: [], + total: 0, + resetPlugins: vi.fn(), + queryPlugins: vi.fn(), + queryPluginsWithDebounced: vi.fn(), + cancelQueryPluginsWithDebounced: vi.fn(), + isLoading: false, + isFetchingNextPage: false, + hasNextPage: false, + fetchNextPage: vi.fn(), + page: 0, + } as ReturnType) + mockUseAllBuiltInTools.mockReturnValue({ data: builtInTools } as ReturnType) + mockUseAllCustomTools.mockReturnValue({ data: customTools } as ReturnType) + mockUseAllWorkflowTools.mockReturnValue({ data: workflowTools } as ReturnType) + mockUseAllMCPTools.mockReturnValue({ data: mcpTools } as ReturnType) + mockUseInvalidateAllBuiltInTools.mockReturnValue(mockInvalidateBuiltInTools) + mockUseInvalidateAllCustomTools.mockReturnValue(mockInvalidateCustomTools) + mockUseInvalidateAllWorkflowTools.mockReturnValue(mockInvalidateWorkflowTools) + mockUseInvalidateAllMCPTools.mockReturnValue(mockInvalidateMcpTools) + mockUseFeaturedToolsRecommendations.mockReturnValue({ + plugins: [], + isLoading: false, + } as ReturnType) + mockCreateCustomCollection.mockResolvedValue(undefined) + mockInstallPackageFromMarketPlace.mockResolvedValue({ + all_installed: true, + task_id: 'task-1', + }) + mockCheckInstalled.mockReturnValue({ + installedInfo: undefined, + isLoading: false, + error: undefined, + }) + window.localStorage.clear() + }) + + it('should request opening when the trigger is clicked unless the picker is disabled', async () => { + const user = userEvent.setup() + const onShowChange = vi.fn() + const disabledOnShowChange = vi.fn() + + renderToolPicker({ onShowChange }) + + await user.click(screen.getByRole('button', { name: 'open-picker' })) + expect(onShowChange).toHaveBeenCalledWith(true) + + renderToolPicker({ + disabled: true, + onShowChange: disabledOnShowChange, + }) + + await user.click(screen.getAllByRole('button', { name: 'open-picker' })[1]!) + expect(disabledOnShowChange).not.toHaveBeenCalled() + }) + + it('should render real search and tool lists, then forward tool selections', async () => { + const user = userEvent.setup() + const onSelect = vi.fn() + const onSelectMultiple = vi.fn() + const queryPluginsWithDebounced = vi.fn() + + mockUseMarketplacePlugins.mockReturnValue({ + plugins: [], + total: 0, + resetPlugins: vi.fn(), + queryPlugins: vi.fn(), + queryPluginsWithDebounced, + cancelQueryPluginsWithDebounced: vi.fn(), + isLoading: false, + isFetchingNextPage: false, + hasNextPage: false, + fetchNextPage: vi.fn(), + page: 0, + } as ReturnType) + + renderToolPicker({ + isShow: true, + scope: 'custom', + onSelect, + onSelectMultiple, + selectedTools: [], + }) + + expect(screen.queryByText('Built-in Provider')).not.toBeInTheDocument() + expect(screen.getByText('Custom Provider')).toBeInTheDocument() + expect(screen.getByText('MCP Provider')).toBeInTheDocument() + + await user.type(screen.getByRole('textbox'), 'weather') + + await waitFor(() => { + expect(queryPluginsWithDebounced).toHaveBeenLastCalledWith({ + query: 'weather', + tags: [], + category: PluginCategoryEnum.tool, + }) + }) + + await waitFor(() => { + expect(screen.getByText('Weather Tool')).toBeInTheDocument() + }) + await user.click(screen.getByText('Weather Tool')) + + expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ + provider_name: 'custom-provider', + tool_name: 'weather-tool', + tool_label: 'Weather Tool', + })) + + await user.hover(screen.getByText('Custom Provider')) + await user.click(screen.getByText('workflow.tabs.addAll')) + + expect(onSelectMultiple).toHaveBeenCalledWith([ + expect.objectContaining({ + provider_name: 'custom-provider', + tool_name: 'weather-tool', + tool_label: 'Weather Tool', + }), + ]) + }) + + it('should create a custom collection from the add button and refresh custom tools', async () => { + const user = userEvent.setup() + const { container } = renderToolPicker({ + isShow: true, + supportAddCustomTool: true, + }) + + const addCustomToolButton = Array.from(container.querySelectorAll('button')).find((button) => { + return button.className.includes('bg-components-button-primary-bg') + }) + + expect(addCustomToolButton).toBeTruthy() + + await user.click(addCustomToolButton!) + expect(screen.getByTestId('edit-custom-tool-modal')).toBeInTheDocument() + + await user.click(screen.getByRole('button', { name: 'submit-custom-tool' })) + + await waitFor(() => { + expect(mockCreateCustomCollection).toHaveBeenCalledWith({ name: 'collection-a' }) + }) + expect(mockNotify).toHaveBeenCalledWith({ + type: 'success', + message: 'common.api.actionSuccess', + }) + expect(mockInvalidateCustomTools).toHaveBeenCalledTimes(1) + expect(screen.queryByTestId('edit-custom-tool-modal')).not.toBeInTheDocument() + }) + + it('should invalidate all tool collections after featured install succeeds', async () => { + const user = userEvent.setup() + + mockUseFeaturedToolsRecommendations.mockReturnValue({ + plugins: [createPlugin({ plugin_id: 'featured-1', latest_package_identifier: 'featured-1@1.0.0' })], + isLoading: false, + } as ReturnType) + + renderToolPicker({ + isShow: true, + selectedTools: [], + }) + + const featuredPluginItem = await screen.findByText('Plugin One') + await user.hover(featuredPluginItem) + await user.click(screen.getByRole('button', { name: 'plugin.installAction' })) + expect(await screen.findByTestId('install-from-marketplace')).toBeInTheDocument() + fireEvent.click(screen.getByRole('button', { name: 'complete-featured-install' })) + + await waitFor(() => { + expect(mockInvalidateBuiltInTools).toHaveBeenCalledTimes(1) + expect(mockInvalidateCustomTools).toHaveBeenCalledTimes(1) + expect(mockInvalidateWorkflowTools).toHaveBeenCalledTimes(1) + expect(mockInvalidateMcpTools).toHaveBeenCalledTimes(1) + }, { timeout: 3000 }) + }) +}) diff --git a/web/app/components/workflow/datasets-detail-store/__tests__/provider.spec.tsx b/web/app/components/workflow/datasets-detail-store/__tests__/provider.spec.tsx new file mode 100644 index 0000000000..c3c3eaf911 --- /dev/null +++ b/web/app/components/workflow/datasets-detail-store/__tests__/provider.spec.tsx @@ -0,0 +1,91 @@ +import type { Node } from '../../types' +import type { DataSet } from '@/models/datasets' +import { render, screen, waitFor } from '@testing-library/react' +import { BlockEnum } from '../../types' +import DatasetsDetailProvider from '../provider' +import { useDatasetsDetailStore } from '../store' + +const mockFetchDatasets = vi.fn() + +vi.mock('@/service/datasets', () => ({ + fetchDatasets: (params: unknown) => mockFetchDatasets(params), +})) + +const Consumer = () => { + const datasetCount = useDatasetsDetailStore(state => Object.keys(state.datasetsDetail).length) + return
{`dataset-count:${datasetCount}`}
+} + +const createWorkflowNode = (datasetIds: string[] = []): Node => ({ + id: `node-${datasetIds.join('-') || 'empty'}`, + type: 'custom', + position: { x: 0, y: 0 }, + data: { + title: 'Knowledge', + desc: '', + type: BlockEnum.KnowledgeRetrieval, + dataset_ids: datasetIds, + }, +} as unknown as Node) + +const createDataset = (id: string): DataSet => ({ + id, + name: `Dataset ${id}`, +} as DataSet) + +describe('datasets-detail-store provider', () => { + beforeEach(() => { + vi.clearAllMocks() + mockFetchDatasets.mockResolvedValue({ data: [] }) + }) + + it('should provide the datasets detail store without fetching when no knowledge datasets are selected', () => { + render( + + + , + ) + + expect(screen.getByText('dataset-count:0')).toBeInTheDocument() + expect(mockFetchDatasets).not.toHaveBeenCalled() + }) + + it('should fetch unique dataset details from knowledge retrieval nodes and store them', async () => { + mockFetchDatasets.mockResolvedValue({ + data: [createDataset('dataset-1'), createDataset('dataset-2')], + }) + + render( + + + , + ) + + await waitFor(() => { + expect(mockFetchDatasets).toHaveBeenCalledWith({ + url: '/datasets', + params: { + page: 1, + ids: ['dataset-1', 'dataset-2'], + }, + }) + expect(screen.getByText('dataset-count:2')).toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx b/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx new file mode 100644 index 0000000000..dc00d61301 --- /dev/null +++ b/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx @@ -0,0 +1,308 @@ +import type { Shape } from '../../store/workflow' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { FlowType } from '@/types/common' +import { renderWorkflowComponent } from '../../__tests__/workflow-test-env' +import { WorkflowVersion } from '../../types' +import HeaderInNormal from '../header-in-normal' +import HeaderInRestoring from '../header-in-restoring' +import HeaderInHistory from '../header-in-view-history' + +const mockUseNodes = vi.fn() +const mockHandleBackupDraft = vi.fn() +const mockHandleLoadBackupDraft = vi.fn() +const mockHandleNodeSelect = vi.fn() +const mockHandleRefreshWorkflowDraft = vi.fn() +const mockCloseAllInputFieldPanels = vi.fn() +const mockInvalidAllLastRun = vi.fn() +const mockRestoreWorkflow = vi.fn() +const mockNotify = vi.fn() +const mockRunAndHistory = vi.fn() +const mockViewHistory = vi.fn() + +let mockNodesReadOnly = false +let mockTheme: 'light' | 'dark' = 'light' + +vi.mock('reactflow', () => ({ + useNodes: () => mockUseNodes(), +})) + +vi.mock('../../hooks', () => ({ + useNodesReadOnly: () => ({ nodesReadOnly: mockNodesReadOnly }), + useNodesInteractions: () => ({ handleNodeSelect: mockHandleNodeSelect }), + useWorkflowRun: () => ({ + handleBackupDraft: mockHandleBackupDraft, + handleLoadBackupDraft: mockHandleLoadBackupDraft, + }), + useNodesSyncDraft: () => ({ + handleSyncWorkflowDraft: vi.fn(), + }), + useWorkflowRefreshDraft: () => ({ + handleRefreshWorkflowDraft: mockHandleRefreshWorkflowDraft, + }), +})) + +vi.mock('@/app/components/rag-pipeline/hooks', () => ({ + useInputFieldPanel: () => ({ + closeAllInputFieldPanels: mockCloseAllInputFieldPanels, + }), +})) + +vi.mock('@/hooks/use-theme', () => ({ + default: () => ({ + theme: mockTheme, + }), +})) + +vi.mock('@/service/use-workflow', () => ({ + useInvalidAllLastRun: () => mockInvalidAllLastRun, + useRestoreWorkflow: () => ({ + mutateAsync: mockRestoreWorkflow, + }), +})) + +vi.mock('../../../base/toast', () => ({ + default: { + notify: (payload: unknown) => mockNotify(payload), + }, +})) + +vi.mock('../editing-title', () => ({ + default: () =>
editing-title
, +})) + +vi.mock('../scroll-to-selected-node-button', () => ({ + default: () =>
scroll-button
, +})) + +vi.mock('../env-button', () => ({ + default: ({ disabled }: { disabled: boolean }) =>
{`${disabled}`}
, +})) + +vi.mock('../global-variable-button', () => ({ + default: ({ disabled }: { disabled: boolean }) =>
{`${disabled}`}
, +})) + +vi.mock('../run-and-history', () => ({ + default: (props: object) => { + mockRunAndHistory(props) + return
+ }, +})) + +vi.mock('../version-history-button', () => ({ + default: ({ onClick }: { onClick: () => void }) => ( + + ), +})) + +vi.mock('../restoring-title', () => ({ + default: () =>
restoring-title
, +})) + +vi.mock('../running-title', () => ({ + default: () =>
running-title
, +})) + +vi.mock('../view-history', () => ({ + default: (props: { withText?: boolean }) => { + mockViewHistory(props) + return
{props.withText ? 'with-text' : 'icon-only'}
+ }, +})) + +const createSelectedNode = (selected = true) => ({ + id: 'node-selected', + data: { + selected, + }, +}) + +const createBackupDraft = (): NonNullable => ({ + nodes: [], + edges: [], + viewport: { x: 0, y: 0, zoom: 1 }, + environmentVariables: [], +}) + +const createCurrentVersion = (): NonNullable => ({ + id: 'version-1', + graph: { + nodes: [], + edges: [], + viewport: { x: 0, y: 0, zoom: 1 }, + }, + created_at: 0, + created_by: { + id: 'user-1', + name: 'Tester', + email: 'tester@example.com', + }, + hash: 'hash-1', + updated_at: 0, + updated_by: { + id: 'user-1', + name: 'Tester', + email: 'tester@example.com', + }, + tool_published: false, + environment_variables: [], + version: WorkflowVersion.Latest, + marked_name: '', + marked_comment: '', +}) + +describe('Header layout components', () => { + beforeEach(() => { + vi.clearAllMocks() + mockNodesReadOnly = false + mockTheme = 'light' + mockUseNodes.mockReturnValue([]) + mockRestoreWorkflow.mockResolvedValue(undefined) + }) + + describe('HeaderInNormal', () => { + it('should render slots, pass read-only state to action buttons, and start restoring mode', () => { + mockNodesReadOnly = true + mockUseNodes.mockReturnValue([createSelectedNode()]) + + const { store } = renderWorkflowComponent( + left-slot
, + middle:
middle-slot
, + chatVariableTrigger:
chat-trigger
, + }} + />, + { + initialStoreState: { + showEnvPanel: true, + showDebugAndPreviewPanel: true, + showVariableInspectPanel: true, + showChatVariablePanel: true, + showGlobalVariablePanel: true, + }, + }, + ) + + expect(screen.getByText('editing-title')).toBeInTheDocument() + expect(screen.getByText('scroll-button')).toBeInTheDocument() + expect(screen.getByText('left-slot')).toBeInTheDocument() + expect(screen.getByText('middle-slot')).toBeInTheDocument() + expect(screen.getByText('chat-trigger')).toBeInTheDocument() + expect(screen.getByTestId('env-button')).toHaveTextContent('true') + expect(screen.getByTestId('global-variable-button')).toHaveTextContent('true') + expect(mockRunAndHistory).toHaveBeenCalledTimes(1) + + fireEvent.click(screen.getByRole('button', { name: 'version-history' })) + + expect(mockHandleBackupDraft).toHaveBeenCalledTimes(1) + expect(mockHandleNodeSelect).toHaveBeenCalledWith('node-selected', true) + expect(mockCloseAllInputFieldPanels).toHaveBeenCalledTimes(1) + expect(store.getState().isRestoring).toBe(true) + expect(store.getState().showWorkflowVersionHistoryPanel).toBe(true) + expect(store.getState().showEnvPanel).toBe(false) + expect(store.getState().showDebugAndPreviewPanel).toBe(false) + expect(store.getState().showVariableInspectPanel).toBe(false) + expect(store.getState().showChatVariablePanel).toBe(false) + expect(store.getState().showGlobalVariablePanel).toBe(false) + }) + }) + + describe('HeaderInRestoring', () => { + it('should cancel restoring mode and reopen the editor state', () => { + const { store } = renderWorkflowComponent( + , + { + initialStoreState: { + isRestoring: true, + showWorkflowVersionHistoryPanel: true, + }, + hooksStoreProps: { + configsMap: { + flowType: FlowType.appFlow, + flowId: 'flow-1', + fileSettings: {}, + }, + }, + }, + ) + + fireEvent.click(screen.getByRole('button', { name: 'workflow.common.exitVersions' })) + + expect(mockHandleLoadBackupDraft).toHaveBeenCalledTimes(1) + expect(store.getState().isRestoring).toBe(false) + expect(store.getState().showWorkflowVersionHistoryPanel).toBe(false) + }) + + it('should restore the selected version, clear backup state, and forward lifecycle callbacks', async () => { + const onRestoreSettled = vi.fn() + const deleteAllInspectVars = vi.fn() + const currentVersion = createCurrentVersion() + + const { store } = renderWorkflowComponent( + , + { + initialStoreState: { + isRestoring: true, + showWorkflowVersionHistoryPanel: true, + backupDraft: createBackupDraft(), + currentVersion, + deleteAllInspectVars, + }, + hooksStoreProps: { + configsMap: { + flowType: FlowType.appFlow, + flowId: 'flow-1', + fileSettings: {}, + }, + }, + }, + ) + + fireEvent.click(screen.getByRole('button', { name: 'workflow.common.restore' })) + + await waitFor(() => { + expect(mockRestoreWorkflow).toHaveBeenCalledWith('/apps/flow-1/workflows/version-1/restore') + expect(store.getState().showWorkflowVersionHistoryPanel).toBe(false) + expect(store.getState().isRestoring).toBe(false) + expect(store.getState().backupDraft).toBeUndefined() + expect(mockHandleRefreshWorkflowDraft).toHaveBeenCalledTimes(1) + expect(deleteAllInspectVars).toHaveBeenCalledTimes(1) + expect(mockInvalidAllLastRun).toHaveBeenCalledTimes(1) + expect(mockNotify).toHaveBeenCalledWith({ + type: 'success', + message: 'workflow.versionHistory.action.restoreSuccess', + }) + }) + expect(onRestoreSettled).toHaveBeenCalledTimes(1) + }) + }) + + describe('HeaderInHistory', () => { + it('should render the history trigger with text and return to edit mode', () => { + const { store } = renderWorkflowComponent( + , + { + initialStoreState: { + historyWorkflowData: { + id: 'history-1', + } as Shape['historyWorkflowData'], + }, + }, + ) + + expect(screen.getByText('running-title')).toBeInTheDocument() + expect(screen.getByTestId('view-history')).toHaveTextContent('with-text') + + fireEvent.click(screen.getByRole('button', { name: 'workflow.common.goBackToEdit' })) + + expect(mockHandleLoadBackupDraft).toHaveBeenCalledTimes(1) + expect(store.getState().historyWorkflowData).toBeUndefined() + expect(mockViewHistory).toHaveBeenCalledWith(expect.objectContaining({ + withText: true, + })) + }) + }) +}) diff --git a/web/app/components/workflow/header/__tests__/index.spec.tsx b/web/app/components/workflow/header/__tests__/index.spec.tsx new file mode 100644 index 0000000000..70d6fae88c --- /dev/null +++ b/web/app/components/workflow/header/__tests__/index.spec.tsx @@ -0,0 +1,106 @@ +import { render, screen } from '@testing-library/react' +import * as React from 'react' +import Header from '../index' + +let mockPathname = '/apps/demo/workflow' +let mockMaximizeCanvas = false +let mockWorkflowMode = { + normal: true, + restoring: false, + viewHistory: false, +} + +vi.mock('@/next/navigation', () => ({ + usePathname: () => mockPathname, +})) + +vi.mock('../../hooks', () => ({ + useWorkflowMode: () => mockWorkflowMode, +})) + +vi.mock('../../store', () => ({ + useStore: (selector: (state: { maximizeCanvas: boolean }) => T) => selector({ + maximizeCanvas: mockMaximizeCanvas, + }), +})) + +vi.mock('@/next/dynamic', async () => { + const ReactModule = await import('react') + + return { + default: ( + loader: () => Promise<{ default: React.ComponentType> }>, + ) => { + const DynamicComponent = (props: Record) => { + const [Loaded, setLoaded] = ReactModule.useState> | null>(null) + + ReactModule.useEffect(() => { + let mounted = true + loader().then((mod) => { + if (mounted) + setLoaded(() => mod.default) + }) + return () => { + mounted = false + } + }, []) + + return Loaded ? : null + } + + return DynamicComponent + }, + } +}) + +vi.mock('../header-in-normal', () => ({ + default: () =>
normal-layout
, +})) + +vi.mock('../header-in-view-history', () => ({ + default: () =>
history-layout
, +})) + +vi.mock('../header-in-restoring', () => ({ + default: () =>
restoring-layout
, +})) + +describe('Header', () => { + beforeEach(() => { + vi.clearAllMocks() + mockPathname = '/apps/demo/workflow' + mockMaximizeCanvas = false + mockWorkflowMode = { + normal: true, + restoring: false, + viewHistory: false, + } + }) + + it('should render the normal layout and show the maximize spacer on workflow canvases', () => { + mockMaximizeCanvas = true + + const { container } = render(
) + + expect(screen.getByTestId('header-normal')).toBeInTheDocument() + expect(screen.queryByTestId('header-history')).not.toBeInTheDocument() + expect(screen.queryByTestId('header-restoring')).not.toBeInTheDocument() + expect(container.querySelector('.h-14.w-\\[52px\\]')).not.toBeNull() + }) + + it('should switch between history and restoring layouts and skip the spacer outside canvas routes', async () => { + mockPathname = '/apps/demo/logs' + mockWorkflowMode = { + normal: false, + restoring: true, + viewHistory: true, + } + + const { container } = render(
) + + expect(await screen.findByTestId('header-history')).toBeInTheDocument() + expect(await screen.findByTestId('header-restoring')).toBeInTheDocument() + expect(screen.queryByTestId('header-normal')).not.toBeInTheDocument() + expect(container.querySelector('.h-14.w-\\[52px\\]')).toBeNull() + }) +}) diff --git a/web/app/components/workflow/hooks-store/__tests__/provider.spec.tsx b/web/app/components/workflow/hooks-store/__tests__/provider.spec.tsx new file mode 100644 index 0000000000..bbd9636e5e --- /dev/null +++ b/web/app/components/workflow/hooks-store/__tests__/provider.spec.tsx @@ -0,0 +1,73 @@ +import { render, screen, waitFor } from '@testing-library/react' +import { useContext } from 'react' +import { HooksStoreContext, HooksStoreContextProvider } from '../provider' + +const mockRefreshAll = vi.fn() +const mockStore = { + getState: () => ({ + refreshAll: mockRefreshAll, + }), +} + +let mockReactflowState = { + d3Selection: null as object | null, + d3Zoom: null as object | null, +} + +vi.mock('reactflow', () => ({ + useStore: (selector: (state: typeof mockReactflowState) => unknown) => selector(mockReactflowState), +})) + +vi.mock('../store', async () => { + const actual = await vi.importActual('../store') + return { + ...actual, + createHooksStore: vi.fn(() => mockStore), + } +}) + +const Consumer = () => { + const store = useContext(HooksStoreContext) + return
{store ? 'has-hooks-store' : 'missing-hooks-store'}
+} + +describe('hooks-store provider', () => { + beforeEach(() => { + vi.clearAllMocks() + mockReactflowState = { + d3Selection: null, + d3Zoom: null, + } + }) + + it('should provide the hooks store context without refreshing when the canvas handles are missing', () => { + render( + + + , + ) + + expect(screen.getByText('has-hooks-store')).toBeInTheDocument() + expect(mockRefreshAll).not.toHaveBeenCalled() + }) + + it('should refresh the hooks store when both d3Selection and d3Zoom are available', async () => { + const handleRun = vi.fn() + mockReactflowState = { + d3Selection: {}, + d3Zoom: {}, + } + + render( + + + , + ) + + await waitFor(() => { + expect(mockRefreshAll).toHaveBeenCalledWith({ + handleRun, + }) + }) + }) +}) diff --git a/web/app/components/workflow/nodes/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/__tests__/index.spec.tsx new file mode 100644 index 0000000000..41eb853a99 --- /dev/null +++ b/web/app/components/workflow/nodes/__tests__/index.spec.tsx @@ -0,0 +1,107 @@ +import type { ReactElement } from 'react' +import type { Node as WorkflowNode } from '../../types' +import { render, screen } from '@testing-library/react' +import { CUSTOM_NODE } from '../../constants' +import { BlockEnum } from '../../types' +import CustomNode, { Panel } from '../index' + +vi.mock('../components', () => ({ + NodeComponentMap: { + [BlockEnum.Start]: () =>
start-node-component
, + }, + PanelComponentMap: { + [BlockEnum.Start]: () =>
start-panel-component
, + }, +})) + +vi.mock('../_base/node', () => ({ + __esModule: true, + default: ({ + id, + data, + children, + }: { + id: string + data: { type: BlockEnum } + children: ReactElement + }) => ( +
+
{`base-node:${id}:${data.type}`}
+ {children} +
+ ), +})) + +vi.mock('../_base/components/workflow-panel', () => ({ + __esModule: true, + default: ({ + id, + data, + children, + }: { + id: string + data: { type: BlockEnum } + children: ReactElement + }) => ( +
+
{`base-panel:${id}:${data.type}`}
+ {children} +
+ ), +})) + +const createNodeData = (): WorkflowNode['data'] => ({ + title: 'Start', + desc: '', + type: BlockEnum.Start, +}) + +const baseNodeProps = { + type: CUSTOM_NODE, + selected: false, + zIndex: 1, + xPos: 0, + yPos: 0, + dragging: false, + isConnectable: true, +} + +describe('workflow nodes index', () => { + it('should render the mapped node inside the base node shell', () => { + render( + , + ) + + expect(screen.getByText('base-node:node-1:start')).toBeInTheDocument() + expect(screen.getByText('start-node-component')).toBeInTheDocument() + }) + + it('should render the mapped panel inside the base panel shell for custom nodes', () => { + render( + , + ) + + expect(screen.getByText('base-panel:node-1:start')).toBeInTheDocument() + expect(screen.getByText('start-panel-component')).toBeInTheDocument() + }) + + it('should return null for non-custom panel types', () => { + const { container } = render( + , + ) + + expect(container).toBeEmptyDOMElement() + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/__tests__/file-support.spec.tsx b/web/app/components/workflow/nodes/_base/components/__tests__/file-support.spec.tsx new file mode 100644 index 0000000000..ffe1e80bb0 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/__tests__/file-support.spec.tsx @@ -0,0 +1,226 @@ +import type { UploadFileSetting } from '@/app/components/workflow/types' +import { fireEvent, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks' +import { SupportUploadFileTypes } from '@/app/components/workflow/types' +import { useFileUploadConfig } from '@/service/use-common' +import { TransferMethod } from '@/types/app' +import FileTypeItem from '../file-type-item' +import FileUploadSetting from '../file-upload-setting' + +const mockUseFileUploadConfig = vi.mocked(useFileUploadConfig) +const mockUseFileSizeLimit = vi.mocked(useFileSizeLimit) + +vi.mock('@/service/use-common', () => ({ + useFileUploadConfig: vi.fn(), +})) + +vi.mock('@/app/components/base/file-uploader/hooks', () => ({ + useFileSizeLimit: vi.fn(), +})) + +vi.mock('@/app/components/base/toast/context', () => ({ + useToastContext: () => ({ + notify: vi.fn(), + close: vi.fn(), + }), +})) + +const createPayload = (overrides: Partial = {}): UploadFileSetting => ({ + allowed_file_upload_methods: [TransferMethod.local_file], + max_length: 2, + allowed_file_types: [SupportUploadFileTypes.document], + allowed_file_extensions: ['pdf'], + ...overrides, +}) + +describe('File upload support components', () => { + beforeEach(() => { + vi.clearAllMocks() + mockUseFileUploadConfig.mockReturnValue({ data: {} } as ReturnType) + mockUseFileSizeLimit.mockReturnValue({ + imgSizeLimit: 10 * 1024 * 1024, + docSizeLimit: 20 * 1024 * 1024, + audioSizeLimit: 30 * 1024 * 1024, + videoSizeLimit: 40 * 1024 * 1024, + maxFileUploadLimit: 10, + } as ReturnType) + }) + + describe('FileTypeItem', () => { + it('should render built-in file types and toggle the selected type on click', () => { + const onToggle = vi.fn() + + render( + , + ) + + expect(screen.getByText('appDebug.variableConfig.file.image.name')).toBeInTheDocument() + expect(screen.getByText('JPG, JPEG, PNG, GIF, WEBP, SVG')).toBeInTheDocument() + + fireEvent.click(screen.getByText('appDebug.variableConfig.file.image.name')) + expect(onToggle).toHaveBeenCalledWith(SupportUploadFileTypes.image) + }) + + it('should render the custom tag editor and emit custom extensions', async () => { + const user = userEvent.setup() + const onCustomFileTypesChange = vi.fn() + + render( + , + ) + + const input = screen.getByPlaceholderText('appDebug.variableConfig.file.custom.createPlaceholder') + await user.type(input, 'csv') + fireEvent.blur(input) + + expect(screen.getByText('json')).toBeInTheDocument() + expect(onCustomFileTypesChange).toHaveBeenCalledWith(['json', 'csv']) + }) + }) + + describe('FileUploadSetting', () => { + it('should update file types, upload methods, and upload limits', async () => { + const user = userEvent.setup() + const onChange = vi.fn() + + render( + , + ) + + await user.click(screen.getByText('appDebug.variableConfig.file.image.name')) + expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ + allowed_file_types: [SupportUploadFileTypes.document, SupportUploadFileTypes.image], + })) + + await user.click(screen.getByText('URL')) + expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ + allowed_file_upload_methods: [TransferMethod.remote_url], + })) + + fireEvent.change(screen.getByRole('spinbutton'), { target: { value: '5' } }) + expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ + max_length: 5, + })) + }) + + it('should toggle built-in and custom file type selections', async () => { + const user = userEvent.setup() + const onChange = vi.fn() + const { rerender } = render( + , + ) + + await user.click(screen.getByText('appDebug.variableConfig.file.document.name')) + expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ + allowed_file_types: [], + })) + + rerender( + , + ) + + await user.click(screen.getByText('appDebug.variableConfig.file.custom.name')) + expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ + allowed_file_types: [SupportUploadFileTypes.custom], + })) + + rerender( + , + ) + + await user.click(screen.getByText('appDebug.variableConfig.file.custom.name')) + expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ + allowed_file_types: [], + })) + }) + + it('should support both upload methods and update custom extensions', async () => { + const user = userEvent.setup() + const onChange = vi.fn() + const { rerender } = render( + , + ) + + await user.click(screen.getByText('appDebug.variableConfig.both')) + expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ + allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url], + })) + + rerender( + , + ) + + const input = screen.getByPlaceholderText('appDebug.variableConfig.file.custom.createPlaceholder') + await user.type(input, 'csv') + fireEvent.blur(input) + + expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ + allowed_file_extensions: ['pdf', 'csv'], + })) + }) + + it('should render support file types in the feature panel and hide them when requested', () => { + const { rerender } = render( + , + ) + + expect(screen.getByText('appDebug.variableConfig.file.supportFileTypes')).toBeInTheDocument() + + rerender( + , + ) + + expect(screen.queryByText('appDebug.variableConfig.file.document.name')).not.toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/__tests__/index.spec.tsx new file mode 100644 index 0000000000..9521f9b307 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/__tests__/index.spec.tsx @@ -0,0 +1,250 @@ +import type { NodeProps } from 'reactflow' +import type { CommonNodeType } from '@/app/components/workflow/types' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { createNode } from '@/app/components/workflow/__tests__/fixtures' +import { renderWorkflowFlowComponent } from '@/app/components/workflow/__tests__/workflow-test-env' +import { NodeRunningStatus, VarType } from '@/app/components/workflow/types' +import DefaultValue from '../default-value' +import ErrorHandleOnNode from '../error-handle-on-node' +import ErrorHandleOnPanel from '../error-handle-on-panel' +import ErrorHandleTip from '../error-handle-tip' +import ErrorHandleTypeSelector from '../error-handle-type-selector' +import FailBranchCard from '../fail-branch-card' +import { useDefaultValue, useErrorHandle } from '../hooks' +import { ErrorHandleTypeEnum } from '../types' + +const { mockDocLink } = vi.hoisted(() => ({ + mockDocLink: vi.fn((path: string) => `https://docs.example.com${path}`), +})) + +vi.mock('@/context/i18n', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useDocLink: () => mockDocLink, + } +}) + +vi.mock('../hooks', () => ({ + useDefaultValue: vi.fn(), + useErrorHandle: vi.fn(), +})) + +vi.mock('../../node-handle', () => ({ + NodeSourceHandle: ({ handleId }: { handleId: string }) =>
, +})) + +const mockUseDefaultValue = vi.mocked(useDefaultValue) +const mockUseErrorHandle = vi.mocked(useErrorHandle) +const originalDOMMatrixReadOnly = window.DOMMatrixReadOnly + +const baseData = (overrides: Partial = {}): CommonNodeType => ({ + title: 'Code', + desc: '', + type: 'code' as CommonNodeType['type'], + ...overrides, +}) + +const ErrorHandleNodeHarness = ({ id, data }: NodeProps) => ( + +) + +const renderErrorHandleNode = (data: CommonNodeType) => + renderWorkflowFlowComponent(
, { + nodes: [createNode({ + id: 'node-1', + type: 'errorHandleNode', + data, + })], + edges: [], + reactFlowProps: { + nodeTypes: { + errorHandleNode: ErrorHandleNodeHarness, + }, + }, + }) + +describe('error-handle path', () => { + beforeAll(() => { + class MockDOMMatrixReadOnly { + inverse() { + return this + } + + transformPoint(point: { x: number, y: number }) { + return point + } + } + + Object.defineProperty(window, 'DOMMatrixReadOnly', { + configurable: true, + writable: true, + value: MockDOMMatrixReadOnly, + }) + }) + + beforeEach(() => { + vi.clearAllMocks() + mockDocLink.mockImplementation((path: string) => `https://docs.example.com${path}`) + mockUseDefaultValue.mockReturnValue({ + handleFormChange: vi.fn(), + }) + mockUseErrorHandle.mockReturnValue({ + collapsed: false, + setCollapsed: vi.fn(), + handleErrorHandleTypeChange: vi.fn(), + }) + }) + + afterAll(() => { + Object.defineProperty(window, 'DOMMatrixReadOnly', { + configurable: true, + writable: true, + value: originalDOMMatrixReadOnly, + }) + }) + + // The error-handle leaf components should expose selectable strategies and contextual help. + describe('Leaf Components', () => { + it('should render the fail-branch card with the resolved learn-more link', () => { + render() + + expect(screen.getByText('workflow.nodes.common.errorHandle.failBranch.customize')).toBeInTheDocument() + expect(screen.getByRole('link')).toHaveAttribute('href', 'https://docs.example.com/use-dify/debug/error-type') + }) + + it('should render string forms and surface array forms in the default value editor', () => { + const onFormChange = vi.fn() + render( + , + ) + + fireEvent.change(screen.getByDisplayValue('hello'), { target: { value: 'updated' } }) + + expect(onFormChange).toHaveBeenCalledWith({ + key: 'message', + type: VarType.string, + value: 'updated', + }) + expect(screen.getByText('items')).toBeInTheDocument() + }) + + it('should toggle the selector popup and report the selected strategy', async () => { + const user = userEvent.setup() + const onSelected = vi.fn() + render( + , + ) + + await user.click(screen.getByRole('button')) + await user.click(screen.getByText('workflow.nodes.common.errorHandle.defaultValue.title')) + + expect(onSelected).toHaveBeenCalledWith(ErrorHandleTypeEnum.defaultValue) + }) + + it('should render the error tip only when a strategy exists', () => { + const { rerender, container } = render() + + expect(container).toBeEmptyDOMElement() + + rerender() + expect(screen.getByText('workflow.nodes.common.errorHandle.failBranch.inLog')).toBeInTheDocument() + + rerender() + expect(screen.getByText('workflow.nodes.common.errorHandle.defaultValue.inLog')).toBeInTheDocument() + }) + }) + + // The container components should show the correct branch card or default-value editor and propagate actions. + describe('Containers', () => { + it('should render the fail-branch panel body when the strategy is active', () => { + render( + , + ) + + expect(screen.getByText('workflow.nodes.common.errorHandle.title')).toBeInTheDocument() + expect(screen.getByText('workflow.nodes.common.errorHandle.failBranch.customize')).toBeInTheDocument() + }) + + it('should render the default-value panel body and delegate form updates', () => { + const handleFormChange = vi.fn() + mockUseDefaultValue.mockReturnValue({ handleFormChange }) + render( + , + ) + + fireEvent.change(screen.getByDisplayValue('draft'), { target: { value: 'next' } }) + + expect(handleFormChange).toHaveBeenCalledWith( + { key: 'answer', type: VarType.string, value: 'next' }, + expect.objectContaining({ error_strategy: ErrorHandleTypeEnum.defaultValue }), + ) + }) + + it('should hide the panel body when the hook reports a collapsed section', () => { + mockUseErrorHandle.mockReturnValue({ + collapsed: true, + setCollapsed: vi.fn(), + handleErrorHandleTypeChange: vi.fn(), + }) + + render( + , + ) + + expect(screen.queryByText('workflow.nodes.common.errorHandle.failBranch.customize')).not.toBeInTheDocument() + }) + + it('should render the default-value node badge', () => { + renderWorkflowFlowComponent( + , + { + nodes: [], + edges: [], + }, + ) + + expect(screen.getByText('workflow.nodes.common.errorHandle.defaultValue.output')).toBeInTheDocument() + }) + + it('should render the fail-branch node badge when the node throws an exception', () => { + const { container } = renderErrorHandleNode(baseData({ + error_strategy: ErrorHandleTypeEnum.failBranch, + _runningStatus: NodeRunningStatus.Exception, + })) + + return waitFor(() => { + expect(screen.getByText('workflow.common.onFailure')).toBeInTheDocument() + expect(screen.getByText('workflow.nodes.common.errorHandle.failBranch.title')).toBeInTheDocument() + expect(container.querySelector('.react-flow__handle')).toHaveAttribute('data-handleid', ErrorHandleTypeEnum.failBranch) + }) + }) + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/input-field/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/_base/components/input-field/__tests__/index.spec.tsx index a6d6d0bf6c..38736c573d 100644 --- a/web/app/components/workflow/nodes/_base/components/input-field/__tests__/index.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-field/__tests__/index.spec.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react' +import Add from '../add' import InputField from '../index' describe('InputField', () => { @@ -14,5 +15,12 @@ describe('InputField', () => { expect(screen.getAllByText('input field')).toHaveLength(2) expect(screen.getByRole('button')).toBeInTheDocument() }) + + it('should render the standalone add action button', () => { + const { container } = render() + + expect(screen.getByRole('button')).toBeInTheDocument() + expect(container.querySelector('svg')).not.toBeNull() + }) }) }) diff --git a/web/app/components/workflow/nodes/_base/components/layout/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/_base/components/layout/__tests__/index.spec.tsx index 680965eb06..071e7f011b 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/__tests__/index.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/__tests__/index.spec.tsx @@ -1,13 +1,47 @@ import { render, screen } from '@testing-library/react' -import { BoxGroupField, FieldTitle } from '../index' +import userEvent from '@testing-library/user-event' +import { Box, BoxGroup, BoxGroupField, Field, Group, GroupField } from '../index' describe('layout index', () => { beforeEach(() => { vi.clearAllMocks() }) - // The barrel exports should compose the public layout primitives without extra wrappers. + // The layout primitives should preserve their composition contracts and collapse behavior. describe('Rendering', () => { + it('should render Box and Group with optional border styles', () => { + render( +
+ Box content + Group content +
, + ) + + expect(screen.getByText('Box content')).toHaveClass('border-b', 'box-test') + expect(screen.getByText('Group content')).toHaveClass('border-b', 'group-test') + }) + + it('should render BoxGroup and GroupField with nested children', () => { + render( +
+ Inside box group + + Group field body + +
, + ) + + expect(screen.getByText('Inside box group')).toBeInTheDocument() + expect(screen.getByText('Grouped field')).toBeInTheDocument() + expect(screen.getByText('Group field body')).toBeInTheDocument() + }) + it('should render BoxGroupField from the barrel export', () => { render( { expect(screen.getByText('Body content')).toBeInTheDocument() }) - it('should render FieldTitle from the barrel export', () => { - render() + it('should collapse and expand Field children when supportCollapse is enabled', async () => { + const user = userEvent.setup() + render( + +
Extra details
+
, + ) - expect(screen.getByText('Advanced')).toBeInTheDocument() + expect(screen.getByText('Extra details')).toBeInTheDocument() + + await user.click(screen.getByText('Advanced')) + expect(screen.queryByText('Extra details')).not.toBeInTheDocument() + + await user.click(screen.getByText('Advanced')) expect(screen.getByText('Extra details')).toBeInTheDocument() }) }) diff --git a/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/__tests__/index.spec.tsx new file mode 100644 index 0000000000..1c68990d34 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/__tests__/index.spec.tsx @@ -0,0 +1,114 @@ +import type { PromptEditorProps } from '@/app/components/base/prompt-editor' +import type { + Node, + NodeOutPutVar, +} from '@/app/components/workflow/types' +import { render } from '@testing-library/react' +import { BlockEnum } from '@/app/components/workflow/types' +import MixedVariableTextInput from '../index' + +let capturedPromptEditorProps: PromptEditorProps[] = [] + +vi.mock('@/app/components/base/prompt-editor', () => ({ + default: ({ + editable, + value, + workflowVariableBlock, + onChange, + }: PromptEditorProps) => { + capturedPromptEditorProps.push({ + editable, + value, + onChange, + workflowVariableBlock, + }) + + return ( +
+
{editable ? 'editable' : 'readonly'}
+
{value || 'empty'}
+ +
+ ) + }, +})) + +describe('MixedVariableTextInput', () => { + beforeEach(() => { + vi.clearAllMocks() + capturedPromptEditorProps = [] + }) + + it('should pass workflow variable metadata to the prompt editor and include system variables for start nodes', () => { + const nodesOutputVars: NodeOutPutVar[] = [{ + nodeId: 'node-1', + title: 'Question Node', + vars: [], + }] + const availableNodes: Node[] = [ + { + id: 'start-node', + position: { x: 0, y: 0 }, + data: { + title: 'Start Node', + desc: 'Start description', + type: BlockEnum.Start, + }, + }, + { + id: 'llm-node', + position: { x: 120, y: 0 }, + data: { + title: 'LLM Node', + desc: 'LLM description', + type: BlockEnum.LLM, + }, + }, + ] + + render( + , + ) + + const latestProps = capturedPromptEditorProps.at(-1) + + expect(latestProps?.editable).toBe(true) + expect(latestProps?.workflowVariableBlock?.variables).toHaveLength(1) + expect(latestProps?.workflowVariableBlock?.workflowNodesMap).toEqual({ + 'start-node': { + title: 'Start Node', + type: 'start', + }, + 'sys': { + title: 'workflow.blocks.start', + type: 'start', + }, + 'llm-node': { + title: 'LLM Node', + type: 'llm', + }, + }) + }) + + it('should forward read-only state, current value, and change callbacks', async () => { + const onChange = vi.fn() + const { findByRole, getByTestId } = render( + , + ) + + expect(getByTestId('editable-flag')).toHaveTextContent('readonly') + expect(getByTestId('value-flag')).toHaveTextContent('seed value') + + const changeButton = await findByRole('button', { name: 'trigger-change' }) + changeButton.click() + + expect(onChange).toHaveBeenCalledWith('updated text') + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/__tests__/placeholder.spec.tsx b/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/__tests__/placeholder.spec.tsx new file mode 100644 index 0000000000..03e67f68de --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/__tests__/placeholder.spec.tsx @@ -0,0 +1,78 @@ +import type { LexicalComposerContextWithEditor } from '@lexical/react/LexicalComposerContext' +import type { LexicalEditor } from 'lexical' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { createEvent, fireEvent, render, screen } from '@testing-library/react' +import { $insertNodes, FOCUS_COMMAND } from 'lexical' +import Placeholder from '../placeholder' + +const mockEditorUpdate = vi.fn((callback: () => void) => callback()) +const mockDispatchCommand = vi.fn() +const mockInsertNodes = vi.fn() +const mockTextNode = vi.fn() + +const mockEditor = { + update: mockEditorUpdate, + dispatchCommand: mockDispatchCommand, +} as unknown as LexicalEditor + +const lexicalContextValue: LexicalComposerContextWithEditor = [ + mockEditor, + { getTheme: () => undefined }, +] + +vi.mock('@lexical/react/LexicalComposerContext', () => ({ + useLexicalComposerContext: vi.fn(), +})) + +vi.mock('lexical', () => ({ + $insertNodes: vi.fn(), + FOCUS_COMMAND: 'focus-command', +})) + +vi.mock('@/app/components/base/prompt-editor/plugins/custom-text/node', () => ({ + CustomTextNode: class MockCustomTextNode { + value: string + + constructor(value: string) { + this.value = value + mockTextNode(value) + } + }, +})) + +describe('Mixed variable placeholder', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.mocked(useLexicalComposerContext).mockReturnValue(lexicalContextValue) + vi.mocked($insertNodes).mockImplementation(nodes => mockInsertNodes(nodes)) + }) + + it('should insert an empty text node and focus the editor when the placeholder background is clicked', () => { + const parentClick = vi.fn() + + render( +
+ +
, + ) + + fireEvent.click(screen.getByText('workflow.nodes.tool.insertPlaceholder1')) + + expect(parentClick).not.toHaveBeenCalled() + expect(mockTextNode).toHaveBeenCalledWith('') + expect(mockInsertNodes).toHaveBeenCalledTimes(1) + expect(mockDispatchCommand).toHaveBeenCalledWith(FOCUS_COMMAND, undefined) + }) + + it('should insert a slash shortcut from the highlighted action and prevent the native mouse down behavior', () => { + render() + + const shortcut = screen.getByText('workflow.nodes.tool.insertPlaceholder2') + const event = createEvent.mouseDown(shortcut) + fireEvent(shortcut, event) + + expect(event.defaultPrevented).toBe(true) + expect(mockTextNode).toHaveBeenCalledWith('/') + expect(mockDispatchCommand).toHaveBeenCalledWith(FOCUS_COMMAND, undefined) + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/__tests__/details.spec.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/__tests__/details.spec.tsx new file mode 100644 index 0000000000..3e02aba077 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/panel-operator/__tests__/details.spec.tsx @@ -0,0 +1,268 @@ +/* eslint-disable ts/no-explicit-any */ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { renderWorkflowFlowComponent } from '@/app/components/workflow/__tests__/workflow-test-env' +import { + useAvailableBlocks, + useIsChatMode, + useNodeDataUpdate, + useNodeMetaData, + useNodesInteractions, + useNodesReadOnly, + useNodesSyncDraft, +} from '@/app/components/workflow/hooks' +import { useHooksStore } from '@/app/components/workflow/hooks-store' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' +import { BlockEnum } from '@/app/components/workflow/types' +import { useAllWorkflowTools } from '@/service/use-tools' +import { FlowType } from '@/types/common' +import ChangeBlock from '../change-block' +import PanelOperatorPopup from '../panel-operator-popup' + +vi.mock('@/app/components/workflow/block-selector', () => ({ + default: ({ trigger, onSelect, availableBlocksTypes, showStartTab, ignoreNodeIds, forceEnableStartTab, allowUserInputSelection }: any) => ( +
+
{trigger()}
+
{`available:${(availableBlocksTypes || []).join(',')}`}
+
{`show-start:${String(showStartTab)}`}
+
{`ignore:${(ignoreNodeIds || []).join(',')}`}
+
{`force-start:${String(forceEnableStartTab)}`}
+
{`allow-start:${String(allowUserInputSelection)}`}
+ +
+ ), +})) + +vi.mock('@/app/components/workflow/hooks', async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + useAvailableBlocks: vi.fn(), + useIsChatMode: vi.fn(), + useNodeDataUpdate: vi.fn(), + useNodeMetaData: vi.fn(), + useNodesInteractions: vi.fn(), + useNodesReadOnly: vi.fn(), + useNodesSyncDraft: vi.fn(), + } +}) + +vi.mock('@/app/components/workflow/hooks-store', () => ({ + useHooksStore: vi.fn(), +})) + +vi.mock('@/app/components/workflow/store/workflow/use-nodes', () => ({ + default: vi.fn(), +})) + +vi.mock('@/service/use-tools', () => ({ + useAllWorkflowTools: vi.fn(), +})) + +const mockUseAvailableBlocks = vi.mocked(useAvailableBlocks) +const mockUseIsChatMode = vi.mocked(useIsChatMode) +const mockUseNodeDataUpdate = vi.mocked(useNodeDataUpdate) +const mockUseNodeMetaData = vi.mocked(useNodeMetaData) +const mockUseNodesInteractions = vi.mocked(useNodesInteractions) +const mockUseNodesReadOnly = vi.mocked(useNodesReadOnly) +const mockUseNodesSyncDraft = vi.mocked(useNodesSyncDraft) +const mockUseHooksStore = vi.mocked(useHooksStore) +const mockUseNodes = vi.mocked(useNodes) +const mockUseAllWorkflowTools = vi.mocked(useAllWorkflowTools) + +describe('panel-operator details', () => { + const handleNodeChange = vi.fn() + const handleNodeDelete = vi.fn() + const handleNodesDuplicate = vi.fn() + const handleNodeSelect = vi.fn() + const handleNodesCopy = vi.fn() + const handleNodeDataUpdate = vi.fn() + const handleSyncWorkflowDraft = vi.fn() + + beforeEach(() => { + vi.clearAllMocks() + mockUseAvailableBlocks.mockReturnValue({ + getAvailableBlocks: vi.fn(() => ({ + availablePrevBlocks: [BlockEnum.HttpRequest], + availableNextBlocks: [BlockEnum.HttpRequest], + })), + availablePrevBlocks: [BlockEnum.HttpRequest], + availableNextBlocks: [BlockEnum.HttpRequest], + } as ReturnType) + mockUseIsChatMode.mockReturnValue(false) + mockUseNodeDataUpdate.mockReturnValue({ + handleNodeDataUpdate, + handleNodeDataUpdateWithSyncDraft: vi.fn(), + }) + mockUseNodeMetaData.mockReturnValue({ + isTypeFixed: false, + isSingleton: false, + isUndeletable: false, + description: 'Node description', + author: 'Dify', + helpLinkUri: 'https://docs.example.com/node', + } as ReturnType) + mockUseNodesInteractions.mockReturnValue({ + handleNodeChange, + handleNodeDelete, + handleNodesDuplicate, + handleNodeSelect, + handleNodesCopy, + } as unknown as ReturnType) + mockUseNodesReadOnly.mockReturnValue({ nodesReadOnly: false } as ReturnType) + mockUseNodesSyncDraft.mockReturnValue({ + doSyncWorkflowDraft: vi.fn(), + handleSyncWorkflowDraft, + syncWorkflowDraftWhenPageClose: vi.fn(), + } as ReturnType) + mockUseHooksStore.mockImplementation((selector: any) => selector({ configsMap: { flowType: FlowType.appFlow } })) + mockUseNodes.mockReturnValue([{ id: 'start', position: { x: 0, y: 0 }, data: { type: BlockEnum.Start } as any }] as any) + mockUseAllWorkflowTools.mockReturnValue({ data: [] } as any) + }) + + // The panel operator internals should expose block-change and popup actions using the real workflow popup composition. + describe('Internal Actions', () => { + it('should select a replacement block through ChangeBlock', async () => { + const user = userEvent.setup() + render( + , + ) + + await user.click(screen.getByText('select-http')) + + expect(screen.getByText('available:http-request')).toBeInTheDocument() + expect(screen.getByText('show-start:true')).toBeInTheDocument() + expect(screen.getByText('ignore:')).toBeInTheDocument() + expect(screen.getByText('force-start:false')).toBeInTheDocument() + expect(screen.getByText('allow-start:false')).toBeInTheDocument() + expect(handleNodeChange).toHaveBeenCalledWith('node-1', BlockEnum.HttpRequest, 'source', undefined) + }) + + it('should expose trigger and start-node specific block selector options', () => { + mockUseAvailableBlocks.mockReturnValueOnce({ + getAvailableBlocks: vi.fn(() => ({ + availablePrevBlocks: [], + availableNextBlocks: [BlockEnum.HttpRequest], + })), + availablePrevBlocks: [], + availableNextBlocks: [BlockEnum.HttpRequest], + } as ReturnType) + mockUseIsChatMode.mockReturnValueOnce(true) + mockUseHooksStore.mockImplementationOnce((selector: any) => selector({ configsMap: { flowType: FlowType.appFlow } })) + mockUseNodes.mockReturnValueOnce([] as any) + + const { rerender } = render( + , + ) + + expect(screen.getByText('available:http-request')).toBeInTheDocument() + expect(screen.getByText('show-start:true')).toBeInTheDocument() + expect(screen.getByText('ignore:trigger-node')).toBeInTheDocument() + expect(screen.getByText('allow-start:true')).toBeInTheDocument() + + mockUseAvailableBlocks.mockReturnValueOnce({ + getAvailableBlocks: vi.fn(() => ({ + availablePrevBlocks: [BlockEnum.Code], + availableNextBlocks: [], + })), + availablePrevBlocks: [BlockEnum.Code], + availableNextBlocks: [], + } as ReturnType) + mockUseHooksStore.mockImplementationOnce((selector: any) => selector({ configsMap: { flowType: FlowType.ragPipeline } })) + mockUseNodes.mockReturnValueOnce([{ id: 'start', position: { x: 0, y: 0 }, data: { type: BlockEnum.Start } as any }] as any) + + rerender( + , + ) + + expect(screen.getByText('available:code')).toBeInTheDocument() + expect(screen.getByText('show-start:false')).toBeInTheDocument() + expect(screen.getByText('ignore:start-node')).toBeInTheDocument() + expect(screen.getByText('force-start:true')).toBeInTheDocument() + }) + + it('should run, copy, duplicate, delete, and expose the help link in the popup', async () => { + const user = userEvent.setup() + renderWorkflowFlowComponent( + , + { + nodes: [], + edges: [{ id: 'edge-1', source: 'node-0', target: 'node-1', sourceHandle: 'branch-a' }], + }, + ) + + await user.click(screen.getByText('workflow.panel.runThisStep')) + await user.click(screen.getByText('workflow.common.copy')) + await user.click(screen.getByText('workflow.common.duplicate')) + await user.click(screen.getByText('common.operation.delete')) + + expect(handleNodeSelect).toHaveBeenCalledWith('node-1') + expect(handleNodeDataUpdate).toHaveBeenCalledWith({ id: 'node-1', data: { _isSingleRun: true } }) + expect(handleSyncWorkflowDraft).toHaveBeenCalledWith(true) + expect(handleNodesCopy).toHaveBeenCalledWith('node-1') + expect(handleNodesDuplicate).toHaveBeenCalledWith('node-1') + expect(handleNodeDelete).toHaveBeenCalledWith('node-1') + expect(screen.getByRole('link', { name: 'workflow.panel.helpLink' })).toHaveAttribute('href', 'https://docs.example.com/node') + }) + + it('should render workflow-tool and readonly popup variants', () => { + mockUseAllWorkflowTools.mockReturnValueOnce({ + data: [{ id: 'workflow-tool', workflow_app_id: 'app-123' }], + } as any) + + const { rerender } = renderWorkflowFlowComponent( + , + { + nodes: [], + edges: [], + }, + ) + + expect(screen.getByRole('link', { name: 'workflow.panel.openWorkflow' })).toHaveAttribute('href', '/app/app-123/workflow') + + mockUseNodesReadOnly.mockReturnValueOnce({ nodesReadOnly: true } as ReturnType) + mockUseNodeMetaData.mockReturnValueOnce({ + isTypeFixed: true, + isSingleton: true, + isUndeletable: true, + description: 'Read only node', + author: 'Dify', + } as ReturnType) + + rerender( + , + ) + + expect(screen.queryByText('workflow.panel.runThisStep')).not.toBeInTheDocument() + expect(screen.queryByText('workflow.common.copy')).not.toBeInTheDocument() + expect(screen.queryByText('common.operation.delete')).not.toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/support-var-input/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/_base/components/support-var-input/__tests__/index.spec.tsx new file mode 100644 index 0000000000..5fbab5e497 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/support-var-input/__tests__/index.spec.tsx @@ -0,0 +1,52 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import SupportVarInput from '../index' + +describe('SupportVarInput', () => { + it('should render plain text, highlighted variables, and preserved line breaks', () => { + render() + + expect(screen.getByText('World').closest('[title]')).toHaveAttribute('title', 'Hello {{user_name}}\nWorld') + expect(screen.getByText('user_name')).toBeInTheDocument() + expect(screen.getByText('Hello')).toBeInTheDocument() + expect(screen.getByText('World')).toBeInTheDocument() + }) + + it('should show the focused child content and call onFocus when activated', async () => { + const user = userEvent.setup() + const onFocus = vi.fn() + + render( + + + , + ) + + const editor = screen.getByRole('textbox', { name: 'inline-editor' }) + expect(editor).toBeInTheDocument() + expect(screen.queryByTitle('draft')).not.toBeInTheDocument() + + await user.click(editor) + + expect(onFocus).toHaveBeenCalledTimes(1) + }) + + it('should keep the static preview visible when the input is read-only', () => { + render( + + + , + ) + + expect(screen.queryByRole('textbox', { name: 'hidden-editor' })).not.toBeInTheDocument() + expect(screen.getByTitle('readonly content')).toBeInTheDocument() + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/variable/__tests__/assigned-var-reference-popup.spec.tsx b/web/app/components/workflow/nodes/_base/components/variable/__tests__/assigned-var-reference-popup.spec.tsx new file mode 100644 index 0000000000..e1a7ae4a4b --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/__tests__/assigned-var-reference-popup.spec.tsx @@ -0,0 +1,72 @@ +import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' +import { render, screen } from '@testing-library/react' +import { VarType } from '@/app/components/workflow/types' +import AssignedVarReferencePopup from '../assigned-var-reference-popup' + +const mockVarReferenceVars = vi.fn() + +vi.mock('../var-reference-vars', () => ({ + default: ({ + vars, + onChange, + itemWidth, + isSupportFileVar, + }: { + vars: NodeOutPutVar[] + onChange: (value: ValueSelector, item: Var) => void + itemWidth?: number + isSupportFileVar?: boolean + }) => { + mockVarReferenceVars({ vars, onChange, itemWidth, isSupportFileVar }) + return
{vars.length}
+ }, +})) + +const createOutputVar = (overrides: Partial = {}): NodeOutPutVar => ({ + nodeId: 'node-1', + title: 'Node One', + vars: [{ + variable: 'answer', + type: VarType.string, + }], + ...overrides, +}) + +describe('AssignedVarReferencePopup', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should render the empty state when there are no assigned variables', () => { + render( + , + ) + + expect(screen.getByText('workflow.nodes.assigner.noAssignedVars')).toBeInTheDocument() + expect(screen.getByText('workflow.nodes.assigner.assignedVarsDescription')).toBeInTheDocument() + expect(screen.queryByTestId('var-reference-vars')).not.toBeInTheDocument() + }) + + it('should delegate populated variable lists to the variable picker with file support enabled', () => { + const onChange = vi.fn() + + render( + , + ) + + expect(screen.getByTestId('var-reference-vars')).toHaveTextContent('1') + expect(mockVarReferenceVars).toHaveBeenCalledWith({ + vars: [createOutputVar()], + onChange, + itemWidth: 280, + isSupportFileVar: true, + }) + }) +}) diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/__tests__/index.spec.tsx index cb44e93427..d75e6b6036 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/__tests__/index.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/__tests__/index.spec.tsx @@ -1,6 +1,11 @@ import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { BlockEnum, VarType } from '@/app/components/workflow/types' -import { VariableLabelInNode, VariableLabelInText } from '../index' +import VariableIcon from '../base/variable-icon' +import VariableLabel from '../base/variable-label' +import VariableName from '../base/variable-name' +import VariableNodeLabel from '../base/variable-node-label' +import { VariableIconWithColor, VariableLabelInEditor, VariableLabelInNode, VariableLabelInSelect, VariableLabelInText } from '../index' describe('variable-label index', () => { beforeEach(() => { @@ -39,5 +44,96 @@ describe('variable-label index', () => { expect(screen.getByText('Source Node')).toBeInTheDocument() expect(screen.getByText('answer')).toBeInTheDocument() }) + + it('should render the select variant with the full variable path', () => { + render( + , + ) + + expect(screen.getByText('payload.answer')).toBeInTheDocument() + }) + + it('should render the editor variant with selected styles and inline error feedback', async () => { + const user = userEvent.setup() + const { container } = render( + suffix} + />, + ) + + const badge = screen.getByText('payload').closest('div') + expect(badge).toBeInTheDocument() + expect(screen.getByText('suffix')).toBeInTheDocument() + + await user.hover(screen.getByText('payload')) + + expect(container.querySelector('[data-icon="Warning"]')).not.toBeNull() + }) + + it('should render the icon helpers for environment and exception variables', () => { + const { container } = render( +
+ + +
, + ) + + expect(container.querySelectorAll('svg').length).toBeGreaterThan(0) + }) + + it('should render the base variable name with shortened path and title', () => { + render( + , + ) + + expect(screen.getByText('answer')).toHaveAttribute('title', 'answer') + }) + + it('should render the base node label only when node type exists', () => { + const { container, rerender } = render() + + expect(container).toBeEmptyDOMElement() + + rerender( + , + ) + + expect(screen.getByText('Code Node')).toBeInTheDocument() + }) + + it('should render the base label with variable type and right slot', () => { + render( + slot} + />, + ) + + expect(screen.getByText('Source Node')).toBeInTheDocument() + expect(screen.getByText('query')).toBeInTheDocument() + expect(screen.getByText('String')).toBeInTheDocument() + expect(screen.getByText('slot')).toBeInTheDocument() + }) }) }) diff --git a/web/app/components/workflow/nodes/agent/__tests__/integration.spec.tsx b/web/app/components/workflow/nodes/agent/__tests__/integration.spec.tsx new file mode 100644 index 0000000000..a7913ae0aa --- /dev/null +++ b/web/app/components/workflow/nodes/agent/__tests__/integration.spec.tsx @@ -0,0 +1,340 @@ +/* eslint-disable ts/no-explicit-any, style/jsx-one-expression-per-line */ +import type { AgentNodeType } from '../types' +import type { StrategyParamItem } from '@/app/components/plugins/types' +import type { PanelProps } from '@/types/workflow' +import { fireEvent, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { FormTypeEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { BlockEnum } from '@/app/components/workflow/types' +import { VarType as ToolVarType } from '../../tool/types' +import { ModelBar } from '../components/model-bar' +import { ToolIcon } from '../components/tool-icon' +import Node from '../node' +import Panel from '../panel' +import { AgentFeature } from '../types' +import useConfig from '../use-config' + +let mockTextGenerationModels: Array<{ provider: string, models: Array<{ model: string }> }> | undefined = [] +let mockModerationModels: Array<{ provider: string, models: Array<{ model: string }> }> | undefined = [] +let mockRerankModels: Array<{ provider: string, models: Array<{ model: string }> }> | undefined = [] +let mockSpeech2TextModels: Array<{ provider: string, models: Array<{ model: string }> }> | undefined = [] +let mockTextEmbeddingModels: Array<{ provider: string, models: Array<{ model: string }> }> | undefined = [] +let mockTtsModels: Array<{ provider: string, models: Array<{ model: string }> }> | undefined = [] + +let mockBuiltInTools: Array | undefined = [] +let mockCustomTools: Array | undefined = [] +let mockWorkflowTools: Array | undefined = [] +let mockMcpTools: Array | undefined = [] +let mockMarketplaceIcon: string | Record | undefined + +const mockResetEditor = vi.fn() + +vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ + useModelList: (modelType: ModelTypeEnum) => { + if (modelType === ModelTypeEnum.textGeneration) + return { data: mockTextGenerationModels } + if (modelType === ModelTypeEnum.moderation) + return { data: mockModerationModels } + if (modelType === ModelTypeEnum.rerank) + return { data: mockRerankModels } + if (modelType === ModelTypeEnum.speech2text) + return { data: mockSpeech2TextModels } + if (modelType === ModelTypeEnum.textEmbedding) + return { data: mockTextEmbeddingModels } + return { data: mockTtsModels } + }, +})) + +vi.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({ + default: ({ defaultModel, modelList }: any) => ( +
{defaultModel ? `${defaultModel.provider}/${defaultModel.model}` : 'no-model'}:{modelList.length}
+ ), +})) + +vi.mock('@/app/components/header/indicator', () => ({ + default: ({ color }: any) =>
{`indicator:${color}`}
, +})) + +vi.mock('@/service/use-tools', () => ({ + useAllBuiltInTools: () => ({ data: mockBuiltInTools }), + useAllCustomTools: () => ({ data: mockCustomTools }), + useAllWorkflowTools: () => ({ data: mockWorkflowTools }), + useAllMCPTools: () => ({ data: mockMcpTools }), +})) + +vi.mock('@/app/components/base/app-icon', () => ({ + default: ({ icon, background }: any) =>
{`app-icon:${background}:${icon}`}
, +})) + +vi.mock('@/app/components/base/icons/src/vender/other', () => ({ + Group: () =>
group-icon
, +})) + +vi.mock('@/utils/get-icon', () => ({ + getIconFromMarketPlace: () => mockMarketplaceIcon, +})) + +vi.mock('@/hooks/use-i18n', () => ({ + useRenderI18nObject: () => (value: string) => value, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/group', () => ({ + Group: ({ label, children }: any) =>
{label}
{children}
, + GroupLabel: ({ className, children }: any) =>
{children}
, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/setting-item', () => ({ + SettingItem: ({ label, status, tooltip, children }: any) =>
{label}:{status}:{tooltip}:{children}
, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/field', () => ({ + default: ({ title, children }: any) =>
{title}
{children}
, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/agent-strategy', () => ({ + AgentStrategy: ({ onStrategyChange }: any) => ( + + ), +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/mcp-tool-availability', () => ({ + MCPToolAvailabilityProvider: ({ children }: any) =>
{children}
, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/memory-config', () => ({ + default: ({ onChange }: any) => , +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/output-vars', () => ({ + default: ({ children }: any) =>
{children}
, + VarItem: ({ name, type, description }: any) =>
{`${name}:${type}:${description}`}
, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/split', () => ({ + default: () =>
split
, +})) + +vi.mock('@/app/components/workflow/store', () => ({ + useStore: (selector: (state: { setControlPromptEditorRerenderKey: typeof mockResetEditor }) => unknown) => selector({ + setControlPromptEditorRerenderKey: mockResetEditor, + }), +})) + +vi.mock('@/utils/plugin-version-feature', () => ({ + isSupportMCP: () => true, +})) + +vi.mock('../use-config', () => ({ + default: vi.fn(), +})) + +const mockUseConfig = vi.mocked(useConfig) + +const createStrategyParam = ( + name: string, + type: FormTypeEnum, + required: boolean, +): StrategyParamItem => ({ + name, + type, + required, + label: { en_US: name } as StrategyParamItem['label'], + help: { en_US: `${name} help` } as StrategyParamItem['help'], + placeholder: { en_US: `${name} placeholder` } as StrategyParamItem['placeholder'], + scope: 'global', + default: null, + options: [], + template: { enabled: false }, + auto_generate: { type: 'none' }, +}) + +const createData = (overrides: Partial = {}): AgentNodeType => ({ + title: 'Agent', + desc: '', + type: BlockEnum.Agent, + output_schema: {}, + agent_strategy_provider_name: 'provider/agent', + agent_strategy_name: 'react', + agent_strategy_label: 'React Agent', + agent_parameters: { + modelParam: { type: ToolVarType.constant, value: { provider: 'openai', model: 'gpt-4o' } }, + toolParam: { type: ToolVarType.constant, value: { provider_name: 'author/tool-a' } }, + multiToolParam: { type: ToolVarType.constant, value: [{ provider_name: 'author/tool-b' }] }, + }, + meta: { version: '1.0.0' } as any, + plugin_unique_identifier: 'provider/agent:1.0.0', + ...overrides, +}) + +const createConfigResult = (overrides: Partial> = {}): ReturnType => ({ + readOnly: false, + inputs: createData(), + setInputs: vi.fn(), + handleVarListChange: vi.fn(), + handleAddVariable: vi.fn(), + currentStrategy: { + identity: { + author: 'provider', + name: 'react', + icon: 'icon', + label: { en_US: 'React Agent' } as any, + provider: 'provider/agent', + }, + parameters: [ + createStrategyParam('modelParam', FormTypeEnum.modelSelector, true), + createStrategyParam('optionalModel', FormTypeEnum.modelSelector, false), + createStrategyParam('toolParam', FormTypeEnum.toolSelector, false), + createStrategyParam('multiToolParam', FormTypeEnum.multiToolSelector, false), + ], + description: { en_US: 'agent description' } as any, + output_schema: {}, + features: [AgentFeature.HISTORY_MESSAGES], + }, + formData: {}, + onFormChange: vi.fn(), + currentStrategyStatus: { + plugin: { source: 'marketplace', installed: true }, + isExistInPlugin: false, + }, + strategyProvider: undefined, + pluginDetail: { + declaration: { + label: 'Mock Plugin', + }, + } as any, + availableVars: [], + availableNodesWithParent: [], + outputSchema: [{ name: 'jsonField', type: 'String', description: 'json output' }], + handleMemoryChange: vi.fn(), + isChatMode: true, + ...overrides, +}) + +const panelProps: PanelProps = { + getInputVars: vi.fn(() => []), + toVarInputs: vi.fn(() => []), + runInputData: {}, + runInputDataRef: { current: {} }, + setRunInputData: vi.fn(), + runResult: null, +} + +describe('agent path', () => { + beforeEach(() => { + vi.clearAllMocks() + mockTextGenerationModels = [{ provider: 'openai', models: [{ model: 'gpt-4o' }] }] + mockModerationModels = [] + mockRerankModels = [] + mockSpeech2TextModels = [] + mockTextEmbeddingModels = [] + mockTtsModels = [] + mockBuiltInTools = [{ name: 'author/tool-a', is_team_authorization: true, icon: 'https://example.com/icon-a.png' }] + mockCustomTools = [] + mockWorkflowTools = [{ id: 'author/tool-b', is_team_authorization: false, icon: { content: 'B', background: '#fff' } }] + mockMcpTools = [] + mockMarketplaceIcon = 'https://example.com/marketplace.png' + mockUseConfig.mockReturnValue(createConfigResult()) + }) + + describe('Path Integration', () => { + it('should render model bars for missing, installed, and missing-install models', () => { + const { rerender, container } = render() + + expect(container).toHaveTextContent('no-model:0') + expect(screen.getByText('indicator:red')).toBeInTheDocument() + + rerender() + expect(container).toHaveTextContent('openai/gpt-4o:1') + expect(screen.queryByText('indicator:red')).not.toBeInTheDocument() + + rerender() + expect(container).toHaveTextContent('openai/gpt-4.1:1') + expect(screen.getByText('indicator:red')).toBeInTheDocument() + }) + + it('should render tool icons across loading, marketplace fallback, authorization warning, and fetch-error states', async () => { + const user = userEvent.setup() + const { unmount } = render() + + expect(screen.getByRole('img', { name: 'tool icon' })).toBeInTheDocument() + + fireEvent.error(screen.getByRole('img', { name: 'tool icon' })) + expect(screen.getByText('group-icon')).toBeInTheDocument() + + unmount() + const secondRender = render() + expect(screen.getByText('app-icon:#fff:B')).toBeInTheDocument() + expect(screen.getByText('indicator:yellow')).toBeInTheDocument() + + mockBuiltInTools = undefined + secondRender.rerender() + expect(screen.getByText('group-icon')).toBeInTheDocument() + + mockBuiltInTools = [] + secondRender.rerender() + expect(screen.getByRole('img', { name: 'tool icon' })).toBeInTheDocument() + await user.unhover(screen.getByRole('img', { name: 'tool icon' })) + }) + + it('should render strategy, models, and toolbox entries in the node', () => { + const { container } = render( + , + ) + + expect(screen.getByText(/workflow\.nodes\.agent\.strategy\.shortLabel/)).toBeInTheDocument() + expect(container).toHaveTextContent('React Agent') + expect(screen.getByText('workflow.nodes.agent.model')).toBeInTheDocument() + expect(screen.getByText('workflow.nodes.agent.toolbox')).toBeInTheDocument() + expect(container).toHaveTextContent('openai/gpt-4o:1') + expect(screen.getByText('indicator:yellow')).toBeInTheDocument() + }) + + it('should render the panel, update the selected strategy, and expose memory plus output vars', async () => { + const user = userEvent.setup() + const config = createConfigResult() + mockUseConfig.mockReturnValue(config) + + render( + , + ) + + expect(screen.getByText('workflow.nodes.agent.strategy.label')).toBeInTheDocument() + expect(screen.getByText('text:String:workflow.nodes.agent.outputVars.text')).toBeInTheDocument() + expect(screen.getByText('jsonField:String:json output')).toBeInTheDocument() + + await user.click(screen.getByRole('button', { name: 'change-strategy' })) + expect(config.setInputs).toHaveBeenCalledWith(expect.objectContaining({ + agent_strategy_provider_name: 'provider/updated', + agent_strategy_name: 'updated-strategy', + agent_strategy_label: 'Updated Strategy', + plugin_unique_identifier: 'provider/updated:1.0.0', + })) + expect(mockResetEditor).toHaveBeenCalledTimes(1) + + await user.click(screen.getByRole('button', { name: 'change-memory' })) + expect(config.handleMemoryChange).toHaveBeenCalledWith({ + window: { enabled: true, size: 8 }, + query_prompt_template: 'history', + }) + }) + }) +}) diff --git a/web/app/components/workflow/nodes/assigner/__tests__/integration.spec.tsx b/web/app/components/workflow/nodes/assigner/__tests__/integration.spec.tsx new file mode 100644 index 0000000000..0b814b8b25 --- /dev/null +++ b/web/app/components/workflow/nodes/assigner/__tests__/integration.spec.tsx @@ -0,0 +1,514 @@ +/* eslint-disable ts/no-explicit-any, style/jsx-one-expression-per-line */ +import type { AssignerNodeOperation, AssignerNodeType } from '../types' +import type { PanelProps } from '@/types/workflow' +import { fireEvent, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { renderWorkflowFlowComponent } from '@/app/components/workflow/__tests__/workflow-test-env' +import { BlockEnum, VarType } from '@/app/components/workflow/types' +import OperationSelector from '../components/operation-selector' +import VarList from '../components/var-list' +import Node from '../node' +import Panel from '../panel' +import { AssignerNodeInputType, WriteMode, writeModeTypesNum } from '../types' +import useConfig from '../use-config' + +const mockHandleAddOperationItem = vi.fn() + +vi.mock('@/app/components/workflow/nodes/_base/components/field', () => ({ + default: ({ title, operations, children }: any) =>
{title}
{operations}
{children}
, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/list-no-data-placeholder', () => ({ + default: ({ children }: any) =>
{children}
, +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/variable/var-reference-picker', () => ({ + default: ({ value, onChange, onOpen, placeholder, popupFor, valueTypePlaceHolder, filterVar }: any) => ( +
+
{Array.isArray(value) ? value.join('.') : String(value ?? '')}
+ {valueTypePlaceHolder &&
{`type:${valueTypePlaceHolder}`}
} + {popupFor === 'toAssigned' && ( +
{`filter:${String(filterVar?.({ nodeId: 'node-1', variable: 'count', type: VarType.string }))}:${String(filterVar?.({ nodeId: 'node-2', variable: 'other', type: VarType.string }))}`}
+ )} + +
+ ), +})) + +vi.mock('@/app/components/workflow/nodes/_base/components/editor/code-editor', () => ({ + default: ({ value, onChange }: any) => ( +