chore: save current workspace progress

This commit is contained in:
梁薄云
2026-08-09 22:13:18 +08:00
parent 650c2ab0e3
commit 2f4fd15e52
449 changed files with 76593 additions and 971 deletions
@@ -0,0 +1,55 @@
# EMPlanner 架构梳理与 LS 优化启动日报
> 日期:2026-08-04
> 模块:emplanner
> 证据原则:已验证事实、静态分析、对话发现和待验证风险必须分开表达。
## 1. 今日结论摘要
已依据 EMPlanner 当前说明和代码目录明确轨迹规划的分层架构与模块边界;LS 横向优化为当前开展方向,尚未在本日报中宣称完成或通过专项回归。
## 2. 今日完成的工作
- **确定 EMPlanner 轨迹规划分层架构**:明确从请求快照、方向段/Frenet 投影和静态走廊,到 LS、ST、轨迹装配、世界空间复核及上层滚动执行的职责划分。(证据:ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md
- **明确规划器与执行侧边界**:EMPlanner 仅产出经校验的时间轨迹;调度、轨迹交接、换向执行和控制命令由 TrajectoryExecution 承担。(证据:ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md、ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md
- **梳理 LS 优化的接口边界**LS 使用 l、dl、ddl、dddl 变量,在静态走廊与车辆几何约束下求解;求解器通过 IQpSolver/QuadraticProgram 抽象接入。(证据:ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md、ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/
## 3. 今日发现的问题
### 3.1 LS 横向优化处于开展阶段 `ls-optimization-in-progress`
- **证据等级**:对话发现
- **实际现象**:当前已明确 LS 位于静态走廊之后、ST 之前,并承担横向路径优化与独立几何复核;本日报未取得本轮专项优化结果或回归输出。
- **正确预期**:在不破坏静态走廊、导数、Frenet 分母和车辆曲率硬约束的前提下,形成可独立复核的横向候选轨迹。
- **原因或当前假设**:优化参数、目标权重、收敛策略及专项场景的本轮验证证据尚待补充。
- **影响**:LS 结果直接决定后续 ST 使用的实际 PathS 与轨迹可发布性,需先完成可行性和稳定性验证。
## 4. 问题如何被发现及证据
### ls-optimization-in-progress · LS 横向优化处于开展阶段
用户说明当前正在开展 LS 优化工作。
| 证据 | 引用 | 结果 |
| --- | --- | --- |
| LS 架构说明 | ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md | 文档定义 LS SQP、独立横向几何复核及其与 ST 的数据边界。 |
| 当前工作说明 | 用户需求(2026-08-04) | 当前开展 LS 优化工作。 |
## 5. 已采取的改善和验证结果
- **ls-optimization-in-progress / 架构与接口静态核对 / 通过**README 与 Lateral 目录表明 LS 的数据流、变量及求解器边界已定义;未执行 LS 专项运行测试。
- **EMPlanner 架构静态核对 / 通过**:核对 README、Lateral 目录和当日 EMPlanner 提交记录;本次未运行构建或自动化测试。
## 6. 尚未解决的风险与下一步
- **ls-optimization-in-progress**:确定 LS 优化目标、权重和约束调参方案。
- **ls-optimization-in-progress**:针对典型前进、倒车及走廊边界场景执行专项回归。
- **P0 / emplanner**:细化并实现 LS 优化策略;完成标志:横向优化目标、约束和候选筛选策略形成可审查实现。
- **P1 / emplanner**:执行 LS 专项回归;完成标志:典型前进、倒车和窄走廊场景均有可复核的运行结果。
- **P1 / emplanner**:联调 LS 到 ST 的 PathS 输入;完成标志:ST 仅消费 LS 已复核且严格递增的实际 PathS,并保留验证记录。
## 7. 证据索引
- **EMPlanner 架构说明**ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md
- **当日 EMPlanner 提交记录**git log --since='2026-08-04 00:00' -- ClumsyPilot/ParkrobTrajplanner/EMPlanner
- **LS/ST 观测计划**docs/superpowers/plans/2026-08-04-em-observation-movementtest.md
@@ -0,0 +1,419 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>开发工作日报|交互式问题与改善说明</title>
<style> :root {
color-scheme: light;
--ink: #142532;
--muted: #60717c;
--paper: #f3f6f5;
--panel: #ffffff;
--line: #ccd7d7;
--teal: #0f6b70;
--teal-soft: #dff1ef;
--green: #24784c;
--green-soft: #e6f4ea;
--amber: #955d12;
--amber-soft: #fff0d7;
--red: #ad3541;
--red-soft: #fbe8ea;
--blue: #285f91;
--blue-soft: #e3eff9;
--shadow: 0 16px 44px rgba(27, 52, 60, .08);
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--paper); color: var(--ink); font: 16px/1.65 system-ui, -apple-system, "Segoe UI", sans-serif; }
button { font: inherit; }
button:focus-visible { outline: 3px solid #e7a628; outline-offset: 3px; }
#daily-summary-app { width: min(1180px, 100%); margin: 0 auto; padding: clamp(18px, 4vw, 44px); }
.hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 20px; align-items: end; border-bottom: 2px solid var(--ink); padding-bottom: 22px; }
.eyebrow { margin: 0 0 5px; color: var(--teal); font-weight: 800; letter-spacing: .06em; }
h1, h2, h3, p { margin-top: 0; }
h1 { margin-bottom: 8px; font-size: clamp(1.8rem, 4vw, 2.65rem); line-height: 1.18; }
h2 { font-size: clamp(1.25rem, 2.8vw, 1.65rem); line-height: 1.3; }
h3 { font-size: 1.04rem; }
.subtitle { max-width: 790px; margin-bottom: 0; color: var(--muted); }
.date-chip { border: 1px solid var(--line); border-radius: 7px; padding: 7px 10px; background: var(--panel); font-weight: 800; white-space: nowrap; }
.overview { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin: 22px 0; }
.stat { border-top: 4px solid var(--teal); padding: 12px 4px 8px; }
.stat:nth-child(2) { border-color: var(--red); }
.stat:nth-child(3) { border-color: var(--green); }
.stat:nth-child(4) { border-color: var(--amber); }
.stat strong { display: block; font-size: 1.8rem; line-height: 1.1; }
.stat span { color: var(--muted); }
.workbench { display: grid; grid-template-columns: minmax(220px, 290px) minmax(0, 1fr); gap: 18px; align-items: start; }
.navigator, .problem-lab { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); }
.navigator { padding: 16px; }
.navigator h2 { margin-bottom: 10px; }
.evidence-filter-list, .issue-list, .solution-steps { display: grid; gap: 8px; }
.evidence-filter-list { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-bottom: 16px; }
.evidence-filter, .issue-button, .solution-step, .before-after-toggle, .step-control { min-height: 42px; border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; color: var(--ink); background: var(--panel); cursor: pointer; text-align: left; }
.evidence-filter[aria-pressed="true"], .issue-button[aria-pressed="true"], .solution-step[aria-pressed="true"], .before-after-toggle[aria-pressed="true"] { border-color: var(--teal); color: #fff; background: var(--teal); }
.issue-button small { display: block; margin-top: 2px; opacity: .76; }
.problem-lab { overflow: hidden; }
.problem-heading { padding: 20px clamp(16px, 3vw, 28px) 12px; border-bottom: 1px solid var(--line); }
.badge { display: inline-flex; align-items: center; gap: 6px; margin-bottom: 7px; border: 1px solid currentColor; border-radius: 999px; padding: 3px 9px; font-size: .84rem; font-weight: 800; }
.badge::before { content: "◆"; font-size: .72em; }
.badge[data-level="已验证"] { color: var(--green); background: var(--green-soft); }
.badge[data-level="静态分析"] { color: var(--amber); background: var(--amber-soft); }
.badge[data-level="对话发现"] { color: var(--blue); background: var(--blue-soft); }
.badge[data-level="待验证风险"], .badge[data-level="结论冲突"] { color: var(--red); background: var(--red-soft); }
.problem-body { padding: 18px clamp(16px, 3vw, 28px) 26px; }
.comparison { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.compare-panel { border-left: 4px solid var(--red); padding: 4px 0 4px 13px; }
.compare-panel.expected { border-color: var(--green); }
.compare-panel p { margin-bottom: 0; color: var(--muted); }
.before-after-toggle { margin: 14px 0; text-align: center; }
.cause-flow { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px; margin: 24px 0; }
.cause-node { position: relative; min-height: 126px; border-top: 4px solid var(--blue); padding: 12px 4px 4px; }
.cause-node:not(:last-child)::after { position: absolute; top: 45px; right: -14px; content: "→"; color: var(--muted); font-weight: 800; }
.cause-node p { margin-bottom: 0; color: var(--muted); }
.concept-note { border-left: 4px solid var(--amber); padding: 9px 12px; color: #6e4813; background: var(--amber-soft); }
.solution-layout { display: grid; grid-template-columns: minmax(180px, .7fr) minmax(0, 1.3fr); gap: 16px; margin-top: 24px; }
.solution-detail { min-height: 150px; border-left: 1px solid var(--line); padding-left: 18px; }
.solution-detail p { color: var(--muted); }
.step-controls { display: flex; gap: 8px; margin-top: 12px; }
.step-control { text-align: center; }
.validation-section, .roadmap-section { padding: 30px 0; border-bottom: 1px solid var(--line); }
.validation-gates { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.validation-gate { border-top: 4px solid var(--line); padding: 12px 4px 4px; }
.validation-gate[data-status="通过"], .validation-gate[data-status="完成"] { border-color: var(--green); }
.validation-gate[data-status="失败"], .validation-gate[data-status="阻断"] { border-color: var(--red); }
.validation-gate[data-status="待验证"] { border-color: var(--amber); }
.validation-gate p { margin-bottom: 0; color: var(--muted); }
.roadmap { display: grid; gap: 10px; }
.roadmap-item { display: grid; grid-template-columns: 70px minmax(0, 1fr); gap: 12px; border-bottom: 1px solid var(--line); padding: 10px 0; }
.roadmap-item strong:first-child { color: var(--teal); }
.roadmap-item p { margin-bottom: 0; color: var(--muted); }
.empty { color: var(--muted); font-style: italic; }
footer { padding-top: 22px; color: var(--muted); font-size: .9rem; }
@media (max-width: 820px) {
.hero, .workbench, .solution-layout { grid-template-columns: 1fr; }
.date-chip { justify-self: start; }
.overview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.navigator { box-shadow: none; }
.issue-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.solution-detail { border-top: 1px solid var(--line); border-left: 0; padding-top: 16px; padding-left: 0; }
}
@media (max-width: 560px) {
.overview, .comparison, .cause-flow, .validation-gates, .issue-list { grid-template-columns: 1fr; }
.cause-node:not(:last-child)::after { top: auto; right: auto; bottom: -17px; left: 50%; content: "↓"; }
.roadmap-item { grid-template-columns: 52px minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { scroll-behavior: auto !important; transition: none !important; animation: none !important; }
}
</style>
</head>
<body>
<main id="daily-summary-app" data-selected-issue="">
<header class="hero">
<div>
<p class="eyebrow">开发日报 · 问题与改善交互说明</p>
<h1 id="report-title">今日工作总结</h1>
<p id="report-summary" class="subtitle"></p>
</div>
<span id="report-date" class="date-chip"></span>
</header>
<section class="overview" aria-label="今日工作总览">
<div class="stat"><strong id="achievement-count">0</strong><span>完成事项</span></div>
<div class="stat"><strong id="issue-count">0</strong><span>发现问题</span></div>
<div class="stat"><strong id="validation-count">0</strong><span>验证记录</span></div>
<div class="stat"><strong id="next-count">0</strong><span>下一步</span></div>
</section>
<div class="workbench">
<aside class="navigator">
<h2>选择问题</h2>
<div id="evidence-filters" class="evidence-filter-list" aria-label="按证据等级筛选">
<button type="button" class="evidence-filter" data-level="全部" aria-pressed="true">全部</button>
</div>
<div id="issue-list" class="issue-list"></div>
</aside>
<section class="problem-lab" aria-live="polite">
<div class="problem-heading">
<span id="issue-evidence" class="badge" data-level="对话发现">对话发现</span>
<h2 id="issue-title">暂无问题</h2>
<p id="issue-meta" class="subtitle"></p>
</div>
<div class="problem-body">
<button id="before-after-toggle" type="button" class="before-after-toggle" aria-pressed="false">切换为改善后视图</button>
<div class="comparison">
<article class="compare-panel">
<h3 id="actual-label">实际发生</h3>
<p id="issue-actual"></p>
</article>
<article class="compare-panel expected">
<h3>正确预期</h3>
<p id="issue-expected"></p>
</article>
</div>
<div id="cause-flow" class="cause-flow" aria-label="问题发现与原因链">
<article class="cause-node"><h3>问题发现</h3><p id="issue-discovery"></p></article>
<article class="cause-node"><h3>实际现象</h3><p id="issue-symptom"></p></article>
<article class="cause-node"><h3>原因或假设</h3><p id="issue-cause"></p></article>
<article class="cause-node"><h3>影响范围</h3><p id="issue-impact"></p></article>
</div>
<p id="concept-note" class="concept-note">概念示意:没有数值证据时不绘制虚构比例。</p>
<div class="solution-layout">
<div>
<h3>改善步骤</h3>
<div id="solution-steps" class="solution-steps"></div>
</div>
<article class="solution-detail">
<h3 id="solution-title">尚未记录改善</h3>
<p id="solution-result">当前只保留问题和下一步,不把候选方案写成已完成。</p>
<div class="step-controls">
<button id="previous-solution" type="button" class="step-control">上一步</button>
<button id="next-solution" type="button" class="step-control">下一步</button>
</div>
</article>
</div>
</div>
</section>
</div>
<section class="validation-section">
<h2>验证门</h2>
<div id="validation-gates" class="validation-gates"></div>
</section>
<section class="roadmap-section">
<h2>下一步路线图</h2>
<div id="roadmap" class="roadmap"></div>
</section>
<footer>本页与 Markdown 主报告来自同一结构化事实源。键盘左右方向键可切换问题。</footer>
<template id="issue-button-template"><button type="button" class="issue-button" aria-pressed="false"><span></span><small></small></button></template>
<template id="evidence-filter-template"><button type="button" class="evidence-filter" aria-pressed="false"></button></template>
<template id="solution-step-template"><button type="button" class="solution-step" aria-pressed="false"></button></template>
<template id="validation-gate-template"><article class="validation-gate"><h3></h3><strong></strong><p></p></article></template>
<template id="roadmap-item-template"><article class="roadmap-item"><strong></strong><div><h3></h3><p></p></div></article></template>
</main>
<script id="report-data" type="application/json">{"date":"2026-08-04","title":"EMPlanner 架构梳理与 LS 优化启动日报","summary":"已依据 EMPlanner 当前说明和代码目录明确轨迹规划的分层架构与模块边界;LS 横向优化为当前开展方向,尚未在本日报中宣称完成或通过专项回归。","modules":["emplanner"],"achievements":[{"title":"确定 EMPlanner 轨迹规划分层架构","detail":"明确从请求快照、方向段/Frenet 投影和静态走廊,到 LS、ST、轨迹装配、世界空间复核及上层滚动执行的职责划分。","evidence":["ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md"]},{"title":"明确规划器与执行侧边界","detail":"EMPlanner 仅产出经校验的时间轨迹;调度、轨迹交接、换向执行和控制命令由 TrajectoryExecution 承担。","evidence":["ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md","ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md"]},{"title":"梳理 LS 优化的接口边界","detail":"LS 使用 l、dl、ddl、dddl 变量,在静态走廊与车辆几何约束下求解;求解器通过 IQpSolver/QuadraticProgram 抽象接入。","evidence":["ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md","ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/"]}],"issues":[{"id":"ls-optimization-in-progress","title":"LS 横向优化处于开展阶段","module":"emplanner","evidence_level":"对话发现","discovery":"用户说明当前正在开展 LS 优化工作。","actual":"当前已明确 LS 位于静态走廊之后、ST 之前,并承担横向路径优化与独立几何复核;本日报未取得本轮专项优化结果或回归输出。","expected":"在不破坏静态走廊、导数、Frenet 分母和车辆曲率硬约束的前提下,形成可独立复核的横向候选轨迹。","cause":"优化参数、目标权重、收敛策略及专项场景的本轮验证证据尚待补充。","impact":"LS 结果直接决定后续 ST 使用的实际 PathS 与轨迹可发布性,需先完成可行性和稳定性验证。","improvements":[],"validation":[{"name":"架构与接口静态核对","status":"通过","detail":"README 与 Lateral 目录表明 LS 的数据流、变量及求解器边界已定义;未执行 LS 专项运行测试。"}],"next_steps":["确定 LS 优化目标、权重和约束调参方案。","针对典型前进、倒车及走廊边界场景执行专项回归。"],"evidence":[{"label":"LS 架构说明","reference":"ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md","result":"文档定义 LS SQP、独立横向几何复核及其与 ST 的数据边界。"},{"label":"当前工作说明","reference":"用户需求(2026-08-04","result":"当前开展 LS 优化工作。"}],"conceptual_visual":true}],"validations":[{"name":"EMPlanner 架构静态核对","status":"通过","detail":"核对 README、Lateral 目录和当日 EMPlanner 提交记录;本次未运行构建或自动化测试。"}],"next_steps":[{"priority":"P0","module":"emplanner","action":"细化并实现 LS 优化策略","done_when":"横向优化目标、约束和候选筛选策略形成可审查实现。"},{"priority":"P1","module":"emplanner","action":"执行 LS 专项回归","done_when":"典型前进、倒车和窄走廊场景均有可复核的运行结果。"},{"priority":"P1","module":"emplanner","action":"联调 LS 到 ST 的 PathS 输入","done_when":"ST 仅消费 LS 已复核且严格递增的实际 PathS,并保留验证记录。"}],"sources":[{"label":"EMPlanner 架构说明","reference":"ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md"},{"label":"当日 EMPlanner 提交记录","reference":"git log --since='2026-08-04 00:00' -- ClumsyPilot/ParkrobTrajplanner/EMPlanner"},{"label":"LS/ST 观测计划","reference":"docs/superpowers/plans/2026-08-04-em-observation-movementtest.md"}]}</script>
<script>'use strict';
globalThis.DailySummaryVisuals = (() => {
const registry = new Map();
function register(name, renderer) {
if (!/^[a-z0-9][a-z0-9-]+$/.test(name) || typeof renderer !== 'function') {
throw new TypeError('invalid visualization adapter');
}
registry.set(name, renderer);
}
function select(name) {
return registry.get(name) || registry.get('composite-scene');
}
function render(view, root, context) {
const renderer = select(view.adapter);
if (!renderer) throw new Error('composite-scene adapter is not registered');
return renderer(view, root, context);
}
return { register, select, render };
})();
</script>
<script> 'use strict';
const data = JSON.parse(document.getElementById('report-data').textContent);
const app = document.getElementById('daily-summary-app');
const issues = Array.isArray(data.issues) ? data.issues : [];
let filteredIssues = issues.slice();
let selectedIssueId = filteredIssues[0]?.id || '';
let selectedSolutionIndex = 0;
let showingAfter = false;
function setText(id, value) {
document.getElementById(id).textContent = value == null || value === '' ? '无已记录项' : String(value);
}
function countIssueValidations() {
return issues.reduce((total, issue) => total + (issue.validation?.length || 0), 0);
}
function renderHeader() {
setText('report-title', data.title);
setText('report-summary', data.summary);
setText('report-date', data.date);
setText('achievement-count', data.achievements?.length || 0);
setText('issue-count', issues.length);
setText('validation-count', (data.validations?.length || 0) + countIssueValidations());
setText('next-count', data.next_steps?.length || 0);
}
function renderFilters() {
const root = document.getElementById('evidence-filters');
const levels = ['全部', ...new Set(issues.map((issue) => issue.evidence_level))];
root.replaceChildren();
levels.forEach((level, index) => {
const button = document.getElementById('evidence-filter-template').content.firstElementChild.cloneNode(true);
button.textContent = level;
button.dataset.level = level;
button.setAttribute('aria-pressed', String(index === 0));
button.addEventListener('click', () => applyFilter(level, button));
root.appendChild(button);
});
}
function applyFilter(level, activeButton) {
document.querySelectorAll('.evidence-filter').forEach((button) => button.setAttribute('aria-pressed', String(button === activeButton)));
filteredIssues = level === '全部' ? issues.slice() : issues.filter((issue) => issue.evidence_level === level);
selectedIssueId = filteredIssues.some((issue) => issue.id === selectedIssueId) ? selectedIssueId : (filteredIssues[0]?.id || '');
renderIssueList();
renderSelectedIssue();
}
function renderIssueList() {
const root = document.getElementById('issue-list');
root.replaceChildren();
filteredIssues.forEach((issue) => {
const button = document.getElementById('issue-button-template').content.firstElementChild.cloneNode(true);
button.dataset.issueId = issue.id;
button.querySelector('span').textContent = issue.title;
button.querySelector('small').textContent = `${issue.evidence_level} · ${issue.module}`;
button.setAttribute('aria-pressed', String(issue.id === selectedIssueId));
button.addEventListener('click', () => selectIssue(issue.id));
root.appendChild(button);
});
if (!filteredIssues.length) {
const empty = document.createElement('p');
empty.className = 'empty';
empty.textContent = '该证据等级下没有问题。';
root.appendChild(empty);
}
}
function selectIssue(issueId) {
selectedIssueId = issueId;
selectedSolutionIndex = 0;
showingAfter = false;
renderIssueList();
renderSelectedIssue();
}
function currentIssue() {
return issues.find((issue) => issue.id === selectedIssueId);
}
function renderSelectedIssue() {
const issue = currentIssue();
app.dataset.selectedIssue = issue?.id || '';
if (!issue) {
setText('issue-title', '暂无问题');
setText('issue-meta', '当前筛选条件没有可展示的问题。');
return;
}
const badge = document.getElementById('issue-evidence');
badge.textContent = issue.evidence_level;
badge.dataset.level = issue.evidence_level;
setText('issue-title', issue.title);
setText('issue-meta', `${issue.id} · ${issue.module}`);
setText('issue-discovery', issue.discovery);
setText('issue-symptom', issue.actual);
setText('issue-cause', issue.cause);
setText('issue-impact', issue.impact);
setText('issue-actual', showingAfter ? improvementSummary(issue) : issue.actual);
setText('actual-label', showingAfter ? '改善后状态' : '实际发生');
setText('issue-expected', issue.expected);
const toggle = document.getElementById('before-after-toggle');
toggle.setAttribute('aria-pressed', String(showingAfter));
toggle.textContent = showingAfter ? '返回改善前视图' : '切换为改善后视图';
document.getElementById('concept-note').hidden = issue.conceptual_visual === false;
renderSolutions(issue);
renderValidations(issue);
renderRoadmap(issue);
}
function improvementSummary(issue) {
if (!issue.improvements?.length) return '尚未记录已完成改善;保持原问题状态。';
return issue.improvements.map((item) => `${item.status}${item.action}${item.result}`).join('');
}
function renderSolutions(issue) {
const steps = issue.improvements?.length ? issue.improvements : issue.next_steps.map((step) => ({ action: step, result: '待执行与验证', status: '下一步' }));
const root = document.getElementById('solution-steps');
root.replaceChildren();
steps.forEach((step, index) => {
const button = document.getElementById('solution-step-template').content.firstElementChild.cloneNode(true);
button.textContent = `${index + 1}. ${step.action}`;
button.setAttribute('aria-pressed', String(index === selectedSolutionIndex));
button.addEventListener('click', () => { selectedSolutionIndex = index; renderSolutions(issue); });
root.appendChild(button);
});
const selected = steps[selectedSolutionIndex];
setText('solution-title', selected?.action || '尚未记录改善');
setText('solution-result', selected ? `${selected.status} · ${selected.result}` : '当前只保留问题,不虚构解决结果。');
document.getElementById('previous-solution').disabled = !selected || selectedSolutionIndex === 0;
document.getElementById('next-solution').disabled = !selected || selectedSolutionIndex === steps.length - 1;
}
function renderValidations(issue) {
const values = [...(issue.validation || []), ...(data.validations || [])];
const root = document.getElementById('validation-gates');
root.replaceChildren();
values.forEach((item) => {
const gate = document.getElementById('validation-gate-template').content.firstElementChild.cloneNode(true);
gate.dataset.status = item.status;
gate.querySelector('h3').textContent = item.name;
gate.querySelector('strong').textContent = item.status;
gate.querySelector('p').textContent = item.detail;
root.appendChild(gate);
});
if (!values.length) root.appendChild(emptyNode('尚未记录验证结果。'));
}
function renderRoadmap(issue) {
const root = document.getElementById('roadmap');
root.replaceChildren();
const issueSteps = (issue.next_steps || []).map((action) => ({ priority: '问题', module: issue.module, action, done_when: '完成专门验证后关闭。' }));
[...issueSteps, ...(data.next_steps || [])].forEach((item) => {
const row = document.getElementById('roadmap-item-template').content.firstElementChild.cloneNode(true);
row.querySelector('strong').textContent = item.priority;
row.querySelector('h3').textContent = `${item.module} · ${item.action}`;
row.querySelector('p').textContent = `完成标志:${item.done_when}`;
root.appendChild(row);
});
if (!root.children.length) root.appendChild(emptyNode('尚未记录下一步。'));
}
function emptyNode(message) {
const paragraph = document.createElement('p');
paragraph.className = 'empty';
paragraph.textContent = message;
return paragraph;
}
function moveIssue(offset) {
if (!filteredIssues.length) return;
const index = Math.max(0, filteredIssues.findIndex((issue) => issue.id === selectedIssueId));
const next = (index + offset + filteredIssues.length) % filteredIssues.length;
selectIssue(filteredIssues[next].id);
}
document.getElementById('before-after-toggle').addEventListener('click', () => { showingAfter = !showingAfter; renderSelectedIssue(); });
document.getElementById('previous-solution').addEventListener('click', () => { if (selectedSolutionIndex > 0) { selectedSolutionIndex -= 1; renderSelectedIssue(); } });
document.getElementById('next-solution').addEventListener('click', () => { const issue = currentIssue(); const count = issue?.improvements?.length || issue?.next_steps?.length || 0; if (selectedSolutionIndex < count - 1) { selectedSolutionIndex += 1; renderSelectedIssue(); } });
document.addEventListener('keydown', (event) => {
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.target.matches('button, input, select, textarea, [contenteditable="true"]')) return;
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
event.preventDefault();
moveIssue(event.key === 'ArrowLeft' ? -1 : 1);
}
});
renderHeader();
renderFilters();
renderIssueList();
renderSelectedIssue();
</script>
</body>
</html>