paseo-req-kit 1.14.0 → 1.15.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/docs/guide//347/273/264/346/212/244/350/200/205/346/214/207/345/215/227.md +2 -0
  3. package/engine/src/review-assets/app.css +153 -234
  4. package/engine/src/review-assets/app.html +44 -32
  5. package/engine/src/review-assets/app.js +11 -7
  6. package/engine/src/review-assets/check.css +11 -56
  7. package/engine/src/review-assets/check.html +48 -22
  8. package/engine/src/review-assets/check.js +169 -86
  9. package/engine/src/review-assets/code.css +15 -92
  10. package/engine/src/review-assets/code.html +41 -26
  11. package/engine/src/review-assets/code.js +196 -101
  12. package/engine/src/review-assets/rk-icons.js +98 -0
  13. package/engine/src/review-assets/rk-tokens.css +252 -0
  14. package/engine/src/review-assets/rk.css +252 -0
  15. package/engine/src/review.ts +6 -4
  16. package/package.json +1 -1
  17. package/paseo-plugin/package.json +1 -1
  18. package/site-dist/404.html +2 -2
  19. package/site-dist/assets/{app.VQ-QxRPH.js → app.D5s_yGHZ.js} +1 -1
  20. package/site-dist/assets/changelog.md.RJaBnmnw.js +1 -0
  21. package/site-dist/assets/chunks/@localSearchIndexroot.Dric-MbI.js +1 -0
  22. package/site-dist/assets/chunks/{VPLocalSearchBox.CT4Vlj76.js → VPLocalSearchBox.B4CyDsTr.js} +1 -1
  23. package/site-dist/assets/chunks/{theme.B_UmHHou.js → theme.CCocenmB.js} +2 -2
  24. package/site-dist/assets/{guide_getting-started.md.CZM53rO_.js → guide_getting-started.md.wFOkpMSx.js} +1 -1
  25. package/site-dist/assets/guide_maintainer.md.mFZprBj1.js +1 -0
  26. package/site-dist/assets/{guide_maintainer.md.C1v7EpSY.lean.js → guide_maintainer.md.mFZprBj1.lean.js} +1 -1
  27. package/site-dist/changelog.html +5 -5
  28. package/site-dist/guide/getting-started.html +5 -5
  29. package/site-dist/guide/how-it-works.html +4 -4
  30. package/site-dist/guide/index.html +4 -4
  31. package/site-dist/guide/install.html +4 -4
  32. package/site-dist/guide/maintainer.html +5 -5
  33. package/site-dist/guide/phases.html +4 -4
  34. package/site-dist/guide/project.html +4 -4
  35. package/site-dist/guide/user.html +4 -4
  36. package/site-dist/hashmap.json +1 -1
  37. package/site-dist/index.html +4 -4
  38. package/site-dist/reference/adr/0001-step-agents-under-origin-chat.html +4 -4
  39. package/site-dist/reference/adr/0002-no-directory-switch-in-chat.html +4 -4
  40. package/site-dist/reference/adr/0003-script-engine-not-hub.html +4 -4
  41. package/site-dist/reference/adr/0004-result-files.html +4 -4
  42. package/site-dist/reference/adr/0005-no-default-branch-local-commits-only.html +4 -4
  43. package/site-dist/reference/adr/0006-skill-install-targets.html +4 -4
  44. package/site-dist/reference/adr/0007-own-interview-skill.html +4 -4
  45. package/site-dist/reference/adr/0008-entries-do-not-call-entries.html +4 -4
  46. package/site-dist/reference/adr/0009-versioned-install-layout.html +4 -4
  47. package/site-dist/reference/adr/0010-generated-feature-overview.html +4 -4
  48. package/site-dist/reference/adr/0011-resume-after-completion.html +4 -4
  49. package/site-dist/reference/adr/0012-review-panel.html +4 -4
  50. package/site-dist/reference/adr/0013-no-release-tooling.html +4 -4
  51. package/site-dist/reference/adr/0014-notifier-slot.html +4 -4
  52. package/site-dist/reference/adr/0015-incremental-continue.html +4 -4
  53. package/site-dist/reference/adr/0016-upgrade-from-tags.html +4 -4
  54. package/site-dist/reference/adr/0017-use-paseo-capabilities.html +4 -4
  55. package/site-dist/reference/adr/0018-own-worktree-option.html +4 -4
  56. package/site-dist/reference/adr/0019-parallel-orchestration.html +4 -4
  57. package/site-dist/reference/adr/0020-scheduling-through-the-chat.html +4 -4
  58. package/site-dist/reference/adr/0021-npm-version-manager.html +4 -4
  59. package/site-dist/reference/adr/0022-develop-and-verify-phases.html +4 -4
  60. package/site-dist/reference/adr/0023-types-gate-slices.html +4 -4
  61. package/site-dist/reference/adr/0024-confirm-early-review-axes.html +4 -4
  62. package/site-dist/reference/adr/0025-joint-requirements.html +4 -4
  63. package/site-dist/reference/adr/0026-machine-records-and-api.html +4 -4
  64. package/site-dist/reference/adr/0027-paseo-plugin.html +4 -4
  65. package/site-dist/reference/adr/0028-external-integrations.html +4 -4
  66. package/site-dist/reference/adr/0029-requirements-from-cards.html +4 -4
  67. package/site-dist/reference/adr/0030-card-intake-process.html +4 -4
  68. package/site-dist/reference/adr/0031-fewer-interventions.html +4 -4
  69. package/site-dist/reference/adr/0032-npm-only-install.html +4 -4
  70. package/site-dist/reference/adr/0033-prototype-review-and-ui-check.html +4 -4
  71. package/site-dist/reference/adr/index.html +4 -4
  72. package/site-dist/reference/commands.html +4 -4
  73. package/site-dist/reference/config.html +4 -4
  74. package/site-dist/reference/contracts/cli-json.html +4 -4
  75. package/site-dist/reference/contracts/feature-files.html +4 -4
  76. package/site-dist/reference/contracts/index.html +4 -4
  77. package/site-dist/reference/contracts/notifier-event.html +4 -4
  78. package/site-dist/reference/contracts/result-file.html +4 -4
  79. package/site-dist/reference/contracts/spec.html +4 -4
  80. package/site-dist/reference/contracts/state.html +4 -4
  81. package/site-dist/reference/contracts/usage-record.html +4 -4
  82. package/site-dist/reference/contracts/verify.html +4 -4
  83. package/site-dist/reference/scope.html +4 -4
  84. package/site-dist/assets/changelog.md.CIKYJXpF.js +0 -1
  85. package/site-dist/assets/chunks/@localSearchIndexroot.h7oQE_np.js +0 -1
  86. package/site-dist/assets/guide_maintainer.md.C1v7EpSY.js +0 -1
  87. /package/site-dist/assets/{changelog.md.CIKYJXpF.lean.js → changelog.md.RJaBnmnw.lean.js} +0 -0
  88. /package/site-dist/assets/{guide_getting-started.md.CZM53rO_.lean.js → guide_getting-started.md.wFOkpMSx.lean.js} +0 -0
@@ -1,16 +1,19 @@
1
- // The acceptance checklist page (1.14). Plain script, no framework: the server serves it next to
2
- // its JSON API (api/info, api/draft, api/try, api/submit).
1
+ // The acceptance checklist page (1.14; design system 1.15). Plain script, no framework: the server
2
+ // serves it next to its JSON API (api/info, api/draft, api/try, api/submit).
3
3
  (function () {
4
4
  "use strict";
5
5
  var state = { info: null, answers: {}, reviewer: "", saveTimer: null, busy: false };
6
6
  var $ = function (id) {
7
7
  return document.getElementById(id);
8
8
  };
9
+ var icon = function (name, cls) {
10
+ return window.RkIcon ? window.RkIcon(name, cls) : null;
11
+ };
9
12
 
10
13
  function el(tag, attrs, children) {
11
14
  var node = document.createElement(tag);
12
15
  Object.keys(attrs || {}).forEach(function (k) {
13
- if (attrs[k] === null || attrs[k] === undefined) return;
16
+ if (attrs[k] === null || attrs[k] === undefined || attrs[k] === false) return;
14
17
  if (k === "text") node.textContent = attrs[k];
15
18
  else if (k === "className") node.className = attrs[k];
16
19
  else if (k.indexOf("on") === 0) node.addEventListener(k.slice(2), attrs[k]);
@@ -22,6 +25,10 @@
22
25
  return node;
23
26
  }
24
27
 
28
+ function badge(kind, iconName, text) {
29
+ return el("span", { className: "rk-badge rk-badge--" + kind }, [iconName ? icon(iconName) : null, text]);
30
+ }
31
+
25
32
  function toast(text) {
26
33
  var t = $("toast");
27
34
  t.textContent = text;
@@ -41,7 +48,14 @@
41
48
  });
42
49
  }
43
50
 
44
- var PIPELINE = { pass: ["流水线:通过", "pass"], fail: ["流水线:没通过", "fail"], unverified: ["流水线:没验证", "unverified"], unknown: ["流水线:没有记录", ""] };
51
+ // What the pipeline found for each criterion: badge kind, icon, words.
52
+ var PIPELINE = { pass: ["success", "circle-check", "流水线已验证"], fail: ["failed", "circle-x", "流水线没通过"], unverified: ["muted", "circle-dashed", "流水线没验证"], unknown: ["muted", "circle-dashed", "流水线没有记录"] };
53
+ var DETAIL = /^(前置条件|操作|预期结果|预期)[::]\s*(.*)$/;
54
+
55
+ function hhmm() {
56
+ var d = new Date();
57
+ return (d.getHours() < 10 ? "0" : "") + d.getHours() + ":" + (d.getMinutes() < 10 ? "0" : "") + d.getMinutes();
58
+ }
45
59
 
46
60
  function answersList() {
47
61
  return Object.keys(state.answers).map(function (id) {
@@ -53,7 +67,13 @@
53
67
  if (state.info.readOnly) return;
54
68
  clearTimeout(state.saveTimer);
55
69
  state.saveTimer = setTimeout(function () {
56
- api("api/draft", "PUT", { answers: answersList(), reviewer: state.reviewer }).catch(function () {});
70
+ api("api/draft", "PUT", { answers: answersList(), reviewer: state.reviewer })
71
+ .then(function () {
72
+ $("saved").textContent = "已自动保存 " + hhmm();
73
+ })
74
+ .catch(function () {
75
+ $("saved").textContent = "没保存上,网络恢复后再改一下会重试";
76
+ });
57
77
  }, 600);
58
78
  }
59
79
 
@@ -70,44 +90,55 @@
70
90
  if (i.required && a && a.mark === "fail") failedRequired++;
71
91
  });
72
92
  var verdict = failedRequired > 0 ? "不通过" : missing === 0 ? "通过" : null;
73
- $("summary").textContent =
74
- "共 " + items.length + " 条:通过 " + c.pass + ",不对 " + c.fail + ",先不验 " + c.skip + ",还没看 " + c.open + "。" +
75
- (verdict === "通过" ? "可以提交:验收通过。" : verdict === "不通过" ? "提交后结论是“不通过”,不对的地方会转给需求的对话。" : "必须验的都核对完才能提交“通过”。");
93
+ $("summary").textContent = "已核对 " + (c.pass + c.fail) + " / " + items.length + (c.fail ? " · " + c.fail + " 项不对" : "") + (c.skip ? " · 先不验 " + c.skip : "");
94
+ $("summary-sub").textContent = state.info.readOnly
95
+ ? "这是只读链接,只能查看"
96
+ : verdict === "通过"
97
+ ? "可以提交:验收通过"
98
+ : verdict === "不通过"
99
+ ? "提交后结论是“不通过”,不对的地方会转给需求的对话处理"
100
+ : "还有 " + missing + " 条必须验的没有核对;核对完,或者有不对的标“不对”后才能提交";
76
101
  $("submit").disabled = state.info.readOnly || state.busy || verdict === null;
77
- $("submit").textContent = verdict === "不通过" ? "提交:验收不通过" : "提交:验收通过";
102
+ $("submit").lastChild.textContent = verdict === "不通过" ? "提交验收(不通过)" : "提交验收";
103
+ }
104
+
105
+ function details(item) {
106
+ var rows = [];
107
+ var rest = [];
108
+ item.details.forEach(function (d) {
109
+ var m = DETAIL.exec(d.replace(/^\s*[-*]\s+/, ""));
110
+ if (m) rows.push(el("dt", { text: m[1] === "预期" ? "预期结果" : m[1] }), el("dd", { text: m[2] }));
111
+ else rest.push(el("li", { text: d.replace(/^\s*[-*]\s+/, "") }));
112
+ });
113
+ return [rows.length ? el("dl", {}, rows) : null, rest.length ? el("ul", { className: "ck-details" }, rest) : null];
78
114
  }
79
115
 
80
- function renderItem(item, n) {
116
+ function renderItem(item) {
81
117
  var a = state.answers[item.id];
82
- var li = el("li", { className: "item" + (a ? " " + a.mark : "") });
83
118
  var p = PIPELINE[item.pipeline] || PIPELINE.unknown;
84
- li.appendChild(
85
- el("div", { className: "item-head" }, [
86
- el("span", { className: "id", text: n + ". " + item.id }),
87
- el("span", { className: "item-title", text: item.title }),
88
- item.kind === "criterion" ? el("span", { className: "badge " + p[1], text: p[0] }) : el("span", { className: "badge unverified", text: "请你表态" }),
89
- item.required ? null : el("span", { className: "badge", text: "可以以后再说" }),
119
+ var li = el("li", { className: "rk-check" + (a && a.mark === "fail" ? " is-fail" : a && a.mark === "pass" ? " is-pass" : ""), id: "item-" + item.id });
120
+ li.appendChild(el("span", { className: "rk-check__id", text: item.id }));
121
+ var main = el("div", { className: "rk-check__main" });
122
+ main.appendChild(
123
+ el("div", { className: "rk-check__title" }, [
124
+ el("span", { text: item.title }),
125
+ item.kind === "criterion" ? badge(p[0], p[1], p[2]) : badge("needs", "circle-dot", "请你表态"),
126
+ item.required ? null : badge("outline", null, "可以以后再说"),
90
127
  ]),
91
128
  );
92
- if (item.details.length) {
93
- li.appendChild(
94
- el(
95
- "ul",
96
- { className: "details" },
97
- item.details.map(function (d) {
98
- return el("li", { text: d });
99
- }),
100
- ),
101
- );
102
- }
103
- if (item.evidence) li.appendChild(el("div", { className: "evidence", text: "流水线的记录:" + item.evidence }));
104
- var labels = item.kind === "decision" ? [["pass", "同意"], ["fail", "不同意"], ["skip", "先不表态"]] : [["pass", "通过"], ["fail", "不对"], ["skip", "先不验"]];
105
- var marks = el(
129
+ details(item).forEach(function (n) {
130
+ if (n) main.appendChild(n);
131
+ });
132
+ if (item.evidence) main.appendChild(el("div", { className: "rk-check__evidence", text: (item.kind === "criterion" ? "流水线的记录:" : "") + item.evidence }));
133
+ var labels = item.kind === "decision" ? [["pass", "同意", "is-pass"], ["fail", "不同意", "is-fix"], ["skip", "先不表态", ""]] : [["pass", "通过", "is-pass"], ["fail", "不对", "is-fix"], ["skip", "先不验", ""]];
134
+ var seg = el(
106
135
  "div",
107
- { className: "marks" },
136
+ { className: "rk-seg", role: "group", "aria-label": item.kind === "decision" ? "表态" : "核对结果", style: "align-self:flex-start" },
108
137
  labels.map(function (l) {
109
138
  return el("button", {
110
- className: "btn" + (a && a.mark === l[0] ? " on-" + l[0] : ""),
139
+ type: "button",
140
+ className: l[2],
141
+ "aria-pressed": String(!!(a && a.mark === l[0])),
111
142
  text: l[1],
112
143
  disabled: state.info.readOnly ? "disabled" : null,
113
144
  onclick: function () {
@@ -115,49 +146,94 @@
115
146
  state.answers[item.id] = { mark: l[0], note: cur ? cur.note : "" };
116
147
  save();
117
148
  render();
149
+ if (l[0] === "fail") setTimeout(function () {
150
+ var t = document.querySelector("#item-" + CSS.escape(item.id) + " textarea");
151
+ if (t && !t.value) t.focus();
152
+ });
118
153
  },
119
154
  });
120
155
  }),
121
156
  );
122
- if (state.info.readOnly) marks.querySelectorAll("button").forEach(function (b) {
123
- b.disabled = true;
124
- });
125
- li.appendChild(marks);
157
+ main.appendChild(seg);
126
158
  if (a && a.mark === "fail") {
127
- var ta = el("textarea", { placeholder: item.kind === "decision" ? "为什么不同意?希望怎样?" : "看到了什么?应该是什么样?(例如:点导出后提示“失败”,应该下载文件)" });
159
+ var ta = el("textarea", { className: "rk-input", rows: "2", "aria-label": "看到了什么", placeholder: item.kind === "decision" ? "为什么不同意?希望怎样?" : "看到了什么?应该是什么样?(例如:点导出后提示“失败”,应该下载文件)" });
128
160
  ta.value = a.note || "";
161
+ ta.disabled = !!state.info.readOnly;
129
162
  ta.addEventListener("input", function () {
130
163
  state.answers[item.id].note = ta.value;
131
164
  save();
132
165
  });
133
- li.appendChild(ta);
166
+ main.appendChild(ta);
134
167
  }
168
+ li.appendChild(main);
135
169
  return li;
136
170
  }
137
171
 
172
+ function navIcon(item) {
173
+ var a = state.answers[item.id];
174
+ if (a && a.mark === "pass") return ["circle-check", "var(--rk-status-success)"];
175
+ if (a && a.mark === "fail") return ["circle-x", "var(--rk-status-failed)"];
176
+ if (item.kind === "decision") return ["circle-dot", "var(--rk-brand-text)"];
177
+ if (item.pipeline === "fail") return ["circle-x", "var(--rk-status-failed)"];
178
+ if (item.pipeline === "pass") return ["circle-check", "var(--rk-text-muted)"];
179
+ return ["circle-dashed", "var(--rk-text-muted)"];
180
+ }
181
+
182
+ function renderNav() {
183
+ var nav = $("nav");
184
+ nav.textContent = "";
185
+ var first = state.info.items.filter(function (i) {
186
+ return i.kind === "decision" || i.pipeline !== "pass";
187
+ });
188
+ var rest = state.info.items.filter(function (i) {
189
+ return first.indexOf(i) < 0;
190
+ });
191
+ [["先看这些", first], ["流水线已验证", rest]].forEach(function (g) {
192
+ if (!g[1].length) return;
193
+ nav.appendChild(el("div", { className: "rk-side__group", text: g[0] }));
194
+ g[1].forEach(function (item) {
195
+ var ic = navIcon(item);
196
+ var svg = icon(ic[0]);
197
+ if (svg) svg.style.color = ic[1];
198
+ nav.appendChild(
199
+ el("a", { href: "#item-" + item.id, onclick: function (e) {
200
+ e.preventDefault();
201
+ var t = $("item-" + item.id);
202
+ if (t) t.scrollIntoView({ block: "start", behavior: "smooth" });
203
+ } }, [svg, el("span", { text: item.id + " " + item.title })]),
204
+ );
205
+ });
206
+ });
207
+ }
208
+
138
209
  function render() {
139
210
  var list = $("items");
140
- list.innerHTML = "";
141
- state.info.items.forEach(function (item, i) {
142
- list.appendChild(renderItem(item, i + 1));
211
+ list.textContent = "";
212
+ if (!state.info.items.length) {
213
+ list.appendChild(el("li", { className: "rk-card rk-card--sm" }, [el("div", { className: "rk-empty" }, [icon("clipboard-check", "ic ic-xl"), el("h3", { text: "没有找到验收标准" }), el("p", { text: "需求说明里没有 AC 编号的验收标准。请在需求的对话里补上,或者按完成报告验收。" })])]));
214
+ }
215
+ state.info.items.forEach(function (item) {
216
+ list.appendChild(renderItem(item));
143
217
  });
218
+ renderNav();
144
219
  summary();
145
220
  }
146
221
 
147
222
  function showTry(result) {
148
223
  var box = $("try-box");
149
- box.hidden = false;
150
- box.innerHTML = "";
151
- box.appendChild(el("h2", { text: result.ok ? "已经跑起来了" : "没能一键跑起来" }));
152
- box.appendChild(el("div", { text: (result.message || "").split("\n")[0] }));
224
+ box.textContent = "";
225
+ $("try-state").textContent = "";
153
226
  var running = (result.services || []).filter(function (s) {
154
227
  return s.running && s.url;
155
228
  });
229
+ $("try-state").appendChild(result.ok && running.length ? badge("running", null, "正在运行") : badge(result.ok ? "success" : "warning", result.ok ? "circle-check" : "triangle-alert", result.ok ? "已启动" : "没能一键跑起来"));
230
+ if (result.ok && running.length) $("try-state").firstChild.insertBefore(el("span", { className: "rk-spin" }), $("try-state").firstChild.firstChild);
231
+ box.appendChild(el("p", { text: (result.message || "").split("\n")[0] }));
156
232
  if (running.length) {
157
233
  box.appendChild(
158
234
  el(
159
235
  "ul",
160
- { className: "services" },
236
+ { className: "ck-services" },
161
237
  running.map(function (s) {
162
238
  return el("li", {}, [s.name + ":", el("a", { href: s.url, target: "_blank", rel: "noopener", text: s.url })]);
163
239
  }),
@@ -165,19 +241,20 @@
165
241
  );
166
242
  $("try-stop").hidden = false;
167
243
  }
168
- if (result.howTo && result.howTo.length) box.appendChild(el("pre", { className: "howto", text: result.howTo.join("\n") }));
244
+ if (result.howTo && result.howTo.length) box.appendChild(el("pre", { className: "ck-howto", text: result.howTo.join("\n") }));
169
245
  }
170
246
 
171
247
  function tryIt(stop) {
172
248
  var button = stop ? $("try-stop") : $("try");
249
+ var label = stop ? button : button.lastChild;
173
250
  button.disabled = true;
174
- button.textContent = stop ? "正在停止…" : "正在启动,可能要一两分钟…";
251
+ label.textContent = stop ? "正在停止…" : "正在启动,可能要一两分钟…";
175
252
  api("api/try", "POST", { stop: !!stop })
176
253
  .then(function (r) {
177
254
  if (stop) {
178
255
  toast(r.message);
179
256
  $("try-stop").hidden = true;
180
- $("try-box").hidden = true;
257
+ $("try-state").textContent = "";
181
258
  } else showTry(r);
182
259
  })
183
260
  .catch(function (e) {
@@ -185,7 +262,7 @@
185
262
  })
186
263
  .then(function () {
187
264
  button.disabled = false;
188
- button.textContent = stop ? "停止试用" : "把它跑起来试用";
265
+ label.textContent = stop ? "停止试用" : "把它跑起来试用";
189
266
  });
190
267
  }
191
268
 
@@ -195,8 +272,9 @@
195
272
  api("api/submit", "POST", { answers: answersList(), reviewer: state.reviewer })
196
273
  .then(function (r) {
197
274
  toast(r.message);
198
- var done = el("section", { className: "card done" }, [el("h2", { text: "已提交:验收" + r.verdict }), el("div", { text: r.message })]);
199
- document.querySelector("main").insertBefore(done, document.querySelector("main").firstChild);
275
+ var ok = r.verdict === "通过";
276
+ var done = el("div", { className: "rk-banner " + (ok ? "rk-banner--ok" : "rk-banner--warn") }, [icon(ok ? "circle-check" : "undo-2"), el("span", { text: "已提交:验收" + r.verdict + "。" + r.message })]);
277
+ $("main").insertBefore(done, $("main").firstChild);
200
278
  window.scrollTo(0, 0);
201
279
  })
202
280
  .catch(function (e) {
@@ -207,40 +285,45 @@
207
285
  }
208
286
 
209
287
  function start() {
210
- api("api/info").then(function (info) {
211
- state.info = info;
212
- document.title = "验收清单:" + info.title;
213
- $("title").textContent = "验收清单:" + info.title;
214
- $("project").textContent = info.project + " · " + info.featureDir;
215
- $("sub").textContent = [info.reportVerdict ? "流水线结论:" + info.reportVerdict : null, info.accepted ? "上次验收:" + info.accepted : null, info.readOnly ? "只读:可以看,不能提交" : null]
216
- .filter(Boolean)
217
- .join(" ");
218
- (info.draft && info.draft.answers ? info.draft.answers : []).forEach(function (a) {
219
- state.answers[a.id] = { mark: a.mark, note: a.note || "" };
288
+ api("api/info")
289
+ .then(function (info) {
290
+ state.info = info;
291
+ document.title = "验收清单 · " + info.title;
292
+ $("title").textContent = info.title;
293
+ $("project").textContent = info.featureDir;
294
+ $("project").hidden = false;
295
+ $("readonly").hidden = !info.readOnly;
296
+ $("sub").textContent = [info.reportVerdict ? "流水线结论:" + info.reportVerdict : null, info.accepted ? "上次验收:" + info.accepted : null].filter(Boolean).join(" ");
297
+ (info.draft && info.draft.answers ? info.draft.answers : []).forEach(function (a) {
298
+ state.answers[a.id] = { mark: a.mark, note: a.note || "" };
299
+ });
300
+ state.reviewer = (info.draft && info.draft.reviewer) || "";
301
+ $("reviewer").value = state.reviewer;
302
+ $("reviewer").disabled = !!info.readOnly;
303
+ if (info.notes && info.notes.length) {
304
+ var n = $("notes");
305
+ n.hidden = false;
306
+ n.appendChild(icon("info"));
307
+ n.appendChild(
308
+ el("div", {}, [
309
+ el("b", { text: "先看一下(来自提测说明)" }),
310
+ el(
311
+ "ul",
312
+ {},
313
+ info.notes.map(function (t) {
314
+ return el("li", { text: t });
315
+ }),
316
+ ),
317
+ ]),
318
+ );
319
+ }
320
+ if (info.readOnly || info.running) $("try-card").hidden = true;
321
+ render();
322
+ })
323
+ .catch(function (e) {
324
+ $("title").textContent = "打不开验收清单";
325
+ $("sub").textContent = e.message;
220
326
  });
221
- state.reviewer = (info.draft && info.draft.reviewer) || "";
222
- $("reviewer").value = state.reviewer;
223
- if (info.notes && info.notes.length) {
224
- var n = $("notes");
225
- n.hidden = false;
226
- n.appendChild(el("h2", { text: "先看一下(来自提测说明)" }));
227
- n.appendChild(
228
- el(
229
- "ul",
230
- {},
231
- info.notes.map(function (t) {
232
- return el("li", { text: t });
233
- }),
234
- ),
235
- );
236
- }
237
- if (info.readOnly || info.running) $("try").hidden = true;
238
- if (!info.items.length) $("items").appendChild(el("li", { className: "item", text: "需求说明里没有找到验收标准(AC-编号)。" }));
239
- render();
240
- }).catch(function (e) {
241
- $("title").textContent = "打不开验收清单";
242
- $("sub").textContent = e.message;
243
- });
244
327
  $("reviewer").addEventListener("input", function (e) {
245
328
  state.reviewer = e.target.value;
246
329
  save();
@@ -1,93 +1,16 @@
1
- :root {
2
- --bg: #f6f7f9; --surface: #ffffff; --surface-2: #f1f3f6; --text: #1c2430; --muted: #5d6b7c; --border: #dfe3e9;
3
- --accent: #2457d6; --accent-text: #ffffff; --ok: #15803d; --ok-soft: #e7f6ec; --danger: #b91c1c; --danger-soft: #fdecec; --warn: #b45309; --warn-soft: #fdf3e2;
4
- --add: #e6f6ea; --del: #fdecec; --flash: #fff4c2;
5
- color-scheme: light;
6
- }
7
- @media (prefers-color-scheme: dark) {
8
- :root {
9
- --bg: #12161c; --surface: #1a2029; --surface-2: #222a35; --text: #e6eaf0; --muted: #9aa7b8; --border: #2e3846;
10
- --accent: #6f9bff; --accent-text: #0d1420; --ok: #4ade80; --ok-soft: #173322; --danger: #f87171; --danger-soft: #3a1c1c; --warn: #fbbf24; --warn-soft: #3a2c12;
11
- --add: #15301f; --del: #3a1c1c; --flash: #4a3f12;
12
- color-scheme: dark;
13
- }
14
- }
15
- * { box-sizing: border-box; }
16
- [hidden] { display: none !important; }
17
- body { margin: 0; background: var(--bg); color: var(--text); font: 15px/1.6 system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; padding-bottom: 110px; }
18
- .top { padding: 20px 24px 8px; }
19
- .eyebrow { font-size: 13px; color: var(--muted); }
20
- h1 { margin: 2px 0 4px; font-size: 22px; }
21
- h2 { font-size: 15px; margin: 0 0 6px; }
22
- .muted { color: var(--muted); }
23
- .small { font-size: 13px; }
24
- .pad { padding: 8px 12px; }
25
- .banner { margin: 8px 24px; padding: 10px 14px; border-radius: 10px; background: var(--surface-2); border: 1px solid var(--border); }
26
- .banner.ok { background: var(--ok-soft); border-color: var(--ok); }
27
- .banner.warn { background: var(--warn-soft); border-color: var(--warn); }
28
- .layout { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 16px; padding: 0 24px; }
29
- .files { position: sticky; top: 8px; align-self: start; max-height: calc(100vh - 140px); overflow: auto; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 10px; font-size: 13px; }
30
- .nav-title { font-weight: 600; margin-bottom: 6px; }
31
- .files a { display: flex; justify-content: space-between; gap: 6px; padding: 4px 6px; border-radius: 6px; color: var(--text); text-decoration: none; word-break: break-all; }
32
- .files a:hover { background: var(--surface-2); }
33
- .files a.doc { color: var(--muted); }
34
- main { min-width: 0; }
35
- .intro { margin: 8px 0 12px; }
36
- .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; margin: 0 0 12px; }
37
- .badge { font-size: 12px; padding: 1px 8px; border-radius: 999px; background: var(--surface-2); color: var(--muted); white-space: nowrap; }
38
- .badge.danger { background: var(--danger-soft); color: var(--danger); }
39
- .badge.warn { background: var(--warn-soft); color: var(--warn); }
40
- .badge.ok { background: var(--ok-soft); color: var(--ok); }
41
- .file { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 12px; overflow: hidden; }
42
- .file-head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 8px 12px; background: var(--surface-2); cursor: pointer; border-bottom: 1px solid var(--border); }
43
- .file.folded .file-body { display: none; }
44
- .file.folded .file-head { border-bottom: 0; }
45
- .path { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; font-weight: 600; word-break: break-all; }
46
- .plus { color: var(--ok); font-size: 13px; }
47
- .minus { color: var(--danger); font-size: 13px; }
48
- .file-body { overflow-x: auto; }
49
- table.diff { border-collapse: collapse; width: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; line-height: 1.5; }
50
- .diff td { padding: 0 8px; vertical-align: top; }
51
- .diff .num { width: 1%; min-width: 44px; text-align: right; color: var(--muted); user-select: none; }
52
- .diff .code { white-space: pre; }
53
- .diff .hunk td { color: var(--muted); background: var(--surface-2); padding: 2px 8px; }
54
- .diff .line { cursor: pointer; }
55
- .diff .line:hover td { outline: 1px solid var(--border); }
56
- .diff .add td { background: var(--add); }
57
- .diff .del td { background: var(--del); }
58
- .diff .flash td, .flash { background: var(--flash) !important; }
59
- .finding { border: 1px solid var(--border); border-left: 4px solid var(--border); border-radius: 8px; padding: 8px 10px; margin: 6px 0; background: var(--surface); font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 14px; white-space: normal; }
60
- .finding.danger { border-left-color: var(--danger); }
61
- .finding.warn { border-left-color: var(--warn); }
62
- .finding.decided-ok { opacity: 0.6; }
63
- .finding-row td, .comment-row td { padding: 4px 12px 8px; }
64
- .finding-head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
65
- .finding-text { margin: 4px 0; }
66
- .where { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; color: var(--accent); }
67
- .marks { display: flex; gap: 8px; margin-top: 6px; }
68
- .comment { border: 1px solid var(--accent); border-radius: 8px; padding: 8px 10px; background: var(--surface); font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
69
- textarea, input { font: inherit; color: inherit; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; width: 100%; }
70
- .note-input { margin-top: 6px; }
71
- .btn { font: inherit; border: 1px solid var(--border); background: var(--surface); color: var(--text); border-radius: 8px; padding: 6px 14px; cursor: pointer; }
72
- .btn.small { padding: 2px 10px; font-size: 13px; }
73
- .btn.on-fix { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }
74
- .btn.on-ok { background: var(--ok-soft); border-color: var(--ok); color: var(--ok); }
75
- .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
76
- .btn.danger { border-color: var(--danger); color: var(--danger); }
77
- .btn:disabled { opacity: 0.5; cursor: default; }
78
- .bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; flex-wrap: wrap; gap: 10px 16px; align-items: center; padding: 10px 24px; background: var(--surface); border-top: 1px solid var(--border); }
79
- .bar .summary { flex: 1 1 240px; font-size: 13px; }
80
- .field { display: flex; align-items: center; gap: 6px; font-size: 13px; white-space: nowrap; }
81
- .field input { width: 200px; }
82
- .actions { display: flex; gap: 8px; }
83
- .toast { position: fixed; left: 50%; bottom: 90px; transform: translateX(-50%); max-width: min(640px, 92vw); background: var(--text); color: var(--bg); padding: 10px 14px; border-radius: 10px; box-shadow: 0 6px 24px rgba(0,0,0,.2); white-space: pre-line; }
84
- @media (max-width: 860px) {
85
- .layout { grid-template-columns: minmax(0, 1fr); padding: 0 12px; }
86
- .files { position: static; max-height: 220px; }
87
- .top { padding: 16px 12px 6px; }
88
- .banner { margin: 8px 12px; }
89
- .bar { padding: 10px 12px; }
90
- .field { flex: 1 1 100%; }
91
- .field input { width: 100%; }
92
- body { padding-bottom: 220px; }
1
+ /* The code review page (1.15): only what the design system (rk.css) does not cover. */
2
+ #reviewers { width: 200px; }
3
+ #reviewer { width: 130px; }
4
+ .cr-files-n { font: 12px/1 var(--rk-font-mono); color: var(--rk-text-muted); }
5
+ .cr-files-n.has-ai { color: var(--rk-ai); }
6
+ .rk-side a.is-doc span:first-of-type { color: var(--rk-text-muted); }
7
+ .rk-finding .rk-input { height: 32px; }
8
+ .rk-finding textarea.rk-input { height: auto; width: 100%; }
9
+ .rk-finding a.rk-finding__where { text-decoration: none; }
10
+ .rk-finding a.rk-finding__where:hover { text-decoration: underline; }
11
+ .rk-finding.is-ok { opacity: 0.7; }
12
+ .cr-summary li::marker { color: var(--rk-status-success); }
13
+ .cr-summary { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 6px; color: var(--rk-text-secondary); }
14
+ @media (max-width: 640px) {
15
+ #reviewers, #reviewer { width: 100%; }
93
16
  }
@@ -2,42 +2,57 @@
2
2
  <html lang="zh-CN">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
6
  <title>代码审查</title>
7
+ <link rel="stylesheet" href="rk-tokens.css">
8
+ <link rel="stylesheet" href="rk.css">
7
9
  <link rel="stylesheet" href="code.css">
10
+ <script src="rk-icons.js"></script>
8
11
  </head>
9
12
  <body>
10
- <header class="top">
11
- <div>
12
- <div class="eyebrow" id="project"></div>
13
- <h1 id="title">代码审查</h1>
14
- <div class="muted" id="sub"></div>
15
- </div>
13
+ <header class="rk-top">
14
+ <span class="rk-brand"><svg data-logo style="--ground:var(--rk-surface-0)"></svg><span>req<i>-</i>kit</span></span>
15
+ <span class="rk-top__sep rk-top__hide-sm"></span>
16
+ <span class="rk-top__title" id="title">代码审查</span>
17
+ <span class="rk-code" id="project" hidden></span>
18
+ <span class="rk-top__spacer"></span>
19
+ <span class="rk-cap rk-top__hide-sm" id="saved"></span>
20
+ <span class="rk-badge rk-badge--outline" id="readonly" hidden><svg class="ic" data-i="lock"></svg>只读</span>
21
+ <button class="rk-btn rk-btn--ghost rk-btn--sm" type="button" data-rk-theme title="切换深浅色" aria-label="切换深浅色"><svg class="ic" data-i="sun"></svg></button>
16
22
  </header>
17
- <section id="status" class="banner" hidden></section>
18
- <div class="layout">
19
- <nav class="files" id="files" aria-label="改动的文件"></nav>
20
- <main>
21
- <section id="summary" class="card" hidden></section>
22
- <section id="ai" class="card" hidden></section>
23
- <p class="muted intro">点代码行写意见(只对这一行);AI 的意见可以标<b>要改</b>或<b>不用改</b>。看完在下面选择<b>退回修改</b>(流水线会按意见改,改完再评审、再验证)或<b>评审通过</b>。进度会自动保存。</p>
24
- <div id="diffs"></div>
25
- <section class="card">
26
- <h2>总体意见(可不填)</h2>
27
- <textarea id="note" rows="3" placeholder="例如:整体可以,但导出接口要加权限校验"></textarea>
23
+
24
+ <div class="rk-layout">
25
+ <nav class="rk-side" id="files" aria-label="改动的文件"></nav>
26
+ <main class="rk-main">
27
+ <div class="rk-banner" id="status" hidden></div>
28
+ <div class="rk-head">
29
+ <span class="rk-eyebrow">Code review</span>
30
+ <h1>看一下这次的改动,决定退回修改还是评审通过</h1>
31
+ <p id="sub"></p>
32
+ <p class="rk-cap">点代码行写意见(只对这一行);AI 的意见可以标"要改"或"不用改"。退回后流水线会按意见修改,改完再评审、再验证。进度会自动保存。</p>
33
+ </div>
34
+ <section class="rk-card rk-card--sm" id="summary" hidden></section>
35
+ <section class="rk-card rk-card--sm" id="ai" hidden></section>
36
+ <div class="rk-col" id="diffs"></div>
37
+ <section class="rk-card rk-card--sm">
38
+ <label class="rk-field" for="note">总体意见(可不填)</label>
39
+ <textarea class="rk-input" id="note" rows="3" placeholder="例如:整体可以,但导出接口要加权限校验"></textarea>
28
40
  </section>
29
41
  </main>
30
42
  </div>
31
- <footer class="bar">
32
- <div class="summary" id="count"></div>
33
- <label class="field">审核人 <input id="reviewers" placeholder="@用户名,用逗号分开"></label>
34
- <label class="field">评审人 <input id="reviewer" placeholder="你的名字" maxlength="60"></label>
35
- <div class="actions">
36
- <button id="changes" class="btn danger" disabled>退回修改</button>
37
- <button id="approve" class="btn primary" disabled>评审通过</button>
43
+
44
+ <footer class="rk-decide">
45
+ <div class="rk-decide__in">
46
+ <div class="rk-decide__sum"><b id="count"></b><span class="rk-cap" id="countsub"></span></div>
47
+ <label class="rk-field">审核人<input class="rk-input" id="reviewers" placeholder="@用户名,用逗号分开"></label>
48
+ <label class="rk-field">评审人<input class="rk-input" id="reviewer" placeholder="你的名字" maxlength="60"></label>
49
+ <div class="rk-decide__acts">
50
+ <button class="rk-btn rk-btn--danger-outline" id="changes" type="button" disabled><svg class="ic" data-i="undo-2"></svg><span>退回修改</span></button>
51
+ <button class="rk-btn rk-btn--primary" id="approve" type="button" disabled><svg class="ic" data-i="check"></svg><span>评审通过</span></button>
52
+ </div>
38
53
  </div>
39
54
  </footer>
40
- <div id="toast" class="toast" hidden></div>
55
+ <div class="rk-toast" id="toast" role="status" hidden></div>
41
56
  <script src="code.js"></script>
42
57
  </body>
43
58
  </html>