git-bug WebUI 组件体系完全指南:从状态清单读懂 Component 分层、GraphQL Fragment 与测试策略
git-bug WebUI 组件体系完全指南从状态清单读懂 Component 分层、GraphQL Fragment 与测试策略【免费下载链接】git-bugDistributed, offline-first bug tracker embedded in git项目地址: https://gitcode.com/GitHub_Trending/gi/git-bug导读本文以 git-bug 仓库中 webui/COMPONENTS.mdComponent Status Tracker组件状态跟踪清单为核心脉络系统讲解 git-bug Web 前端React 19 TypeScript 6 Vite 8 Tailwind v4 TanStack Router Apollo Client 4的组件组织方式、每类组件的完成状态、GraphQL Fragment 拆分约定与三层测试策略。读完本文你将掌握如何按ui / shared / bugs / code / content / layout分层组织组件、如何用graphql()标记模板内联定义 Fragment 并通过 codegen 生成类型、如何为组件补齐 Stories、交互测试与快照测试以及withApollo、withRouter等测试装饰器与vitest.setup.ts的工作机理。一、清单概览如何读懂这份 Component Status Trackerwebui/COMPONENTS.md 是 git-bug WebUI 的组件健康档案用统一的状态图例done / partial / todo / n/a逐组件记录四类关键指标Fragments该组件是否已拆分/复用了 GraphQL Fragment如LabelFields、IdentitySummary、BugSummarySplit/Compound组件是否采用了拆分导出compound component 组合式 API如Root / AuthorAvatar / Card / CardHeader / CardBodyStoriesStorybook 故事是否完成Interaction Tests / Snapshot Tests交互测试与快照测试是否就绪。清单按src/components/下的目录划分了六个分区shared/共享业务组件、bugs/Bug 特性组件、code/代码浏览组件、content/内容渲染组件、layout/布局组件、ui/UI 原语最后还包含 Route Pages路由页与 Infrastructure测试基础设施两节。它与 webui/README.md 中描述的组件分层章节互为表里——前者讲设计原则后者以清单形式逐项核验落地状态。二、组件分层的三条军规ui / shared / feature在深入清单前先理解 git-bug WebUI 的组件组织原则见 webui/README.mdui/——无领域知识由 shadcn CLInpx shadcn add管理或手写的通用原语例如 button、input、avatar、badge、popover、separator、skeleton、textarea。交互式下拉菜单基于floating-ui/reacthooks按消费方需求接线配以Listbox.*纯展示型组合组件。shared/——应用级可复用组件了解领域bug 状态、标签、身份但不包含任何数据获取。它们使用组合 APIcompound components并按同目录 GraphQL Fragment进行类型约束。例如 issue-row、label-badge、status-badge、status-tabs、comment-card、pagination、query-input、write-preview、empty-state、section-heading、issue-filters。bugs/、code/——特性组件包含 GraphQL mutations、useAuth及其他副作用负责组合shared/与ui/组件。这一分层直接决定了清单中各列的取值含义ui/原语因为不含领域知识其 Fragment/Split 列固定为 n/a。三、Shared Components组合式 API 与 Fragment 复用的样板区清单中 Shared Components 共列出 11 个组件是观察 git-bug 复合组件 内联 Fragment 模式的最佳窗口。3.1 状态速览ComponentFragmentsSplit/CompoundStoriesInteraction TestsSnapshot TestsNotescomment-carddoneIdentitySummarydoneRoot/AuthorAvatar/Card/CardHeader/CardBodydonen/a纯展示done使用withApollo装饰器empty-staten/an/a简单包装donen/adoneissue-filtersn/a仅回调todo单一导出、复杂donepartial已有排序下拉/键盘测试待补todo存在复杂 floating-ui 交互需要withApolloissue-rowdoneBugSummary展开IdentitySummaryLabelFieldsdoneRoot/StatusIcon/TitleArea/Meta/CommentCountdonen/a纯展示done使用withApollowithRouterlabel-badgedoneLabelFieldsn/adonen/adone使用withApollo装饰器paginationn/adoneRoot/Info/Previous/Nextdonen/a仅链接donequery-inputn/adoneRoot/Icon/Input/Completionsdonedonedone复杂自动补全section-headingn/an/adonen/adonestatus-badgen/an/adonen/adonestatus-tabsn/adoneRoot/Tab/OpenIndicator/ClosedIndicator/Countdonen/a仅链接donewrite-previewn/adoneRoot/Tabs/WriteSlot/PreviewSlotdonedonedone3.2 Fragment 如何定义与消费以LabelFields为例清单中反复出现LabelFields、IdentitySummary、BugSummary三个共享 Fragment。它们遵循 README 规定的模式——在消费组件的文件中用graphql()标记模板内联定义例如 label-badge.tsximport { graphql } from /__generated__/gql; export const LABEL_FIELDS_FRAGMENT graphql( fragment LabelFields on Label { name color { R G B } } );消费方通过FragmentType与useFragment进行类型安全的解包见 comment-card.tsxinterface AuthorAvatarProps { author: FragmentTypetypeof IDENTITY_SUMMARY_FRAGMENT; } export function AuthorAvatar({ author }: AuthorAvatarProps) { const data useFragment(IDENTITY_SUMMARY_FRAGMENT, author); return ( Avatar classNamemt-1 size-8 shrink-0 AvatarImage src{data.avatarUrl ?? undefined} alt{data.displayName} / AvatarFallback{data.displayName.slice(0, 2).toUpperCase()}/AvatarFallback /Avatar ); }IdentitySummary在 comment-card.tsx 中定义为id / humanId / displayName / avatarUrl被 issue-row、timeline 等多处复用BugSummary则在 issue-row.tsx 中通过 Fragment spread 组合了LabelFields与IdentitySummary并在labels、author字段上展开这正是清单中 spreadsIdentitySummaryLabelFields 的源码实锤。修改任何 Fragment 或 Query 后需要重新生成类型化 hookspnpm codegen # graphql-codegen --config codegen.ts扫描 src/**/*.{ts,tsx} 中的 graphql() 调用3.3 组合式 API 的实现范式Write/Preview 与 Pagination清单标注 Split/Compound 为 done 的组件普遍采用根组件提供 Context 子组件消费的结构。以 write-preview.tsx 为例它拆分出Root / Tabs / WriteSlot / PreviewSlot四个导出Root通过WritePreviewContext共享preview状态Tabs中的 Preview 按钮在hasContent为 false 时禁用WriteSlot/PreviewSlot根据 preview 布尔值选择性渲染。它还支持受控模式传入preview与onPreviewChange。这正是 comment-box 底部评论编辑区Write / Preview切换的实现基础。pagination.tsx 则展示了另一模式Root / Info / Previous / Next中Previous/Next通过 TanStack Router 的createLink()包装成路由链接并自带preloadintent悬停预加载与disabled态处理aria-disabledpointer-events-none opacity-50。清单中 status-tabs 的Tab、ButtonLink、BackLink、LabelBadgeLink 均属此类createLink-wrapped 组件。3.4 复杂交互的样板query-input 的双层渲染架构清单对 query-input 的注释是Complex autocomplete其实现 query-input.tsx 本身是一份值得细读的架构文档双层渲染一个aria-hidden的 backdropdiv用彩色span逐 token 渲染语法高亮真实input以透明文字/背景浮于其上光标可见但文字由 backdrop 呈现见文件头部注释与Input组件 L443-L491。可插拔补全提供方CompletionProvider接口定义prefix如label:、highlightClass与getSuggestions(query)支持同步或 Promise 返回SyntaxRule则为不可补全的静态 token如status:open、sort:前缀提供着色规则L54-L68。floating-ui 接线useFloatingoffset(4)flipsize下拉宽度跟随输入框useDismissEsc/外部点击、useListNavigation虚拟列表 loop 导航最终通过Listbox.Content在FloatingPortal中渲染补全面板。键盘语义Enter 在有激活项时先补全否则提交Tab 也可补全补全结果自动在 token 后补空格并把光标定位到新 token 起点。这也是清单中issue-filters标注Complex floating-ui interactions需withApollo的原因——两者的下拉/自动补全交互同源。四、Bug Components数据获取与副作用集中的特性层清单 Bug Components 记录了 4 个组件它们对应 Bug 详情页的核心交互ComponentFragmentsSplit/CompoundStoriesInteraction TestsSnapshot TestsNotestimelinedone5 个子 fragment connectiondone内部子组件使用useSuspenseFragmentdonedonedone4 个 storyFullTimeline、CreateOnly、EmptyMessage、StatusReopencomment-boxn/a仅 mutationsn/adonedonedone使用useAuth需要 Apollo mocktitle-editorn/a仅 mutationn/adonedonedone使用useAuthlabel-editorpartial经 LabelBadge 使用LabelFieldsn/adonedonetodo带本地状态的 Demo story4.1 timeline连接级 Fragment 与按类型分派的 Timelinetimeline 是 Fragment 拆分最彻底的组件。在 timeline.tsx 中五个内联子 fragment 分别对应五种时间线条目类型BugCreateCommentFields创建、BugAddCommentFields评论、LabelChangeFields标签变更、StatusChangeFields状态变更、TitleChangeFields标题变更随后在连接级 fragmentTimelineItems on BugTimelineItemConnection中用内联类型条件... on BugXxxTimelineItem把五者聚合起来L93-L115。渲染层Timeline组件通过item.__typename分发到CreateCommentItem / AddCommentItem / LabelChangeItem / StatusChangeItem / TitleChangeItem五个内部子组件每个子组件用useFragment取出对应字段。评论条目Create/Add支持内联编辑仅当useAuth()返回的当前用户id与评论作者id一致时才显示 Edit 按钮编辑通过BUG_EDIT_COMMENT_MUTATION提交并以BugDetailDocumentrefetch 刷新L215-L303。清单中标注的 4 个 stories——FullTimeline、CreateOnly、EmptyMessage、StatusReopen——覆盖了完整时间线、仅创建事件、空消息与状态重开四种典型场景配合.stories.tsx与快照/交互测试共同保证该复杂组件的行为回归。4.2 comment-box5 个 mutation 的分支策略comment-box.tsx 定义了 5 个 mutationBugAddComment、BugAddCommentAndClose、BugAddCommentAndReopen、BugStatusOpen、BugStatusClose。其状态切换逻辑L106-L126值得作为产品级决策示例Bug 处于 open 时点击Close issue若输入框有内容则走addAndClose评论并关闭否则走statusClose仅关闭Bug 处于 closed 时点击Reopen issue若有内容则addAndReopen否则statusOpen。所有 mutation 共享refetchQueries: [{ query: BugDetailDocument, variables: { ref: ref_, prefix: bugPrefix } }]保证提交后详情页即时刷新。无登录用户useAuth()返回 null时整个评论框直接返回 null——这就是清单Hidden in read-only mode注释对应的只读模式。4.3 title-editor 与 label-editor行内编辑与浮层弹窗title-editor.tsx 是典型的行内标题编辑悬停显示铅笔图标auth-gatedEnter 保存、Escape 取消useEffect保证非编辑态下本地值与 props 同步refetch 后标题变化不丢失。label-editor.tsx 则是侧边栏的齿轮图标 Popover通过useFloatinguseClickuseDismissuseRole(listbox)useListNavigation实现键盘可达的标签多选面板逐项调用BUG_CHANGE_LABELS_MUTATIONadded/Removed二选一传数组切换标签并在弹窗内用LabelBadge展示每个候选标签。它接收validLabels由_issues布局路由预取的仓库合法标签集合——这正是清单中Demo story with local state与 Fragment 标记为 partial 的原因弹窗列表复用了LabelFields但弹窗自身不拥有独立 fragment。五、Code / Content / Layout 组件代码浏览器与骨架5.1 Code Components清单 Code Components 覆盖代码浏览器的六个组件ComponentFragmentsSplit/CompoundStoriesInteraction TestsSnapshot TestsNotesref-selectordone连接上的RefSelectorRefsn/adonedonedone使用withApollo装饰器file-viewerdoneFileViewerBlobn/adonetodo复制、行选择、shift-click 范围doneShiki WASM 从浏览器测试中排除file-treen/a来自 2 个 query 的数据、本地接口n/adonen/a仅链接donefile-diff-viewtodo自有DIFF_QUERY无 fragmenttodoHunk 为内部组件tododonetodocommit-listtodo自有COMMITS_QUERY无 fragmenttodoCommitRow 为内部组件donedonedoneCommitRow使用未类型化的字符串to无路由预加载code-breadcrumbn/an/adonen/a仅链接done从源码结构看file-viewer使用 Shiki 进行语法高亮依赖shiki及shikijs/*系列见 webui/package.json其 WASM 引擎在 Vitest 浏览器模式下不可用因此 vitest.config.ts 中明确exclude: [src/components/code/file-viewer.stories.tsx]——清单中Shiki WASM excluded from browser tests即指此配置。同理file-diff-view与commit-list目前各自持有整条 GraphQL queryDIFF_QUERY/COMMITS_QUERY而尚未拆分 fragment属于清单中标记 todo 的演进空间。5.2 Content 与 Layoutmarkdowncontent/基于react-markdown remark/rehype 插件链remark-gfm、remark-emoji、rehype-slug、rehype-autolink-headings、rehype-raw、rehype-sanitize、rehype-external-links见 package.json负责评论正文与时间线的 Markdown 渲染快照测试 done。headerlayout/拆分导出RepoNav使用useAuth router主题切换的交互测试标记为 todo。shelllayout/纯布局包装器n/a 全覆盖。六、UI Primitives基于 base-ui/react 的原语层清单 UI Primitives 明确说明所有 UI 原语构建于base-ui/react之上见 webui/package.json 的base-ui/react: ^1.3.0因此 Fragment/Split 列对它们恒为 n/a。11 个原语中除back-link外Stories 与 Snapshot Tests 均已完成交互测试整体为 n/a原语不承载业务交互交互逻辑由shared/层通过 floating-ui 接线。值得注意的两点avatar是复合组件Avatar / Image / Fallback / Badge / Grouplistbox也是复合组件Content / ScrollArea / Search / Group / Item / Empty它是 query-input 与 label-editor 下拉面板共用的纯展示底座。button-link是 TanStack Router 的createLink包装即 README 中的ButtonLink带 preload-on-intent。back-link仍为 todo——README 说明它使用router.history.back()优先、退化到类型化 Link 的策略。七、Route Pages路由页的 Fragment 使用与可抽取 UI清单 Route Pages 记录了 7 个路由页面对 fragment 的使用现状。这些页面本身不期望有 stories但标记了可能需要组件抽取的位置RouteFragmentsExtractable UINotes/$repo/_issues/issues/$iddone展开 BugSummary、IdentitySummary、TimelineItemstodoparticipants 列表直接访问 participant 字段/$repo/_issues/issues/indexdone展开 BugSummarytodo补全 providers大文件、复杂查询解析/$repo/_issues/user/$idpartial展开 BugSummary、IdentitySummarytodoprofile header直接访问 identity 字段/$repo/_code/tree/$ref/$todon/a直接访问 tree/commit 字段数据来自 2 个 query/$repo/commit/$hashtodotodocommit header直接访问所有 commit 字段/$repo/_codedone经 preload 使用 RefSelectorRefsn/a布局路由/$repo/_issuesdonepreload 标签与 LabelFieldsn/a布局路由/$repodoneREFS_QUERY RefSelectorRefsn/a布局路由这些Extractable UItodo 项与 Code Components 中的 todo 形成呼应当路由页里出现data.participants、data.commit这类直接字段访问且被多处复用时就应当抽出为带 fragment 的shared/组件。这也解释了清单把 issues 列表页的completion providers标记为可抽取 UI——查询输入的补全提供方目前内嵌于路由页未来可下沉为可复用模块。八、Infrastructure测试基础设施三件套清单最后的 Infrastructure 是理解为什么这些组件能测、怎么测的关键三项基础设施分别解决三个问题withApolloApollo mock 装饰器为 stories 提供 mockApolloClient配置dataMasking: false为 Label/GitBlob/连接类型配置keyFields并注入 mock UserIdentity 数据供useAuth()使用。凡是清单中 Notes 标了 UseswithApollo 的组件comment-card、issue-row、label-badge、issue-filters、ref-selector其 story 都必须套此装饰器才能通过useFragment与 fragment 类型匹配的 mock 数据。withRouterRouter 装饰器提供 catch-all TanStack Router让Link类组件createLink-wrapped 的 Previous/Next、Tab、LabelBadgeLink、ButtonLink在 Storybook 中可直接渲染而不依赖真实路由树。vitest.setup.ts快照 setup把useSuspenseFragmentmock 为 passthrough使 happy-dom 环境能直接渲染使用 Suspense fragment 的组件如 timeline 的内部子组件。测试运行依托 vitest.config.ts 定义的两个 projectstorybookPlaywright Chromium 浏览器冒烟每个 story axe-core a11y 检查 play 交互测试optimizeDeps预打包apollo/client与valibot以避免冷启动时 Vite 二次优化导致测试挂起注释详见于配置文件与snapshothappy-dom vitest.setup.ts。相关命令见 webui/README.mdpnpm test # 运行全部测试 pnpm test -- --projectstorybook # 仅 Storybook浏览器测试 pnpm test -- --projectsnapshot # 仅快照测试 pnpm test -- -u # 更新快照九、把清单变成行动给新组件补齐状态的完整链路综合清单与源码一个shared/新组件要拿到全 done 状态标准路径是定义内联 fragment在组件文件内用graphql()定义如LabelFields运行pnpm codegen生成FragmentType与 typed hooks写入src/__generated__/该目录与routeTree.gen.ts一样标注do not edit。设计组合 API若内部有多个视觉/逻辑单元按Root 子部件拆分导出用 Context 共享状态参考 write-preview、comment-card、pagination。编写 CSF3 storiessatisfies Metatypeof Component获得完整类型推断mock 数据对齐 fragment 类型纯展示组件用withApollo/withRouter装饰器补齐依赖。补测试相邻放置*.test.tsx用composeStories遍历生成快照交互逻辑在 story 的play函数中写userEvent断言模板见 webui/README.md。更新清单回到 webui/COMPONENTS.md 同步该行的 Fragments / Split / Stories / Interaction / Snapshot / Notes 状态。十、总结webui/COMPONENTS.md绝不仅仅是一张谁做完了的进度表它是 git-bug WebUI 组件架构的可执行规范ui → shared → feature的分层约束了领域知识的扩散边界内联graphql()fragment codegen 保证了组件级类型安全与数据形状的局部自治compound component API 让展示层既可复用又保持可测试而withApollo/withRouter/vitest.setup.ts三件套加上 storybook snapshot 双 project 测试配置则让每个组件都有故事、有快照、可交互从口号变成可自动执行的工程事实。对希望参与 git-bug WebUI 开发的读者这份清单既是路线图也是判断组件还差什么的验收清单。【免费下载链接】git-bugDistributed, offline-first bug tracker embedded in git项目地址: https://gitcode.com/GitHub_Trending/gi/git-bug创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →