@openprd/cli 0.1.1 → 0.1.8
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.
- package/.openprd/README.md +43 -69
- package/.openprd/README_EN.md +84 -0
- package/.openprd/benchmarks/index.md +7 -0
- package/.openprd/benchmarks/sources.yaml +25 -3
- package/.openprd/discovery/config.json +16 -2
- package/.openprd/engagements/active/flows.md +19 -14
- package/.openprd/engagements/active/handoff.md +11 -4
- package/.openprd/engagements/active/prd.md +99 -71
- package/.openprd/engagements/active/review.html +4 -4
- package/.openprd/engagements/active/roles.md +9 -8
- package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +4 -4
- package/.openprd/engagements/work-units/wu-20260602113956-a99b5b88.json +18 -0
- package/.openprd/engagements/work-units/wu-20260602122244-78656aaf.json +18 -0
- package/.openprd/engagements/work-units/wu-20260602122442-e96489e2.json +18 -0
- package/.openprd/engagements/work-units/wu-20260602132835-695429e8.json +18 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/candidate.json +78 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/diagnostic-report.json +129 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/root-cause-candidates.json +41 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/timeline.json +14 -0
- package/.openprd/knowledge/drafts/openprd-experience-diagnostic-candidate-turn-1780116203372-5f266a79e968c758/SKILL.md +49 -0
- package/.openprd/knowledge/index.json +44 -4
- package/.openprd/reviews/v0001.html +195 -129
- package/.openprd/reviews/v0002.html +1150 -0
- package/.openprd/reviews/v0003.html +1150 -0
- package/.openprd/reviews/v0004.html +1150 -0
- package/.openprd/reviews/v0005.html +1150 -0
- package/.openprd/standards/config.json +12 -9
- package/.openprd/state/changes.json +17 -2
- package/.openprd/state/current.json +399 -63
- package/.openprd/state/release-ledger.json +344 -0
- package/.openprd/state/version-index.json +52 -0
- package/.openprd/state/versions/v0002.json +264 -0
- package/.openprd/state/versions/v0002.md +183 -0
- package/.openprd/state/versions/v0003.json +269 -0
- package/.openprd/state/versions/v0003.md +188 -0
- package/.openprd/state/versions/v0004.json +274 -0
- package/.openprd/state/versions/v0004.md +193 -0
- package/.openprd/state/versions/v0005.json +299 -0
- package/.openprd/state/versions/v0005.md +189 -0
- package/.openprd/templates/agent/intake.md +5 -4
- package/.openprd/templates/b2b/intake.md +5 -4
- package/.openprd/templates/base/intake.md +10 -4
- package/.openprd/templates/company/README.md +9 -7
- package/.openprd/templates/company/README_EN.md +12 -0
- package/.openprd/templates/consumer/intake.md +5 -4
- package/.openprd/templates/industry/README.md +12 -10
- package/.openprd/templates/industry/README_EN.md +18 -0
- package/.openprd/templates/project/README.md +11 -9
- package/.openprd/templates/project/README_EN.md +16 -0
- package/.openprd/templates/session/README.md +11 -9
- package/.openprd/templates/session/README_EN.md +16 -0
- package/AGENTS.md +12 -8
- package/README.md +399 -438
- package/README_CN.md +4 -578
- package/README_EN.md +850 -0
- package/docs/assets/openprd-requirement-routing-en.png +0 -0
- package/docs/assets/openprd-requirement-routing-en.svg +102 -0
- package/docs/assets/openprd-requirement-routing-zh-refined.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.svg +102 -0
- package/package.json +6 -2
- package/scripts/dev-check-wrapup-copy.mjs +110 -0
- package/scripts/openprd-github-release-notes.mjs +99 -0
- package/scripts/quality-perf-check.mjs +203 -0
- package/skills/openprd-benchmark-router/SKILL.md +1 -0
- package/skills/openprd-benchmark-router/references/benchmark-sources.md +1 -0
- package/skills/openprd-benchmark-router/references/source-policy.md +2 -0
- package/skills/openprd-discovery-loop/SKILL.md +2 -2
- package/skills/openprd-harness/SKILL.md +46 -24
- package/skills/openprd-harness/references/workflow-gates.md +15 -0
- package/skills/openprd-quality/SKILL.md +10 -4
- package/skills/openprd-requirement-intake/SKILL.md +31 -20
- package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -6
- package/skills/openprd-requirement-intake/references/routing-rubric.md +10 -2
- package/skills/openprd-router/SKILL.md +2 -2
- package/skills/openprd-shared/SKILL.md +51 -23
- package/skills/openprd-standards/SKILL.md +2 -1
- package/src/agent-integration.js +265 -65
- package/src/benchmark/constants.js +107 -0
- package/src/benchmark/operations.js +235 -0
- package/src/benchmark/registry.js +64 -0
- package/src/benchmark/render.js +115 -0
- package/src/benchmark/source.js +617 -0
- package/src/benchmark/storage.js +121 -0
- package/src/benchmark/verify.js +235 -0
- package/src/benchmark.js +50 -851
- package/src/change-summary.js +339 -0
- package/src/cli/args.js +67 -6
- package/src/cli/basic-print.js +365 -0
- package/src/cli/benchmark-print.js +91 -0
- package/src/cli/change-print.js +221 -0
- package/src/cli/doctor-print.js +268 -0
- package/src/cli/growth-print.js +176 -0
- package/src/cli/print.js +73 -1384
- package/src/cli/quality-print.js +284 -0
- package/src/cli/run-print.js +297 -0
- package/src/cli/shared-print.js +127 -0
- package/src/cli/workflow-print.js +195 -0
- package/src/codex-hook-runner-template.mjs +639 -117
- package/src/codex-runtime.js +324 -0
- package/src/dev-standards.js +178 -5
- package/src/diagram-core.js +5 -5
- package/src/discovery.js +2 -1
- package/src/execution-strategy.js +369 -0
- package/src/fleet.js +4 -0
- package/src/github-release.js +156 -0
- package/src/growth.js +311 -13
- package/src/html-artifact-utils.js +25 -0
- package/src/html-artifacts.js +157 -1596
- package/src/knowledge.js +1176 -75
- package/src/language-policy.js +2 -112
- package/src/learning-html-artifact.js +1031 -0
- package/src/learning-review.js +3 -2
- package/src/loop.js +280 -9
- package/src/openprd.js +341 -38
- package/src/openspec/change-validate.js +0 -9
- package/src/openspec/execute.js +79 -3
- package/src/openspec/generate.js +33 -20
- package/src/openspec/tasks.js +33 -2
- package/src/prd-core.js +10 -9
- package/src/product-type-copy.js +69 -0
- package/src/quality-html-artifact.js +108 -9
- package/src/quality-learning.js +30 -0
- package/src/quality-visual-review.js +237 -0
- package/src/quality.js +329 -43
- package/src/registry-hygiene.js +54 -0
- package/src/release-ledger.js +413 -0
- package/src/review-presentation.js +12 -6
- package/src/run-harness.js +722 -48
- package/src/session-binding.js +40 -3
- package/src/session-registry.js +159 -0
- package/src/standards.js +5 -3
- package/src/test-strategy.js +386 -0
- package/src/visual-compare.js +915 -34
- package/src/work-unit-migration.js +5 -1
- package/src/workspace-core.js +343 -19
- package/src/workspace-workflow.js +538 -134
|
@@ -0,0 +1,1150 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>OpenPrd 用户视角变化摘要</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light;
|
|
10
|
+
--review-bg: #f6f8fb;
|
|
11
|
+
--review-panel: #ffffff;
|
|
12
|
+
--review-panel-soft: #f9fafb;
|
|
13
|
+
--review-text: #172033;
|
|
14
|
+
--review-muted: #667085;
|
|
15
|
+
--review-line: #d8dee8;
|
|
16
|
+
--review-blue: #2563eb;
|
|
17
|
+
--review-teal: #0f766e;
|
|
18
|
+
--review-indigo: #4f46e5;
|
|
19
|
+
--review-amber: #b45309;
|
|
20
|
+
--review-red: #dc2626;
|
|
21
|
+
--review-green: #15803d;
|
|
22
|
+
--review-mono: "JetBrains Mono", "SFMono-Regular", Menlo, monospace;
|
|
23
|
+
}
|
|
24
|
+
* { box-sizing: border-box; }
|
|
25
|
+
body {
|
|
26
|
+
margin: 0;
|
|
27
|
+
background: var(--review-bg);
|
|
28
|
+
color: var(--review-text);
|
|
29
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
30
|
+
overflow-x: hidden;
|
|
31
|
+
}
|
|
32
|
+
.review-page {
|
|
33
|
+
max-width: 1220px;
|
|
34
|
+
margin: 0 auto;
|
|
35
|
+
padding: 28px 22px 120px;
|
|
36
|
+
}
|
|
37
|
+
.review-topbar {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
flex-wrap: wrap;
|
|
42
|
+
gap: 16px;
|
|
43
|
+
margin-bottom: 16px;
|
|
44
|
+
}
|
|
45
|
+
.review-brand {
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
min-height: 34px;
|
|
49
|
+
border: 1px solid var(--review-line);
|
|
50
|
+
border-radius: 999px;
|
|
51
|
+
background: var(--review-panel);
|
|
52
|
+
color: var(--review-muted);
|
|
53
|
+
padding: 0 12px;
|
|
54
|
+
font-size: 13px;
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
letter-spacing: 0;
|
|
57
|
+
}
|
|
58
|
+
.review-project-version {
|
|
59
|
+
display: grid;
|
|
60
|
+
gap: 4px;
|
|
61
|
+
min-width: 220px;
|
|
62
|
+
margin-left: auto;
|
|
63
|
+
padding: 12px 16px;
|
|
64
|
+
border: 1px solid #bfdbfe;
|
|
65
|
+
border-radius: 16px;
|
|
66
|
+
background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%);
|
|
67
|
+
box-shadow: 0 12px 24px rgba(37, 99, 235, 0.08);
|
|
68
|
+
}
|
|
69
|
+
.review-project-version-label {
|
|
70
|
+
color: var(--review-blue);
|
|
71
|
+
font-size: 12px;
|
|
72
|
+
font-weight: 800;
|
|
73
|
+
letter-spacing: 0;
|
|
74
|
+
}
|
|
75
|
+
.review-project-version-value {
|
|
76
|
+
color: var(--review-text);
|
|
77
|
+
font-family: var(--review-mono);
|
|
78
|
+
font-size: 22px;
|
|
79
|
+
line-height: 1.1;
|
|
80
|
+
}
|
|
81
|
+
.review-project-version-meta {
|
|
82
|
+
color: var(--review-muted);
|
|
83
|
+
font-size: 12px;
|
|
84
|
+
font-weight: 700;
|
|
85
|
+
letter-spacing: 0;
|
|
86
|
+
}
|
|
87
|
+
.review-kicker {
|
|
88
|
+
margin: 0 0 6px;
|
|
89
|
+
color: var(--review-muted);
|
|
90
|
+
font-size: 13px;
|
|
91
|
+
font-weight: 800;
|
|
92
|
+
letter-spacing: 0;
|
|
93
|
+
}
|
|
94
|
+
.review-overview,
|
|
95
|
+
.review-map {
|
|
96
|
+
border: 1px solid var(--review-line);
|
|
97
|
+
border-radius: 8px;
|
|
98
|
+
background: var(--review-panel);
|
|
99
|
+
box-shadow: 0 16px 34px rgba(15, 23, 42, 0.06);
|
|
100
|
+
}
|
|
101
|
+
.review-overview {
|
|
102
|
+
display: block;
|
|
103
|
+
padding: 24px;
|
|
104
|
+
}
|
|
105
|
+
.review-overview-copy,
|
|
106
|
+
.review-panel {
|
|
107
|
+
min-width: 0;
|
|
108
|
+
}
|
|
109
|
+
.review-overview h1,
|
|
110
|
+
.review-map h2,
|
|
111
|
+
.review-panel h3 {
|
|
112
|
+
margin: 0;
|
|
113
|
+
color: var(--review-text);
|
|
114
|
+
letter-spacing: 0;
|
|
115
|
+
overflow-wrap: anywhere;
|
|
116
|
+
}
|
|
117
|
+
.review-overview h1 {
|
|
118
|
+
font-size: 32px;
|
|
119
|
+
line-height: 1.16;
|
|
120
|
+
word-break: break-word;
|
|
121
|
+
}
|
|
122
|
+
.review-problem {
|
|
123
|
+
max-width: 760px;
|
|
124
|
+
margin: 12px 0 0;
|
|
125
|
+
color: var(--review-muted);
|
|
126
|
+
font-size: 16px;
|
|
127
|
+
line-height: 1.75;
|
|
128
|
+
overflow-wrap: anywhere;
|
|
129
|
+
}
|
|
130
|
+
.review-map {
|
|
131
|
+
margin-top: 18px;
|
|
132
|
+
padding: 20px;
|
|
133
|
+
}
|
|
134
|
+
.review-section-heading,
|
|
135
|
+
.review-panel-head {
|
|
136
|
+
display: flex;
|
|
137
|
+
gap: 12px;
|
|
138
|
+
align-items: flex-start;
|
|
139
|
+
}
|
|
140
|
+
.review-section-heading h2 {
|
|
141
|
+
font-size: 22px;
|
|
142
|
+
}
|
|
143
|
+
.review-icon {
|
|
144
|
+
flex: 0 0 auto;
|
|
145
|
+
display: inline-flex;
|
|
146
|
+
width: 38px;
|
|
147
|
+
height: 38px;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
border-radius: 8px;
|
|
151
|
+
}
|
|
152
|
+
.review-icon svg {
|
|
153
|
+
width: 22px;
|
|
154
|
+
height: 22px;
|
|
155
|
+
fill: none;
|
|
156
|
+
stroke: currentColor;
|
|
157
|
+
stroke-width: 2;
|
|
158
|
+
stroke-linecap: round;
|
|
159
|
+
stroke-linejoin: round;
|
|
160
|
+
}
|
|
161
|
+
.review-icon-map { color: var(--review-indigo); background: #eef2ff; }
|
|
162
|
+
.review-icon-flow { color: var(--review-teal); background: #ccfbf1; }
|
|
163
|
+
.review-icon-function { color: var(--review-blue); background: #dbeafe; }
|
|
164
|
+
.review-icon-guardrail { color: var(--review-amber); background: #fef3c7; }
|
|
165
|
+
.review-icon-risk { color: var(--review-red); background: #fee2e2; }
|
|
166
|
+
.review-map-canvas {
|
|
167
|
+
margin-top: 14px;
|
|
168
|
+
overflow-x: auto;
|
|
169
|
+
max-width: 100%;
|
|
170
|
+
}
|
|
171
|
+
.review-map-canvas svg {
|
|
172
|
+
display: block;
|
|
173
|
+
width: 100%;
|
|
174
|
+
min-width: 680px;
|
|
175
|
+
height: auto;
|
|
176
|
+
}
|
|
177
|
+
.review-map-bg {
|
|
178
|
+
fill: #f8fafc;
|
|
179
|
+
stroke: #e2e8f0;
|
|
180
|
+
}
|
|
181
|
+
.review-map-arrow {
|
|
182
|
+
fill: none;
|
|
183
|
+
stroke: var(--review-indigo);
|
|
184
|
+
stroke-width: 3;
|
|
185
|
+
stroke-linecap: round;
|
|
186
|
+
}
|
|
187
|
+
.review-map-link {
|
|
188
|
+
fill: none;
|
|
189
|
+
stroke: #a5b4fc;
|
|
190
|
+
stroke-width: 2.5;
|
|
191
|
+
stroke-linecap: round;
|
|
192
|
+
}
|
|
193
|
+
.review-map-node {
|
|
194
|
+
fill: #ffffff;
|
|
195
|
+
stroke: #cbd5e1;
|
|
196
|
+
stroke-width: 1.5;
|
|
197
|
+
filter: drop-shadow(0 10px 16px rgba(15, 23, 42, 0.08));
|
|
198
|
+
}
|
|
199
|
+
.review-map-center {
|
|
200
|
+
fill: #eef2ff;
|
|
201
|
+
stroke: #818cf8;
|
|
202
|
+
stroke-width: 1.5;
|
|
203
|
+
filter: drop-shadow(0 14px 18px rgba(79, 70, 229, 0.12));
|
|
204
|
+
}
|
|
205
|
+
.review-map-node.node-1 { stroke: #99f6e4; }
|
|
206
|
+
.review-map-node.node-2 { stroke: #bfdbfe; }
|
|
207
|
+
.review-map-node.node-3 { stroke: #fde68a; }
|
|
208
|
+
.review-map-node.node-4 { stroke: #fecaca; }
|
|
209
|
+
.review-map-step {
|
|
210
|
+
fill: var(--review-indigo);
|
|
211
|
+
font-size: 13px;
|
|
212
|
+
font-weight: 800;
|
|
213
|
+
}
|
|
214
|
+
.review-map-tag {
|
|
215
|
+
fill: var(--review-muted);
|
|
216
|
+
font-size: 11px;
|
|
217
|
+
font-weight: 800;
|
|
218
|
+
}
|
|
219
|
+
.review-map-tag-pill {
|
|
220
|
+
fill: #f8fafc;
|
|
221
|
+
stroke: #cbd5e1;
|
|
222
|
+
stroke-width: 1;
|
|
223
|
+
}
|
|
224
|
+
.review-map-tag-pill.center { fill: #e0e7ff; stroke: #a5b4fc; }
|
|
225
|
+
.review-map-tag-pill.node-1 { fill: #ccfbf1; stroke: #5eead4; }
|
|
226
|
+
.review-map-tag-pill.node-2 { fill: #dbeafe; stroke: #93c5fd; }
|
|
227
|
+
.review-map-tag-pill.node-3 { fill: #fef3c7; stroke: #facc15; }
|
|
228
|
+
.review-map-tag-pill.node-4 { fill: #fee2e2; stroke: #fca5a5; }
|
|
229
|
+
.review-map-tag.center { fill: var(--review-indigo); }
|
|
230
|
+
.review-map-tag.node-1 { fill: #0f766e; }
|
|
231
|
+
.review-map-tag.node-2 { fill: #2563eb; }
|
|
232
|
+
.review-map-tag.node-3 { fill: #b45309; }
|
|
233
|
+
.review-map-tag.node-4 { fill: #dc2626; }
|
|
234
|
+
.review-map-label {
|
|
235
|
+
fill: var(--review-text);
|
|
236
|
+
font-size: 12px;
|
|
237
|
+
font-weight: 680;
|
|
238
|
+
}
|
|
239
|
+
.review-map-note {
|
|
240
|
+
margin: 10px 0 0;
|
|
241
|
+
color: var(--review-muted);
|
|
242
|
+
font-size: 13px;
|
|
243
|
+
}
|
|
244
|
+
.review-panel-grid {
|
|
245
|
+
display: grid;
|
|
246
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
247
|
+
gap: 16px;
|
|
248
|
+
margin-top: 18px;
|
|
249
|
+
}
|
|
250
|
+
.review-panel {
|
|
251
|
+
min-height: 260px;
|
|
252
|
+
border: 1px solid var(--review-line);
|
|
253
|
+
border-radius: 8px;
|
|
254
|
+
background: var(--review-panel);
|
|
255
|
+
padding: 18px;
|
|
256
|
+
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.05);
|
|
257
|
+
}
|
|
258
|
+
.review-panel h3 {
|
|
259
|
+
font-size: 20px;
|
|
260
|
+
}
|
|
261
|
+
.review-panel-head p {
|
|
262
|
+
margin: 5px 0 0;
|
|
263
|
+
color: var(--review-muted);
|
|
264
|
+
font-size: 14px;
|
|
265
|
+
line-height: 1.55;
|
|
266
|
+
}
|
|
267
|
+
.review-chip-row {
|
|
268
|
+
display: flex;
|
|
269
|
+
flex-wrap: wrap;
|
|
270
|
+
gap: 8px;
|
|
271
|
+
margin-top: 16px;
|
|
272
|
+
padding: 12px;
|
|
273
|
+
border-radius: 8px;
|
|
274
|
+
background: var(--review-panel-soft);
|
|
275
|
+
border: 1px solid var(--review-line);
|
|
276
|
+
}
|
|
277
|
+
.review-chip {
|
|
278
|
+
display: inline-flex;
|
|
279
|
+
align-items: center;
|
|
280
|
+
width: fit-content;
|
|
281
|
+
max-width: 100%;
|
|
282
|
+
min-height: 28px;
|
|
283
|
+
padding: 5px 10px;
|
|
284
|
+
border-radius: 999px;
|
|
285
|
+
border: 1px solid var(--review-line);
|
|
286
|
+
background: #ffffff;
|
|
287
|
+
color: var(--review-text);
|
|
288
|
+
font-size: 13px;
|
|
289
|
+
font-weight: 750;
|
|
290
|
+
line-height: 1.25;
|
|
291
|
+
white-space: nowrap;
|
|
292
|
+
overflow-wrap: normal;
|
|
293
|
+
word-break: keep-all;
|
|
294
|
+
}
|
|
295
|
+
.review-panel-flow .review-chip { border-color: #99f6e4; background: #f0fdfa; color: #115e59; }
|
|
296
|
+
.review-panel-function .review-chip { border-color: #bfdbfe; background: #eff6ff; color: #1d4ed8; }
|
|
297
|
+
.review-panel-guardrail .review-chip { border-color: #fde68a; background: #fffbeb; color: #92400e; }
|
|
298
|
+
.review-panel-risk .review-chip { border-color: #fecaca; background: #fff1f2; color: #991b1b; }
|
|
299
|
+
.review-chip.empty {
|
|
300
|
+
color: var(--review-muted);
|
|
301
|
+
background: #ffffff;
|
|
302
|
+
border-color: var(--review-line);
|
|
303
|
+
}
|
|
304
|
+
.review-journey-map {
|
|
305
|
+
margin-top: 12px;
|
|
306
|
+
border: 1px solid var(--review-line);
|
|
307
|
+
border-radius: 8px;
|
|
308
|
+
background: #f8fafc;
|
|
309
|
+
overflow-x: auto;
|
|
310
|
+
overflow-y: hidden;
|
|
311
|
+
}
|
|
312
|
+
.review-journey-map svg {
|
|
313
|
+
display: block;
|
|
314
|
+
width: 100%;
|
|
315
|
+
min-width: 0;
|
|
316
|
+
min-height: 230px;
|
|
317
|
+
}
|
|
318
|
+
.review-journey-bg {
|
|
319
|
+
fill: #fbfdff;
|
|
320
|
+
stroke: none;
|
|
321
|
+
}
|
|
322
|
+
.review-journey-arrow {
|
|
323
|
+
fill: none;
|
|
324
|
+
stroke: #0d9488;
|
|
325
|
+
stroke-width: 2;
|
|
326
|
+
stroke-linecap: round;
|
|
327
|
+
}
|
|
328
|
+
.review-journey-arrow.branch {
|
|
329
|
+
stroke: #94a3b8;
|
|
330
|
+
stroke-dasharray: 5 6;
|
|
331
|
+
}
|
|
332
|
+
.review-journey-node {
|
|
333
|
+
fill: #ffffff;
|
|
334
|
+
stroke-width: 1.6;
|
|
335
|
+
filter: drop-shadow(0 10px 18px rgba(15, 23, 42, 0.08));
|
|
336
|
+
}
|
|
337
|
+
.review-journey-node.stage-journey { stroke: #5eead4; }
|
|
338
|
+
.review-journey-node.stage-step { stroke: #93c5fd; }
|
|
339
|
+
.review-journey-node.stage-outcome { stroke: #a5b4fc; }
|
|
340
|
+
.review-journey-node.stage-boundary { stroke: #fde68a; }
|
|
341
|
+
.review-journey-node.stage-recovery { stroke: #fecaca; }
|
|
342
|
+
.review-journey-dot {
|
|
343
|
+
fill: #0f172a;
|
|
344
|
+
}
|
|
345
|
+
.review-journey-dot.stage-journey { fill: #0d9488; }
|
|
346
|
+
.review-journey-dot.stage-step { fill: #2563eb; }
|
|
347
|
+
.review-journey-dot.stage-outcome { fill: #4f46e5; }
|
|
348
|
+
.review-journey-dot.stage-boundary { fill: #ca8a04; }
|
|
349
|
+
.review-journey-dot.stage-recovery { fill: #dc2626; }
|
|
350
|
+
.review-journey-number {
|
|
351
|
+
fill: #ffffff;
|
|
352
|
+
font-size: 11px;
|
|
353
|
+
font-weight: 850;
|
|
354
|
+
dominant-baseline: central;
|
|
355
|
+
}
|
|
356
|
+
.review-journey-tag {
|
|
357
|
+
fill: #64748b;
|
|
358
|
+
font-size: 12px;
|
|
359
|
+
font-weight: 850;
|
|
360
|
+
}
|
|
361
|
+
.review-journey-label {
|
|
362
|
+
fill: #0f172a;
|
|
363
|
+
font-size: 12px;
|
|
364
|
+
font-weight: 760;
|
|
365
|
+
}
|
|
366
|
+
.review-panel-list {
|
|
367
|
+
margin: 16px 0 0;
|
|
368
|
+
padding-left: 18px;
|
|
369
|
+
color: var(--review-text);
|
|
370
|
+
font-size: 15px;
|
|
371
|
+
line-height: 1.72;
|
|
372
|
+
overflow-wrap: anywhere;
|
|
373
|
+
}
|
|
374
|
+
.review-panel-list li + li {
|
|
375
|
+
margin-top: 9px;
|
|
376
|
+
}
|
|
377
|
+
.review-detail-summary {
|
|
378
|
+
font-weight: 850;
|
|
379
|
+
color: var(--review-text);
|
|
380
|
+
}
|
|
381
|
+
.review-detail-body {
|
|
382
|
+
color: var(--review-text);
|
|
383
|
+
}
|
|
384
|
+
.review-panel-list .empty {
|
|
385
|
+
color: var(--review-muted);
|
|
386
|
+
}
|
|
387
|
+
.review-bottom-bar {
|
|
388
|
+
position: fixed;
|
|
389
|
+
left: 0;
|
|
390
|
+
right: 0;
|
|
391
|
+
bottom: 0;
|
|
392
|
+
z-index: 30;
|
|
393
|
+
padding: 12px 22px calc(12px + env(safe-area-inset-bottom));
|
|
394
|
+
border-top: 1px solid var(--review-line);
|
|
395
|
+
background: rgba(246, 248, 251, 0.94);
|
|
396
|
+
box-shadow: 0 -14px 32px rgba(15, 23, 42, 0.08);
|
|
397
|
+
backdrop-filter: blur(14px);
|
|
398
|
+
}
|
|
399
|
+
.review-bottom-bar-inner {
|
|
400
|
+
display: flex;
|
|
401
|
+
justify-content: flex-end;
|
|
402
|
+
gap: 12px;
|
|
403
|
+
max-width: 1220px;
|
|
404
|
+
margin: 0 auto;
|
|
405
|
+
}
|
|
406
|
+
.review-bottom-action {
|
|
407
|
+
cursor: pointer;
|
|
408
|
+
display: inline-flex;
|
|
409
|
+
align-items: center;
|
|
410
|
+
justify-content: center;
|
|
411
|
+
min-width: 152px;
|
|
412
|
+
min-height: 48px;
|
|
413
|
+
border: 1px solid transparent;
|
|
414
|
+
border-radius: 12px;
|
|
415
|
+
padding: 0 20px;
|
|
416
|
+
font: inherit;
|
|
417
|
+
font-size: 16px;
|
|
418
|
+
font-weight: 850;
|
|
419
|
+
letter-spacing: 0;
|
|
420
|
+
line-height: 1;
|
|
421
|
+
white-space: nowrap;
|
|
422
|
+
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06);
|
|
423
|
+
transition: background-color 160ms ease, border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
|
|
424
|
+
}
|
|
425
|
+
.review-bottom-action.revise {
|
|
426
|
+
border-color: #fecaca;
|
|
427
|
+
background: #fff1f2;
|
|
428
|
+
color: #b42318;
|
|
429
|
+
}
|
|
430
|
+
.review-bottom-action.confirm {
|
|
431
|
+
border-color: #bbf7d0;
|
|
432
|
+
background: #ecfdf3;
|
|
433
|
+
color: #067647;
|
|
434
|
+
}
|
|
435
|
+
.review-bottom-action:hover,
|
|
436
|
+
.review-bottom-action:focus-visible {
|
|
437
|
+
box-shadow: 0 10px 22px rgba(15, 23, 42, 0.1);
|
|
438
|
+
transform: translateY(-1px);
|
|
439
|
+
outline: none;
|
|
440
|
+
}
|
|
441
|
+
.review-bottom-action.revise:hover,
|
|
442
|
+
.review-bottom-action.revise:focus-visible {
|
|
443
|
+
border-color: #fda4af;
|
|
444
|
+
background: #ffe4e6;
|
|
445
|
+
}
|
|
446
|
+
.review-bottom-action.confirm:hover,
|
|
447
|
+
.review-bottom-action.confirm:focus-visible {
|
|
448
|
+
border-color: #86efac;
|
|
449
|
+
background: #dcfce7;
|
|
450
|
+
}
|
|
451
|
+
.review-bottom-action:active {
|
|
452
|
+
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.08);
|
|
453
|
+
transform: translateY(0);
|
|
454
|
+
}
|
|
455
|
+
@media (max-width: 860px) {
|
|
456
|
+
.review-overview {
|
|
457
|
+
grid-template-columns: 1fr;
|
|
458
|
+
}
|
|
459
|
+
.review-panel-grid {
|
|
460
|
+
grid-template-columns: 1fr;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
@media (max-width: 620px) {
|
|
464
|
+
.review-page { padding: 18px 12px 128px; }
|
|
465
|
+
.review-topbar { align-items: flex-start; flex-direction: column; }
|
|
466
|
+
.review-project-version {
|
|
467
|
+
min-width: 0;
|
|
468
|
+
width: 100%;
|
|
469
|
+
margin-left: 0;
|
|
470
|
+
}
|
|
471
|
+
.review-overview { padding: 18px; }
|
|
472
|
+
.review-overview h1 {
|
|
473
|
+
font-size: 26px;
|
|
474
|
+
word-break: break-all;
|
|
475
|
+
}
|
|
476
|
+
.review-problem { word-break: break-all; }
|
|
477
|
+
.review-map-canvas svg { min-width: 0; }
|
|
478
|
+
.review-journey-map svg { min-width: 620px; }
|
|
479
|
+
.review-section-heading h2 { font-size: 20px; }
|
|
480
|
+
.review-bottom-bar { padding-inline: 12px; }
|
|
481
|
+
.review-bottom-bar-inner {
|
|
482
|
+
display: grid;
|
|
483
|
+
grid-template-columns: 1fr 1fr;
|
|
484
|
+
gap: 8px;
|
|
485
|
+
}
|
|
486
|
+
.review-bottom-action {
|
|
487
|
+
justify-content: center;
|
|
488
|
+
padding-inline: 10px;
|
|
489
|
+
font-size: 15px;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
</style>
|
|
493
|
+
</head>
|
|
494
|
+
<body>
|
|
495
|
+
<main class="review-page">
|
|
496
|
+
<header class="review-topbar">
|
|
497
|
+
<div class="review-brand">OpenPrd / 评审面板</div>
|
|
498
|
+
|
|
499
|
+
<div class="review-project-version" aria-label="项目版本">
|
|
500
|
+
<span class="review-project-version-label">项目版本</span>
|
|
501
|
+
<strong class="review-project-version-value">0.1.2</strong>
|
|
502
|
+
<span class="review-project-version-meta">当前版本 · 6 条变化</span>
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
</header>
|
|
506
|
+
|
|
507
|
+
<section class="review-overview" aria-labelledby="reviewOverviewTitle">
|
|
508
|
+
<div class="review-overview-copy">
|
|
509
|
+
<p class="review-kicker">需求概览</p>
|
|
510
|
+
<h1 id="reviewOverviewTitle">OpenPrd 用户视角变化摘要</h1>
|
|
511
|
+
<p class="review-problem">把 Feature Git Commit 的用户可感知变化写法沉淀成 OpenPrd 的共享变化摘要能力,并落地到 loop commit、handoff / 版本说明、review 摘要三处。</p>
|
|
512
|
+
</div>
|
|
513
|
+
</section>
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
<section class="review-map" aria-labelledby="reviewMapTitle">
|
|
517
|
+
<div class="review-section-heading">
|
|
518
|
+
<span class="review-icon review-icon-map" aria-hidden="true"><svg viewBox="0 0 24 24" role="img" aria-label="图谱"><path d="M12 5v14" /><path d="M5 8h14" /><path d="M7 16h10" /><circle cx="12" cy="5" r="2" /><circle cx="5" cy="8" r="2" /><circle cx="19" cy="8" r="2" /><circle cx="7" cy="16" r="2" /><circle cx="17" cy="16" r="2" /></svg></span>
|
|
519
|
+
<div>
|
|
520
|
+
<h2 id="reviewMapTitle">需求关系图</h2>
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
<div class="review-map-canvas">
|
|
524
|
+
<svg viewBox="0 0 960 336" role="img" aria-label="需求关系图" preserveAspectRatio="xMidYMid meet">
|
|
525
|
+
<rect class="review-map-bg" x="2" y="2" width="956" height="332" rx="8" />
|
|
526
|
+
<path class="review-map-link" d="M 480 168 L 250 94" /><path class="review-map-link" d="M 480 168 L 710 94" /><path class="review-map-link" d="M 480 168 L 250 242" /><path class="review-map-link" d="M 480 168 L 710 242" />
|
|
527
|
+
|
|
528
|
+
<g>
|
|
529
|
+
<rect class="review-map-node node-1" x="128" y="51" width="244" height="86" rx="8" />
|
|
530
|
+
|
|
531
|
+
<rect class="review-map-tag-pill node-1" x="209" y="59" width="82" height="26" rx="13" />
|
|
532
|
+
<text class="review-map-tag node-1" x="250" y="76" text-anchor="middle">统一写法</text>
|
|
533
|
+
|
|
534
|
+
<text class="review-map-label" x="156" y="100" text-anchor="start"><tspan x="156" dy="0">三处都改成用户视角短句</tspan></text>
|
|
535
|
+
</g>
|
|
536
|
+
|
|
537
|
+
<g>
|
|
538
|
+
<rect class="review-map-node node-2" x="588" y="51" width="244" height="86" rx="8" />
|
|
539
|
+
|
|
540
|
+
<rect class="review-map-tag-pill node-2" x="669" y="59" width="82" height="26" rx="13" />
|
|
541
|
+
<text class="review-map-tag node-2" x="710" y="76" text-anchor="middle">三处落点</text>
|
|
542
|
+
|
|
543
|
+
<text class="review-map-label" x="616" y="100" text-anchor="start"><tspan x="616" dy="0">commit、handoff、review同</tspan><tspan x="616" dy="14">步接线</tspan></text>
|
|
544
|
+
</g>
|
|
545
|
+
|
|
546
|
+
<g>
|
|
547
|
+
<rect class="review-map-node node-3" x="128" y="199" width="244" height="86" rx="8" />
|
|
548
|
+
|
|
549
|
+
<rect class="review-map-tag-pill node-3" x="216" y="207" width="68" height="26" rx="13" />
|
|
550
|
+
<text class="review-map-tag node-3" x="250" y="224" text-anchor="middle">主流程</text>
|
|
551
|
+
|
|
552
|
+
<text class="review-map-label" x="156" y="248" text-anchor="start"><tspan x="156" dy="0">共享contract生成短标题和动作</tspan><tspan x="156" dy="14">条目</tspan></text>
|
|
553
|
+
</g>
|
|
554
|
+
|
|
555
|
+
<g>
|
|
556
|
+
<rect class="review-map-node node-4" x="588" y="199" width="244" height="86" rx="8" />
|
|
557
|
+
|
|
558
|
+
<rect class="review-map-tag-pill node-4" x="669" y="207" width="82" height="26" rx="13" />
|
|
559
|
+
<text class="review-map-tag node-4" x="710" y="224" text-anchor="middle">主要风险</text>
|
|
560
|
+
|
|
561
|
+
<text class="review-map-label" x="616" y="248" text-anchor="start"><tspan x="616" dy="0">包装过度或三处继续分叉</tspan></text>
|
|
562
|
+
</g>
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
<g class="review-map-center-group">
|
|
566
|
+
<rect class="review-map-center" x="330" y="124" width="300" height="88" rx="8" />
|
|
567
|
+
|
|
568
|
+
<rect class="review-map-tag-pill center" x="439" y="133" width="82" height="26" rx="13" />
|
|
569
|
+
<text class="review-map-tag center" x="480" y="150" text-anchor="middle">问题定义</text>
|
|
570
|
+
|
|
571
|
+
<text class="review-map-label center" x="360" y="176" text-anchor="start"><tspan x="360" dy="0">变化摘要仍偏技术口吻</tspan></text>
|
|
572
|
+
</g>
|
|
573
|
+
|
|
574
|
+
</svg>
|
|
575
|
+
</div>
|
|
576
|
+
</section>
|
|
577
|
+
|
|
578
|
+
<section class="review-panel-grid" aria-label="固定评审项">
|
|
579
|
+
|
|
580
|
+
<section class="review-panel review-panel-flow">
|
|
581
|
+
<header class="review-panel-head">
|
|
582
|
+
<span class="review-icon review-icon-flow" aria-hidden="true"><svg viewBox="0 0 24 24" role="img" aria-label="流程"><path d="M5 6.5h6.4a3.6 3.6 0 0 1 3.6 3.6v.8" /><path d="M15 17.5H8.6A3.6 3.6 0 0 1 5 13.9v-.8" /><path d="m12 8.5 3-3 3 3" /><path d="m8 15.5-3 3-3-3" /></svg></span>
|
|
583
|
+
<div>
|
|
584
|
+
<h3>主流程与边界情况</h3>
|
|
585
|
+
<p>确认用户旅程、关键步骤和恢复路径是否已经讲清楚,能否进入实现前确认</p>
|
|
586
|
+
</div>
|
|
587
|
+
</header>
|
|
588
|
+
<div class="review-chip-row" aria-label="主流程与边界情况重点摘要">
|
|
589
|
+
<span class="review-chip">共享摘要</span><span class="review-chip">提交说明</span><span class="review-chip">版本说明</span><span class="review-chip">评审摘要</span>
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
<div class="review-journey-map" aria-label="主流程小图">
|
|
593
|
+
<svg viewBox="0 0 680 320" role="img" aria-label="用户旅程、关键步骤、边界情况和恢复路径" preserveAspectRatio="xMidYMid meet">
|
|
594
|
+
<defs>
|
|
595
|
+
<marker id="reviewJourneyArrow" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto">
|
|
596
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0d9488" />
|
|
597
|
+
</marker>
|
|
598
|
+
</defs>
|
|
599
|
+
<rect class="review-journey-bg" x="2" y="2" width="676" height="316" rx="8" />
|
|
600
|
+
<path class="review-journey-arrow" d="M 202 88 H 248" marker-end="url(#reviewJourneyArrow)" />
|
|
601
|
+
<path class="review-journey-arrow" d="M 432 88 H 478" marker-end="url(#reviewJourneyArrow)" />
|
|
602
|
+
<path class="review-journey-arrow branch" d="M 340 134 V 164 H 236 V 190" marker-end="url(#reviewJourneyArrow)" />
|
|
603
|
+
<path class="review-journey-arrow branch" d="M 340 134 V 164 H 454 V 190" marker-end="url(#reviewJourneyArrow)" />
|
|
604
|
+
<g>
|
|
605
|
+
<rect class="review-journey-node stage-journey" x="26" y="40" width="176" height="96" rx="8" />
|
|
606
|
+
<circle class="review-journey-dot stage-journey" cx="56" cy="64" r="12" />
|
|
607
|
+
<text class="review-journey-number" x="56" y="64" text-anchor="middle">1</text>
|
|
608
|
+
<text class="review-journey-tag" x="114" y="66" text-anchor="middle">用户旅程</text>
|
|
609
|
+
<text class="review-journey-label" x="114" y="92" text-anchor="middle"><tspan x="114" dy="0">重点说明</tspan></text>
|
|
610
|
+
</g>
|
|
611
|
+
<g>
|
|
612
|
+
<rect class="review-journey-node stage-step" x="252" y="40" width="176" height="96" rx="8" />
|
|
613
|
+
<circle class="review-journey-dot stage-step" cx="282" cy="64" r="12" />
|
|
614
|
+
<text class="review-journey-number" x="282" y="64" text-anchor="middle">2</text>
|
|
615
|
+
<text class="review-journey-tag" x="340" y="66" text-anchor="middle">关键步骤</text>
|
|
616
|
+
<text class="review-journey-label" x="340" y="92" text-anchor="middle"><tspan x="340" dy="0">重点说明</tspan></text>
|
|
617
|
+
</g>
|
|
618
|
+
<g>
|
|
619
|
+
<rect class="review-journey-node stage-outcome" x="478" y="40" width="176" height="96" rx="8" />
|
|
620
|
+
<circle class="review-journey-dot stage-outcome" cx="508" cy="64" r="12" />
|
|
621
|
+
<text class="review-journey-number" x="508" y="64" text-anchor="middle">3</text>
|
|
622
|
+
<text class="review-journey-tag" x="566" y="66" text-anchor="middle">结果确认</text>
|
|
623
|
+
<text class="review-journey-label" x="566" y="92" text-anchor="middle"><tspan x="566" dy="0">重点说明</tspan></text>
|
|
624
|
+
</g>
|
|
625
|
+
<g>
|
|
626
|
+
<rect class="review-journey-node stage-boundary" x="126" y="194" width="220" height="88" rx="8" />
|
|
627
|
+
<circle class="review-journey-dot stage-boundary" cx="158" cy="218" r="12" />
|
|
628
|
+
<text class="review-journey-number" x="158" y="218" text-anchor="middle">B</text>
|
|
629
|
+
<text class="review-journey-tag" x="236" y="220" text-anchor="middle">边界情况</text>
|
|
630
|
+
<text class="review-journey-label" x="236" y="246" text-anchor="middle"><tspan x="236" dy="0">当改动主要是底层重构</tspan></text>
|
|
631
|
+
</g>
|
|
632
|
+
<g>
|
|
633
|
+
<rect class="review-journey-node stage-recovery" x="356" y="194" width="220" height="88" rx="8" />
|
|
634
|
+
<circle class="review-journey-dot stage-recovery" cx="388" cy="218" r="12" />
|
|
635
|
+
<text class="review-journey-number" x="388" y="218" text-anchor="middle">R</text>
|
|
636
|
+
<text class="review-journey-tag" x="466" y="220" text-anchor="middle">恢复路径</text>
|
|
637
|
+
<text class="review-journey-label" x="466" y="246" text-anchor="middle"><tspan x="466" dy="0">commit</tspan></text>
|
|
638
|
+
</g>
|
|
639
|
+
</svg>
|
|
640
|
+
</div>
|
|
641
|
+
|
|
642
|
+
<ul class="review-panel-list"><li><strong class="review-detail-summary">共享摘要</strong><span class="review-detail-body">:先定一套共享 contract,再分发到 commit、handoff 和 review。</span></li><li><strong class="review-detail-summary">提交说明</strong><span class="review-detail-body">:task commit 默认改成短标题加新增、修复、优化条目。</span></li><li><strong class="review-detail-summary">版本说明</strong><span class="review-detail-body">:handoff 直接产出可复用的变化摘要和版本说明片段。</span></li><li><strong class="review-detail-summary">评审摘要</strong><span class="review-detail-body">:review 先给短标签和一句话,不先暴露技术实现。</span></li></ul>
|
|
643
|
+
</section>
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
<section class="review-panel review-panel-function">
|
|
647
|
+
<header class="review-panel-head">
|
|
648
|
+
<span class="review-icon review-icon-function" aria-hidden="true"><svg viewBox="0 0 24 24" role="img" aria-label="功能"><path d="M5 7h14" /><path d="M5 12h14" /><path d="M5 17h14" /><circle cx="8" cy="7" r="2" /><circle cx="16" cy="12" r="2" /><circle cx="11" cy="17" r="2" /></svg></span>
|
|
649
|
+
<div>
|
|
650
|
+
<h3>功能与约束</h3>
|
|
651
|
+
<p>区分必须交付、非功能要求和当前依赖假设</p>
|
|
652
|
+
</div>
|
|
653
|
+
</header>
|
|
654
|
+
<div class="review-chip-row" aria-label="功能与约束重点摘要">
|
|
655
|
+
<span class="review-chip">保留覆盖</span><span class="review-chip">本地生成</span><span class="review-chip">诚实回退</span>
|
|
656
|
+
</div>
|
|
657
|
+
|
|
658
|
+
<ul class="review-panel-list"><li><strong class="review-detail-summary">保留覆盖</strong><span class="review-detail-body">:显式 message 仍优先,避免覆盖用户自定义提交文案。</span></li><li><strong class="review-detail-summary">本地生成</strong><span class="review-detail-body">:只用本地上下文推导摘要,不新增外部依赖或付费调用。</span></li><li><strong class="review-detail-summary">诚实回退</strong><span class="review-detail-body">:上下文不足时回退安全文案,不硬包装用户收益。</span></li></ul>
|
|
659
|
+
</section>
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
<section class="review-panel review-panel-guardrail">
|
|
663
|
+
<header class="review-panel-head">
|
|
664
|
+
<span class="review-icon review-icon-guardrail" aria-hidden="true"><svg viewBox="0 0 24 24" role="img" aria-label="护栏"><path d="M12 3 5 6v5c0 4.4 2.8 8.4 7 9.8 4.2-1.4 7-5.4 7-9.8V6l-7-3Z" /><path d="M9 12.2 11 14l4-4.4" /></svg></span>
|
|
665
|
+
<div>
|
|
666
|
+
<h3>业务成本与滥用护栏</h3>
|
|
667
|
+
<p>涉及免费额度、消耗型成本或第三方调用时,先确认限制、报警和止损动作</p>
|
|
668
|
+
</div>
|
|
669
|
+
</header>
|
|
670
|
+
<div class="review-chip-row" aria-label="业务成本与滥用护栏重点摘要">
|
|
671
|
+
<span class="review-chip">不并高危</span><span class="review-chip">保持门禁</span>
|
|
672
|
+
</div>
|
|
673
|
+
|
|
674
|
+
<ul class="review-panel-list"><li><strong class="review-detail-summary">不并高危</strong><span class="review-detail-body">:不把 auto tag、auto push、历史重写并进默认能力。</span></li><li><strong class="review-detail-summary">保持门禁</strong><span class="review-detail-body">:现有 review、commit 和 handoff 门禁继续生效。</span></li></ul>
|
|
675
|
+
</section>
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
<section class="review-panel review-panel-risk">
|
|
679
|
+
<header class="review-panel-head">
|
|
680
|
+
<span class="review-icon review-icon-risk" aria-hidden="true"><svg viewBox="0 0 24 24" role="img" aria-label="风险"><path d="M12 4 3.5 19h17L12 4Z" /><path d="M12 9v4" /><path d="M12 16.5h.01" /></svg></span>
|
|
681
|
+
<div>
|
|
682
|
+
<h3>开放问题与风险</h3>
|
|
683
|
+
<p>需求定稿前还没关掉的问题要留在这里,不要默默假定解决</p>
|
|
684
|
+
</div>
|
|
685
|
+
</header>
|
|
686
|
+
<div class="review-chip-row" aria-label="开放问题与风险重点摘要">
|
|
687
|
+
<span class="review-chip">过度包装</span><span class="review-chip">三处分叉</span><span class="review-chip">词表成长</span>
|
|
688
|
+
</div>
|
|
689
|
+
|
|
690
|
+
<ul class="review-panel-list"><li><strong class="review-detail-summary">过度包装</strong><span class="review-detail-body">:底层重构若被写成用户功能,会损害说明可信度。</span></li><li><strong class="review-detail-summary">三处分叉</strong><span class="review-detail-body">:如果不共用 formatter,commit、handoff 和 review 会再次失去一致性。</span></li><li><strong class="review-detail-summary">词表成长</strong><span class="review-detail-body">:默认动作词后续可能需要 grow-aware 化。</span></li></ul>
|
|
691
|
+
</section>
|
|
692
|
+
|
|
693
|
+
</section>
|
|
694
|
+
|
|
695
|
+
<nav class="review-bottom-bar" aria-label="评审决定">
|
|
696
|
+
<div class="review-bottom-bar-inner">
|
|
697
|
+
<button type="button" class="review-bottom-action revise" data-copy-value="OpenPrD Review: 需要调整
|
|
698
|
+
|
|
699
|
+
命令:
|
|
700
|
+
|
|
701
|
+
openprd review . --mark needs-revision --version 'v0002' --digest 'e44eccd0d6fe1a7fd962ece31187f920e1a71de76122d15b826fb0a66d934d35' --work-unit 'wu-20260602113956-a99b5b88' --notes '说明需要调整的点'
|
|
702
|
+
|
|
703
|
+
上下文:
|
|
704
|
+
|
|
705
|
+
{
|
|
706
|
+
"versionId": "v0002",
|
|
707
|
+
"title": "OpenPrd 用户视角变化摘要",
|
|
708
|
+
"digest": "e44eccd0d6fe1a7fd962ece31187f920e1a71de76122d15b826fb0a66d934d35",
|
|
709
|
+
"workUnitId": "wu-20260602113956-a99b5b88",
|
|
710
|
+
"targetRoot": "/Users/chaojifeng/Projects/harness-engineer/openprd",
|
|
711
|
+
"reviewStatus": "pending-confirmation",
|
|
712
|
+
"recommendedActions": [
|
|
713
|
+
"确认问题与目标",
|
|
714
|
+
"确认范围内 / 范围外",
|
|
715
|
+
"确认主流程与失败路径",
|
|
716
|
+
"确认关键风险与开放问题"
|
|
717
|
+
],
|
|
718
|
+
"summaryStyle": {
|
|
719
|
+
"perspective": "从用户可感知变化出发,优先写用户现在能做什么、会看到什么、哪个问题被修好。",
|
|
720
|
+
"preferredVerbs": [
|
|
721
|
+
"新增",
|
|
722
|
+
"修复",
|
|
723
|
+
"优化",
|
|
724
|
+
"调整",
|
|
725
|
+
"移除"
|
|
726
|
+
],
|
|
727
|
+
"panelExamples": {
|
|
728
|
+
"flow": {
|
|
729
|
+
"summary": "新增入口",
|
|
730
|
+
"detail": "用户现在可以直接进入对应流程,不用再自己找路径。"
|
|
731
|
+
},
|
|
732
|
+
"function": {
|
|
733
|
+
"summary": "优化说明",
|
|
734
|
+
"detail": "用户先看到新增、修复、优化这类短摘要,再决定是否继续细读。"
|
|
735
|
+
},
|
|
736
|
+
"guardrail": {
|
|
737
|
+
"summary": "调整边界",
|
|
738
|
+
"detail": "只保留用户需要知道的限制、影响和下一步。"
|
|
739
|
+
},
|
|
740
|
+
"risk": {
|
|
741
|
+
"summary": "修复误判",
|
|
742
|
+
"detail": "避免把实现授权写成再次索取确认。"
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
"sectionKeys": [
|
|
747
|
+
"meta",
|
|
748
|
+
"problem",
|
|
749
|
+
"users",
|
|
750
|
+
"goals",
|
|
751
|
+
"scope",
|
|
752
|
+
"scenarios",
|
|
753
|
+
"requirements",
|
|
754
|
+
"businessGuardrails",
|
|
755
|
+
"constraints",
|
|
756
|
+
"risks",
|
|
757
|
+
"handoff",
|
|
758
|
+
"typeSpecific"
|
|
759
|
+
],
|
|
760
|
+
"presentationContract": {
|
|
761
|
+
"intent": "这些限制用于反馈给 Agent 重新概括,不由 HTML 模板截断原文。",
|
|
762
|
+
"summaryStyle": {
|
|
763
|
+
"perspective": "从用户可感知变化出发,优先写用户现在能做什么、会看到什么、哪个问题被修好。",
|
|
764
|
+
"preferredVerbs": [
|
|
765
|
+
"新增",
|
|
766
|
+
"修复",
|
|
767
|
+
"优化",
|
|
768
|
+
"调整",
|
|
769
|
+
"移除"
|
|
770
|
+
],
|
|
771
|
+
"panelExamples": {
|
|
772
|
+
"flow": {
|
|
773
|
+
"summary": "新增入口",
|
|
774
|
+
"detail": "用户现在可以直接进入对应流程,不用再自己找路径。"
|
|
775
|
+
},
|
|
776
|
+
"function": {
|
|
777
|
+
"summary": "优化说明",
|
|
778
|
+
"detail": "用户先看到新增、修复、优化这类短摘要,再决定是否继续细读。"
|
|
779
|
+
},
|
|
780
|
+
"guardrail": {
|
|
781
|
+
"summary": "调整边界",
|
|
782
|
+
"detail": "只保留用户需要知道的限制、影响和下一步。"
|
|
783
|
+
},
|
|
784
|
+
"risk": {
|
|
785
|
+
"summary": "修复误判",
|
|
786
|
+
"detail": "避免把实现授权写成再次索取确认。"
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
"expectedDataShape": {
|
|
791
|
+
"reviewPresentation": {
|
|
792
|
+
"diagram": {
|
|
793
|
+
"type": "map",
|
|
794
|
+
"note": "默认用关系图;只有确认为线性流程时改为 flow,并用 flowEdges 明确哪些节点有箭头。"
|
|
795
|
+
},
|
|
796
|
+
"mapNodes": {
|
|
797
|
+
"problem": {
|
|
798
|
+
"title": "问题定义",
|
|
799
|
+
"text": "30 字以内的图中正文"
|
|
800
|
+
},
|
|
801
|
+
"goal": {
|
|
802
|
+
"title": "15 字以内标题",
|
|
803
|
+
"text": "30 字以内的图中正文"
|
|
804
|
+
},
|
|
805
|
+
"scope": {
|
|
806
|
+
"title": "15 字以内标题",
|
|
807
|
+
"text": "30 字以内的图中正文"
|
|
808
|
+
},
|
|
809
|
+
"flow": {
|
|
810
|
+
"title": "15 字以内标题",
|
|
811
|
+
"text": "30 字以内的图中正文"
|
|
812
|
+
},
|
|
813
|
+
"risk": {
|
|
814
|
+
"title": "15 字以内标题",
|
|
815
|
+
"text": "30 字以内的图中正文"
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
"flowNodes": [
|
|
819
|
+
{
|
|
820
|
+
"id": "step1",
|
|
821
|
+
"text": "30 字以内的流程卡片正文"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
"id": "step2",
|
|
825
|
+
"text": "30 字以内的流程卡片正文"
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
"id": "step3",
|
|
829
|
+
"text": "30 字以内的流程卡片正文"
|
|
830
|
+
}
|
|
831
|
+
],
|
|
832
|
+
"flowEdges": [
|
|
833
|
+
{
|
|
834
|
+
"from": "step1",
|
|
835
|
+
"to": "step2"
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
"from": "step2",
|
|
839
|
+
"to": "step3"
|
|
840
|
+
}
|
|
841
|
+
],
|
|
842
|
+
"panels": {
|
|
843
|
+
"flow": [
|
|
844
|
+
{
|
|
845
|
+
"summary": "新增入口",
|
|
846
|
+
"detail": "用户现在可以直接进入对应流程,不用再自己找路径。"
|
|
847
|
+
}
|
|
848
|
+
],
|
|
849
|
+
"function": [
|
|
850
|
+
{
|
|
851
|
+
"summary": "优化说明",
|
|
852
|
+
"detail": "用户先看到新增、修复、优化这类短摘要,再决定是否继续细读。"
|
|
853
|
+
}
|
|
854
|
+
],
|
|
855
|
+
"guardrail": [
|
|
856
|
+
{
|
|
857
|
+
"summary": "调整边界",
|
|
858
|
+
"detail": "只保留用户需要知道的限制、影响和下一步。"
|
|
859
|
+
}
|
|
860
|
+
],
|
|
861
|
+
"risk": [
|
|
862
|
+
{
|
|
863
|
+
"summary": "修复误判",
|
|
864
|
+
"detail": "避免把实现授权写成再次索取确认。"
|
|
865
|
+
}
|
|
866
|
+
]
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
"rules": [
|
|
871
|
+
{
|
|
872
|
+
"id": "review-map-card-text",
|
|
873
|
+
"area": "需求关系图 / 需求流程图",
|
|
874
|
+
"target": "图中每个卡片的正文",
|
|
875
|
+
"maxChars": 30,
|
|
876
|
+
"action": "请写入 reviewPresentation.mapNodes.*.text 或 reviewPresentation.flowNodes[].text,重写成用户一眼能扫懂的短句,不要靠省略号或截断。"
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
"id": "review-map-card-title",
|
|
880
|
+
"area": "需求关系图 / 需求流程图",
|
|
881
|
+
"target": "图中卡片标题胶囊",
|
|
882
|
+
"maxChars": 15,
|
|
883
|
+
"action": "请写入 reviewPresentation.mapNodes.*.title,重写成短标题,优先使用业务词,不使用内部技术词。"
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
"id": "review-highlight-chip",
|
|
887
|
+
"area": "四个评审卡片",
|
|
888
|
+
"target": "重点摘要胶囊",
|
|
889
|
+
"maxChars": 15,
|
|
890
|
+
"action": "请重写成短标签,优先使用 新增 / 修复 / 优化 / 调整 / 移除 这类用户能一眼看懂的动作词,再补必要对象。"
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
"id": "review-panel-detail-format",
|
|
894
|
+
"area": "四个评审卡片",
|
|
895
|
+
"target": "明细分点",
|
|
896
|
+
"format": "- **摘要内容**:明细一句话",
|
|
897
|
+
"action": "请写入 reviewPresentation.panels.<kind>[],把每个明细改写为“加粗短摘要 + 一句话说明”,短摘要优先使用 新增 / 修复 / 优化 / 调整 / 移除。"
|
|
898
|
+
}
|
|
899
|
+
]
|
|
900
|
+
},
|
|
901
|
+
"presentationFeedback": [],
|
|
902
|
+
"exportedAt": "2026-06-05T00:44:22.420Z"
|
|
903
|
+
}" title="openprd review . --mark needs-revision --version 'v0002' --digest 'e44eccd0d6fe1a7fd962ece31187f920e1a71de76122d15b826fb0a66d934d35' --work-unit 'wu-20260602113956-a99b5b88' --notes '说明需要调整的点'">
|
|
904
|
+
需要调整
|
|
905
|
+
</button>
|
|
906
|
+
<button type="button" class="review-bottom-action confirm" data-copy-value="OpenPrD Review: 认可并继续下一步
|
|
907
|
+
|
|
908
|
+
当前稳定评审稿 v0002已确认。请先记录这版稳定评审稿,并继续当前 OpenPrd 下一步。若 review 后 tasks 已就绪但还需要执行授权,请直接展示执行确认清单,不要再泛泛确认。
|
|
909
|
+
|
|
910
|
+
命令:
|
|
911
|
+
|
|
912
|
+
openprd review . --mark confirmed --version 'v0002' --digest 'e44eccd0d6fe1a7fd962ece31187f920e1a71de76122d15b826fb0a66d934d35' --work-unit 'wu-20260602113956-a99b5b88'
|
|
913
|
+
|
|
914
|
+
上下文:
|
|
915
|
+
|
|
916
|
+
{
|
|
917
|
+
"versionId": "v0002",
|
|
918
|
+
"title": "OpenPrd 用户视角变化摘要",
|
|
919
|
+
"digest": "e44eccd0d6fe1a7fd962ece31187f920e1a71de76122d15b826fb0a66d934d35",
|
|
920
|
+
"workUnitId": "wu-20260602113956-a99b5b88",
|
|
921
|
+
"targetRoot": "/Users/chaojifeng/Projects/harness-engineer/openprd",
|
|
922
|
+
"reviewStatus": "pending-confirmation",
|
|
923
|
+
"recommendedActions": [
|
|
924
|
+
"确认问题与目标",
|
|
925
|
+
"确认范围内 / 范围外",
|
|
926
|
+
"确认主流程与失败路径",
|
|
927
|
+
"确认关键风险与开放问题"
|
|
928
|
+
],
|
|
929
|
+
"summaryStyle": {
|
|
930
|
+
"perspective": "从用户可感知变化出发,优先写用户现在能做什么、会看到什么、哪个问题被修好。",
|
|
931
|
+
"preferredVerbs": [
|
|
932
|
+
"新增",
|
|
933
|
+
"修复",
|
|
934
|
+
"优化",
|
|
935
|
+
"调整",
|
|
936
|
+
"移除"
|
|
937
|
+
],
|
|
938
|
+
"panelExamples": {
|
|
939
|
+
"flow": {
|
|
940
|
+
"summary": "新增入口",
|
|
941
|
+
"detail": "用户现在可以直接进入对应流程,不用再自己找路径。"
|
|
942
|
+
},
|
|
943
|
+
"function": {
|
|
944
|
+
"summary": "优化说明",
|
|
945
|
+
"detail": "用户先看到新增、修复、优化这类短摘要,再决定是否继续细读。"
|
|
946
|
+
},
|
|
947
|
+
"guardrail": {
|
|
948
|
+
"summary": "调整边界",
|
|
949
|
+
"detail": "只保留用户需要知道的限制、影响和下一步。"
|
|
950
|
+
},
|
|
951
|
+
"risk": {
|
|
952
|
+
"summary": "修复误判",
|
|
953
|
+
"detail": "避免把实现授权写成再次索取确认。"
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
"sectionKeys": [
|
|
958
|
+
"meta",
|
|
959
|
+
"problem",
|
|
960
|
+
"users",
|
|
961
|
+
"goals",
|
|
962
|
+
"scope",
|
|
963
|
+
"scenarios",
|
|
964
|
+
"requirements",
|
|
965
|
+
"businessGuardrails",
|
|
966
|
+
"constraints",
|
|
967
|
+
"risks",
|
|
968
|
+
"handoff",
|
|
969
|
+
"typeSpecific"
|
|
970
|
+
],
|
|
971
|
+
"presentationContract": {
|
|
972
|
+
"intent": "这些限制用于反馈给 Agent 重新概括,不由 HTML 模板截断原文。",
|
|
973
|
+
"summaryStyle": {
|
|
974
|
+
"perspective": "从用户可感知变化出发,优先写用户现在能做什么、会看到什么、哪个问题被修好。",
|
|
975
|
+
"preferredVerbs": [
|
|
976
|
+
"新增",
|
|
977
|
+
"修复",
|
|
978
|
+
"优化",
|
|
979
|
+
"调整",
|
|
980
|
+
"移除"
|
|
981
|
+
],
|
|
982
|
+
"panelExamples": {
|
|
983
|
+
"flow": {
|
|
984
|
+
"summary": "新增入口",
|
|
985
|
+
"detail": "用户现在可以直接进入对应流程,不用再自己找路径。"
|
|
986
|
+
},
|
|
987
|
+
"function": {
|
|
988
|
+
"summary": "优化说明",
|
|
989
|
+
"detail": "用户先看到新增、修复、优化这类短摘要,再决定是否继续细读。"
|
|
990
|
+
},
|
|
991
|
+
"guardrail": {
|
|
992
|
+
"summary": "调整边界",
|
|
993
|
+
"detail": "只保留用户需要知道的限制、影响和下一步。"
|
|
994
|
+
},
|
|
995
|
+
"risk": {
|
|
996
|
+
"summary": "修复误判",
|
|
997
|
+
"detail": "避免把实现授权写成再次索取确认。"
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
"expectedDataShape": {
|
|
1002
|
+
"reviewPresentation": {
|
|
1003
|
+
"diagram": {
|
|
1004
|
+
"type": "map",
|
|
1005
|
+
"note": "默认用关系图;只有确认为线性流程时改为 flow,并用 flowEdges 明确哪些节点有箭头。"
|
|
1006
|
+
},
|
|
1007
|
+
"mapNodes": {
|
|
1008
|
+
"problem": {
|
|
1009
|
+
"title": "问题定义",
|
|
1010
|
+
"text": "30 字以内的图中正文"
|
|
1011
|
+
},
|
|
1012
|
+
"goal": {
|
|
1013
|
+
"title": "15 字以内标题",
|
|
1014
|
+
"text": "30 字以内的图中正文"
|
|
1015
|
+
},
|
|
1016
|
+
"scope": {
|
|
1017
|
+
"title": "15 字以内标题",
|
|
1018
|
+
"text": "30 字以内的图中正文"
|
|
1019
|
+
},
|
|
1020
|
+
"flow": {
|
|
1021
|
+
"title": "15 字以内标题",
|
|
1022
|
+
"text": "30 字以内的图中正文"
|
|
1023
|
+
},
|
|
1024
|
+
"risk": {
|
|
1025
|
+
"title": "15 字以内标题",
|
|
1026
|
+
"text": "30 字以内的图中正文"
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
"flowNodes": [
|
|
1030
|
+
{
|
|
1031
|
+
"id": "step1",
|
|
1032
|
+
"text": "30 字以内的流程卡片正文"
|
|
1033
|
+
},
|
|
1034
|
+
{
|
|
1035
|
+
"id": "step2",
|
|
1036
|
+
"text": "30 字以内的流程卡片正文"
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
"id": "step3",
|
|
1040
|
+
"text": "30 字以内的流程卡片正文"
|
|
1041
|
+
}
|
|
1042
|
+
],
|
|
1043
|
+
"flowEdges": [
|
|
1044
|
+
{
|
|
1045
|
+
"from": "step1",
|
|
1046
|
+
"to": "step2"
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
"from": "step2",
|
|
1050
|
+
"to": "step3"
|
|
1051
|
+
}
|
|
1052
|
+
],
|
|
1053
|
+
"panels": {
|
|
1054
|
+
"flow": [
|
|
1055
|
+
{
|
|
1056
|
+
"summary": "新增入口",
|
|
1057
|
+
"detail": "用户现在可以直接进入对应流程,不用再自己找路径。"
|
|
1058
|
+
}
|
|
1059
|
+
],
|
|
1060
|
+
"function": [
|
|
1061
|
+
{
|
|
1062
|
+
"summary": "优化说明",
|
|
1063
|
+
"detail": "用户先看到新增、修复、优化这类短摘要,再决定是否继续细读。"
|
|
1064
|
+
}
|
|
1065
|
+
],
|
|
1066
|
+
"guardrail": [
|
|
1067
|
+
{
|
|
1068
|
+
"summary": "调整边界",
|
|
1069
|
+
"detail": "只保留用户需要知道的限制、影响和下一步。"
|
|
1070
|
+
}
|
|
1071
|
+
],
|
|
1072
|
+
"risk": [
|
|
1073
|
+
{
|
|
1074
|
+
"summary": "修复误判",
|
|
1075
|
+
"detail": "避免把实现授权写成再次索取确认。"
|
|
1076
|
+
}
|
|
1077
|
+
]
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
"rules": [
|
|
1082
|
+
{
|
|
1083
|
+
"id": "review-map-card-text",
|
|
1084
|
+
"area": "需求关系图 / 需求流程图",
|
|
1085
|
+
"target": "图中每个卡片的正文",
|
|
1086
|
+
"maxChars": 30,
|
|
1087
|
+
"action": "请写入 reviewPresentation.mapNodes.*.text 或 reviewPresentation.flowNodes[].text,重写成用户一眼能扫懂的短句,不要靠省略号或截断。"
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
"id": "review-map-card-title",
|
|
1091
|
+
"area": "需求关系图 / 需求流程图",
|
|
1092
|
+
"target": "图中卡片标题胶囊",
|
|
1093
|
+
"maxChars": 15,
|
|
1094
|
+
"action": "请写入 reviewPresentation.mapNodes.*.title,重写成短标题,优先使用业务词,不使用内部技术词。"
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
"id": "review-highlight-chip",
|
|
1098
|
+
"area": "四个评审卡片",
|
|
1099
|
+
"target": "重点摘要胶囊",
|
|
1100
|
+
"maxChars": 15,
|
|
1101
|
+
"action": "请重写成短标签,优先使用 新增 / 修复 / 优化 / 调整 / 移除 这类用户能一眼看懂的动作词,再补必要对象。"
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
"id": "review-panel-detail-format",
|
|
1105
|
+
"area": "四个评审卡片",
|
|
1106
|
+
"target": "明细分点",
|
|
1107
|
+
"format": "- **摘要内容**:明细一句话",
|
|
1108
|
+
"action": "请写入 reviewPresentation.panels.<kind>[],把每个明细改写为“加粗短摘要 + 一句话说明”,短摘要优先使用 新增 / 修复 / 优化 / 调整 / 移除。"
|
|
1109
|
+
}
|
|
1110
|
+
]
|
|
1111
|
+
},
|
|
1112
|
+
"presentationFeedback": [],
|
|
1113
|
+
"exportedAt": "2026-06-05T00:44:22.420Z"
|
|
1114
|
+
}" title="openprd review . --mark confirmed --version 'v0002' --digest 'e44eccd0d6fe1a7fd962ece31187f920e1a71de76122d15b826fb0a66d934d35' --work-unit 'wu-20260602113956-a99b5b88'">
|
|
1115
|
+
认可并继续
|
|
1116
|
+
</button>
|
|
1117
|
+
</div>
|
|
1118
|
+
</nav>
|
|
1119
|
+
|
|
1120
|
+
<script>
|
|
1121
|
+
async function copyReviewText(text) {
|
|
1122
|
+
try {
|
|
1123
|
+
await navigator.clipboard.writeText(text);
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
const textarea = document.createElement('textarea');
|
|
1126
|
+
textarea.value = text;
|
|
1127
|
+
textarea.setAttribute('readonly', '');
|
|
1128
|
+
textarea.style.position = 'fixed';
|
|
1129
|
+
textarea.style.left = '-9999px';
|
|
1130
|
+
document.body.appendChild(textarea);
|
|
1131
|
+
textarea.select();
|
|
1132
|
+
document.execCommand('copy');
|
|
1133
|
+
textarea.remove();
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
function flashCopied(button) {
|
|
1137
|
+
const old = button.innerHTML;
|
|
1138
|
+
button.textContent = '已复制';
|
|
1139
|
+
setTimeout(() => { button.innerHTML = old; }, 1200);
|
|
1140
|
+
}
|
|
1141
|
+
document.querySelectorAll('[data-copy-value]').forEach((button) => {
|
|
1142
|
+
button.addEventListener('click', async () => {
|
|
1143
|
+
await copyReviewText(button.dataset.copyValue || '');
|
|
1144
|
+
flashCopied(button);
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
</script>
|
|
1148
|
+
</main>
|
|
1149
|
+
</body>
|
|
1150
|
+
</html>
|