From 4f03b7193e5936fd12f3cfdb86326759b2ca7aa2 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:54:49 +0800 Subject: [PATCH] docs(frontend): align docs and comments (#35364) --- .agents/skills/frontend-testing/SKILL.md | 6 +- .../frontend-testing/references/checklist.md | 4 +- .../frontend-testing/references/mocking.md | 26 ++--- packages/dify-ui/README.md | 105 ++++++++++++++++++ packages/dify-ui/src/dialog/index.tsx | 2 +- .../dify-ui/src/scroll-area/index.stories.tsx | 2 +- .../src/toast/__tests__/index.spec.tsx | 2 +- web/AGENTS.md | 5 +- web/README.md | 2 +- .../base/portal-to-follow-elem/index.tsx | 2 +- .../__tests__/index.spec.tsx | 2 +- .../workflow-onboarding-modal/index.tsx | 2 +- web/app/components/workflow/run/node.tsx | 36 ++---- web/docs/overlay-migration.md | 34 +++--- web/docs/test.md | 6 +- web/eslint.config.mjs | 7 -- web/eslint.constants.mjs | 2 +- web/scripts/analyze-component.js | 6 +- 18 files changed, 166 insertions(+), 85 deletions(-) create mode 100644 packages/dify-ui/README.md diff --git a/.agents/skills/frontend-testing/SKILL.md b/.agents/skills/frontend-testing/SKILL.md index 4da070bdbf..105c979c58 100644 --- a/.agents/skills/frontend-testing/SKILL.md +++ b/.agents/skills/frontend-testing/SKILL.md @@ -200,7 +200,7 @@ When assigned to test a directory/path, test **ALL content** within that path: - ✅ **Import real project components** directly (including base components and siblings) - ✅ **Only mock**: API services (`@/service/*`), `next/navigation`, complex context providers -- ❌ **DO NOT mock** base components (`@/app/components/base/*`) +- ❌ **DO NOT mock** base components (`@/app/components/base/*`) or dify-ui primitives (`@langgenius/dify-ui/*`) - ❌ **DO NOT mock** sibling/child components in the same directory > See [Test Structure Template](#test-structure-template) for correct import/mock patterns. @@ -325,12 +325,12 @@ For more detailed information, refer to: ### Reference Examples in Codebase - `web/utils/classnames.spec.ts` - Utility function tests -- `web/app/components/base/button/index.spec.tsx` - Component tests +- `web/app/components/base/radio/__tests__/index.spec.tsx` - Component tests - `web/__mocks__/provider-context.ts` - Mock factory example ### Project Configuration -- `web/vitest.config.ts` - Vitest configuration +- `web/vite.config.ts` - Vite/Vitest configuration - `web/vitest.setup.ts` - Test environment setup - `web/scripts/analyze-component.js` - Component analysis tool - Modules are not mocked automatically. Global mocks live in `web/vitest.setup.ts` (for example `react-i18next`, `next/image`); mock other modules like `ky` or `mime` locally in test files. diff --git a/.agents/skills/frontend-testing/references/checklist.md b/.agents/skills/frontend-testing/references/checklist.md index 10b8fb66f9..99258498dd 100644 --- a/.agents/skills/frontend-testing/references/checklist.md +++ b/.agents/skills/frontend-testing/references/checklist.md @@ -36,7 +36,7 @@ Use this checklist when generating or reviewing tests for Dify frontend componen ### Integration vs Mocking -- [ ] **DO NOT mock base components** (`Loading`, `Button`, `Tooltip`, etc.) +- [ ] **DO NOT mock base components or dify-ui primitives** (base `Loading`, `Input`, `Badge`; dify-ui `Button`, `Tooltip`, `Dialog`, etc.) - [ ] Import real project components instead of mocking - [ ] Only mock: API calls, complex context providers, third-party libs with side effects - [ ] Prefer integration testing when using single spec file @@ -73,7 +73,7 @@ Use this checklist when generating or reviewing tests for Dify frontend componen ### Mocks -- [ ] **DO NOT mock base components** (`@/app/components/base/*`) +- [ ] **DO NOT mock base components or dify-ui primitives** (`@/app/components/base/*` or `@langgenius/dify-ui/*`) - [ ] `vi.clearAllMocks()` in `beforeEach` (not `afterEach`) - [ ] Shared mock state reset in `beforeEach` - [ ] i18n uses global mock (auto-loaded in `web/vitest.setup.ts`); only override locally for custom translations diff --git a/.agents/skills/frontend-testing/references/mocking.md b/.agents/skills/frontend-testing/references/mocking.md index f58377c4a5..8c2f1c0c58 100644 --- a/.agents/skills/frontend-testing/references/mocking.md +++ b/.agents/skills/frontend-testing/references/mocking.md @@ -2,29 +2,27 @@ ## ⚠️ Important: What NOT to Mock -### DO NOT Mock Base Components +### DO NOT Mock Base Components or dify-ui Primitives -**Never mock components from `@/app/components/base/`** such as: +**Never mock components from `@/app/components/base/` or from `@langgenius/dify-ui/*`** such as: -- `Loading`, `Spinner` -- `Button`, `Input`, `Select` -- `Tooltip`, `Modal`, `Dropdown` -- `Icon`, `Badge`, `Tag` +- Legacy base (`@/app/components/base/*`): `Loading`, `Spinner`, `Input`, `Badge`, `Tag` +- dify-ui primitives (`@langgenius/dify-ui/*`): `Button`, `Tooltip`, `Dialog`, `Popover`, `DropdownMenu`, `ContextMenu`, `Select`, `AlertDialog`, `Toast` **Why?** -- Base components will have their own dedicated tests +- These components have their own dedicated tests - Mocking them creates false positives (tests pass but real integration fails) - Using real components tests actual integration behavior ```typescript -// ❌ WRONG: Don't mock base components +// ❌ WRONG: Don't mock base components or dify-ui primitives vi.mock('@/app/components/base/loading', () => () =>
Loading
) -vi.mock('@/app/components/base/button', () => ({ children }: any) => ) +vi.mock('@langgenius/dify-ui/button', () => ({ Button: ({ children }: any) => })) -// ✅ CORRECT: Import and use real base components +// ✅ CORRECT: Import and use the real components import Loading from '@/app/components/base/loading' -import Button from '@/app/components/base/button' +import { Button } from '@langgenius/dify-ui/button' // They will render normally in tests ``` @@ -319,7 +317,7 @@ const renderWithQueryClient = (ui: React.ReactElement) => { ### ✅ DO -1. **Use real base components** - Import from `@/app/components/base/` directly +1. **Use real base components and dify-ui primitives** - Import from `@/app/components/base/` or `@langgenius/dify-ui/*` directly 1. **Use real project components** - Prefer importing over mocking 1. **Use real Zustand stores** - Set test state via `store.setState()` 1. **Reset mocks in `beforeEach`**, not `afterEach` @@ -330,7 +328,7 @@ const renderWithQueryClient = (ui: React.ReactElement) => { ### ❌ DON'T -1. **Don't mock base components** (`Loading`, `Button`, `Tooltip`, etc.) +1. **Don't mock base components or dify-ui primitives** (`Loading`, `Input`, `Button`, `Tooltip`, `Dialog`, etc.) 1. **Don't mock Zustand store modules** - Use real stores with `setState()` 1. Don't mock components you can import directly 1. Don't create overly simplified mocks that miss conditional logic @@ -342,7 +340,7 @@ const renderWithQueryClient = (ui: React.ReactElement) => { ``` Need to use a component in test? │ -├─ Is it from @/app/components/base/*? +├─ Is it from @/app/components/base/* or @langgenius/dify-ui/*? │ └─ YES → Import real component, DO NOT mock │ ├─ Is it a project component? diff --git a/packages/dify-ui/README.md b/packages/dify-ui/README.md new file mode 100644 index 0000000000..5e4e439e5f --- /dev/null +++ b/packages/dify-ui/README.md @@ -0,0 +1,105 @@ +# @langgenius/dify-ui + +Shared UI primitives, design tokens, Tailwind preset, and the `cn()` utility consumed by Dify's `web/` app. + +The primitives are thin, opinionated wrappers around [Base UI] headless components, styled with `cva` + `cn` and Dify design tokens. + +> `private: true` — this package is consumed by `web/` via the pnpm workspace and is not published to npm. Treat the API as internal to Dify, but stable within the workspace. + +## Installation + +Already wired as a workspace dependency in `web/package.json`. Nothing to install. + +For a new workspace consumer, add: + +```jsonc +{ + "dependencies": { + "@langgenius/dify-ui": "workspace:*" + } +} +``` + +## Imports + +Always import from a **subpath export** — there is no barrel: + +```ts +import { Button } from '@langgenius/dify-ui/button' +import { cn } from '@langgenius/dify-ui/cn' +import { Dialog, DialogContent, DialogTrigger } from '@langgenius/dify-ui/dialog' +import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' +import '@langgenius/dify-ui/styles.css' // once, in the app root +``` + +Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported — it keeps tree-shaking trivial and makes Storybook / test coverage attribution per-primitive. + +## Primitives + +| Category | Subpath | Notes | +| -------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | +| Overlay | `./alert-dialog`, `./context-menu`, `./dialog`, `./dropdown-menu`, `./popover`, `./select`, `./toast`, `./tooltip` | Portalled. See [Overlay & portal contract] below. | +| Form | `./number-field`, `./slider`, `./switch` | Controlled / uncontrolled per Base UI defaults. | +| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. | +| Media | `./avatar`, `./button` | Button exposes `cva` variants. | + +Utilities: + +- `./cn` — `clsx` + `tailwind-merge` wrapper. Use this for conditional class composition. +- `./tailwind-preset` — Tailwind v4 preset with Dify tokens. Apps extend it from their own `tailwind.config.ts`. +- `./styles.css` — the one CSS entry that ships the design tokens, theme variables, and base reset. Import it once from the app root. + +## Overlay & portal contract + +All overlay primitives (`dialog`, `alert-dialog`, `popover`, `dropdown-menu`, `context-menu`, `select`, `tooltip`, `toast`) render their content inside a [Base UI Portal] attached to `document.body`. This is the Base UI default — see the upstream [Portals][Base UI Portal] docs for the underlying behavior. Consumers **do not** need to wrap anything in a portal manually. + +### Root isolation requirement + +The host app **must** establish an isolated stacking context at its root so the portalled overlay layer is not clipped or re-ordered by ancestor `transform` / `filter` / `contain` styles. In the Dify web app this is done in `web/app/layout.tsx`: + +```tsx + +
{children}
+ +``` + +Equivalent: any root element with `isolation: isolate` in CSS. Without it, overlays can be visually clipped on Safari when a descendant creates a new stacking context. + +### z-index layering + +Every overlay primitive uses a single, shared z-index. Do **not** override it at call sites. + +| Layer | z-index | Where | +| ----------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------- | +| Overlays (Dialog, AlertDialog, Popover, DropdownMenu, ContextMenu, Select, Tooltip) | `z-1002` | Positioner / Backdrop | +| Toast viewport | `z-1003` | One layer above overlays so notifications are never hidden under a dialog. | + +Rationale: during Dify's migration from legacy `portal-to-follow-elem` / `base/modal` / `base/dialog` overlays to this package, new and old overlays coexist in the DOM. `z-1002` sits above any common legacy layer, eliminating per-call-site z-index hacks. Among themselves, new primitives share the same z-index and **rely on DOM order** for stacking — the portal mounted later wins. + +See `[web/docs/overlay-migration.md](../../web/docs/overlay-migration.md)` for the Dify-web migration history and the remaining legacy allowlist. Once the legacy overlays are gone, the values in this table can drop back to `z-50` / `z-51`. + +### Rules + +- Never add `z-1003` / `z-9999` / etc. overrides on primitives from this package. If something is getting clipped, the **parent** overlay (typically a legacy one) is the problem and should be migrated. +- Never portal an overlay manually on top of our primitives — use `DialogTrigger`, `PopoverTrigger`, etc. Base UI handles focus management, scroll-locking, and dismissal. +- When a primitive needs additional presentation chrome (e.g. a custom backdrop), add it **inside** the exported component, not at call sites. + +## Development + +- `pnpm -C packages/dify-ui test` — Vitest unit tests for primitives. +- `pnpm -C packages/dify-ui storybook` — Storybook on the default port. Each primitive has `index.stories.tsx`. +- `pnpm -C packages/dify-ui type-check` — `tsc --noEmit` for this package only. + +See `[AGENTS.md](./AGENTS.md)` for: + +- Component authoring rules (one-component-per-folder, `cva` + `cn`, relative imports inside the package, subpath imports from consumers). +- Figma `--radius/`* token → Tailwind `rounded-*` class mapping. + +## Not part of this package + +- Application state (`jotai`, `zustand`), data fetching (`ky`, `@tanstack/react-query`, `@orpc/*`), i18n (`next-i18next` / `react-i18next`), and routing (`next`) all live in `web/`. This package has zero dependencies on them and must stay that way so it can eventually be consumed by other apps or extracted. +- Business components (chat, workflow, dataset views, etc.). Those belong in `web/app/components/...`. + +[Base UI Portal]: https://base-ui.com/react/overview/quick-start#portals +[Base UI]: https://base-ui.com/react +[Overlay & portal contract]: #overlay--portal-contract diff --git a/packages/dify-ui/src/dialog/index.tsx b/packages/dify-ui/src/dialog/index.tsx index 517d6cc9fc..24c5bcc463 100644 --- a/packages/dify-ui/src/dialog/index.tsx +++ b/packages/dify-ui/src/dialog/index.tsx @@ -1,7 +1,7 @@ 'use client' // z-index strategy (relies on root `isolation: isolate` in layout.tsx): -// All base/ui/* overlay primitives — z-1002 +// All @langgenius/dify-ui/* overlay primitives — z-1002 // Toast stays one layer above overlays at z-1003. // Overlays share the same z-index; DOM order handles stacking when multiple are open. // This ensures overlays inside a Dialog (e.g. a Tooltip on a dialog button) render diff --git a/packages/dify-ui/src/scroll-area/index.stories.tsx b/packages/dify-ui/src/scroll-area/index.stories.tsx index e1f8f9cfb5..433817948f 100644 --- a/packages/dify-ui/src/scroll-area/index.stories.tsx +++ b/packages/dify-ui/src/scroll-area/index.stories.tsx @@ -174,7 +174,7 @@ const StickyListPane = () => (
Operational queue
-

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

+

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

24 items diff --git a/packages/dify-ui/src/toast/__tests__/index.spec.tsx b/packages/dify-ui/src/toast/__tests__/index.spec.tsx index 8bdf4417e0..edbdacd203 100644 --- a/packages/dify-ui/src/toast/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/toast/__tests__/index.spec.tsx @@ -8,7 +8,7 @@ declare global { var BASE_UI_ANIMATIONS_DISABLED: boolean | undefined } -describe('base/ui/toast', () => { +describe('@langgenius/dify-ui/toast', () => { beforeAll(() => { // Base UI waits for `requestAnimationFrame` + `getAnimations().finished` // before unmounting a toast. Fake timers can't reliably drive that path, diff --git a/web/AGENTS.md b/web/AGENTS.md index 4a705bf4b8..5e9f7ed11c 100644 --- a/web/AGENTS.md +++ b/web/AGENTS.md @@ -4,8 +4,9 @@ ## Overlay Components (Mandatory) -- `./docs/overlay-migration.md` is the source of truth for overlay-related work. -- In new or modified code, use only overlay primitives from `@/app/components/base/ui/*`. +- `../packages/dify-ui/README.md` is the permanent contract for overlay primitives, portals, root `isolation: isolate`, and the `z-1002` / `z-1003` layering. +- `./docs/overlay-migration.md` is the source of truth for the ongoing migration (deprecated import paths, allowlist, coexistence rules). +- In new or modified code, use only overlay primitives from `@langgenius/dify-ui/*`. - Do not introduce deprecated overlay imports from `@/app/components/base/*`; when touching legacy callers, prefer migrating them and keep the allowlist shrinking (never expanding). ## Query & Mutation (Mandatory) diff --git a/web/README.md b/web/README.md index 683a18c769..eb964b01e3 100644 --- a/web/README.md +++ b/web/README.md @@ -165,7 +165,7 @@ The Dify community can be found on [Discord community], where you can ask questi [Storybook]: https://storybook.js.org [Vite+]: https://viteplus.dev [Vitest]: https://vitest.dev -[index.spec.tsx]: ./app/components/base/button/index.spec.tsx +[index.spec.tsx]: ./app/components/base/radio/__tests__/index.spec.tsx [pnpm]: https://pnpm.io [vinext]: https://github.com/cloudflare/vinext [web/docs/test.md]: ./docs/test.md diff --git a/web/app/components/base/portal-to-follow-elem/index.tsx b/web/app/components/base/portal-to-follow-elem/index.tsx index 48099c7020..8b531be309 100644 --- a/web/app/components/base/portal-to-follow-elem/index.tsx +++ b/web/app/components/base/portal-to-follow-elem/index.tsx @@ -1,6 +1,6 @@ 'use client' /** - * @deprecated Use semantic overlay primitives from `@/app/components/base/ui/` instead. + * @deprecated Use semantic overlay primitives from `@langgenius/dify-ui/*` instead. * This component will be removed after migration is complete. * See: https://github.com/langgenius/dify/issues/32767 * diff --git a/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx b/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx index e2248a44c3..206a7c0148 100644 --- a/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/edit-workspace-modal/__tests__/index.spec.tsx @@ -54,7 +54,7 @@ describe('EditWorkspaceModal', () => { expect(await screen.findByDisplayValue('Test Workspace')).toBeInTheDocument() }) - it('should render on the base/ui overlay layer', async () => { + it('should render on the dify-ui overlay layer', async () => { renderModal() expect(await screen.findByRole('dialog')).toHaveClass('z-1002') diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx index cbb875a758..c72a515925 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx @@ -45,7 +45,7 @@ const WorkflowOnboardingModal: FC = ({
- {/* TODO: reduce z-1002 to match base/ui primitives after legacy overlay migration completes */} + {/* TODO: reduce z-1002 to match @langgenius/dify-ui primitives after legacy overlay migration completes */}
{t('onboarding.escTip.press', { ns: 'workflow' })} diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index b683467d5f..3f7e9e9999 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -69,7 +69,15 @@ const NodePanel: FC = ({ doSetCollapseState(state) }, [hideProcessDetail]) const titleRef = useRef(null) - const [isTitleTruncated, setIsTitleTruncated] = useState(false) + const [isTooltipOpen, setIsTooltipOpen] = useState(false) + const handleTooltipOpenChange = useCallback((open: boolean) => { + if (open) { + const el = titleRef.current + if (!el || el.scrollWidth <= el.clientWidth) + return + } + setIsTooltipOpen(open) + }, []) const { t } = useTranslation() const docLink = useDocLink() @@ -94,27 +102,6 @@ const NodePanel: FC = ({ setCollapseState(!nodeInfo.expand) }, [nodeInfo.expand, setCollapseState]) - useEffect(() => { - const titleElement = titleRef.current - if (!titleElement) - return - - let frameId = 0 - const updateIsTitleTruncated = () => { - setIsTitleTruncated(titleElement.scrollWidth > titleElement.clientWidth) - } - - frameId = requestAnimationFrame(updateIsTitleTruncated) - - const resizeObserver = new ResizeObserver(updateIsTitleTruncated) - resizeObserver.observe(titleElement) - - return () => { - cancelAnimationFrame(frameId) - resizeObserver.disconnect() - } - }, [nodeInfo.title]) - const isIterationNode = nodeInfo.node_type === BlockEnum.Iteration && !!nodeInfo.details?.length const isLoopNode = nodeInfo.node_type === BlockEnum.Loop && !!nodeInfo.details?.length const isRetryNode = hasRetryNode(nodeInfo.node_type) && !!nodeInfo.retryDetail?.length @@ -155,14 +142,13 @@ const NodePanel: FC = ({ /> )} - + diff --git a/web/docs/overlay-migration.md b/web/docs/overlay-migration.md index 0016f34e12..0fa584ecfc 100644 --- a/web/docs/overlay-migration.md +++ b/web/docs/overlay-migration.md @@ -1,6 +1,8 @@ # Overlay Migration Guide -This document tracks the migration away from legacy overlay APIs. +This document tracks the Dify-web migration away from legacy overlay APIs. + +> **See also:** [`packages/dify-ui/README.md`] for the permanent overlay / portal / z-index contract of the replacement primitives. This document covers the one-off migration mechanics (allowlist, deprecated import paths, coexistence z-index strategy) and is expected to shrink and eventually be removed once the legacy overlays are gone. ## Scope @@ -31,7 +33,7 @@ This document tracks the migration away from legacy overlay APIs. ## Migration phases 1. Business/UI features outside `app/components/base/**` - - Migrate old calls to semantic primitives from `@/app/components/base/ui/**`. + - Migrate old calls to semantic primitives from `@langgenius/dify-ui/*`. - Keep deprecated imports out of newly touched files. 1. Legacy base components in allowlist - Migrate allowlisted base callers gradually. @@ -53,7 +55,7 @@ pnpm -C web lint:fix --prune-suppressions ## z-index strategy -All new overlay primitives in `base/ui/` share a single z-index value: +All new overlay primitives in `@langgenius/dify-ui` share a single z-index value: **`z-1002`**, except Toast which stays one layer above at **`z-1003`**. ### Why z-[1002]? @@ -61,13 +63,13 @@ All new overlay primitives in `base/ui/` share a single z-index value: During the migration period, legacy and new overlays coexist. Legacy overlays portal to `document.body` with explicit z-index values: -| Layer | z-index | Components | -| --------------------------------- | -------------- | -------------------------------------------- | -| Legacy Drawer | `z-30` | `base/drawer` | -| Legacy Modal | `z-60` | `base/modal` (default) | -| Legacy PortalToFollowElem callers | up to `z-1001` | various business components | -| **New UI primitives** | **`z-1002`** | `base/ui/*` (Popover, Dialog, Tooltip, etc.) | -| Toast | `z-1003` | `base/ui/toast` | +| Layer | z-index | Components | +| --------------------------------- | -------------- | -------------------------------------------------------- | +| Legacy Drawer | `z-30` | `base/drawer` | +| Legacy Modal | `z-60` | `base/modal` (default) | +| Legacy PortalToFollowElem callers | up to `z-1001` | various business components | +| **New UI primitives** | **`z-1002`** | `@langgenius/dify-ui/*` (Popover, Dialog, Tooltip, etc.) | +| Toast | `z-1003` | `@langgenius/dify-ui/toast` | `z-1002` sits above all common legacy overlays, so new primitives always render on top without needing per-call-site z-index hacks. Among themselves, @@ -81,8 +83,8 @@ back to `z-9999`. ### Rules - **Do NOT add z-index overrides** (e.g. `className="z-1003"`) on new - `base/ui/*` components. If you find yourself needing one, the parent legacy - overlay should be migrated instead. + `@langgenius/dify-ui/*` components. If you find yourself needing one, the + parent legacy overlay should be migrated instead. - When migrating a legacy overlay that has a high z-index, remove the z-index entirely — the new primitive's default `z-1002` handles it. - `portalToFollowElemContentClassName` with z-index values (e.g. `z-1000`) @@ -92,12 +94,8 @@ back to `z-9999`. Once all legacy overlays are removed: -1. Reduce `z-1002` back to `z-50` across all `base/ui/` primitives. +1. Reduce `z-1002` back to `z-50` across all `@langgenius/dify-ui` primitives. 1. Reduce Toast from `z-1003` to `z-51`. 1. Remove this section from the migration guide. -## React Refresh policy for base UI primitives - -- We keep primitive aliases (for example `DropdownMenu = Menu.Root`) in the same module. -- For `app/components/base/ui/**/*.tsx`, `react-refresh/only-export-components` is currently set to `off` in ESLint to avoid false positives and IDE noise during migration. -- Do not use file-level `eslint-disable` comments for this policy; keep control in the scoped ESLint override. +[`packages/dify-ui/README.md`]: ../../packages/dify-ui/README.md diff --git a/web/docs/test.md b/web/docs/test.md index 58acccec2a..836ab7ad56 100644 --- a/web/docs/test.md +++ b/web/docs/test.md @@ -95,7 +95,7 @@ Use `pnpm analyze-component ` to analyze component complexity and adopt di - Testing time-based behavior (delays, animations) - If you mock all time-dependent functions, fake timers are unnecessary 1. **Prefer importing over mocking project components**: When tests need other components from the project, import them directly instead of mocking them. Only mock external dependencies, APIs, or complex context providers that are difficult to set up. -1. **DO NOT mock base components**: Never mock components from `@/app/components/base/` (e.g., `Loading`, `Button`, `Tooltip`, `Modal`). Base components will have their own dedicated tests. Use real components to test actual integration behavior. +1. **DO NOT mock base components or dify-ui primitives**: Never mock components from `@/app/components/base/` (e.g., `Loading`, `Input`, `Badge`, `Tag`) or from `@langgenius/dify-ui/*` (e.g., `Button`, `Tooltip`, `Dialog`, `Select`, `Popover`). They have their own dedicated tests. Use real components to test actual integration behavior. **Why this matters**: Mocks that don't match actual behavior can lead to: @@ -134,7 +134,7 @@ When using a single spec file: - ✅ **Import real project components** directly (including base components and siblings) - ✅ **Only mock**: API services (`@/service/*`), `next/navigation`, complex context providers -- ❌ **DO NOT mock** base components (`@/app/components/base/*`) +- ❌ **DO NOT mock** base components (`@/app/components/base/*`) or dify-ui primitives (`@langgenius/dify-ui/*`) - ❌ **DO NOT mock** sibling/child components in the same directory > See [Example Structure] for correct import/mock patterns. @@ -539,4 +539,4 @@ Test examples in the project: [Testing Library Best Practices]: https://kentcdodds.com/blog/common-mistakes-with-react-testing-library [Vitest Documentation]: https://vitest.dev/guide [Vitest Mocking Guide]: https://vitest.dev/guide/mocking.html -[index.spec.tsx]: ../app/components/base/button/index.spec.tsx +[index.spec.tsx]: ../app/components/base/radio/__tests__/index.spec.tsx diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index eab02ec664..62cc63536f 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -160,13 +160,6 @@ export default antfu( 'hyoban/no-dependency-version-prefix': 'error', }, }, - { - name: 'dify/base-ui-primitives', - files: ['app/components/base/ui/**/*.tsx'], - rules: { - 'react-refresh/only-export-components': 'off', - }, - }, { name: 'dify/no-direct-next-imports', files: [GLOB_TS, GLOB_TSX], diff --git a/web/eslint.constants.mjs b/web/eslint.constants.mjs index 46690035fb..5a2330c00e 100644 --- a/web/eslint.constants.mjs +++ b/web/eslint.constants.mjs @@ -26,7 +26,7 @@ export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [ '**/portal-to-follow-elem', '**/portal-to-follow-elem/index', ], - message: 'Deprecated: use semantic overlay primitives from @/app/components/base/ui/ instead. See issue #32767.', + message: 'Deprecated: use semantic overlay primitives from @langgenius/dify-ui (popover / dropdown-menu / tooltip / context-menu) instead. See issue #32767.', }, { group: [ diff --git a/web/scripts/analyze-component.js b/web/scripts/analyze-component.js index 04bf0d8f71..36f31f3d3d 100755 --- a/web/scripts/analyze-component.js +++ b/web/scripts/analyze-component.js @@ -374,13 +374,13 @@ Options: Examples: # Analyze a component and generate test prompt - pnpm analyze-component app/components/base/button/index.tsx + pnpm analyze-component app/components/base/radio/index.tsx # Output as JSON - pnpm analyze-component app/components/base/button/index.tsx --json + pnpm analyze-component app/components/base/radio/index.tsx --json # Review existing test - pnpm analyze-component app/components/base/button/index.tsx --review + pnpm analyze-component app/components/base/radio/index.tsx --review For complete testing guidelines, see: web/docs/test.md `)