十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Dify 前端评审路由:读懂 `@langgenius/dify-ui` 的契约归属与代码审查路由体系

Dify 前端评审路由:读懂 `@langgenius/dify-ui` 的契约归属与代码审查路由体系 Dify 前端评审路由读懂langgenius/dify-ui的契约归属与代码审查路由体系【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify在 Dify 的前端代码评审流程中.agents/skills/frontend-code-review/references/dify-ui.md是一份专门服务于 Agent/评审者的路由参考文档它本身不重定义packages/dify-ui/包的任何契约而是把一次涉及langgenius/dify-ui/*的评审请求精准路由到对应的契约所有者文档owner doc并要求评审者以“实现、公开类型、测试、stories 即证据”的原则只报告可复现的契约违反或可观测缺陷。读完本篇你能掌握 Dify UI 包的分层文档体系包边界、组件契约、跨组件契约如何运作以及如何按评审区域Button、表单、Overlay、Styling、测试等快速定位权威契约、结合源码与测试完成有据可查的代码审查。一、路由文档的定位不重定义契约只负责“导航”dify-ui.md开篇即声明了自己的边界Use this reference when a review touchespackages/dify-ui/or consumeslanggenius/dify-ui/*. It routes to owner documentation; it does not redefine package contracts.也就是说它是 frontend-code-review 技能 的规则包之一。当评审范围命中“Dify UI imports、Base UI wrappers、overlays、tokens、或 primitive contracts”时该技能读取此参考而参考自身的工作方式是先读包边界与实现先读 packages/dify-ui/AGENTS.md包边界与文档路由和具体 primitive 的实现代码再按评审区域只读对应的 owner 文档避免把整个文档树全部加载进上下文。路由表完整继承自 dify-ui.md如下评审区域契约所有者canonical owner包边界与文档路由packages/dify-ui/AGENTS.md导入、导出、公开类型与结构anatomypackages/dify-ui/docs/authoring.mdButton 或纯图标操作packages/dify-ui/src/button/README.md、packages/dify-ui/src/icon-button/README.md复合输入compound inputspackages/dify-ui/src/input-group/README.md表单与字段语义packages/dify-ui/docs/forms.md选择与类型化取值typed valuespackages/dify-ui/docs/selection.mdPortal、层叠与浮动表面packages/dify-ui/docs/overlays.mdTailwind 与圆角 tokenpackages/dify-ui/docs/styling.md包测试与 Storybookpackages/dify-ui/docs/testing.md对web/下消费方代码路由文档还额外要求读取 web/AGENTS.md 中的应用侧复用策略以及 packages/dify-ui/README.md 中列出的全部公开子路径。路由文档的结尾给出了评审证据标准这是整套路由体系的质量底线以实现、公开类型、测试和 stories 作为文档化契约的证据。若文档与实际不一致先弄清真正的契约所有者再报告问题上游Base UI派生的行为应以当前官方 Base UI 文档和已安装的base-ui/react类型声明为准只报告可复现的契约违反或可观测缺陷不报告从路由文件本身推断出的“偏好”preference。二、包边界packages/dify-ui/AGENTS.md是什么的契约所有者路由表中“包边界与文档路由”一栏指向 packages/dify-ui/AGENTS.md。从该文件看langgenius/dify-ui的边界规则有四点保持独立 primitive 包不从应用包导入不依赖路由、i18n、应用状态、schema、数据获取或业务 API优先base-ui/react凡 Base UI 已拥有所需 headless 行为时直接使用它用cva、cn与 Dify 设计 token 做样式化一个src/name/目录对应一个 primitive可附带共置的 stories 与测试优先 Base UI data attributes 与 CSS 变量表达视觉状态不要把 primitive 状态镜像到 React 里仅为了加 class上游 API 或 selector 契约不明时先读当前官方 Base UI 文档与已安装的base-ui/react声明再动手。该文件同时声明了“契约所有者”清单Public API authoringdocs/authoring.md、Button 与 Icon Button 契约src/button/README.md、src/icon-button/README.md、复合输入src/input-group/README.md、Formsdocs/forms.md、Selectiondocs/selection.md、Overlaysdocs/overlays.md、Stylingdocs/styling.md、Testingdocs/testing.md。并有一条值得注意的原则组件只有在拥有类型、stories 和上游文档无法表达的实质性 Dify 契约时才需要本地 README不为完整性而创建——这解释了为何docs/下只有 7 份跨组件指南而组件级文档只存在于button、icon-button、input-group三个目录。从 src 目录结构 看包内实际包含 alert-dialog、autocomplete、button、checkbox、combobox、dialog、drawer、dropdown-menu、field、form、icon-button、input-group、number-field、select、slider、switch、tabs、toast、tooltip 等约 40 个 primitive以及cn.ts、placement.ts、overlay-shared.ts、form-control-shared.ts等包内共享实现与 README.md 中的分类表Actions/Controls/Display/Feedback/Form/Layout/Media/Navigation/Overlay and menu/Search and pick一一对应。三、包的使用方式子路径导入、无根 barrelpackages/dify-ui/README.md 是路由表中“可用公开子路径”的来源也是评审消费方导入是否合法的第一依据。关键约定{ dependencies: { langgenius/dify-ui: workspace:* } }import { Button } from langgenius/dify-ui/button import { Dialog, DialogContent, DialogTrigger } from langgenius/dify-ui/dialog import { Field, FieldLabel } from langgenius/dify-ui/field import { Input } from langgenius/dify-ui/input import { cn } from langgenius/dify-ui/cn import langgenius/dify-ui/styles.css包有意没有根 barrel必须从公开子路径导入每个公开 primitive 在package.json#exports中都有对应子路径这是 docs/authoring.md 的硬要求也是评审“新增导出是否配了 exports 子路径”的判据styles.css由消费方在根样式表或入口处只导入一次提供设计 token、主题变量与共享工具类cn用clsxtailwind-merge组合条件类。从 README 的自述看绝大多数交互 primitive 是 Base UI headless 组件的“薄而有主见的包装”thin, opinionated wrappers包本身对 workspace 私有private to the workspace但其公开子路径被视为稳定的包边界——这正是评审中“消费方是否绕过子路径深导入src/”这类发现的依据。四、公开 API 的写作契约authoring评审“imports、exports、public types、anatomy”时路由到 docs/authoring.md。其核心规则每个src/primitive/index.tsx是显式的公开 API 边界实现细节保持 module-local文件底部用独立的export { ... }与export type { ... }清单发布完整表面禁止散落的内联导出与通配导出命名canonical 组件与 props 类型用同名不加Root后缀Select/SelectProps、Drawer/DrawerProps仅当同一子路径同时导出底层 anatomy 和高层便捷组件时才保留Root如CheckboxRoot与Checkbox每个运行时组件必须有准确、可导入的 props 类型对未改动的 Base UI 部件用直接别名Dify 自研组合 props 在 Dify UI 边界定义控制态/非控制态、单选/多选等“一个 prop 改变相关 props 合法形状”的场景用可辨识联合discriminated union泛型契约端到端保持picker 的Value与Multiple、表单值、radio/slider 值、overlay payload 与 handles 不得被any或硬编码string抹掉unknown只作为“独立消费的 anatomy 无法从父级推断值”时的安全默认公开表面保持最小状态、事件细节、actions、受控态辅助、context 值、渲染辅助、上游透传别名默认私有wrapper 用cn()消费className时应暴露className?: string而非上游 state-callback 形态用公开子路径类型测试守护泛型推断改上游派生契约前先读官方 Base UI 文档与已安装类型声明。其中一条具体的“反例”值得评审时引用Tabs有意沿用 Base UI 的非泛型 root因为其当前 tab 值类型是any | null——“不要宣称整套 anatomy 都无法执行的类型关系”。五、Button 与 IconButton 契约评审高频区5.1 Button路由表把“Button 或 icon-only actions”指向 src/button/README.md 与 src/icon-button/README.md。Button 契约要点均为该文档的完整内容语义选择有可见文字标签用Button纯图标命令用IconButton持久按压态用Toggle跳转 URL 用原生 link不用Button渲染链接——导航保持原生 anchor/routing link仅复用buttonVariants({ variant: secondary })视觉a className{buttonVariants({ variant: secondary })} href/settings Settings /arender配nativeButton{false}仅用于“非 button 元素确实需要 button 语义”的场景不是链接模式。默认渲染button typebutton提交表单必须显式typesubmit。disabled与loading描述不同的事实Prop含义默认焦点行为disabled操作不可用native-disabled移出 tab 顺序loading操作已触发、处理中阻止激活但按钮保留焦点内部到 Base UI 的映射为disabled{disabled || loading} focusableWhenDisabled{focusableWhenDisabled ?? loading}即 loading 按钮以aria-disabled保留可聚焦而非 nativedisabled移出激活且仍在 tab 顺序中。由此推导出评审可直接引用的三条写法判据// 正确独立可用性条件留在 disabled Button loading{isSaving} disabled{!canSave}Save/Button // 错误不要重复 pending 状态 Button loading{isSaving} disabled{isSaving}Save/Button // 错误 Button loading{isSaving} disabled{isSaving || !canSave}Save/Button无障碍加载反馈loading spinner 是装饰性的Button不加aria-busy按 WAI-ARIA 定义aria-busy描述的是“正在被修改、内容更新可能被辅助技术延迟”的元素不是 pending 操作的通用替代。长时操作的状态播报、live region、进度组件由功能侧负责。内容间距由Button拥有不要在调用处加 icon margin 或常规gap-*。medium/large间距为 4px/6pxsmall的primary为 3px、其他 variant 为 4px。5.2 IconButtonicon-button README 的契约每个 icon button 必须且只能有一个 accessible name 来源aria-label或aria-labelledbytooltip 是视觉增强不是 accessible name子元素恰好一个 React 元素承载装饰 glyph 并aria-hiddenIconButton aria-labelClose span aria-hiddentrue classNamei-ri-close-line size-4 / /IconButton子元素拥有 glyph 与光学尺寸IconButton拥有按钮尺寸、圆角、颜色、hover、disabled、focus-visible 样式variant省略时为 icon-button 专属的 neutral 外观破坏性意图用tonedestructive组合原则当 Toggle/Menu/Popover/Tooltip/Collapsible 拥有交互状态时让该 primitive 在外层通过renderprop 组合IconButton不把 pressed/open/expanded 状态镜像到 icon button 上——这与包边界文档第 3 条“不要镜像 primitive 状态”一致。六、表单契约提交边界、值所有权与字段标签docs/forms.md是“Forms and field semantics”评审区域的 ownerdocs/forms.md。它首先定调Dify UI 表单 primitive 组合 Base UI 的原生表单语义、字段可访问性与 Dify 样式不是表单状态管理或 schema 框架。核心契约提交边界。每组一起保存/提交的控件需要真实的form边界不能把Input和一个 click-onlyButton拼成非正式表单。需要 Base UI 的结构化onFormSubmit值、合并错误、actionsRef或validationMode时用Form渲染原生form若另一套表单库拥有提交与校验原生表单依然正确但不要嵌套两个 form owner。表单内除提交按钮外所有按钮保持typebutton。值与状态所有权。Form拥有提交与校验边界不拥有每个字段的草稿。controlled 与否按“source-of-truth 需求”独立决定与草稿存放位置无关应用 React 代码不需要拥有当前值时优先defaultValue只有应用渲染或协调必须在编辑期间拥有该值时才用value change handler。监听 change 事件、跟踪 dirty、原生校验本身都不要求 controlled 状态。草稿状态放在生命周期最窄的拥有它的组件里非受控字段也可以参与在显式持久化边界取值的持久化工作流。字段与标签。需要共享 name/label/校验/描述/错误时把控件包进Field独立Input可用原生label htmlFor但常规表单行应优先可见 labelFieldDescription/FieldError提供可访问的 message 关联。标签 primitive 按控件选择文本类输入、Textarea、input 型Combobox/Autocomplete、单个Checkbox、每个Radio选项、Switch、NumberField用FieldLabeltrigger 型Select字段用SelectLabelSlider用SliderLabel按 Base UI Slider anatomy仅多 thumb 时给每个 thumb 加aria-label区分SelectGroupLabel/AutocompleteGroupLabel是弹层内选项组标签不是字段标签。分组控件。一个字段包含多个相关控件checkbox/radio 组、多 thumb slider时用FieldsetFieldsetLegend每个选项用FieldItem包裹并自带 labelFieldset拥有组语义与 legend 关系disabled/value/defaultValue/change handlers 传给组 primitive。文档给出完整示例Field nameallowedNetworkProtocols Fieldset render{CheckboxGroup /} FieldsetLegendAllowed network protocols/FieldsetLegend FieldItem FieldLabel classNameflex items-center gap-2 Checkbox valuehttps / HTTPS /FieldLabel /FieldItem /Fieldset /Field所有Radio必须属于某个RadioGroup不渲染孤立Radio表单状态、schema、服务端校验与 reset 行为留在最近的、生命周期足够的应用拥有者中通过公开 field 与 control props 传入可观测状态。七、复合输入 InputGroup 与选择器 Selection7.1 InputGroup“Compound inputs”区域路由到 src/input-group/README.md。契约要点一个文本输入与前缀/后缀/动作共享视觉表面时用InputGroup否则用独立Input结构anatomy恰好一个直接的InputGroupInput 一个或多个InputGroupAddonInputGroup拥有边框、背景、焦点态与非交互指针表面InputGroupInput拥有原生 input 与值addon 拥有布局与辅助内容。不要用绝对定位把内容盖在独立Input上仿造共享表面DOM 顺序InputGroupInput排在所有 addon 之前输入是主控件addon 在其后被读取/到达视觉位置用aligninline-start/aligninline-end不改变语义与焦点顺序可访问性与交互InputGroupInput必须有 accessible name需要共享 name/label/校验态时整组包进FieldField 状态传播给 inputInputGroup从直接 input 派生 invalid/disabled/focus 视觉不要在 addon 上重复这些状态装饰 icon 加aria-hidden交互内容放进语义化Button/IconButton/link 而非给 addon 加点击行为点击非交互表面聚焦直接 input点击交互 addon 只作用于该控件消费方可在InputGroup的onMouseDown中preventDefault()取消共享表面行为尺寸新尺寸加到InputGroup而不是分别调整 input 与 addon。7.2 Selection 与类型化取值docs/selection.md 是“Selection and typed values”的 owner。primitive 选择矩阵契约组件从可见选项集中选一个持久字段值RadioGroup每个Radio/RadioItem必须属于组选一个 mode/filter/view遵循 radio-group 语义不可 toggle off、Tab 选中项、方向键移动并选中SegmentedControl选 panel提供tablist/tabpanel语义Tabs自由文本 可选建议Autocomplete从可搜索集合中选中并记住一个或多个值Combobox闭集、可扫读列表无文本输入Select多选 combobox 遵循 Base UI chips 组合chips 与 input 共享 input group、可换行、组垂直生长三个弹层型选择器用 Base UI 的--anchor-width与--available-width跟随 trigger 并钳制到视口不得用固定宽度或未钳制的最小宽度替代。类型化取值是评审重点不要把领域值放宽成stringSelectValue, Multiple、RadioGroupValue支持枚举、联合、布尔、数字、对象与可空占位值root 泛型类型value/defaultValue及值相关回调JSX children 不继承父泛型独立消费的 anatomy 需自行标注RadioGroupPromptMode value{promptMode} onValueChange{setPromptMode} RadioPromptMode value{PROMPT_MODE.default} / RadioItemPromptMode value{PROMPT_MODE.custom} RadioControl / Custom prompt /RadioItem /RadioGroupSelect/Combobox的字面 multiple 类型必须与运行时模式一致ComboboxSubject, true multiple独立消费的渲染 anatomy 重复标注领域类型而非标注回调参数文档给出的ComboboxValue/ComboboxList示例即按此写法。优先 Base UI 的items集合模式让 root、value 显示与 item 列表共享同一运行时 source of truth只在真实序列化边界把值转字符串CheckboxGroup遵循 Base UI 使用string[]更强的业务 ID 区分建模在领域边界。八、OverlayPortal、层叠与语义选择docs/overlays.md 是“Portals, layers, and floating surfaces”的 owner。契约要点Portal 与根隔离。浮动表面通过 Base UI Portal 渲染到document.body便捷组件DialogContent、PopoverContent、SelectContent内部拥有自己的 portal显式 anatomy 的 primitive 则暴露组成部件。挂载与状态生命周期。overlay root 的 React 生命周期、open 状态、portal 子树的 presence 是三者分离的例如DialogContent拥有的Dialog.Portal子树在打开时挂载、关闭过渡完成后卸载而Dialogroot 可保持挂载并由外部独立控制——用与关闭相同的条件移除受控 root 会绕过 primitive 的关闭生命周期。卸载的 portal 子树重置的只是该子树拥有的 DOM/组件状态祖先声明或外部 store 的状态存活portal 位置本身不是重置边界。需要跨会话的状态放更长寿的 feature owner。宿主必须建立隔离的层叠上下文这是评审 web 应用宿主代码时的可检查项body div classNameisolate h-full{children}/div /body层叠表评审“调用处私加 z-index”类发现的依据层z-indexDialogs、pickers、drawers、menus、popovers、preview cards、tooltipsz-50Toast viewportz-60z-50之间依赖 portal DOM 顺序后挂载者在上禁止在调用处加z-*覆盖、禁止把 Dify UI overlay 再包一层手动 portal、共享 backdrop 放在拥有它的导出组件内部。primitive 语义选择Dialog用于需要焦点包含与滚动锁定的模态内容AlertDialog仅用于必须明确回答的破坏性决策Drawer用于侧板交互DropdownMenu用于按钮触发的动作列表、ContextMenu用于上下文动作Tooltip仅用于短小的非交互视觉标签touch 上必须可达的信息用PopoverPreviewCard是非交互的链接目的地增强Popover用于解释性、结构化或 touch/辅助技术必须可达的交互内容。按钮状 trigger 用真实button typebuttonBase UI trigger 有意渲染非 button 元素时设nativeButton{false}。九、StylingTailwind v4 集成与 Figma 圆角映射docs/styling.md 是“Tailwind and radius tokens”的 owner。样式入口写法消费方根样式表import tailwindcss; import langgenius/dify-ui/styles.css;当 workspace 消费方直接扫描 Dify UI 源码时需为该包src/目录加source条目从该消费方样式表解析路径并排除测试与 stories/* Example only: resolve paths from this stylesheet. */ source ../../../packages/dify-ui/src; source not ../../../packages/dify-ui/src/**/*.{spec,test}.{ts,tsx}; source not ../../../packages/dify-ui/src/**/*.stories.{ts,tsx};Figma 圆角 token 与 Tailwind CSS v4 默认值偏移一步必须使用映射表而非自定义主题值或radius-*工具类Figma tokenTailwind 类--radius/2xsrounded-xs--radius/xsrounded-sm--radius/smrounded-md--radius/mdrounded-lg--radius/lgrounded-[10px]--radius/xlrounded-xl--radius/2xlrounded-2xl--radius/3xlrounded-[20px]--radius/6xlrounded-[28px]--radius/fullrounded-fullFigma 输出的rounded-[var(--radius/sm, 6px)]应转换为映射后的标准类无标准类对应时才用任意值。其他规则优先语义化 Dify token 与既有组件 variant而不是硬编码值important 修饰符只用于“拥有该状态的 variant/data attribute/selector 结构都无法表达”时紧作用域的兼容覆盖focus-visible 样式挂在视觉上代表焦点的元素上若可见 wrapper 包含原生焦点目标从 wrapper 选该后代状态例如SliderThumb用has-[:focus-visible]。十、Testing双 Vitest project、Storybook 与 a11y 策略docs/testing.md 是“Package tests and Storybook”的 owner也是评审测试归属哪个行为该测在哪的依据。命令仓库根目录跑检查包目录跑其余命令命令工作目录作用vp check packages/dify-ui仓库根格式、lint、TypeScript 诊断vp test --project unitpackages/dify-ui/primitive 单元测试vp run storybookpackages/dify-ui/启动 Storybookvp test --project storybook --runpackages/dify-ui/浏览器模式跑 Storybook 组件测试vp testpackages/dify-ui/两个测试 project 都跑测试边界。包有两个 Vitest project均跑在 Playwright Chromium 的 Browser Mode 下project 名标识“行为所有者”不是不同运行时。归属规则Storybook story 文档化组件示例 渲染契约通过 Storybook Vitest addon 跑配置好的 a11y 检查当示例还拥有可见状态变化、用户交互、键盘路径、overlay 流程、表单行为、loading 行为或受控态协同时补play普通 Vitest 测试用于低层 wrapper 契约class variant、Base UI 透传 props、hidden input 序列化、data-attribute hooks、stores、以及不需要文档化示例的边缘用例Storybook a11y 配置a11y.test error启用即失败颜色对比是唯一全局禁用的规则已知设计 token 缺口不得再加全局例外临时例外必须局部化到受影响 story且不得用play测试替代 a11y 修复。动画配置。Base UI 卸载 transition 驱动组件前会等待element.getAnimations()断言最终 DOM 状态而非动画行为的测试应在 Vitest setup 中设置;(globalThis as typeof globalThis { BASE_UI_ANIMATIONS_DISABLED: boolean }).BASE_UI_ANIMATIONS_DISABLED truevitest.setup.ts 已为 primitive 测试应用该设置Storybook 使用 preview setup 并保留真实动画生命周期有意断言动画行为的单测可局部恢复为false但清理时必须还原原值。十一、消费方web/侧的配套要求路由文档明确评审web/下消费langgenius/dify-ui/*的代码时还要读 web/AGENTS.md 的应用侧复用策略。从该文件可见的配套约定是优先使用langgenius/dify-ui/*primitive、data attributes 与设计 token从 Dify UI 包索引 开始选择 primitive 或共享契约并确保最终可聚焦元素保留可见焦点指示。这意味着消费方评审的两类典型发现是绕过子路径直接深导入包内部实现以及在应用层重建 overlay 的 z-index/portal 责任违反 Overlays 契约。十二、如何把这套路由用于一次实际评审综合路由文档、技能定义与包文档一次涉及packages/dify-ui/或langgenius/dify-ui/*的评审可按下述流程执行确定评审范围按 SKILL.md 的 Evidence First 原则从请求的文件或当前 diff 出发读变更行、行为拥有者与最近的 scopedAGENTS.md按路由表只加载命中的 owner 文档例如 diff 触及Combobox泛型读docs/selection.mddocs/authoring.md 对应src/实现而不是通读全部文档以实现/类型/测试/stories 为契约证据文档与实现不一致时先判定真正的契约所有者Dify 自研契约 vs Base UI 上游派生行为上游行为以官方 Base UI 文档与已安装类型声明为准只报告可复现的契约违反或可观测缺陷按技能的严重级P0 安全/数据丢失/生产崩溃/关键流程不可访问P1 用户可见回归或破坏主交互P2 具体的可维护性/性能/测试/a11y 缺陷P3 次要清理每条 finding 给出紧凑的文件与行号引用、违反的契约或复现路径、影响与具体修复方向无发现时明确输出No issues found.并说明实质性验证缺口不添加赞美段落、推测性风险或未经请求的修复承诺。这套“路由文件不定义契约、owner 文档单一来源、代码与测试即证据”的三层结构使得 Dify 的 UI 包文档既是人可读的设计指南也是可被 Agent 按区域精准检索与引用的评审规则包——这正是 dify-ui.md 作为路由参考的全部价值所在。【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表