OpenObserve 前端 UI 评审 Agent 实战指南:从机械门禁到判断型评审的职责边界
发布时间:2026/9/13 1:58:30 作者:尧图编辑部 阅读量:1,286

OpenObserve 前端 UI 评审 Agent 实战指南从机械门禁到判断型评审的职责边界【免费下载链接】openobserveOpen source observability platform for logs, metrics, traces, RUM, Session replay, pipelines, SLO and LLM observability. A sophisticated, simple and highly performant alternative to Datadog, Splunk, and Elasticsearch with 140x lower storage costs and single binary deployment.项目地址: https://gitcode.com/GitHub_Trending/op/openobserve导读本文剖析 OpenObserve 开源仓库中 AI 代码评审流水线的前端 UI 评审 Agent位于 scripts/ai-review/agents/frontend.md——它只负责.claude/skills/ui-architect/技能中任何 lint 规则或检查脚本都无法捕捉的判断型部分结构选择、跨文件一致性、字符串语义、值的正确性而非格式正确性。读完本文你将掌握一套可直接复用的前端评审清单结构、跨文件一致性、i18n 语义、交互打磨四大类 明确的不评审边界理解它与 CI 门禁的分工原则并看到这套方法论在 OpenObserve 仓库中对应的真实组件与执行引擎源码。一、定位AI 评审流水线中的条件型前端 AgentOpenObserve 的 AI Code Review 系统scripts/ai-review/README.md由 GitHub Actions 工作流驱动run-review.mjs在每次 PR 上拉取 diff、过滤噪音、按风险档位trivial/lite/full并行运行只读专家 Agent最后由 coordinator 汇总成一条评论。前端 Agent 在其中扮演特殊角色它是唯一的条件型conditionalAgent。从 scripts/ai-review/run-review.mjs 可以看到它的定义frontend: { promptFile: agents/frontend.md, opencodeAgent: ai-review-frontend, // ... requiresFocus: true, }, // ... const CONDITIONAL_AGENTS [ { agent: frontend, matches: isFrontendFile }, ];配套的 isFrontendFile 过滤函数 决定了它的触发边界// Scoped to web/src on purpose: the ui-architect rules govern app UI, not the build config, // the e2e suite or web/scripts. Specs are excluded — they carry deliberate throwaway strings // and fixture markup that the house rules do not apply to. function isFrontendFile(filePath) { if (!filePath.startsWith(web/src/)) return false; if (/\.(spec|test)\.[jt]sx?$/.test(filePath)) return false; return /\.(vue|ts|js|css)$/.test(filePath); }三个要点值得注意严格限定web/src/构建配置、e2e 套件、web/scripts都不在前端评审范围内排除 spec/test 文件测试里有意携带的一次性字符串和 fixture 标记不受设计规范约束requiresFocus: true是另一半契约当 Agent 匹配不到任何文件时通用焦点过滤器会回退到完整 diff——那将导致前端评审者对 Rust 代码发表意见。设置该标志后它直接跳过整个 PR。这也解释了为什么6 行 UI 改动在trivial档位也能得到评审只要 diff 触及web/srcfrontend就会被追加到当前档位选中的 Agent 集合中。二、评审范围只做判断型那一半Agent 的核心分工原则是一条边界线CI 已经在每个可机械检测的违规上让构建失败这些门禁在Never flag清单中列出属于评审者的职责之外——重复上报只是噪音因为作者的 PR 已经红了。剩下的是需要读者的那一半结构性选择用共享组件还是手工拼装跨文件一致性单文件工具永远看不见的维度字符串的语义该不该翻译、该不该内插变量值的正确而非well-formed格式对但语义错。这就是 Agent 的全部工作。它对应的技能底座是仓库根目录下的 .claude/skills/ui-architect/ 技能目录其references/下按主题拆分的规范文件构成了评审时的索引。三、如何使用 ui-architect 技能先读拥有证据的引用再下结论Agent 的提示词规定当一条 finding 需要精确的 prop、类字符串、路径或理由时必须先读取拥有它的引用文件再写 finding。下表是原文档给出的需求 → 引用映射路径均为仓库根目录相对路径需求读取六条房屋规则及完整理由.claude/skills/ui-architect/references/house-rules.md分层、表单容器、间距、卡片、注释.claude/skills/ui-architect/references/conventions.md存在哪些O*组件.claude/skills/ui-architect/references/component-catalog.mdOTable的 props、服务端模式、单元格组件.claude/skills/ui-architect/references/core-controls-table.mdOForm Zod schema 契约.claude/skills/ui-architect/references/forms-validation.md页面骨架、列表工具栏、空状态.claude/skills/ui-architect/references/page-recipes.md路由 导航面 环境/角色门禁.claude/skills/ui-architect/references/navigation-menus.mdtoken 注册、theme inline、dark.css.claude/skills/ui-architect/references/design-tokens.md颜色即信息的剧本.claude/skills/ui-architect/references/calm-signal.md构建新的可复用组件.claude/skills/ui-architect/references/creating-components.md新 web 代码的类型/lint 约定.claude/skills/eslint-error-handling/SKILL.md此外还有一条关键方法论在标记任何结构性选择之前先在代码库中 Grep 同侪模式。这个列表页和其他页不一致只有在你能指出一个确实一致的页面时才成立。这些引用的目标组件在当前仓库中真实存在例如页面骨架 web/src/lib/core/PageLayout/OPageLayout.vue表头组件 web/src/lib/core/PageHeader/OPageHeader.vue表格主件 web/src/lib/core/Table/OTable.vue 及其子组件目录OTableBody、OTableHeader、OTableColumnToggle、OTableEmpty、OTablePagination等 11 个空状态 web/src/lib/core/EmptyState/OEmptyState.vue、页面边缘网格 web/src/lib/core/Content/OContent.vue弹层容器 web/src/lib/overlay/Dialog/ODialog.vue 与 web/src/lib/overlay/Drawer/ODrawer.vue快捷键注册表 web/src/lib/vue-shortcut-manager/shortcutRegistry.ts。以 house-rules.md 对OPageHeader的论述为例可以看到规范为何如此强调复用而非手拼该组件编码了全应用统一的头部契约——第一行是固定高度带图标块 h1 右对齐操作区标题下带subtitle标签行第二行是全宽的同级标签条每个手写头部都会重新争论标题字号、图标块几何、返回按钮位置和标签下划线从而产生漂移。值得注意的细节包括OPageHeader没有 breadcrumb prop子页面用back代替同级标签必须传tabs-below#tabs槽默认渲染在标题旁标题块是shrink-0标签条的 x 位置会随标题宽度移动在导航时从光标下移开头部icon必须与页面导航条声明的IconName一致。四、要标记什么What to Flag四大类判断型问题1. 结构手工拼装取代了共享组件路由视图不基于OPageLayout页面/模块头部用div classheader/h1/q-toolbar手拼而非OPageHeader裸div 工具类的拼装重建了 O2 库已有的东西卡片、chip、统计块、工具栏、空状态重复的自包含元素在两个及以上调用点内联而不是抽取成组件通用 →web/src/lib的O*应用特定 →web/src/components在存在O*等价物的场景使用第三方 UI 原语Quasarq-*等用外观覆盖驱动 O2 组件工具类对抗内部、!important、:deep()深入组件而不是用variant/size/ 状态 props——正解是在组件上加新 variant而不是调用点覆盖表格数据用手建 grid/list 而非OTableOTableColumnDef[]手写内容内边距wrapper 上的px-2、p-4、p-2.5而OPageLayout的 body inset 或OContent已拥有页面边缘网格镜像问题是把OTabs条包进px-page-edge导致标签双重内缩组件内直接裸调http/ axios而不是 视图 → 领域服务src/services→ Vuex共享或局部ref临时模板里临时keydown监听或硬编码⌘N而非shortcutRegistry.tsuseShortcuts()。2. 跨文件一致性单文件工具永远看不见的维度这是价值最高的 finding 类别因为没有单文件工具能看见它们OTable服务端模式与后端失配标记sortable: true的列其排序键 Rust handler 不识别——未知键会静默回退并按别的字段排序。标记前先 grep handler不标记之前也要 grep服务端分页表上的页面相对设备ODataBarCell的柱条或#subheader计数条只在客户端分页表上有意义服务端模式下它们描述的是一页而非全集新页面的路由、导航条目、SectionRail的visible门禁互相矛盾路由条件、导航条目门禁、rail 可见性必须表达同一条config.isEnterprise/config.isCloud/zoConfig.*/ 角色规则。三处有两处不一致页面要么可达但不可见要么列了却 404。还要标记新页面注册在零个导航面或多个导航面上已有画面上的图形或标签被重新实现用新 formatter 或第二个 i18n key 而非复用原画面——同一个数字有两种拼写是用户能看见的 bug新的--color-*/ 阴影 token 定义在亮色:root却缺失于dark.css或定义了但从未在theme inline注册——未注册的 token 不产生任何工具类属性静默回退裸border画的是 Tailwind 默认边框色而非currentColor为已有值的重复 token 命名别名会静默分裂采用。3. 格式正确但值错误well-formed but wrongtext-[0.8125rem]及其他rem 任意的字号——local/no-hardcoded-px只抓 px 写法所以 rem 形式能静默编译通过。两者同罪应对齐字号刻度text-3xs…text-4xl设计守卫的盲区lint:design:strict在构造上就看不见的独立的.css文件守卫只遍历.vue和.ts、任意属性形式[background:…]无工具类前缀可匹配、以及用var()回退走私字面量var(--color-x,#fff)双主题都应用裸shadow-colour而本意只在暗色生效——shadow-xs shadow-white/8在亮色模式下白上加白需要dark:在:roottoken 内写var(--glow-color, fallback)——自定义属性在:root处替换而覆盖值未设置回退永远赢后代永远无法覆盖JS 用parseInt回读的 px→rem 转换——parseInt(18.75rem)得 18一个没有错误、没有失败测试的静默 16 倍缩小两个工具类设置同一属性新类输掉了级联战争而它替换掉的内联样式过去总能赢w-22藏在已有w-full之后凭肉眼选圆角/间距而非按角色——rounded-default是控件、rounded-surface是表面内边距/间距/卡片表面类应从屏幕所属的同侪面板家族逐字复制绝不为每个页面发明。4. i18n 语义类型无法裁决的事I18nText/I18nKey只保证形状正确只有读者能判断字符串是否根本不该翻译对真实 UI 文案用raw()——一个句子、按钮标签、校验消息。类型检查通过但字符串被冻结在英文里为全球只有一个正确形式的词添加 catalogue key——产品名Kafka、NATS、Airflow、作为名字的缩写RUM、DAG、IAM、P95、代码比较或持久化的值、用户复制的代码SQL、正则、模型 id、环境变量产品名冻结在翻译句子内部而非内插出来——raw(Route all telemetry through the OTel Collector)应写成t(…, { product: raw(OTel Collector) })同时是机器值的标签被翻译——它喂养的比较逻辑对非英语用户就坏了。显示与值需要分开的字段由碎片拼接的句子或靠追加s造复数{{ n }} {{ t(x) }}{{ n 1 ? s : }}而非 vue-i18n 管道语法模块作用域的gt()没有放在 getter 后面——{ name: gt(x) }在 import 时冻结 locale必须是{ get name() { return gt(x) } }。以及gt()用在函数本可以接收t: TranslateFn参数的地方eslint.config.js新增 allowlist 条目并非真正通用——那里的条目是全局、永久、无上下文的。5. 交互与打磨表单容器重量与交互不匹配确认 →ConfirmDialog短表单 →ODialog高而语境化 →ODrawer主要多节流程 → 完整的页内视图已校验表单不在OForm 同置 ZodForm.schema.ts上或保留了v-model/ref镜像、或formData对象与name绑定的OForm*字段并存、或手动useLoading/:loading、或在无效时禁用 Save、或用{ ...value }展开构建 payload 而非显式键、或字段数组用除:keyindex之外的键列表页缺少三个工具栏手段之一搜索 过滤#toolbar、刷新#toolbar-trailing、列显示/隐藏:persist-columnstable-idhideable列或空状态不是单个OEmptyState配preset:filteredaction上的clear-filters取消/保存行不合规取消variantoutline、保存variantprimary、两者sizesm-action、父容器gap-2Calm Signal 违规颜色花在装饰而非画面唯一的主信号上高亮常规而非异常0或—以全对比度渲染用填充式选中而非标准边框式hover/状态变化导致布局位移新交互或关键输出元素缺data-test或未遵循module-filename-descriptor格式注释叙述改动而非不明显的原因——ticket id、review finding、as discussed、复述代码。一两行是常态。五、不标记什么What NOT to Flag机械门禁与边界绝不标记任何已被 CI 门禁的内容。PR 已经红了你毫无增量。以下是原文档给出的完整门禁映射表列名从文档直接继承已由以下强制不要标记local/no-hardcoded-px任何px字面量模板、style块、.ts、.css中local/no-legacy-o2-tokensvar(--o2-*)、新增--o2-*、.body--darklint:design:strict硬编码 hex、style中的rgb()/hsl()、裸 Tailwind 色板bg-gray-*、裸grey-*/primary-*色阶、任意圆角rounded-[..]、裸rounded、退役的rounded-{sm,md,lg,xl}、任意shadow-[…]/ 字面box-shadow/boxShadow、无作用域style、tw:前缀、字面字体栈、arbZ、组件中裸var(--color-*)vue/no-bare-strings-in-template、local/no-bare-bound-text-props、intlify/vue-i18n/no-missing-keys、local/no-missing-gt-keys文本节点 / mustache /v-text/ 原生title、alt、aria-label、placeholder中的裸字符串t()或gt()键缺失于en-US.jsontype-check:appI18nText/I18nKey文本承载 prop 或字段处使用裸string文本位置的组合/拼接字符串no-restricted-imports从vue-i18n导入useI18nvue/block-langstyle langscss\|sass\|lessvue/no-mutating-props、vue/no-undef-components、vue/component-name-in-template-casing、vue/require-v-for-key等web/eslint.config.js其余规则配置设为error的任何内容format:checkprettier、stylelint、lint:tokens、lint:token-purity格式、引号风格、导入顺序此外还有四条重要边界PR 仅触及的文件中的既有违规不标记只关心改动行无法指出同侪的结构选择不标记——没有路径的其他页面做法不同是猜测不是 finding口味问题不标记不同但等价组件组合、命名偏好、考虑抽取但没有已存在的第二调用点后端文件、测试或web/之外的一切不标记。最后一条关于缺失 O2 组件的边界尤其重要缺少 O2 组件不是违规。如果库中确实没有等价物构建新的可复用组件就是正确答案——只有当作者手拼 div 时才标记。六、严重度分级约定与一致性问题不是故障这些是约定与一致性 finding不是宕机。校准标准如下critical— 只留给你能描述的、真实面向用户的破坏导航门禁不匹配导致页面在无权限情况下可达可排序列静默按错误字段排序翻译字符串破坏代码比较未注册 token 使控件在暗色模式下不可见。warning— 有具体后果的房屋规则违规手写头部将与每个其他页面漂移表单容器与交互重量不匹配重复的 formatter对真实文案用raw()。suggestion— 其他一切。不要制造 critical 只为被听见。一个喊狼来了的前端评审者会被静音——这是提示词对评审者可信度的直接警告也与 coordinator 的决策准则scripts/ai-review/agents/coordinator.md中倾向于批准的立场一致一个警告在干净 PR 中仍得approved_with_comments。七、从 Agent 到方法论这套清单能带走的通用价值虽然frontend.md是写给 AI 评审者的系统提示词但它沉淀了一套对任何 Vue 3 / TypeScript 前端团队都可复用的评审方法论明确机械门禁与判断评审的边界——lint 能抓的交给 lint评审者只做 lint 做不了的避免噪音跨文件一致性需要命名同侪的证据纪律——每个结构 finding 必须能指出一个现存兄弟实现语义问题优先于格式问题——raw()冻结英文、翻译标签破坏比较逻辑、同一数字两种拼写这些是用户真正看见的 bugseverity 以用户影响为准——critical 只给可描述的破坏且宁可少报也不制造噪音。在 OpenObserve 仓库中这套方法论由三个部件共同落地执行引擎 scripts/ai-review/run-review.mjs条件触发 requiresFocus保护、评审标准 scripts/ai-review/agents/frontend.md本文主体、以及规范底座 .claude/skills/ui-architect/引用索引与房屋规则。三者构成了一个引擎定边界、提示词定职责、技能库定证据的完整闭环值得在阅读源码时对照体察。【免费下载链接】openobserveOpen source observability platform for logs, metrics, traces, RUM, Session replay, pipelines, SLO and LLM observability. A sophisticated, simple and highly performant alternative to Datadog, Splunk, and Elasticsearch with 140x lower storage costs and single binary deployment.项目地址: https://gitcode.com/GitHub_Trending/op/openobserve创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考