界面实现映射
本文是 界面规范 的落地层:规范回答”应该怎样”,本文回答”这件事写在哪个文件的哪一段”。
规范与本文配套。改了界面就同时改这两份;
UiContractTest会校验本文提到的令牌与类名 是否真的在代码里。
1. 文件职责
| 文件 | 行数 | 职责 | 不要在这里做的事 |
|---|---|---|---|
src/main/resources/static/index.html |
~1253 | 静态骨架:图标 sprite、外壳三列、9 个 section.page、6 个浮层、首帧主题脚本 |
不要写业务渲染逻辑;不要新增内联样式(现有 20+ 处是历史遗留) |
src/main/resources/static/style.css |
~2049 | 全部令牌(浅/深两套)+ 全部组件规则 + 5 个断点 | 不要出现十六进制颜色字面量(令牌定义处除外) |
src/main/resources/static/app.js |
~5771 | 渲染(字符串拼 HTML 写 innerHTML)、交互、轮询、主题与导航状态 |
不要硬编码颜色(走 themeColor());不要引入框架或构建工具 |
src/main/resources/static/vendor/cytoscape.min.js |
— | 依赖图渲染库,唯一第三方运行时依赖 | 不要改;不要升级到需要构建的版本 |
src/main/resources/static/favicon.svg |
— | 站点图标 | — |
零依赖、零构建是硬约束:改完刷新即生效,没有编译步骤。
2. 令牌映射
2.1 分组 → CSS 变量 → 主要落点
| 规范分组 | CSS 变量 | 主要使用选择器 |
|---|---|---|
| 表面 | --app-bg --surface --surface-2 --surface-3 --surface-hover |
.app(径向渐变中间色)、.card、.stat、.callout、.tr:hover |
| 品牌 | --primary --primary-600 --primary-700 --primary-50 --primary-100 |
.sidebar .nav-item.active、.btn-primary、.pill.brand、.tag-chip、:focus-visible |
| 文字 | --text --text-2 --text-3 --text-body |
body、.card-sub、.breadcrumb、.code |
| 线条 | --border --border-strong --card-border |
.tr + .tr、.icon-btn、.card 描边 |
| 状态 | --success --warning --danger --danger-strong + 各 -bg -line -fg |
.pill.*、.callout.*、.stat-icon.*、.modal-icon.* |
| 风险色阶 | --risk-critical --risk-high --risk-medium --risk-low --risk-info |
.legend-dot.risk-*;app.js 的 riskColor() |
| 依赖图 | --map-edge --map-arrow --map-edge-label --map-label-bg |
只被 app.js 的 themeColor() 读取(cytoscape 无法继承 CSS 变量) |
| 代码 diff | --code-add-fg/bg --code-del-fg/bg |
.code .add / .code .del |
| 几何 | --r-card --r-tile --r-control --r-pill |
.card、.stat、.btn / input / select、.pill / .tag-chip / .tabs |
| 阴影 | --shadow-card --shadow-soft --shadow-hover --shadow-pop --shadow-drawer --app-shadow |
.card、.nav-item.active、.card-row:hover、.modal、.drawer、.app |
| 动效 | --ease --t-fast --t-base |
37 / 43 / 10 处 transition 与 animation |
| 布局 | --sidebar-w --sidebar-w-collapsed |
.app 的两列网格;收起态换 --sidebar-w-collapsed |
| 字体 | --font --mono |
body;.mono、.code、.path-sym、.tr .sym、.ev-*、.rel-version |
| 外壳 | --rail-grad-* --sidenav-bg --nav-hover-bg --nav-count-bg --rail-btn-* 等 |
.sidebar、.nav-item、.sidebar-tool |
| 控件 | --focus-ring --focus-ring-soft --select-arrow --input-focus-bg --ring-track 等 |
表单控件、.search、.ring |
| 浮层 | --overlay-bg --toast-* --scrollbar-* |
.overlay、.toast、::-webkit-scrollbar |
2.2 深色块未覆盖的令牌
:root[data-theme="dark"] 定义了 98 个变量,浅色块 109 个。未覆盖的 11 个全部不是颜色:
| 令牌 | 类别 | 为什么不需要覆盖 |
|---|---|---|
--r-card --r-tile --r-control --r-pill |
几何 | 圆角与主题无关 |
--ease --t-fast --t-base |
动效 | 手感与主题无关 |
--sidebar-w --sidebar-w-collapsed |
布局 | 尺寸与主题无关 |
--font --mono |
字体 | 字体栈与主题无关 |
结论:深色块零颜色遗漏,且没有引入浅色不存在的变量(dark \ light = ∅)。
这正是规范 2.1 那条纪律的直接验证:深色只换颜色,不动布局。
2.3 声明了但没有 CSS 引用的令牌
| 令牌 | 状态 |
|---|---|
--app-bg-tint --nav-bg --text-inverse --neutral |
真死引用(浅深两套都定义,全仓零引用) |
--map-edge --map-arrow --map-edge-label --map-label-bg |
canvas 专用:CSS 不引用,由 themeColor() 读取 |
清理死令牌时注意别误删后四个——它们在 app.js 里被按名字读取,grep CSS 找不到使用者。
3. 组件类名契约
每条都是「组件 → 根类名 → 必需子元素 → 状态类 → 生成它的代码」。新增组件必须五列齐全。
引用代码位置时优先写函数名,不写行号。
app.js是单文件 6000 行,行号在每次功能提交后 都会漂移;函数名稳定,且能在编辑器里直接跳转。本文只在index.html的骨架节点上保留行号, 因为那些位置几乎不变。
3.1 外壳与导航
| 组件 | 根类名 | 必需子元素 | 状态类 | 生成位置 |
|---|---|---|---|---|
| 应用外壳 | .app |
.sidebar + .workspace |
.nav-collapsed |
index.html:76 |
| 侧栏 | .sidebar |
.sidebar-top .sidebar-context .search .nav-scroll .sidebar-foot |
.app.nav-collapsed 换图标轨道 |
index.html:82 |
| 侧栏品牌 | .sidebar-top |
.sidebar-logo .sidebar-brand .sidebar-toggle |
收起态只剩 Logo 与展开按钮 | index.html:83 |
| 导航项 | .nav-item |
svg + .side-label + .count |
.active、.count.alert |
index.html:117 |
| 代码源条目 | .nav-item(data-source-id) |
.row-icon + 名称 + .count |
.active;失败行 .row-icon.danger |
renderNavSources() |
| 侧栏工具 | .sidebar-tools |
.sidebar-tool .sidebar-avatar |
收起态纵向排列 | index.html:140 |
| 二级导航 | .nav-sub |
.nav-item |
— | renderNavSources() |
| 标签分组头 | .nav-tag-head |
.nav-tag-name .nav-tag-count |
.open |
renderNavSources() |
| 图标按钮 | .icon-btn |
svg |
:disabled |
index.html:112 |
| 搜索框 | .search |
svg + input + kbd |
:focus-within |
index.html:120 |
3.2 页头与容器
| 组件 | 根类名 | 必需子元素 | 状态类 | 生成位置 |
|---|---|---|---|---|
| 页头 | .page-head |
.breadcrumb + .title-row + .page-sub + .head-right |
— | 每个 section.page 内 |
| 标题行 | .title-row |
.title-badge + .page-title + .pill |
— | 同上 |
| 页头指标 | .head-metric |
.label + .value(small) |
— | 同上 |
| 页头动作 | .head-actions |
.btn |
— | 同上 |
| 卡片 | .card |
.card-head + 内容 |
.card.flush |
各页与各 render* |
| 卡片头 | .card-head |
.card-title .card-sub .card-head-actions |
— | 同上 |
| 统计块 | .stats / .stat |
.stat-icon + .stat-body(.value .label) |
.stat-icon.danger/.success/.warning |
statTile() |
| 栅格 | .grid + .grid-2 / .grid-2-even / .grid-3 / .stack |
— | — | index.html |
3.3 展示元素
| 组件 | 根类名 | 必需子元素 | 状态类 | 生成位置 |
|---|---|---|---|---|
| 徽章 | .pill |
.dot(可选)+ 文本 |
.success .warning .danger .neutral .brand .outline |
pill(text, tone, withDot) |
| chip | .chip / .chips |
.k(键)+ 值 |
— | chip(text, key) |
| 标签输入 | .tag-input |
.tag-chips + input |
:focus-within |
renderTagInput() |
| 标签芯片 | .tag-chip |
文本 + .tag-chip-x |
— | 同上 |
| 标签候选 | .tag-cands |
.tag-cands-label .tag-cand |
— | renderTagInput() |
| 行内标签 | .tag-pill |
— | .active |
renderSourcesPage() |
| 提示块 | .callout |
svg + 文本 |
.warning .danger .brand |
各 render* |
| 空态 | .empty |
strong + 说明 |
— | emptyBlock(title, sub) |
| 代码块 | .code |
.add / .del / .ctx 行 |
— | renderAnalysis() |
| 源码片段 | .code-snippet |
.code-snippet-caption(片段上方的范围说明)+ .code-line(.code-n 行号 + .code-t 文本,引用行 .hl) |
— | codeSnippetHtml() 异步注入 .code-snippet-host |
| 证据 | .evidence / .ev-row |
.ev-type .ev-main .ev-meta |
— | renderReviewEvidence() |
| 路径 | .path / .path-step |
.path-rail(.path-dot .path-line)+ .path-body(.path-sym .path-rel) |
.path-dot.start |
renderReviewEvidence() |
| 时间线 | .timeline / .tl-item |
.tl-rail(.tl-dot .tl-line)+ .tl-body(.tl-title .tl-meta) |
.tl-dot.danger/.success |
renderOverview() |
| 流程卡 | .flow / .flow-card |
.flow-label .flow-value .flow-note |
.accent .ok、.flow-value.danger/.success |
renderImpact() |
| KV 列表 | .kv-list / .kv-row |
.k + .v |
.v.mono .v.kv-wrap |
设置页、导入结果 |
3.4 数据展示
| 组件 | 根类名 | 必需子元素 | 状态类 | 生成位置 |
|---|---|---|---|---|
| 表格 | .table / .tr |
.cell |
.tr.head、.cell.actions .cell.optional .cell.wrap |
各列表渲染 |
| 表格模板 | .tr-sources .tr-history .tr-changes .tr-commits .tr-ledger |
— | — | 见规范 7.10 |
| 行卡片 | .card-list / .card-row |
.row-icon .row-main(.row-title .row-desc .row-meta).row-right |
.selected |
renderReviewList() |
| 连续列表 | .list / .list-row |
同上 | .selected |
各渲染函数 |
| 对比程序汇总 | .peer-strip |
.pill(程序 · 契约数 · 影响处数;未比较时标原因) |
— | renderOvPeerStrip() / renderReviewPeerStrip() |
| 影响截断说明 | #rv-impacts-note |
.callout.warning + svg + 文本(文案来自 report.impactsNote) |
.hidden(未截断时整块隐藏) |
renderReviewImpactsNote() |
| 分页 | .pager |
.pager-info .pager-pages .pager-gap |
.hidden(单页时) |
renderReviewPager() |
| 进度条 | .progress |
> span |
.thin、span.success/.danger/.warning、.indeterminate |
renderAnalysisProgress() |
| 环形进度 | .ring |
.ring-track .ring-value .ring-label |
— | ringSvg(percent) |
| 步骤条 | .stepper / .step |
.step-rail(.step-dot .step-line)+ 标题 + 元信息 |
完成 / 进行中 / 失败 | renderAnalysisProgress() |
| 依赖图 | .map |
cytoscape 画布 | — | renderMap() |
| 图例 | .map-legend / .legend-item |
.legend-dot.risk-* |
— | renderMap() |
3.5 浮层与反馈
| 组件 | 根类名 | 必需子元素 | 状态类 | 生成位置 |
|---|---|---|---|---|
| 遮罩 | .overlay |
.modal 或 .drawer |
.hidden、.drawer-overlay |
index.html 底部 6 个 |
| 弹窗 | .modal |
.modal-head .modal-body .modal-foot |
.modal.picker |
同上 |
| 弹窗图标 | .modal-icon |
svg |
.brand .danger |
同上 |
| 抽屉 | .drawer |
.drawer-head .drawer-body |
— | openImpactDrawer() / openChangeDrawer() |
| 轻提示 | .toast-root / .toast |
svg + 文本 |
.success .error |
toast(message, tone) |
| 选择器 | .picker-list / .picker-row |
.nm .tags .empty-hint |
— | renderPickerList() |
| 提交选择 | .commit-list / .commit-row |
.commit-sha .commit-subject .commit-meta + input[type="checkbox"] |
.selectable .selected |
loadRangePreview() / renderRangePreview() |
| 提交选择动作 | .commit-actions |
#mr-range-all #mr-range-none(.btn.btn-sm) |
.hidden(列表为空时) |
index.html 的 #mr-range-actions + bindEvents() 的全选 / 清空监听 |
| 对比程序 | .peer-picker / .peer-row |
input + .nm + .tags |
.peer-empty |
renderPeers() |
| 更新横幅 | .upd-banner |
.upd-banner-icon .upd-banner-text .upd-banner-actions |
.hidden .failed |
renderUpdateNotice() |
| AI 审核卡 | #an-ai-card |
#an-ai-body .ai-text .ai-foot .ai-actions |
.hidden(未配置时整卡隐藏) |
renderAnalysisAi(report) |
| AI 待验证标注 | .ai-verified-note |
svg + 文本 |
— | renderAnalysisAi() |
| AI 逐条意见 | .ai-comments / .ai-comment |
.ai-comments-head .ai-comments-title;每条 .ai-comment-head(.ai-comment-sev + .ai-comment-cat + .ai-comment-title + .ai-comment-loc)、.ai-comment-body .ai-comment-suggestion .ai-comment-code .ai-comment-meta |
.sev-blocker / .sev-critical / .sev-major / .sev-minor / .sev-info(左边界与严重级标签同色);无评论时 .ai-comment-empty |
aiCommentsHtml() / aiCommentHtml()(由 renderAnalysisAi() 调用) |
| AI 评论统计 | .ai-comment-stats / .ai-comment-note |
一行计数 + 小字 notes | — | aiReviewStatsHtml() |
| AI 设置卡 | #card-ai |
.ai-status-row .ai-presets #ai-test-result #ai-comments #ai-max-comments #ai-reflection |
— | fillAiForm() / renderAiStatus() |
| GitLab 私有仓库卡 | #card-gitlab |
.ai-status-row(复用 7.23 配方)+ #gl-status #gl-status-note #gl-token #gl-clear #gl-settings-file |
— | fillGitLabForm() / renderGitLabStatus() |
| 配置导入导出卡 | #card-config |
.checkbox #cfg-secrets + .kv-list #cfg-transfer-hint + #cfg-result(.import-summary / .import-counts / .import-errors)+ #cfg-export #cfg-import #cfg-file |
.hidden(#cfg-file 常隐);结果区 .callout.danger |
renderConfigHint() / renderConfigResult() |
4. 交互行为 → 代码位置
| 行为 | 实现 | 位置 | ||||
|---|---|---|---|---|---|---|
| 主题三态 + 跟随系统 | initTheme() / applyTheme(pref, opts) / toggleTheme() |
initTheme / applyTheme / toggleTheme |
||||
| 首帧防闪 | <head> 内联脚本读 ca.theme 并写 data-theme |
index.html <head> 内联脚本 |
||||
| 画布取色 | themeColor(name, fallback) 读 getComputedStyle |
themeColor() |
||||
| 导航收起 | initNav() / applyNavCollapsed() / setNavCollapsed() / toggleNav() |
initNav / applyNavCollapsed / setNavCollapsed / toggleNav |
||||
Ctrl/⌘ + B |
keydown 监听,输入控件内让位 |
initNav() 内的 keydown 监听 |
||||
Ctrl/⌘ + K |
聚焦 #global-search |
全局 keydown 监听 | ||||
| 页面切换 | go(page) + state.page + ca.page |
go(page) |
||||
| 标签 chip 输入 | Enter / 逗号 / 退格 / 粘贴拆分 | renderTagInput() 651 |
||||
| 标签分组折叠 | toggleNavTagGroup(name) |
toggleNavTagGroup() |
||||
| 文件夹选择器 | openDirPicker() → GET /api/fs/dirs |
openDirPicker() |
||||
| 复核决策 | setDecision() + ca.review.v1 |
setDecision() |
||||
| 证据源码片段 | 渲染后收集 .code-snippet-host,逐个请求 GET /api/analyses/{id}/code(固定 mode=block,取引用行所在代码块;整块超过 160 行时只取引用行附近),按 分析 |
mode | peer | 路径 | 行 缓存 | hydrateCodeSnippets() / loadCodeSnippet() / codeSnippetHtml() |
| 复核页回到契约命中 | 证据面板选中一条发现后显示证据链;「契约命中」按钮清空 state.selectedImpactId 并恢复默认态 |
data-clear-impact / renderReviewEvidence() |
||||
| 影响清单截断说明 | report.impactsNote 非空时在发现列表上方显示 .callout.warning,为空则整块 .hidden(换报告时同步清掉上一条);页头「N 项影响」读 report.impactsTotal(缺省回退 counts.total),列表条数仍用 counts.total |
renderReview() / renderReviewImpactsNote() |
||||
| 分页 | renderReviewPager(),REVIEW_PAGE_SIZE = 50 |
REVIEW_PAGE_SIZE / renderReviewPager() |
||||
| 新建分析的模式字段 | applyRunMode() 同步标签、占位符与显隐(范围模式隐藏基线输入、给 #mr-scope-row 加 .single、清空日期与勾选) |
applyRunMode() |
||||
| 提交选择(分支 / 日期 / 勾选) | scheduleRangePreview() 防抖 300ms → loadRangePreview();勾选只写 state.rangePicked(不重渲染整表);筛选后 pruneRangePicked() 收走看不见的勾选 |
loadRangePreview() / renderRangePreview() / renderRangePickHint() / pruneRangePicked() / clearRangeList() |
||||
| 进度轮询 | 活跃时 2s;发现探测 15s | pollUpdate 一类的活跃轮询 |
||||
| 图重建 | renderMapsFor(page) → destroy + 重建 |
renderMapsFor() / renderMap() |
||||
| 弹窗开关 | openModal() / closeModal();遮罩点击 + Escape |
openModal() / closeModal() + 全局 keydown / 遮罩点击 |
||||
| 密度偏好 | body.compact + ca.density |
启动时的密度恢复 | ||||
| 导入导出 | GET /api/sources/export / POST /api/sources/import |
导出按钮的 window.location 跳转 |
||||
| 配置导入导出 | GET /api/settings/export(?secrets=1 才含密钥)/ POST /api/settings/import;导出走 window.location,导入在浏览器解析 JSON 后 POST,8 MB 上限 |
exportConfig() / onConfigFileChosen() / importConfigPayload();事件走 data-config-export / data-config-import + #cfg-file 的 change |
||||
| 配置导入后的刷新 | 清 state.ai.loaded / formFilled、state.gitlab.loaded / formFilled、state.health → renderSettings() → refreshAll(false) → loadTags() → renderNavSources() → renderSourcesPage() |
importConfigPayload() |
||||
| AI 配置表单 | fillAiForm() 填静态骨架(不重渲染,否则会擦掉用户正在输入的内容) |
renderSettings() 每轮刷新时调用 |
||||
| GitLab 令牌表单 | ensureGitLabLoaded() 只拉一次;fillGitLabForm() 只在首载 / 保存 / 清除后回填,轮询只更新状态行;空输入 = 保持已保存令牌 |
renderSettings() → renderGitLabSettings() |
||||
| GitLab 令牌保存 / 清除 | PUT /api/gitlab,body {token} 或 {clearToken: true};清除前确认 |
saveGitLabSettings() / clearGitLabToken() |
||||
| AI 生成 | POST /api/analyses/{id}/ai-review → 写回报告 |
分析页卡片按钮 | ||||
| AI 逐条意见渲染 | aiCommentsHtml(section):有 comments 数组才渲染;空数组显示「本次变更未发现需要评论的问题」;没有该字段的老报告不新增区块 |
renderAnalysisAi() 内调用 |
||||
| AI 意见开关 | comments / maxComments / reflection 三个字段随 PUT /api/ai 一起保存并回填 |
aiFormBody() / fillAiForm() |
4.1 localStorage 键清单
| 键 | 值 | 用途 |
|---|---|---|
ca.theme |
system / light / dark |
主题三态 |
ca.nav |
collapsed / expanded |
菜单折叠 |
ca.page |
页面 id | 上次打开的页面 |
ca.density |
comfortable / compact |
信息密度 |
ca.review.v1 |
影响 id → 决策 | 复核结果(仅本地,不回写服务端) |
ca.navtags |
折叠的标签分组名 | 侧栏标签分组 |
ca.update.skip.v1 |
版本号 | 忽略的版本 |
新增键必须:带 ca. 前缀、在本文登记、读取处包 try/catch(隐私模式下 localStorage 会抛)。
5. 数据契约
前端只通过 api(path, options) 访问后端:统一前缀 /api、
非 2xx 抛 Error(优先取响应体 error 字段)、204 返回 null。
5.1 端点
| 端点 | 驱动 |
|---|---|
GET /api/health |
设置页服务状态 |
GET/POST/PUT/DELETE /api/sources |
代码源列表与增删改 |
GET /api/sources/{id}/refs |
分支 / 标签下拉 |
GET /api/sources/{id}/commits |
提交选择列表。参数全部可选:ref(分支)、base + target(限定区间;界面不再使用)、limit(默认 30、上限 500,界面用 100)、since / until(yyyy-MM-dd,含两端,按提交时间)。响应 commits count ref base target since until more note:more=true 表示还有更早的匹配提交;坏日期与「起始晚于结束」仍是 HTTP 200 + note,不进错误态 |
GET /api/sources/export / POST /api/sources/import |
导入导出卡片 |
GET /api/settings/export / POST /api/settings/import |
配置导入导出卡片(设置项 + 全部代码源;?secrets=1 才导出密钥) |
GET /api/tags |
侧栏分组与标签候选 |
GET /api/analyses / /api/analyses/{id} |
历史、报告、进度 |
GET /api/analyses/{id}/code |
证据内联源码:path、line、mode(block = 引用行所在代码块,界面固定传;window = 旧行为,默认)、context(只对 window 生效,默认 3,上限 10)、peerId(省略 = 本程序);返回 lines / startLine / endLine / totalLines / block / blockLabel / blockStart / blockEnd / caption / revision / note;startLine / endLine 是实际返回的行范围(被 160 行上限截断时是展示范围,不是块的起止);只允许读取报告引用过的文件 |
GET /api/peers |
对比程序多选 |
GET /api/fs/dirs |
文件夹选择器 |
GET /api/update / progress / history / settings |
版本卡、进度条、台账、开关 |
POST /api/update/check / download / apply / restart / rollback |
更新动作 |
GET / PUT /api/ai |
AI 审核配置(Key 只回显掩码) |
GET / PUT /api/gitlab |
GitLab 私有仓库卡(令牌只回显掩码;PUT 空令牌 = 保持,clearToken = 清除) |
POST /api/analyses/{id}/ai-review |
历史报告补生成 / 重新生成 AI 审核 |
5.2 报告字段 → 界面区块
| 字段 | 区块 |
|---|---|
report.summary |
总览统计块、页头指标 |
report.impacts[] |
影响审核列表、复核队列 |
report.peers[] |
总览合并卡的程序 pill;影响审核发现列表顶部的程序汇总;未选中发现时右侧的契约命中与对方使用点;peerCommit 供证据源码按分析版本读取。peers[].impacts 不再随 JSON 下发,前端只用 report.impacts |
report.impactsTotal / impactsShown / impactsTruncated / impactsNote |
影响审核页头「N 项影响」(全量)与发现列表上方的截断说明;impactsNote 为空时整块隐藏。summary 的风险与影响计数仍是全量 |
report.changes[] |
变更清单 |
report.moduleImpacts[] |
模块影响明细表 |
report.moduleGraph |
依赖图 |
report.commits / commitCount / rangeNote |
提交范围卡片 |
report.diffs |
代码差异折叠块 |
report.blindSpots |
分析边界与盲区 |
report.notes / peersNote |
结论说明 |
report.mode / baseLabel / headLabel / generatedAt |
页头副标题与模式徽章 |
report.ai |
AI 审核卡片(与证据分区存放,失败不影响其他区块) |
report.ai.comments[] |
逐条评审意见列表(严重级配色、文件:行、正文、建议、代码引用、证据、定位方式) |
report.ai.reviewStats |
评论统计一行(审阅 / 跳过 / 保留 / 未定位丢弃 / 过滤 / 反思删除 / 合并 / 重定位 / tokens)。budgetSeconds / budgetExpired 是报告与 GET /api/analyses/{id}/report 里的诊断字段,解释「为什么意见不全」(预算耗尽后剩余调用被跳过);界面展示的是 reviewStats.notes 与 reviewError,不直接渲染这两个键 |
report.ai.reviewError |
逐条评审部分失败的小字提示(摘要照常显示) |
5.3 影响条目字段
id riskLevel riskScore confidence confidenceScore depth impactType changeType
changedModule changedFile changedLine changedSymbol targetSignature peerModule
peerSourceName targetFile targetLine targetSymbol path[] evidence[] reason
riskFactors suggestedTests[] verificationStatus
5.3.1 AI 评论字段
id severity category title detail suggestion file startLine endLine
side anchor existingCode evidence[] confidence。
行号由后端三层定位得出(anchor = HUNK / FILE / RELOCATED),前端只展示、不改写。
新增字段时:后端加字段 → app.js 渲染 → 规范补文案 → 本文补这一行。四步缺一不可。
5.4 进度字段
progressPercent progressStage status;更新流水线另有 phase(idle / downloading /
verifying / applying / restarting / succeeded / failed)、bytes / totalBytes / percent、
automatic、message / error。percent = -1 表示总量未知,走 .indeterminate。
5.5 GitLab 设置字段
tokenSet tokenMasked configuredBy settingsFile。configuredBy 为 file(设置页快照)
或 application.yml;tokenMasked 在空令牌时为空串、令牌长度 ≤ 8 时为 ••••。
任何接口都不返回明文令牌,前端也只把掩码写进 placeholder。
6. 样式加载顺序与首帧
index.html <head>
1. <meta charset> / <meta viewport>
2. <title>
3. <link rel="icon">
4. preconnect fonts.googleapis / fonts.gstatic
5. <link> Inter 400;500;550;600;700
6. <script> 内联:读 ca.theme → 解析 system → 写 data-theme / data-theme-pref
7. <link rel="stylesheet" href="/style.css">
第 6 步必须在第 7 步之前,否则深色会话会先画一帧白底再变深。这是规范 2.2 的硬约束,
UiContractTest 会校验这个顺序。
字体是外链,因此:
- 必须保留系统字体兜底(
--font栈里的-apple-system/PingFang SC/Microsoft YaHei); - 离线环境下界面仍然可用,只是字形回退;
font-weight: 650这类未加载的档位由浏览器合成,应避免(见规范第 13 章 #13)。
7. 变更检查表
改界面的最小闭环是四处同改,加一次验证:
index.html:骨架或静态节点style.css:令牌(如需新增)与组件规则app.js:渲染与交互docs/wiki/界面规范.md:规范本身(新增组件要补第 7 章)docs/wiki/界面实现映射.md:本文(新增组件补第 3 章,新增行为补第 4 章)mvn test(含UiContractTest)
改完自检:按规范第 12 章的验收清单逐条过一遍。如果改动修掉了规范第 13 章里的某条偏差, 记得在同一次提交里删掉那一行。
8. 相关文档
| 文档 | 内容 |
|---|---|
| 界面规范 | 视觉与交互的唯一权威规范 |
| 架构设计 | 后端分层与数据流 |
| 核心概念 | 契约、传播、风险与置信度 |
| 设计取舍 | 能力边界 |