HyperDX 代码规范实战指南:grep-first 复用、Mantine 语义变体与前端工程最佳实践

📅 发布时间:2026/9/24 16:07:40
HyperDX 代码规范实战指南:grep-first 复用、Mantine 语义变体与前端工程最佳实践
HyperDX 代码规范实战指南grep-first 复用、Mantine 语义变体与前端工程最佳实践【免费下载链接】hyperdxResolve production issues, fast. An open source observability platform unifying session replays, logs, metrics, traces and errors powered by ClickHouse and OpenTelemetry.项目地址: https://gitcode.com/gh_mirrors/hy/hyperdx本文以仓库 agent_docs/code_style.md 为骨架结合packages/下的真实源码与 CI 脚本展开。它面向在 HyperDX 仓库中新增类型、Zod schema、辅助函数或 React 组件的开发者含 AI 编码 Agent系统讲解先搜索后实现的复用纪律、common-utils共享代码地图、Mantine 组件变体约束、语义设计 token、文案规范与重构原则。读完你可以做到在动手写代码前用几分钟定位到仓库里已有的实现写出与 HyperDX / ClickStack 双品牌主题完全一致的 UI并且不踩中已被明令禁止的代码模式。文档定位一份给编码 Agent 的渐进式披露规范agent_docs/目录采用渐进式披露设计详细的领域知识不塞进每个会话都会加载的AGENTS.md而是按任务按需读取。agent_docs/README.md 明确说明code_style.md的适用时机是在任何包中新增类型、Zod schema、辅助函数或组件之前grep-first 规则 common-utils 内容地图以及任何packages/appUI 改动之前。开篇有一条重要提示Pre-commit hooks 会自动处理格式化因此本文档聚焦于实现模式而非格式细节——代码风格约束的核心是模式正确而不是缩进正确。TypeScript消灭any拥抱类型推导HyperDX 的 TypeScript 基调是用类型系统表达契约避免any使用恰当的类型避免as强制断言尽可能使用satisfies或类型推导运行时校验统一走Zod schemas为数据结构定义清晰的 interface实现正确的错误边界error boundaries定义并导入可复用的具名类型而不是到处重复冗长的类型标注。命名类型复用在仓库中有直接的落地证据。packages/app/src/types.ts通过别名重导出common-utils中的类型避免在 app 包内重复定义// packages/app/src/types.ts import { NumberFormat as _NumberFormat } from hyperdx/common-utils/dist/types; export type NumberFormat _NumberFormat;这种别名垫片alias-shim模式会在下文找到已有实现之后怎么办一节中详细展开。代码组织单一职责、行数上限与强制 DRY组织规范共四条单一职责每个组件/函数只做一件事文件大小单文件上限300 行接近上限就要重构DRY必选在新增类型、schema、辅助函数或组件之前先 grep 是否已有实现——即下一节要讲的 grep-first 规则上下文学习实现前先阅读仓库中相似的文件对齐既有写法。新增类型 / Schema / 辅助函数之前先 grepREQUIRED这是本文档唯一的强制流程值得单独成节。规则原文在你定义新的类型、Zod schema、辅助函数或 React 组件之前先搜索是否已有实现。关键技巧是搜索的是操作把列表切分、按时间戳分桶、校验 URL、格式化时长而不是你正准备给它起的名字。名字各不相同但操作是相同的。官方给出的搜索命令约 20ms 内完成# 用两三个词描述操作作为备选关键词 grep -rniE export (const|function|type|interface|enum) [a-zA-Z]*(bucket|interval|granular) \ packages/common-utils/src packages/app/src packages/api/src \ --include*.ts --include*.tsx搜索顺序先搜你正在编辑的文件 → 再搜packages/common-utils/src/→ 最后搜你所在包的其余部分。一个关键的架构事实common-utils没有根 barrelroot barrel——没有src/index.tspackage.json也不声明入口点因此所有导入都是形如hyperdx/common-utils/dist/...的深路径deep import。packages/common-utils/src/types.ts仅这一个文件就有约 2850 行、280 个导出实测当前仓库为 3150 行、314 个 export 行还在持续增长。你无法通过读 index 发现已有什么grep 才是索引。共享代码已经住在哪里common-utils 内容地图仓库中跨包的导入约 90% 集中在九个模块下表中.../dist均指hyperdx/common-utils/dist导入路径承载内容.../dist/types所有共享 Zod schema 及其推断类型sources、alerts、dashboards、tiles、webhooks、chart configs以及SQLInterval、MetricsDataType、StacktraceFrame等.../dist/core/utils时间分桶与粒度计算toStartOfInterval、timeBucketByGranularity、convertGranularityToSeconds、字符串拆分splitAndTrimCSV、splitAndTrimWithBracket、escapeSqlString、hashCode、parseJSON、formatDate.../dist/core/metadataClickHouse 表/列内省——Metadata、getMetadata、parseKeyPath、unquoteIdentifier.../dist/clickhouse/node、/browser查询客户端、ChSql/chSql/concatChSql、ClickHouse ↔ JS 类型转换.../dist/core/renderChartConfigChartConfig→ SQL.../dist/filters过滤器状态、序列化与校验FilterState、filtersToQuery、parseQuery.../dist/guardsChart-config 与 source 类型守卫isBuilderChartConfig、isRawSqlChartConfig、isHeatmapCompatibleSource.../dist/validationisValidUrl、isValidSlackUrl、密码校验器.../dist/variablesDashboard 变量解析与模板展开时间分桶函数在源码中有完整实现例如 packages/common-utils/src/core/utils.ts#L599 的convertGranularityToSeconds将SQLInterval粒度换算为秒#L668 的timeBucketByGranularity在此基础上按起点做分桶迭代——这正是先 grep 到它、别自己重写的典型场景。第二顺位的包内位置packages/app/src/utils.ts、packages/app/src/types.ts、packages/app/src/ChartUtils.tsx、packages/api/src/utils/。找到已有实现之后怎么办文档给出了一张决策表共五种情形情形做法存在规范版本且适用直接导入删掉你正在写的那份拷贝存在但想用本地名别名垫片——如 packages/app/src/types.ts#L9 的export type NumberFormat _NumberFormat;绝不重新输入一遍定义存在但形状几乎合适从它派生Pick、Omit、.extend()、Alert { … }或参数化规范版本保持单一事实来源两个组件间有共享逻辑提炼成兄弟模块——如 packages/app/src/components/sourceSelectUtils.tsx#L51 的useFilteredSortedSourceItems被SourceSelect和SourceMultiSelect共同使用两个版本必须并存用 doc-comment 说明为什么并点名孪生版本。典型例子见 packages/common-utils/src/types.ts#L571-L582Mongoose 的IWebhook定义于 packages/api/src/models/webhook.ts#L17与 JSON 序列化用的WebhookSchema成对存在不可接受的做法出现第二个定义却没有别名、没有解释孪生关系的 doc-comment、也没有把两者钉在一起的测试。整文件跨包移植source标记当整个文件必须从其他包复制过来packages/cli就为Web Frontend Alignment清单中的组件做了这件事见 packages/cli/AGENTS.md必须在文件头部用source标签标明来源一个来源文件一个标签/** * Source helper functions. * * source packages/app/src/source.ts */CI 脚本 scripts/ci/ratchet.mjs 会统计这些标签让总量在 CI 中保持可见。但注意文档的明确态度该统计是建议性的不是门禁——标注移植永远不会让构建失败而删除标签来变绿只是隐藏了拷贝而非移除拷贝。想降低计数正确做法是把共享代码迁入common-utils然后运行yarn ratchet:update锁定基线。React 模式函数组件 Hooks 自定义 Hook使用函数组件 hooks不用 class 组件写小而聚焦的组件把可复用逻辑提取为自定义 hooks为 props 定义 TypeScript interface列表使用正确的 key昂贵计算使用 memoization。Mantine UI自定义变体是唯一正解项目以Mantine UI为组件层但通过自定义变体variants在品牌主题中统一样式。注意文档原文写的packages/app/src/theme/mantineTheme.ts在仓库中实际位于品牌子目录——packages/app/src/theme/themes/hyperdx/mantineTheme.ts 与 packages/app/src/theme/themes/clickstack/mantineTheme.ts两个品牌主题各自构建MantineThemeOverride。两条总原则优先用 Mantine 组件而不是自写样式的元素优先用 Mantine 单个 style props如mxs而不是原始 styles如style{{ margin: 4px }}。Button 与 ActionIcon 变体REQUIREDButton 和 ActionIcon 只能用下表这五种变体这是硬性约束变体用途示例variantprimary主操作Submit、Save、Create、RunButton variantprimarySave/Buttonvariantsecondary次操作Cancel、Clear、辅助动作Button variantsecondaryCancel/Buttonvariantdanger破坏性操作Delete、Remove、Rotate API KeyButton variantdangerDelete/Buttonvariantlink无背景无边框的链接式操作View Details、导航式 CTAButton variantlinkView Details/Buttonvariantsubtle透明背景 hover 高亮工具栏/工具控件折叠开关、关闭按钮Button variantsubtleFilter/Button正确用法Button variantprimarySave/Button Button variantsecondaryCancel/Button Button variantdangerDelete/Button Button variantsubtleFilter/Button Button variantlinkView Details/Button ActionIcon variantprimary.../ActionIcon ActionIcon variantsecondary.../ActionIcon ActionIcon variantdanger.../ActionIcon ActionIcon variantlink.../ActionIcon ActionIcon variantsubtle.../ActionIcon禁止模式light/outline/filled/default 调色板颜色均不可用于 Button/ActionIconButton variantlight colorgreenSave/Button Button variantoutline colorredDelete/Button Button variantfilled colorgrayCancel/Button Button variantdefaultCancel/Button ActionIcon variantlight colorred.../ActionIcon ActionIcon variantfilled colorgray.../ActionIcon两个变体的行为细节在主题源码中均有对应实现link无背景、无边框、文字用 muted 色--color-text-secondaryhover 时文字提亮到全对比度适合混入周围内容的链接式 CTA如 View Details、View Full Trace——见 mantineTheme.ts#L323-L329 的Buttonvars 实现。subtle透明背景 标准文字色hover 出现--color-bg-hover背景高亮。它是ActionIcon 的默认变体defaultProps: { variant: subtle }适合工具栏图标、折叠开关、关闭按钮。与 link 的区别是subtle 显示 hover 背景link 改变文字颜色。注意variantfilled对表单输入Select、TextInput 等仍然有效只是不能用于 Button/ActionIcon。纯图标按钮必须用 ActionIcon如果 Button 里只有图标、没有文字就必须换成 ActionIcon// ❌ 错误 —— Button 只装了一个图标 Button variantsecondary pxxs IconRefresh size{18} / /Button // ✅ 正确 —— 纯图标按钮用 ActionIcon ActionIcon variantsecondary sizeinput-sm IconRefresh size{18} / /ActionIcon文档特别说明这条规则无法被 ESLint 强制执行必须靠人工代码审查。语义组件变体Alert / Text / 危险控件项目为Alert、Text、Button、ActionIcon提供主题化的语义变体让提示框和状态文字由设计 token 驱动在 HyperDX 与 ClickStack 两个品牌以及明/暗模式下保持一致。优先使用这些变体而不是裸的 Mantine 调色板颜色coloryellow、colorred、cgreen等。变体 → token 的映射集中定义在 packages/app/src/theme/themes/semanticVariants.ts两个品牌主题共用的唯一事实来源SEMANTIC_TEXT_COLORS、SEMANTIC_CONTROL_COLORS、SEMANTIC_ALERT_VARS分别驱动 Text、Button/ActionIcon 与 Alert。Alert—— 支持info|success|warning|danger渲染带色调的-subtle背景标题、图标以及正文都使用语义色 tokenMantine 默认会把 message 强制成黑/白主题通过styles覆盖让正文跟随--alert-color// ✅ token 驱动两个品牌 明暗模式都正确满足 WCAG AA Alert variantwarning titleHeads upThis may take a while./Alert Alert variantdanger titleFailedCould not save the alert./Alert // ❌ 硬编码 Mantine 调色板 —— 不感知主题、对比度不一致 Alert coloryellow titleHeads up.../Alert Alert colorred titleFailed.../AlertText—— 支持danger|warning|success用于行内状态/校验文字Text variantdangerRequired field/Text Text variantsuccessConnection verified/Text // ❌ 语义状态文字不要用裸调色板 Text cred.5Required field/TextButton/ActionIcon的variantdanger是软控件色调化的--color-bg-danger-subtle背景带 hover加语义前景色不是实心红填充。warning/success刻意不开放为控件变体只用于Text和Alert。兼容性说明已有的Alert color...调用点不会被改动语义变体是 opt-in 的但新增的提示框应优先用变体动到附近代码时顺手迁移color...的旧写法。确认对话框一律使用useConfirmREQUIRED任何你确定吗步骤都必须用useConfirm/useConfirm禁止手搓带 Cancel/Confirm 按钮的Modal。Provider 已在 packages/app/pages/_app.tsx 全应用挂载调用点零配置const confirm useConfirm(); const handleDelete async () { if ( await confirm( Deleting {name} is bnot reversible/b. /, Delete, { variant: danger }, ) ) { await deleteThing.mutateAsync({ id }); } };要点消息是ReactNode可以携带强调与多句话破坏性操作传{ variant: danger }确认按钮文案默认Confirm它恰好 resolve 一次源码 packages/app/src/useConfirm.tsx#L44-L63 用 Promise 封装onConfirm/onClose各自 resolve 后立即setState(null)所以关闭动画期间双击 Confirm 不会触发两次操作——手搓 Modal 必须自己防重测试 id 是共享且已存在的confirm-modal、confirm-confirm-button、confirm-cancel-button。不要为每个流程发明新的 confirm/cancel 测试 id——E2E 页面对象依赖这些共享 id。已知限制它不向 Modal 传title正文按sizesm opacity{0.7}渲染且 CSS opacity 作用于整个子树嵌套的Text无法单独恢复全对比度。如果某个流程确实需要标题或全对比度正文请扩展useConfirm加可选 prop作用于所有调用点而不是另起一个一次性 Modal。组件测试中要 mock 它——ConfirmProvider依赖next/routerjsdom 里不可用jest.mock(/useConfirm, () ({ useConfirm: jest.fn() }));对参数做断言需要检查文案时把消息ReactNode渲染出来真正的对话框行为交给 E2E 验证。空状态一律使用EmptyState组件REQUIRED所有空/无数据状态都用EmptyState/components/EmptyState禁止临时内联的空状态 div。组件实现在 packages/app/src/components/EmptyState.tsx。Prop类型默认值说明iconReactNode—主题圆形内的图标不传则隐藏titlestring—标题文字headline 风格不加句号descriptionReactNode—标题下方的说明文字childrenReactNode—说明下方的操作按钮、链接variantdefault \| carddefaultcard时包一层带边框的 Paper// ❌ 差 —— 随手内联的空状态 div classNametext-center my-4 fs-8No data/div Text tacenter cdimmedNothing here/Text // ✅ 好 —— 使用 EmptyState 组件 EmptyState icon{IconBell size{32} /} titleNo alerts created yet descriptionCreate alerts from dashboard charts or saved searches. variantcard /标题文案title当作短 headline类似 UI 里的Title不要以句号结尾完整句子放进description使用正常标点需要时以句号结尾。与列表页保持平行措辞如 dashboards 和 saved searches 使用 No matching … yet / No … yet 且不带句号。代码片段用 MantineCode与CopySnippetREQUIRED禁止渲染裸pre、临时Paper 等宽字体、或未加样式的code。统一使用既有组件与 Terraform export、onboarding 及 Storybook 指南保持一致种类组件使用时机行内代码MantineCode散文中的短 tokenSession、列名、flag围栏 / 多行 / 可复制CopySnippet/components/ClickStackOnboarding/CopySnippet安装命令、HCL、JSX 示例等用户可能复制的块内部是Code block Copy 按钮// ✅ 行内 —— Mantine Code Create a source with CodeSession/Code type. // ✅ 围栏 / 可复制 —— CopySnippetCode Copy CopySnippet labelImport block snippet{import { clickstack_dashboard } from clickhouse/clickstack} / // 周围标题已说明内容时label 可省略 CopySnippet snippet{USAGE} / // ❌ 差 —— 裸 pre / 手写 Paper 外壳 pre{snippet}/pre Paper bgvar(--color-bg-code)Text componentpre ffmonospace{snippet}/Text/PaperCopySnippet的实现在 packages/app/src/components/ClickStackOnboarding/CopySnippet.tsx它还支持accessKey属性——设置后片段会被脱敏直到用户显式 Reveal内部借助RevealSnippet。SQL 查询预览渲染高亮的 ClickHouse SQL仍用SQLPreview/ChartSQLPreview——那些是编辑器不是片段外壳。图表卡片用ChartCard而不是手搓边框用ChartCard/components/charts/ChartCard包图表卡片实现见 packages/app/src/components/charts/ChartCard.tsx。它是带边框的表面标题下方有与自定义 dashboard tile 相同的全宽分隔线取代了旧的ChartBox不要手搓带边框的div/Paper包图表。ChartCard只渲染卡片外壳。头部分隔线只有在后代渲染了带title或toolbarItems的ChartContainer时才会出现——ChartCard提供ChartContainerCardHeaderProvider把头部切换到卡片模式——所以要在里面放一个渲染ChartContainer的图表DBTimeChart、DBTableChart、DBHeatmapChart、DBListBarChart等。自带标题的内容如定制表格卡片也应让标题走带title的ChartContainer而不是裸Text从而获得同样的卡片头部含分隔线与顶部内边距而不是紧贴顶边框。tile 级控件全屏、线/柱切换、kebab 菜单属于 dashboard tile刻意不属于ChartCard。Prop类型说明childrenReactNode图表通常是DB*Chart或带标题的ChartContainerstyleCSSProperties尺寸/溢出覆盖——传固定height或用flex: 1; height: 100%填满 flex 行paddingInline被钉住以保持分隔线对齐data-testidstring测试钩子// ✅ 好 —— 共享卡片外壳与 dashboard tile 一致 ChartCard style{{ height: 350 }} DBTimeChart titleRequest Latency config{config} / /ChartCard // ❌ 差 —— 手搓卡片与 dashboard 观感脱节 Box style{{ border: 1px solid var(--color-border), borderRadius: 4 }} DBTimeChart titleRequest Latency config{config} / /Box务必给它一个高度ChartCard是width: 100%并填满父容器所以父容器或style{{ height }}必须定义高度。并排等宽图表如 RED 行在Flex内使用style{{ flex: 1, minWidth: 0, minHeight: 0, height: 100% }}。UI 文案一律 sentence case所有用户可见文本使用句子式大小写sentence case——只大写首词以及专有名词/缩写禁止 Title Case每个实词都大写。适用于用户读到的每个字符串字段标签、按钮、tab/菜单项、标题、节标题、弹窗标题、占位符、tooltip、表格列头、空状态、toast/通知文案。Title Case避免Sentence case使用Data SourceData sourceChart NameChart nameAdd SeriesAdd seriesCount of EventsCount of eventsSave ChangesSave changesDelete DashboardDelete dashboard专有名词、产品名、缩写保持原样——sentence case 只改变它们周围的普通词HyperDX、ClickHouse、ClickStack、OpenTelemetry/OTel、Lucene、SQL、PromQL、MongoDB、Kubernetes、JSON、CSV、URL、ID、API、MCP、CPU、P95。例如Search your events w/ Lucene、Edit SQL、Copy as cURL、View in ClickHouse。只有静态 UI 外壳需要 sentence case永远不要改写动态/用户数据列名、tag 值、日志/trace 内容、用户输入的资源名——按原样渲染。语义设计 token优先于裸 Mantine 颜色UI 用 Mantine 组件搭建但颜色和表面应遵循主题中的语义 CSS 自定义属性--color-*等而不是临时 Mantine 调色板值。token 定义在packages/app/src/theme/themes/**/_tokens.scsshyperdx 与 clickstack 各有 hyperdx/_tokens.scss 和 clickstack/_tokens.scss另有共享的_base-tokens.scss对齐 Click UI 风格系统让 HyperDX 与 ClickStack 视觉一致是通往统一设计系统的路径。Do布局、组件、间距用 Mantine主题化背景、文字色、边框、状态用语义 token如style{{ color: var(--color-text-muted) }}或style{{ border: 1px solid var(--color-border) }}Do not存在语义 token 时依赖裸 Mantine 颜色 props 处理应用外壳与内容——如cgray.5、bgdark.7、任意colorblue.4用于应该与产品其余部分一致的表层参考packages/app/src/theme/semanticColorsGrouped.tstoken 名清单、packages/app/src/theme/themes/下的主题 SCSS以及 Storybook 的SemanticColors等主题故事。packages/app/src/theme/**中的 Mantine 主题覆盖可能把 Mantine 的色阶映射到项目调色板例如 mantineTheme.ts 里Button/ActionIcon/Alert的 vars 都指向--color-*token但这不取代新样式中显式使用var(--color-...)。图表与可视化颜色是另一套更具体的契约——它们有独立的 categorical、semantic、heatmap 调色板通过packages/app/src/utils.ts的辅助函数接线。不要硬编码系列颜色也不要用--color-text-*系列给图表上色。动手渲染任何数据之前先读 agent_docs/data_viz_colors.md。重构直接编辑不留 v2 副本直接编辑文件——不要创建component-v2.tsx之类的副本检查受影响区域的重复代码改动后验证所有调用方与集成点重构是为了提升清晰度或降低复杂度不是为了改而改。文件命名与组织遵循包内约定的清晰、描述性命名永久文件名中避免temp、refactored、improved 等字眼相关组件放进同一个目录。小结落地清单写代码前快速过一遍这份清单新增类型/schema/helper 前先按操作 grep顺序当前文件 →common-utils→ 包内命中就导入或派生而不是重写Button/ActionIcon 只用五种变体、纯图标用ActionIcon确认弹窗走useConfirm、空状态走EmptyState、代码块走Code/CopySnippet、图表卡片走ChartCard新文案用 sentence case 并保留专有名词原样颜色优先--color-*语义 token图表颜色另看data_viz_colors.md。把这些纪律内化后你的改动会在视觉、类型与复用三个维度上都与现有代码库融为一体。【免费下载链接】hyperdxResolve production issues, fast. An open source observability platform unifying session replays, logs, metrics, traces and errors powered by ClickHouse and OpenTelemetry.项目地址: https://gitcode.com/gh_mirrors/hy/hyperdx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考