dsh-session-rollover 0.0.0-stage → 1.2.0

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 (69) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +201 -0
  3. package/LICENSE.upstream +21 -0
  4. package/NOTICE +9 -0
  5. package/README.md +157 -2
  6. package/assets/icon.png +0 -0
  7. package/assets/icon.svg +7 -0
  8. package/client.js +656 -0
  9. package/compatibility/policy.json +21 -0
  10. package/compatibility/runtime-0.2.2-rc.1.json +32 -0
  11. package/cordis.patch.yml +9 -0
  12. package/docs/COMPATIBILITY.md +70 -0
  13. package/docs/DETAIL.md +44 -0
  14. package/docs/DETAIL.zh-CN.md +49 -0
  15. package/docs/DEVELOPMENT.md +104 -0
  16. package/docs/PUBLISHING.md +80 -0
  17. package/docs/architecture.md +71 -0
  18. package/docs/live-host-validation.json +37 -0
  19. package/docs/marketplace-validation.md +22 -0
  20. package/docs/prototype-validation.json +10 -0
  21. package/docs/r3-demo.html +981 -0
  22. package/docs/r3-validation.md +59 -0
  23. package/docs/screenshots/help-dark.png +0 -0
  24. package/docs/screenshots/help-light.png +0 -0
  25. package/docs/screenshots/host-archived.png +0 -0
  26. package/docs/screenshots/host-dark.png +0 -0
  27. package/docs/screenshots/host-rollover.png +0 -0
  28. package/docs/screenshots/prototype-fixed.png +0 -0
  29. package/index.js +2 -0
  30. package/locale/en.json +7 -0
  31. package/locale/zh.json +7 -0
  32. package/marketplace/Oak-liuhj__dsh-session-rollover.yml +7 -0
  33. package/marketplace/SUBMISSION.md +37 -0
  34. package/package.json +103 -4
  35. package/screenshots.json +6 -0
  36. package/scripts/build-client.mjs +12 -0
  37. package/scripts/check-dsh-compatibility.mjs +44 -0
  38. package/scripts/check-package.mjs +51 -0
  39. package/scripts/check-release.mjs +76 -0
  40. package/scripts/dsh-contracts.mjs +69 -0
  41. package/src/application/handoff-message.mjs +53 -0
  42. package/src/application/rollover.mjs +236 -0
  43. package/src/assembly.mjs +4 -0
  44. package/src/client/rollover-ui.js +311 -0
  45. package/src/client/toast-client.js +342 -0
  46. package/src/cluster.mjs +3 -0
  47. package/src/domain/cluster-index.mjs +65 -0
  48. package/src/domain/handoff.mjs +67 -0
  49. package/src/domain/inflight.mjs +25 -0
  50. package/src/domain/mode.mjs +34 -0
  51. package/src/domain/naming.mjs +78 -0
  52. package/src/domain/summary.mjs +68 -0
  53. package/src/domain/todos.mjs +23 -0
  54. package/src/domain/turns.mjs +43 -0
  55. package/src/handoff.mjs +2 -0
  56. package/src/inflight.mjs +2 -0
  57. package/src/mode.mjs +2 -0
  58. package/src/naming.mjs +2 -0
  59. package/src/notices.mjs +2 -0
  60. package/src/server/compatibility.mjs +84 -0
  61. package/src/server/host-adapter.mjs +146 -0
  62. package/src/server/notice-route.mjs +104 -0
  63. package/src/server/plugin.mjs +447 -0
  64. package/src/storage/index-store.mjs +48 -0
  65. package/src/storage/notices.mjs +117 -0
  66. package/src/storage/rollover-state.mjs +75 -0
  67. package/src/summary.mjs +2 -0
  68. package/src/todos.mjs +2 -0
  69. package/src/turns.mjs +2 -0
@@ -0,0 +1,981 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN" data-theme="light">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>DSH R3 优化验证版 · R2 右侧栏任务进度面板 / R3 会话分卷接续</title>
7
+ <style>
8
+ /* ============ 设计 token:取自 DSH Web GUI 实测值(dsh-web-frontend dist / TodoPanel.module.css / boot 样式) ============ */
9
+ :root{
10
+ --font-ui:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",Helvetica,Arial,sans-serif;
11
+ --font-code:"SF Mono","JetBrains Mono","Fira Code",Consolas,"Liberation Mono",Menlo,Courier,"PingFang SC","Microsoft YaHei",monospace;
12
+ --bg:#f7f8fa; --bg-side:#f2f3f5; --card:#ffffff; --card-2:#fafbfc;
13
+ --ink:#0f1115; --ink-2:#61666b; --ink-3:#6b7076;
14
+ --line:rgba(0,0,0,.10); --line-2:rgba(0,0,0,.06);
15
+ --accent:#4d6bfe; --accent-weak:rgba(77,107,254,.09); --accent-weak-2:rgba(77,107,254,.16);
16
+ --ok:#189a5d; --ok-weak:rgba(24,154,93,.10);
17
+ --warn:#b25e09; --warn-weak:rgba(178,94,9,.10);
18
+ --bad:#cf3038; --bad-weak:rgba(207,48,56,.09);
19
+ /* 文本安全色:小字号(≤12.5px)文字专用,保证 AA 4.5:1;填充/描边仍用上面的品牌色 */
20
+ --ink-3-txt:#6b7076; --accent-txt:#3d56d6; --ok-txt:#127a4a; --warn-txt:#a3540a;
21
+ --menu-bg:rgba(255,255,255,.88); --menu-blur:saturate(1.4) blur(14px);
22
+ --elev:0 2px 12px rgba(15,17,21,.07),0 0 .5px rgba(15,17,21,.18);
23
+ --r-sm:6px; --r-md:8px; --r-lg:10px; --r-xl:14px;
24
+ }
25
+ html[data-theme="dark"]{
26
+ --bg:#0f1115; --bg-side:#15171c; --card:#171a1f; --card-2:#1c1f25;
27
+ --ink:#f9fafb; --ink-2:#cfd3d6; --ink-3:#adb2b8;
28
+ --line:rgba(255,255,255,.12); --line-2:rgba(255,255,255,.07);
29
+ --accent:#6b85ff; --accent-weak:rgba(107,133,255,.13); --accent-weak-2:rgba(107,133,255,.22);
30
+ --ok:#3ecf8e; --ok-weak:rgba(62,207,142,.12);
31
+ --warn:#e3a354; --warn-weak:rgba(227,163,84,.13);
32
+ --bad:#f2727b; --bad-weak:rgba(242,114,123,.12);
33
+ --ink-3-txt:#adb2b8; --accent-txt:#95a8ff; --ok-txt:#5cd9a3; --warn-txt:#e8b06a;
34
+ --menu-bg:rgba(23,26,31,.9); --menu-blur:saturate(1.2) blur(14px);
35
+ --elev:0 2px 14px rgba(0,0,0,.5),0 0 .5px rgba(255,255,255,.14);
36
+ }
37
+ *{box-sizing:border-box}
38
+ html,body{margin:0;padding:0}
39
+ body{font:14px/22px var(--font-ui);background:var(--bg);color:var(--ink);-webkit-font-smoothing:antialiased}
40
+ button{font:inherit;color:inherit;background:none;border:none;cursor:pointer;padding:0}
41
+ ::-webkit-scrollbar{width:8px;height:8px} ::-webkit-scrollbar-thumb{background:var(--line);border-radius:8px}
42
+
43
+ /* ============ 原型外层框架 ============ */
44
+ .topbar{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:16px;padding:0 20px;height:52px;background:var(--menu-bg);backdrop-filter:var(--menu-blur);border-bottom:.5px solid var(--line)}
45
+ .topbar .logo{font-weight:600;font-size:14px;letter-spacing:.2px}
46
+ .topbar .logo small{color:var(--ink-3-txt);font-weight:400;margin-left:8px}
47
+ .screens{display:flex;gap:4px}
48
+ .screens button{padding:5px 12px;border-radius:99px;font-size:13px;color:var(--ink-2)}
49
+ .screens button.on{background:var(--accent-weak-2);color:var(--accent-txt);font-weight:500}
50
+ .screens button:hover{background:var(--accent-weak)}
51
+ .topbar .grow{flex:1}
52
+ .theme-btn{border:.5px solid var(--line);border-radius:99px;padding:4px 12px;font-size:12px;color:var(--ink-2)}
53
+ main{max-width:1240px;margin:0 auto;padding:20px 24px 80px}
54
+ section.screen{display:none}
55
+ section.screen.on{display:block}
56
+ h2.s-title{font-size:18px;font-weight:600;margin:6px 0 2px}
57
+ p.s-sub{color:var(--ink-2);font-size:13px;margin:0 0 14px}
58
+
59
+ /* 演示控制条 */
60
+ .demo-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 12px;margin:0 0 12px;border:.5px dashed var(--line);border-radius:var(--r-lg);background:var(--card-2)}
61
+ .demo-bar .lab{font-size:12px;color:var(--ink-3);margin-right:2px}
62
+ .demo-bar button{border:.5px solid var(--line);background:var(--card);border-radius:var(--r-md);padding:5px 11px;font-size:12.5px;color:var(--ink)}
63
+ .demo-bar button:hover{border-color:var(--accent);color:var(--accent)}
64
+ .demo-bar button.on{background:var(--accent);border-color:var(--accent);color:#fff}
65
+ .demo-bar .state-err{color:var(--bad);font-size:12px}
66
+
67
+ /* ============ DSH 窗口壳 ============ */
68
+ .dsh-window{display:flex;flex-direction:column;height:640px;border:.5px solid var(--line);border-radius:var(--r-xl);overflow:hidden;background:var(--card);box-shadow:var(--elev)}
69
+ .titlebar{height:34px;flex:none;display:flex;align-items:center;gap:6px;padding:0 12px;border-bottom:.5px solid var(--line-2);background:var(--card-2);font-size:12px;color:var(--ink-3)}
70
+ .titlebar .dot{width:11px;height:11px;border-radius:50%}
71
+ .body3{display:flex;flex:1;min-height:0}
72
+
73
+ /* 左侧栏 */
74
+ .side-l{width:218px;flex:none;border-right:.5px solid var(--line-2);background:var(--bg-side);display:flex;flex-direction:column;padding:10px 8px;gap:2px;overflow-y:auto}
75
+ .side-l .sec{font-size:11px;color:var(--ink-3);padding:10px 8px 4px}
76
+ .side-l .item{display:flex;flex-direction:column;gap:1px;width:100%;text-align:left;padding:7px 9px;border-radius:var(--r-md);font-size:13px;color:var(--ink-2);line-height:18px}
77
+ .side-l .item small{font-size:11px;color:var(--ink-3)}
78
+ .side-l .item:hover{background:var(--line-2)}
79
+ .side-l .item.on{background:var(--card);color:var(--ink);box-shadow:var(--elev)}
80
+ .side-l .item .nm{display:flex;align-items:center;gap:6px;min-width:0}
81
+ .side-l .item .nm b{font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
82
+ .badge-mini{flex:none;font-size:10px;line-height:14px;padding:0 5px;border-radius:99px;background:var(--accent-weak-2);color:var(--accent);font-weight:500}
83
+ .badge-mini.gray{background:var(--line-2);color:var(--ink-3)}
84
+ .badge-mini.warn{background:var(--warn-weak);color:var(--warn)}
85
+ .round-tick{font-size:11px;color:var(--ink-3);font-variant-numeric:tabular-nums}
86
+
87
+ /* 中栏会话 */
88
+ .chat{flex:1;min-width:0;display:flex;flex-direction:column;background:var(--card)}
89
+ .chat-head{flex:none;display:flex;align-items:center;gap:8px;padding:10px 18px;border-bottom:.5px solid var(--line-2)}
90
+ .chat-head .t{font-size:14px;font-weight:600}
91
+ .chat-head .chip{font-size:11px;color:var(--ink-3);border:.5px solid var(--line);border-radius:99px;padding:1px 8px}
92
+ .chat-body{flex:1;overflow-y:auto;padding:18px 22px;display:flex;flex-direction:column;gap:14px}
93
+ .msg{max-width:78%;display:flex;flex-direction:column;gap:3px}
94
+ .msg .who{font-size:11.5px;color:var(--ink-3)}
95
+ .msg .bub{padding:9px 13px;border-radius:var(--r-lg);font-size:13.5px;line-height:21px;border:.5px solid var(--line-2);background:var(--card-2)}
96
+ .msg.user{align-self:flex-end}
97
+ .msg.user .bub{background:var(--accent-weak);border-color:transparent}
98
+ .msg.sys{align-self:center;max-width:92%}
99
+ .msg.sys .bub{background:transparent;border:.5px dashed var(--line);color:var(--ink-3);font-size:12px}
100
+ /* 交接摘要卡 */
101
+ .rollover{border:.5px solid var(--line);border-radius:var(--r-lg);background:var(--menu-bg);box-shadow:var(--elev);overflow:hidden;font-size:13px}
102
+ .rollover .rh{display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:.5px solid var(--line-2);background:var(--accent-weak)}
103
+ .rollover .rh b{font-size:13.5px;font-weight:600}
104
+ .rollover .rh .sp{flex:1}
105
+ .rollover .rh .src{font-size:11.5px;color:var(--ink-2)}
106
+ .rollover .rh a{font-size:11.5px;color:var(--accent);text-decoration:none}
107
+ .rollover .rsec{padding:9px 14px;border-bottom:.5px solid var(--line-2)}
108
+ .rollover .rsec:last-child{border-bottom:none}
109
+ .rollover .rsec .k{font-size:11px;color:var(--ink-3);letter-spacing:.3px;margin-bottom:2px}
110
+ .rollover .rsec .v{font-size:13px;color:var(--ink);line-height:20px}
111
+ .rollover .rsec .v .em{color:var(--accent);font-weight:600}
112
+ .rollover details{margin-top:4px}
113
+ .rollover summary{cursor:pointer;font-size:12px;color:var(--ink-2);user-select:none}
114
+ .rollover pre{font-family:var(--font-code);font-size:11.5px;line-height:17px;background:var(--bg-side);border:.5px solid var(--line-2);border-radius:var(--r-md);padding:8px 10px;overflow:auto;margin:6px 0 0;max-height:160px}
115
+ .hl-step{display:inline-block;background:var(--accent-weak-2);color:var(--accent);border-radius:var(--r-sm);padding:0 6px;font-weight:600}
116
+ /* 输入框区 */
117
+ .composer-wrap{flex:none;padding:10px 18px 14px;border-top:.5px solid var(--line-2);position:relative;background:var(--card)}
118
+ /* R2-08 旧位置面板(输入框上方 dock) */
119
+ .legacy-dock{width:calc(100% - 0px);max-width:640px;margin:0 auto 8px;border-radius:var(--r-lg);background:var(--menu-bg);backdrop-filter:var(--menu-blur);box-shadow:var(--elev);padding:6px 12px;display:none}
120
+ .legacy-dock.show{display:block}
121
+ .legacy-dock .h{display:flex;align-items:center;gap:8px;cursor:pointer}
122
+ .legacy-dock .h .tt{font-size:13px;font-weight:500;line-height:24px}
123
+ .legacy-dock .h .pg{flex:1;min-width:0;font-size:13px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
124
+ .legacy-dock .h .cv{color:var(--ink-3)}
125
+ .composer{max-width:640px;margin:0 auto;border:.5px solid var(--line);border-radius:var(--r-xl);background:var(--card);box-shadow:var(--elev);padding:10px 14px;display:flex;align-items:center;gap:10px}
126
+ .composer input{flex:1;border:none;outline:none;background:none;color:var(--ink);font:13.5px var(--font-ui)}
127
+ .composer .send{width:26px;height:26px;border-radius:50%;background:var(--accent);color:#fff;display:grid;place-items:center;font-size:13px}
128
+ .composer.disabled{opacity:.55}
129
+ .ro-banner{max-width:640px;margin:0 auto 8px;display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--warn);background:var(--warn-weak);border-radius:var(--r-md);padding:6px 10px}
130
+ .ro-banner .sw{margin-left:auto;font-size:11.5px;border:.5px solid var(--warn);color:var(--warn);border-radius:99px;padding:2px 9px}
131
+
132
+ /* 右侧栏 */
133
+ .side-r{width:302px;flex:none;border-left:.5px solid var(--line-2);background:var(--bg-side);display:flex;flex-direction:column;min-height:0}
134
+ .r-tabs{flex:none;display:flex;align-items:center;gap:2px;padding:8px 8px 0}
135
+ .r-tabs button{display:flex;align-items:center;gap:5px;font-size:12.5px;color:var(--ink-2);padding:6px 10px;border-radius:var(--r-md) var(--r-md) 0 0;border:.5px solid transparent;border-bottom:none}
136
+ .r-tabs button:hover{background:var(--line-2)}
137
+ .r-tabs button.on{background:var(--card);color:var(--ink);font-weight:500;border-color:var(--line-2)}
138
+ .r-tabs button svg{width:14px;height:14px}
139
+ .r-body{flex:1;min-height:0;overflow:hidden;background:var(--card);border-top:.5px solid var(--line-2);display:flex;flex-direction:column}
140
+ .r-pane{display:none;flex-direction:column;min-height:0;flex:1;overflow:hidden}
141
+ .r-pane.on{display:flex}
142
+ .r-pane>*{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}
143
+ .r-ph{padding:26px 18px;color:var(--ink-3);font-size:12.5px;line-height:20px}
144
+ /* 任务进度面板(R2 核心) */
145
+ .tp{padding:14px 14px 16px;display:flex;flex-direction:column;gap:12px;flex:1;min-height:0}
146
+ .tp-head .row1{display:flex;align-items:center;gap:6px}
147
+ .tp-head .tt{font-size:13px;font-weight:500;line-height:24px;white-space:nowrap}
148
+ .tp-head .cnt{margin-left:auto;font-size:12.5px;color:var(--ink-2);font-variant-numeric:tabular-nums;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
149
+ .tp-head .pct{font-size:12px;color:var(--ink-3);width:34px;text-align:right;font-variant-numeric:tabular-nums}
150
+ .tp-bar{height:5px;border-radius:99px;background:var(--line-2);overflow:hidden;margin-top:8px;position:relative}
151
+ .tp-bar i{display:block;height:100%;background:var(--accent);border-radius:99px;transition:width .5s ease}
152
+ .tp-meta{display:flex;align-items:center;gap:6px;font-size:11.5px;color:var(--ink-3);margin-top:6px}
153
+ .tp-cur{font-size:12px;color:var(--ink-2);background:var(--accent-weak);border-radius:var(--r-md);padding:5px 9px;line-height:18px}
154
+ .tp-cur b{color:var(--accent);font-weight:600}
155
+ .tp-chips{display:flex;gap:6px;flex-wrap:wrap}
156
+ .tp-chips button{font-size:11.5px;color:var(--ink-2);border:.5px solid var(--line);border-radius:99px;padding:2px 9px;background:var(--card)}
157
+ .tp-chips button.on{background:var(--accent-weak-2);border-color:transparent;color:var(--accent);font-weight:500}
158
+ .tp-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;flex:1;min-height:0;overflow-y:auto}
159
+ .tp-item{display:flex;align-items:flex-start;gap:8px;padding:6px 8px;border-radius:var(--r-md);border:.5px solid transparent}
160
+ .tp-item .ix{flex:none;width:16px;font-size:11px;color:var(--ink-3);text-align:right;line-height:20px;font-variant-numeric:tabular-nums}
161
+ .tp-item .glyph{flex:none;width:16px;height:16px;display:grid;place-items:center;margin-top:2px}
162
+ .tp-item .ct{flex:1;min-width:0;font-size:13px;line-height:20px;color:var(--ink-2)}
163
+ .tp-item.completed .ct{color:var(--ink-3)}
164
+ .tp-item.current{background:var(--accent-weak);border-color:var(--accent-weak-2)}
165
+ .tp-item.current .ct{color:var(--ink)}
166
+ .dot{width:9px;height:9px;border-radius:50%}
167
+ .dot.idle{background:none;border:1.5px solid var(--ink-3)}
168
+ .dot.ongoing{background:var(--accent);box-shadow:0 0 0 0 var(--accent-weak-2);animation:pulse 1.6s infinite}
169
+ .dot.done{background:var(--ok)}
170
+ .dot.interrupt{background:none;border:1.5px solid var(--warn)}
171
+ @keyframes pulse{0%{box-shadow:0 0 0 0 rgba(77,107,254,.25)}70%{box-shadow:0 0 0 5px rgba(77,107,254,0)}100%{box-shadow:0 0 0 0 rgba(77,107,254,0)}}
172
+ .tag{display:inline-block;font-size:10.5px;line-height:15px;padding:0 5px;border-radius:4px;font-weight:500;margin-left:6px;vertical-align:1px;white-space:nowrap}
173
+ .tag.doing{background:var(--accent-weak-2);color:var(--accent-txt)}
174
+ .tag.done{background:var(--ok-weak);color:var(--ok-txt)}
175
+ .tag.pending{background:var(--line-2);color:var(--ink-3-txt)}
176
+ .tag.interrupt{background:var(--warn-weak);color:var(--warn-txt)}
177
+ .tag.carry{background:var(--ok-weak);color:var(--ok-txt)}
178
+ /* 小字号(≤12.5px)文本统一走 AA 文本安全色,大字号与图标仍用原色 */
179
+ .tp-meta,.tp-head .pct,.tp-item .ix,.tp-folded-hint,.tp-empty,.r-ph,.tp-item .sub,.legacy-dock .h .pg,.round-tick,.doc .muted{color:var(--ink-3-txt)}
180
+ .tp-chips button.on,.tp-cur b,.tp-carry-chip a,.rollover .rh a,.rollover .rsec .v .em,.tp-empty .g code,.badge-mini{color:var(--accent-txt)}
181
+ /* 中断提示卡(R2-07) */
182
+ .tp-interrupt{display:flex;gap:8px;align-items:flex-start;border:.5px solid var(--warn);background:var(--warn-weak);border-radius:var(--r-lg);padding:10px 12px;font-size:12.5px;line-height:19px;color:var(--ink)}
183
+ .tp-interrupt b{color:var(--warn)}
184
+ .tp-interrupt .btn{margin-top:6px;display:inline-block;border:.5px solid var(--warn);color:var(--warn);border-radius:99px;font-size:11.5px;padding:2px 10px}
185
+ /* 空态(R2-05) */
186
+ .tp-empty{margin:auto;text-align:center;color:var(--ink-3);font-size:12.5px;line-height:20px;padding:30px 24px}
187
+ .tp-empty svg{opacity:.5;margin-bottom:10px}
188
+ .tp-empty .g{margin-top:8px;font-size:11.5px;color:var(--ink-3);background:var(--card-2);border:.5px dashed var(--line);border-radius:var(--r-md);padding:8px 10px;text-align:left}
189
+ .tp-empty .g code{font-family:var(--font-code);font-size:11px;color:var(--accent)}
190
+ .tp-carry-chip{font-size:11.5px;color:var(--ink-2);background:var(--card-2);border:.5px solid var(--line-2);border-radius:var(--r-md);padding:6px 9px;line-height:18px}
191
+ .tp-carry-chip a{color:var(--accent);text-decoration:none}
192
+ .tp-done-card{text-align:center;padding:18px 10px;color:var(--ink-2);font-size:12.5px}
193
+ .tp-done-card .big{font-size:26px;color:var(--ok);line-height:40px}
194
+ /* 可展开/收起:面板头折叠 + 任务项展开详情 */
195
+ .tp-head .cv{flex:none;width:16px;height:16px;display:grid;place-items:center;color:var(--ink-3);cursor:pointer;border-radius:4px;transition:transform .18s}
196
+ .tp-head .cv:hover{background:var(--line-2);color:var(--ink)}
197
+ .tp-head .cv.fold{transform:rotate(-90deg)}
198
+ .tp-head .more{flex:none;width:20px;height:20px;display:grid;place-items:center;color:var(--ink-3);cursor:pointer;border-radius:4px;font-size:14px;line-height:1;margin-left:2px}
199
+ .tp-head .more:hover{background:var(--line-2);color:var(--ink)}
200
+ .tp.folded .tp-bar,.tp.folded .tp-meta,.tp.folded .tp-cur,.tp.folded .tp-chips,.tp.folded .tp-list,.tp.folded .tp-done-card{display:none}
201
+ .tp.folded .tp-folded-hint{display:block}
202
+ .tp-folded-hint{display:none;font-size:11.5px;color:var(--ink-3);padding:4px 0 0}
203
+ .tp{position:relative}
204
+ .tp-item{position:relative;flex-wrap:wrap}
205
+ .tp-item .exp{flex:none;width:14px;height:14px;margin-top:3px;display:grid;place-items:center;color:var(--ink-3);opacity:.55;transition:transform .18s;cursor:pointer}
206
+ .tp-item.open .exp{transform:rotate(90deg);opacity:1;color:var(--accent)}
207
+ .tp-item .ct{cursor:pointer}
208
+ .tp-item .sub{display:none;flex-basis:100%;margin:3px 0 2px 26px;padding:6px 8px;border-left:2px solid var(--line);font-size:11.5px;line-height:18px;color:var(--ink-3)}
209
+ .tp-item.open .sub{display:block}
210
+ .tp-item .sub b{color:var(--ink-2);font-weight:500}
211
+ .tp-item .sub .kv{display:flex;gap:6px}
212
+ .tp-item .sub .kv span:first-child{flex:none;color:var(--ink-3);opacity:.8;min-width:44px}
213
+ .tp-item.open{background:var(--card-2);border-color:var(--line)}
214
+ /* 可达性:图标按钮的命中区补到 24×24(视觉尺寸不变),并给出键盘焦点环 */
215
+ .tp-head .cv,.tp-head .more,.tp-item .exp{position:relative}
216
+ .tp-head .cv::after{content:"";position:absolute;inset:-4px}
217
+ .tp-head .more::after{content:"";position:absolute;inset:-2px}
218
+ .tp-item .exp::after{content:"";position:absolute;inset:-5px}
219
+ :focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-sm)}
220
+ @media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;transition-duration:.01ms!important}}
221
+ /* 面板头 ⋯ 菜单(B12 手动换会话 / 快照 / 兼容) */
222
+ .tp-menu{position:absolute;right:8px;left:8px;top:44px;z-index:40;min-width:0;max-width:none;background:var(--card);border:.5px solid var(--line);border-radius:var(--r-lg);box-shadow:0 8px 26px rgba(0,0,0,.14);padding:5px;backdrop-filter:blur(8px)}
223
+ [data-theme="dark"] .tp-menu{box-shadow:0 8px 26px rgba(0,0,0,.5)}
224
+ .tp-menu .mi{display:flex;align-items:center;gap:8px;width:100%;text-align:left;font-size:12.5px;line-height:18px;color:var(--ink);padding:6px 8px;border-radius:var(--r-md);background:none}
225
+ .tp-menu .mi:hover{background:var(--card-2)}
226
+ .tp-menu .mi .k{margin-left:auto;font-size:10.5px;color:var(--ink-3)}
227
+ .tp-menu .sep{height:.5px;background:var(--line);margin:4px 6px}
228
+ .tp-menu .hint{font-size:11px;color:var(--ink-3);padding:4px 8px 6px;line-height:16px}
229
+
230
+ /* 屏幕标注 chips */
231
+ .map-chips{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 26px}
232
+ .map-chips span{font-size:11.5px;color:var(--ink-2);border:.5px solid var(--line);background:var(--card);border-radius:99px;padding:2px 10px}
233
+ .map-chips span b{color:var(--accent-txt);font-weight:600}
234
+
235
+ /* 总览页 */
236
+ .ov-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:14px}
237
+ .ov-card{border:.5px solid var(--line);border-radius:var(--r-xl);background:var(--card);padding:18px 20px;box-shadow:var(--elev)}
238
+ .ov-card h3{margin:0 0 6px;font-size:15px}
239
+ .ov-card p{margin:0;font-size:13px;color:var(--ink-2);line-height:21px}
240
+ .ov-card .go{margin-top:10px;display:inline-block;font-size:12.5px;color:var(--accent)}
241
+ .ov-note{grid-column:1/-1;border:.5px dashed var(--line);border-radius:var(--r-lg);background:var(--card-2);padding:12px 16px;font-size:12.5px;color:var(--ink-2);line-height:20px}
242
+
243
+ /* 界面说明页 */
244
+ .doc h3{font-size:15px;margin:26px 0 8px}
245
+ .doc table{width:100%;border-collapse:collapse;font-size:12.8px;background:var(--card);border:.5px solid var(--line);border-radius:var(--r-lg);overflow:hidden}
246
+ .doc th,.doc td{border:.5px solid var(--line-2);padding:7px 10px;text-align:left;vertical-align:top;line-height:19px}
247
+ .doc th{background:var(--card-2);font-weight:600;color:var(--ink)}
248
+ .doc td b{color:var(--accent-txt)}
249
+ .doc .warnrow td{background:var(--warn-weak)}
250
+ .lockchip{display:inline-block;vertical-align:2px;margin-left:7px;font-size:10px;line-height:15px;font-weight:500;letter-spacing:.02em;color:var(--ok-txt);background:var(--ok-weak);border-radius:4px;padding:0 5px}
251
+ .base-lock{border:.5px solid var(--line);border-left:3px solid var(--accent);border-radius:var(--r-lg);background:var(--card-2);padding:12px 14px;margin:12px 0 14px;font-size:12.5px;line-height:20px;color:var(--ink-2)}
252
+ .base-lock p{margin:0 0 8px}
253
+ .base-lock p:last-child{margin:0}
254
+ .base-lock b{color:var(--ink)}
255
+ .base-lock code{font-family:var(--font-code);font-size:11.5px;background:var(--line-2);border-radius:4px;padding:1px 5px}
256
+ .doc p{font-size:13px;color:var(--ink-2);line-height:21px}
257
+ .kbd{font-family:var(--font-code);font-size:11.5px;background:var(--line-2);border-radius:4px;padding:1px 5px}
258
+ /* toast 日志 */
259
+ .logbox{position:fixed;right:18px;bottom:18px;z-index:99;width:330px;display:flex;flex-direction:column;gap:6px}
260
+ .logbox .l{background:var(--card);border:.5px solid var(--line);box-shadow:var(--elev);border-radius:var(--r-lg);padding:8px 12px;font-size:12px;line-height:18px;color:var(--ink-2);animation:slide .25s ease}
261
+ .logbox .l b{color:var(--ink)}
262
+ .logbox .l.acc b{color:var(--accent)}
263
+ @keyframes slide{from{transform:translateY(8px);opacity:0}to{transform:none;opacity:1}}
264
+ .muted{color:var(--ink-3)}
265
+ .small{font-size:12px}
266
+ @media (max-width:1180px){ .side-l{width:180px} .side-r{width:272px} }
267
+
268
+ /* R3 优化:卡片不参与压缩,展开后的内容由聊天区滚动承接。 */
269
+ .rollover{flex-shrink:0;overflow:visible}
270
+ .rollover.summary-collapsed>.rsec{display:none}
271
+ .rollover.summary-collapsed>.rh+.rsec{display:block}
272
+ .rollover.summary-collapsed>.rh+.rsec .v{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
273
+ .summary-toggle{display:block;color:var(--accent-txt);padding:8px 14px;min-height:28px}
274
+ .rollover .rh{flex-wrap:wrap;border-radius:10px 10px 0 0}
275
+ .tp-menu[hidden]{display:none}
276
+ .legacy-dock ul{max-height:140px;overflow:auto;padding-left:18px;margin:6px 0;font-size:12px}
277
+ .tp-item .sub .kv span:first-child{opacity:1;color:var(--ink-3-txt)}
278
+ .tp-item .sub{color:var(--ink-3-txt)}
279
+ .tp-item .sub span[style*="opacity"]{opacity:1!important}
280
+ #logbox{max-height:160px;overflow:auto;max-width:min(400px,calc(100vw - 32px))}
281
+ </style>
282
+ </head>
283
+ <body>
284
+
285
+ <header class="topbar">
286
+ <div class="logo">DSH 设计基线 v1.0 <span class="lockchip">已冻结</span><small>R2 右侧栏任务进度面板 · R3 会话分卷接续 |依据需求规格说明书 v1.2(11-04)|已并入 11-04 九条决议</small></div>
287
+ <div class="grow"></div>
288
+ <nav class="screens" id="navScreens">
289
+ <button data-s="ov" class="on">总览</button>
290
+ <button data-s="r2">屏1 · 右侧栏进度面板</button>
291
+ <button data-s="r3">屏2 · 会话分卷接续</button>
292
+ <button data-s="doc">界面说明</button>
293
+ </nav>
294
+ <button class="theme-btn" id="themeBtn">切换深色</button>
295
+ </header>
296
+
297
+ <main>
298
+
299
+ <!-- ================= 总览 ================= -->
300
+ <section class="screen on" id="scr-ov">
301
+ <h2 class="s-title">两条 DSH 需求的可交互原型</h2>
302
+ <p class="s-sub">单文件 HTML,浏览器直接打开。视觉取 DSH Web GUI 现行设计语言(13px 列表 / 0.5px 细边 / 大圆角浮层 / 浅灰侧栏),演示数据为虚构。</p>
303
+ <div class="ov-grid">
304
+ <div class="ov-card">
305
+ <h3>屏 1 · 右侧栏「任务进度」面板(R2)</h3>
306
+ <p>在右侧栏新增 tab(席位 <code>sidebar.right.pane.tab</code>),复用 todos 投影渲染待办:完成 n / 共 m、百分比、当前步高亮、四态标签(待办/进行中/完成/中断)、空态引导、中断标记、旧位置兼容开关。</p>
307
+ <button class="go" onclick="go('r2')">打开屏 1 →</button>
308
+ </div>
309
+ <div class="ov-card">
310
+ <h3>屏 2 · 会话自动分卷与进度续接(R3 优化验证版)</h3>
311
+ <p>模拟「巡检」会话跑满 10 轮:改名「巡检-1」→ 新开「巡检-2」→ 八段交接摘要 + 待办快照注入首条消息 → 右侧栏自动加载「第 4 步进行中」进度条并续跑;含 -1 只读、命名冲突、同簇上限等边界演示。</p>
312
+ <button class="go" onclick="go('r3')">打开屏 2 →</button>
313
+ </div>
314
+ <div class="ov-card">
315
+ <h3>界面说明</h3>
316
+ <p>每屏元素 ↔ 需求点编号(R2-01…)/ 验收标准(AC-R2-x、AC-R3-x)对照表;<b>11-04 已确认的 9 条决议</b>与落地位置;仍未定项(Q1 费率、D-1′ 徽章时长、D-2′ 重启按钮)。</p>
317
+ <button class="go" onclick="go('doc')">打开说明 →</button>
318
+ </div>
319
+ <div class="ov-card">
320
+ <h3>原型纪律</h3>
321
+ <p>面板<b>只读</b>:不提供勾选 / 编辑 / 拖拽(R2-10 Won't,避免第二真相)。数据源仅 todos 投影,字段口径与 <code>progressLabel</code> 完全一致;并行 in_progress 全部高亮(本机预设 allowParallelInProgress=true)。</p>
322
+ </div>
323
+ <div class="ov-note">
324
+ 现状依据(只读核对,不改宿主):右侧栏 tab 席位 <span class="kbd">sidebar.right.pane.tab</span>(dsh-client-ui-sidebar-right);输入框上方旧待办面板为 <span class="kbd">conversation.input.dock</span> 的 TodoPanel(dsh-client-ui-conversation)——屏 1 的列表项样式即按该组件 CSS 复刻后右移放大。
325
+ </div>
326
+ </div>
327
+ </section>
328
+
329
+ <!-- ================= 屏 1:R2 右侧栏面板 ================= -->
330
+ <section class="screen" id="scr-r2">
331
+ <h2 class="s-title">屏 1 · 右侧栏「任务进度」面板(R2)</h2>
332
+ <p class="s-sub">下方为 DSH 工作区仿真。用演示控制条切换面板五种状态,或点「▶ 模拟实时推帧」观察 ≤2s 更新。</p>
333
+
334
+ <div class="demo-bar">
335
+ <span class="lab">面板状态:</span>
336
+ <button data-r2="default" class="on">默认(tab 不自动展开)</button>
337
+ <button data-r2="empty">空态</button>
338
+ <button data-r2="doing">进行中(含并行)</button>
339
+ <button data-r2="interrupt">中断</button>
340
+ <button data-r2="done">完成</button>
341
+ <span class="lab" style="margin-left:10px">其他:</span>
342
+ <button id="r2-stream">▶ 模拟实时推帧</button>
343
+ <button id="r2-legacy">兼容开关:同时显示输入框上方旧面板</button>
344
+ </div>
345
+
346
+ <div class="dsh-window">
347
+ <div class="titlebar"><span class="dot" style="background:#ff5f57"></span><span class="dot" style="background:#febc2e"></span><span class="dot" style="background:#28c840"></span><span style="margin-left:8px">DeepSeek Harness · 原型仿真(非真实运行)</span></div>
348
+ <div class="body3">
349
+ <aside class="side-l">
350
+ <div class="sec">Bots</div>
351
+ <button class="item"><span class="nm"><b>资深UI/UX设计师</b></span><small>原型 v0.1 · 本文件</small></button>
352
+ <button class="item"><span class="nm"><b>需求分析师</b></span><small>v1.2 已交付</small></button>
353
+ <div class="sec">会话</div>
354
+ <button class="item on"><span class="nm"><b>每日站点巡检</b></span><small>进行中 · todos 活跃</small></button>
355
+ <button class="item"><span class="nm"><b>插件市场周报</b></span><small>3 天前</small></button>
356
+ </aside>
357
+
358
+ <div class="chat">
359
+ <div class="chat-head"><span class="t">每日站点巡检</span><span class="chip">todo_write 投影活跃</span><span class="chip">本轮 4 轮</span></div>
360
+ <div class="chat-body">
361
+ <div class="msg user"><span class="who">用户</span><div class="bub">对 8 个站点做每日巡检,检查首页可用性、接口延迟、日志异常,汇总成报表。</div></div>
362
+ <div class="msg"><span class="who">巡检 Bot</span><div class="bub">收到。我把任务拆成 8 步待办并逐步执行,进度实时同步到右侧栏「任务进度」面板。</div></div>
363
+ <div class="msg"><span class="who">巡检 Bot</span><div class="bub">已完成站点 1–3 的检查;「拉取站点 4 首页快照」与「拉取站点 5 首页快照」并行进行中。</div></div>
364
+ </div>
365
+ <div class="composer-wrap">
366
+ <div class="legacy-dock" id="r2-legacy-dock">
367
+ <div class="h"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M2 3.5h12M2 8h12M2 12.5h8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg><span class="tt">待办</span><span class="pg" id="r2-legacy-pg"></span><span class="cv">⌃</span></div>
368
+ </div>
369
+ <div class="composer"><input placeholder="给 Bot 发消息…(原型示意,不真实发送)"><span class="send">↑</span></div>
370
+ </div>
371
+ </div>
372
+
373
+ <aside class="side-r">
374
+ <div class="r-tabs" id="r2-tabs">
375
+ <button data-t="files"><svg viewBox="0 0 16 16" fill="none"><path d="M4 1.5h5.5L13 5v9.5H4z" stroke="currentColor" stroke-width="1.2"/><path d="M9 1.5V5h4" stroke="currentColor" stroke-width="1.2"/></svg>文件</button>
376
+ <button data-t="terminal"><svg viewBox="0 0 16 16" fill="none"><rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" stroke-width="1.2"/><path d="M4 6l3 2.5L4 11M8.5 11.5H12" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>终端</button>
377
+ <button data-t="todo" class="on"><svg viewBox="0 0 16 16" fill="none"><path d="M2 3.5h12M2 8h12M2 12.5h8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>任务进度<span class="badge-mini" id="r2-tab-badge" style="display:none">●</span></button>
378
+ </div>
379
+ <div class="r-body">
380
+ <div class="r-pane" data-p="files"><div class="r-ph">「文件」标签页——现有功能占位,保持原样,不在此原型范围。</div></div>
381
+ <div class="r-pane" data-p="terminal"><div class="r-ph">「终端」标签页——现有功能占位,保持原样,不在此原型范围。</div></div>
382
+ <div class="r-pane on" data-p="todo">
383
+ <div id="r2-panel"></div>
384
+ </div>
385
+ </div>
386
+ </aside>
387
+ </div>
388
+ </div>
389
+ <div class="map-chips">
390
+ <span><b>R2-01</b> tab 注册,默认不自动展开(切到「进行中」前徽章提示)</span>
391
+ <span><b>R2-02</b> 复用 TodoPanel 视觉右移放大</span>
392
+ <span><b>R2-03</b> 完成 n/共 m + 百分比 + 当前步高亮</span>
393
+ <span><b>R2-04</b> 四态标签</span>
394
+ <span><b>R2-05</b> 空态引导</span>
395
+ <span><b>R2-06</b> 实时推帧 ≤2s</span>
396
+ <span><b>R2-07</b> 中断标记</span>
397
+ <span><b>R2-08</b> 兼容开关</span>
398
+ <span><b>表9</b> 并行 in_progress 全高亮,当前步取第一个</span>
399
+ <span><b>决议1</b> 任务项与面板均可展开/收起</span>
400
+ <span><b>决议7</b> ⋯ 菜单「现在换会话」(B12)</span>
401
+ </div>
402
+ </section>
403
+
404
+ <!-- ================= 屏 2:R3 分卷接续 ================= -->
405
+ <section class="screen" id="scr-r3">
406
+ <h2 class="s-title">屏 2 · 会话到轮次自动分卷与进度续接(R3)</h2>
407
+ <p class="s-sub">点「▶ 模拟一轮」推进轮次计数;跑满 10 轮后自动触发分卷。阈值 10 轮(Q3 已确认:一条用户消息 = 1 轮;轮次标签按「上轮标题 -N 轮」表述)。演示为压缩节奏:从第 9 轮起跑。</p>
408
+
409
+ <div class="demo-bar">
410
+ <button id="r3-round">▶ 模拟一轮</button>
411
+ <button id="r3-carry" disabled>▶ 在「巡检-2」继续一轮</button>
412
+ <button id="r3-conflict">边界演示:命名冲突(B7)</button>
413
+ <button id="r3-reset">↺ 重置演示</button>
414
+ <span class="lab" style="margin-left:8px">会话视角:</span>
415
+ <button id="r3-view2" class="on" disabled>巡检-2(当前)</button>
416
+ <button id="r3-view1" disabled>巡检-1(归档只读)</button>
417
+ <span class="lab" style="margin-left:8px">轮次:</span>
418
+ <b id="r3-round-n" style="font-variant-numeric:tabular-nums">9 / 10</b>
419
+ </div>
420
+
421
+ <div class="dsh-window">
422
+ <div class="titlebar"><span class="dot" style="background:#ff5f57"></span><span class="dot" style="background:#febc2e"></span><span class="dot" style="background:#28c840"></span><span style="margin-left:8px">DeepSeek Harness · 原型仿真(非真实运行)</span></div>
423
+ <div class="body3">
424
+ <aside class="side-l">
425
+ <div class="sec">会话(本簇)</div>
426
+ <div id="r3-sessions"></div>
427
+ <div class="sec">其他会话</div>
428
+ <button class="item"><span class="nm"><b>插件市场周报</b></span><small>3 天前</small></button>
429
+ </aside>
430
+
431
+ <div class="chat">
432
+ <div class="chat-head" id="r3-chat-head"></div>
433
+ <div class="chat-body" id="r3-chat"></div>
434
+ <div class="composer-wrap" id="r3-composer-wrap"></div>
435
+ </div>
436
+
437
+ <aside class="side-r">
438
+ <div class="r-tabs">
439
+ <button data-t="files"><svg viewBox="0 0 16 16" fill="none"><path d="M4 1.5h5.5L13 5v9.5H4z" stroke="currentColor" stroke-width="1.2"/><path d="M9 1.5V5h4" stroke="currentColor" stroke-width="1.2"/></svg>文件</button>
440
+ <button data-t="todo" class="on"><svg viewBox="0 0 16 16" fill="none"><path d="M2 3.5h12M2 8h12M2 12.5h8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>任务进度</button>
441
+ </div>
442
+ <div class="r-body">
443
+ <div class="r-pane" data-p="files"><div class="r-ph">「文件」标签页——现有功能占位。</div></div>
444
+ <div class="r-pane on" data-p="todo"><div id="r3-panel"></div></div>
445
+ </div>
446
+ </aside>
447
+ </div>
448
+ </div>
449
+ <div class="map-chips">
450
+ <span><b>4.3</b> 阈值 10 轮 · 触发时机=回合结束后</span>
451
+ <span><b>表14</b> 旧→原标题-1 / 新→原标题-2,不嵌套</span>
452
+ <span><b>4.4/表15</b> 八段摘要+待办快照(人读+JSON 双份)</span>
453
+ <span><b>B3/AC-R3-3</b> 第 4 步进行中标出并续跑</span>
454
+ <span><b>B9/Q2</b> -1 默认只读+手动开关</span>
455
+ <span><b>B7/表14</b> 命名冲突序号递增并写日志</span>
456
+ <span><b>B11</b> 同簇上限 9 卷</span>
457
+ <span><b>R2</b> 进度条数据源与 R2 面板同一投影</span>
458
+ </div>
459
+ </section>
460
+
461
+ <!-- ================= 界面说明 ================= -->
462
+ <section class="screen doc" id="scr-doc">
463
+ <h2 class="s-title">界面说明</h2>
464
+ <p class="s-sub">原型每个元素对应哪条需求点 / 验收标准;第 3、4 节是 11-04 已确认的交互默认与 9 条决议落地对照。</p>
465
+
466
+ <h3>0. 基线冻结声明(v1.0 · 2026-11-04)</h3>
467
+ <div class="base-lock">
468
+ <p><b>本文件即设计基线 v1.0,自 2026-11-04 冻结。</b>右侧栏 <code>sidebar.right.pane.tab</code> 形态是唯一主形态,屏 1 / 屏 2 的布局、文案、状态、交互默认即为开发实现口径;输入框上方旧 dock 只作为 R2-08 兼容开关存在,不再作为独立形态演进。</p>
469
+ <p><b>冲突时以本文件为准的方向只有一处</b>:UI 形态与交互。需求 v1.2 与 PRD v1.1 应回写条款与判据来对齐本文件,不能反过来要求原型改回底部抽屉。数值型判据(字数、卷数、命中区、对比度)以本文件与 <code>设计基线_R2R3_v1.0.md</code> 为准。</p>
470
+ <p><b>v0.1 → v1.0 只动了三类</b>:① 基线标识与冻结声明(本段);② WCAG 2.2 AA 可达性修订——状态标签改为随文案流内联、三处图标按钮命中区补到 24×24、状态/次要文字改用文本安全色(<code>--*-txt</code>,详见 §5);③ 无结构改动,屏数、组件、五态、九条决议落地位置均未变。<b>冲突清单全文</b>见同目录 <code>设计基线_R2R3_v1.0.md</code>,含每条的设计建议与验收判据;下表为摘要。</p>
471
+ </div>
472
+ <table>
473
+ <tr><th style="width:38px">#</th><th style="width:220px">冲突点</th><th>基线裁定</th><th style="width:190px">需回写到</th></tr>
474
+ <tr><td>C1</td><td>PRD Q7 底部抽屉 vs 右侧栏 tab</td><td>右侧栏 tab 为唯一主形态;<b>AC-R2-6「375px 窄屏、dock 展开 ≤60% 视口」判据作废</b>,改为「侧栏内部滚动、不遮挡中栏与输入框」</td><td>PRD v1.1 重写 Q7 与 AC-R2-6;需求 v1.2 删窄屏抽屉条款</td></tr>
475
+ <tr><td>C2</td><td>八段交接摘要 vs 需求「固定四段、≤1500 字」</td><td>四段 = 生成层契约(正文 ≤1500 字);八段 = 渲染层信息分组,⑦簇标识 / ⑧重试计数取自元数据不占字数,⑥快照 JSON 亦不计入 1500 字</td><td>需求 v1.2 补「四段→八段」映射表与字数口径</td></tr>
476
+ <tr><td>C3</td><td>-1 只读 / 同簇 9 卷上限 / 簇索引反向跳转:v1.1 无此三条</td><td>三条全部保留为新增条款:只读默认开 + 手动开关(与簇索引同落点,不写 session 元数据);第 9 卷后拒绝第 10 卷并写日志;来源 chip 按簇索引(<code>parent_session_id</code> / <code>root_session_id</code> / <code>vol_no</code>)只跳上一卷。索引存 <code>plugin-data/dsh-session-rollover/</code>,不丢靠三层:home 升级安全 + <code>rebuildFromTitles</code> 对账重建 + 不可写时 <code>cluster-index-missing</code> 降级</td><td>需求 v1.2 增条款与编号;数据模型加三个字段;存储与「无静默丢」口径按 PRD v1.1 §8⑧</td></tr>
477
+ <tr><td>C4</td><td>R2-08 兼容开关打开后的形态未定义</td><td>渲染旧 TodoPanel 完整形态(与屏 1 同源只读),不是一行摘要;默认关、状态持久化</td><td>需求 v1.2 / PRD v1.1 各补一句形态</td></tr>
478
+ <tr><td>C5</td><td>「默认不自动展开」缺可验收的在场语义</td><td>tab 常驻;有活跃 todos 才亮蓝点徽章;切 tab 不改变滚动位置;无 todos 不亮徽章</td><td>PRD v1.1 补一条 AC</td></tr>
479
+ <tr><td>C6</td><td>密度与可达性未达 AA(实测:文案列 125px / 命中区 14–20px / 三处对比度 3.6–4.3)</td><td>状态标签内联 + 修掉双层 <code>.tp</code>(文案列 125→192px,列表项 245→273px);列表去掉写死的 340px,改 <code>flex:1</code> 占满面板;三处命中区补 24×24;四处状态色改文本安全色</td><td>无(本文件已改);AC-R2-8 增「24×24 命中区、正文 4.5:1」</td></tr>
480
+ <tr><td>C7</td><td>一处待终裁(已闭环):B2 分卷前置校验——硬闸只设 in-flight=0,job 不闸但与快照同一时刻取全集、全数入交接第⑤段;另:R3 面板头是否复用 R2 同一组件(现原型 R3 只有 ⌄、无 ⋯)</td><td>B2 已由产品终裁采纳开发口径,<b>不新增「等待后台 job」第六态</b>;R3 应复用同一面板组件,⋯ 菜单随之继承</td><td>开发按此建测;R3 组件复用进 M1 设计核对</td></tr>
481
+ <tr><td>C8</td><td>段号三份文档三种叫法(本文件⑤段 / PRD ⑤段 / 开发七件套④项)</td><td>UI 顺序以本文件八段为准;契约与 PRD 引用写段名而不是只写编号</td><td>需求 v1.2 统一一次</td></tr>
482
+ </table>
483
+ <p class="s-sub"><b>不在基线内</b>:375px 窄屏抽屉形态(随 Q7 翻案作废,代码已从本文件移除)· 中断卡「一键重启任务」(建议不做,与只读纪律冲突)。完成徽章 30 分钟阈值已于 PRD v1.1 定为 <b>AC-R2-10(阈值可配)</b>,纳入基线。</p>
484
+ <p class="s-sub">引用口径:<b>AC 编号一律以需求 v1.2 为准</b>,跨文档引用带版本号;本页出现的 PRD 条款均为 <b>PRD v1.1</b>(11-04)。</p>
485
+
486
+ <h3>1. 屏 1 元素 ↔ R2 需求点</h3>
487
+ <table>
488
+ <tr><th>原型元素</th><th>需求点 / 验收标准</th><th>说明</th></tr>
489
+ <tr><td>右侧栏新增「任务进度」tab(与文件/终端并列)</td><td><b>R2-01</b> Must</td><td>注册席位 <code>sidebar.right.pane.tab</code>;打开 tab ≠ 面板自动展开,tab 上的小圆点徽章仅在有活跃 todos 时出现。</td></tr>
490
+ <tr><td>待办列表(步号+状态点+文案逐字)</td><td><b>R2-02 / R2-03 / AC-R2-2</b></td><td>复用 TodoPanel 的 16px glyph + 13px/20px 文案,右栏宽度内允许换行不截断(对齐“逐字一致、无截断”验收);content 不加工。</td></tr>
491
+ <tr><td>「完成 3 / 共 8」+ 百分比条 + 当前步行</td><td><b>R2-03 / AC-R2-3</b></td><td>done/active/pending 三数与 progressLabel 同口径;「当前第 X 步」取第一个 in_progress 的 index;百分比仅展示(44 进制向下取整)。</td></tr>
492
+ <tr><td>两个并行 in_progress 同时高亮脉冲</td><td><b>表9 active 行</b></td><td>本机预设 allowParallelInProgress=true → UI 必须高亮全部 in_progress,不假定唯一。</td></tr>
493
+ <tr><td>状态筛选 chips(全部/进行中/待办/完成)</td><td><b>R2-04 辅助</b></td><td>纯前端过滤,不改数据;「中断」不单列 chip(中断是会话级标记,不是单项状态)。</td></tr>
494
+ <tr><td>空态:「模型本轮未产生待办」+ 触发引导</td><td><b>R2-05 / AC-R2-4</b></td><td>禁止空白面板;投影为 null 时降级空态,不报错不白屏。</td></tr>
495
+ <tr><td>「▶ 模拟实时推帧」逐步流更新</td><td><b>R2-06 / AC-R2-1</b></td><td>真实实现订阅 session/projection 推帧(≤2s),原型用 900ms 节奏模拟。</td></tr>
496
+ <tr><td>中断提示卡(黄色)</td><td><b>R2-07 / AC-R2-5</b></td><td>三条件同时成立才置真:上一帧见 in_progress ∧ 投影清空 ∧ 本轮无新 todo/write。仅 null 不算证据。提供「查看上一轮快照」入口。</td></tr>
497
+ <tr><td>「完成」态大勾 + 归档文案</td><td><b>R2-04</b></td><td>全完成后投影仍在(todos 全 done),面板 30 分钟后收起为完成徽章(默认值,见拍板 D-1)。</td></tr>
498
+ <tr><td>「兼容开关」→ 输入框上方旧 TodoPanel 同屏出现</td><td><b>R2-08 Could / Q6</b></td><td>迁移期两处同渲染(同一数据源双视图);开关持久化,默认关。</td></tr>
499
+ <tr><td>任务项展开 / 收起(点文案或右侧 ⌄ 展开该步详情)</td><td><b>决议 1</b>(11-04)</td><td>每条任务可展开:状态 / 说明 / 产物路径 / 耗时。收起态仍是一行 13px 文案,不改变 R2-02 的逐字不截断。面板头 ⌄ 可整块收起为一行摘要。</td></tr>
500
+ <tr><td>面板头 ⋯ 菜单</td><td><b>决议 7(Q5/B12)· 决议 6(Q4)</b></td><td>「现在换会话 / 新建会话」按 R3 交接规则开新卷;「复制待办快照 JSON」给原样快照;「兼容:显示旧面板」等同 R2-08 开关;「收起面板」。</td></tr>
501
+ <tr><td>列表无勾选框/拖拽柄</td><td><b>R2-10 Won't</b></td><td>只读纪律:写操作一律回到会话里由模型 todo_write 整表更新。</td></tr>
502
+ </table>
503
+
504
+ <h3>2. 屏 2 元素 ↔ R3 规则与验收</h3>
505
+ <table>
506
+ <tr><th>原型元素 / 流程步</th><th>规则 / 验收标准</th><th>说明</th></tr>
507
+ <tr><td>左栏「巡检」轮次角标 9/10 → 10/10;会话头「上轮:巡检-1 第 10 轮 / 本卷:巡检-2 第 1 轮」</td><td><b>表14 轮次口径 / Q3 已确认</b></td><td>按确认口径:轮次标签用「上轮标题 -N 轮」表述——计数单位是一条用户消息=1 轮,agent 内部 turn 不计;新卷从第 1 轮重新起算,并在头部标出上一卷跑到第几轮。</td></tr>
508
+ <tr><td>回合结束→下一轮开始前触发分卷</td><td><b>表14 触发时机</b></td><td>原型里「▶ 模拟一轮」按钮点完(回合结束)即检查阈值;有后台 job 时等 job 结束或句柄入交接(表15-5)。</td></tr>
509
+ <tr><td>会话列表:巡检→巡检-1(灰显+只读徽章),插入巡检-2(选中)</td><td><b>4.3 / AC-R3-1 / AC-R3-6</b></td><td>序号从 1 起、同簇递增不嵌套;第三卷为 -3。角标「卷 N/9」体现上限 9 卷(B11)。</td></tr>
510
+ <tr><td>「巡检-2」首条消息 = 交接摘要卡(八段)</td><td><b>4.4 表15 / AC-R3-2</b></td><td>段:上轮工作摘要 / 当前工作 / 关键约束(原文摘录)/ 文件与代码 / 未竟后台 job / 待办快照(人读+JSON)/ 簇标识 / 失败重试计数。快照 JSON 可折叠直读,供 -2 续跑。</td></tr>
511
+ <tr><td>摘要卡内「第 4 步进行中」高亮</td><td><b>B3 / AC-R3-3</b></td><td>active 为 0 时改写「尚未开始」,不造假。</td></tr>
512
+ <tr><td>右侧栏面板自动出现 3/8 进度条 + 「承自巡检-1」来源 chip</td><td><b>R2×R3 接口</b></td><td>R3 续跑的进度就是 R2 面板的数据源(todos 投影快照),同一真相;来源 chip 提供反向跳转到 -1(簇标识 parentSessionId)。</td></tr>
513
+ <tr><td>「在 -2 继续一轮」→ 第 4 步完成、进度 4/8</td><td><b>4.1 目标④</b></td><td>模型在 -2 里自行续表(Q4 建议:原样快照带入,模型自行调整)。</td></tr>
514
+ <tr><td>切到「巡检-1」:输入框禁用 + 归档横幅 + 「允许再次写入」开关</td><td><b>B9 / AC-R3-7 / Q2</b></td><td>默认转只读保留历史全景;开关恢复写入(Q2 已确认:默认只读 + 手动放行)。</td></tr>
515
+ <tr><td>「命名冲突」演示:已存在巡检-2 → 新卷名称顺延为巡检-3(卷标仍记 卷 2/9)+ toast 写日志</td><td><b>B7 / 表14 冲突行 / 决议 5</b></td><td>名称递增到第一个空位;<b>簇内卷标仍记真实卷序</b>(名称 ≠ 卷序)。用户手动改过名则不覆盖、用内部序号并提示(B8,原型未演示,说明见 D-3)。</td></tr>
516
+ <tr><td>摘要失败重试(原型 toast 模拟)</td><td><b>B5 / AC-R3-4</b></td><td>生成失败不分卷、旧会话不动,记 summary-error 下轮重试 ≤2 次。</td></tr>
517
+ <tr><td>subagent 会话不触发分卷</td><td><b>B10</b></td><td>仅 owner 会话生效;原型以角标「不受分卷管理」示意,可省略。</td></tr>
518
+ </table>
519
+
520
+ <h3>3. 交互默认(11-04 已定稿)</h3>
521
+ <table>
522
+ <tr><th>#</th><th>分叉点</th><th>已定做法</th><th>说明</th></tr>
523
+ <tr><td>D-1</td><td>面板常驻 or 可开关</td><td>tab 常驻可开;<b>默认不自动展开</b>;有活跃 todos 时 tab 出现蓝点徽章;面板内任务项与整块均可展开/收起。</td><td>不打扰 + 可追溯;徽章是唯一的在场信号。</td></tr>
524
+ <tr><td>D-2</td><td>进度条粒度</td><td>细进度条 = 已完成步数 / 总步数(决议 4);不做环形、不做甘特、不按 token 折算。</td><td>与 progressLabel 同源,同一真相。</td></tr>
525
+ <tr><td>D-3</td><td>-N 命名冲突提示</td><td>名称顺延到第一个空位 + toast 一行写日志;<b>簇内卷标仍记真实卷序</b>;用户手动改名不覆盖。</td><td>名称是展示层,卷序是数据层,两者分开才不会把日志读错。</td></tr>
526
+ <tr><td>D-4</td><td>交接摘要卡位置</td><td>作为新卷首条 user/message 卡片渲染(八段、JSON 可折叠)。</td><td>表15 指定的去向;可见可追溯。</td></tr>
527
+ </table>
528
+
529
+ <h3>4. 11-04 决议对照表(9 条已确认,均已落进原型)</h3>
530
+ <table>
531
+ <tr><th>#</th><th>决议</th><th>原型落地位置</th><th>剩余动作</th></tr>
532
+ <tr><td>1</td><td>右侧面板默认不自动展开;面板内任务内容可展开/收起</td><td>屏 1:任务项点文案或 ⌄ 展开「状态/说明/产物/耗时」;面板头 ⌄ 整块收起;tab 徽章只在有活跃 todos 时亮</td><td>无</td></tr>
533
+ <tr><td>2</td><td>旧会话默认只读 + 手动开关放行(Q2)</td><td>屏 2:巡检-1 归档横幅 + 输入禁用 + 「允许再次写入」开关</td><td>无</td></tr>
534
+ <tr><td>3</td><td>轮次口径按「上轮标题 -N 轮」(Q3)</td><td>屏 2:会话头双 chip「上轮:巡检-1 第 10 轮」「本卷:巡检-2 第 1 轮」;左栏角标同步</td><td>若你要的是另一种读法(例如只显示累计总轮次),告我一句即可改</td></tr>
535
+ <tr><td>4</td><td>续接进度条按已完成步数比例,不按 token</td><td>屏 2 右侧栏:3/8=38% → 续跑后 4/8=50%</td><td>无</td></tr>
536
+ <tr><td>5</td><td>-N 冲突序号递增到空位 + 写日志,簇内卷标记真实卷序</td><td>屏 2「边界演示:命名冲突」:名称 巡检-3、卷标仍「卷 2/9」,toast 写日志</td><td>无</td></tr>
537
+ <tr><td>6</td><td>Q4:接力待办原样快照,模型自行调整</td><td>屏 2 交接卡第⑥段:人读清单 + 可折叠 JSON 原样快照</td><td>无</td></tr>
538
+ <tr><td>7</td><td>Q5/B12:要「现在换会话」手动按钮,放面板头部 ⋯ 菜单</td><td>屏 1 面板头 ⋯ 菜单第一项「现在换会话 / 新建会话」</td><td>无(真实实现走 R3 同一套交接)</td></tr>
539
+ <tr><td>8</td><td>Q6:旧位置面板保留一个版本作兼容开关</td><td>屏 1「兼容开关(R2-08)」按钮,也可从 ⋯ 菜单触发</td><td>无</td></tr>
540
+ <tr><td>9</td><td>Q8:插件改名 dsh-task-panel → dsh-todo-progress</td><td>说明与落地命名统一用 <code>dsh-todo-progress</code>,与 tab 文案「任务进度」对齐</td><td>发布前在插件清单里改名,旧名留一个 alias</td></tr>
541
+ </table>
542
+ <p class="s-sub" style="margin-top:8px">仍未定:Q1 费率(不阻塞)· D-1′ 完成徽章 30 分钟消失阈值 · D-2′ 中断卡是否加「一键重启任务」(建议不加,违反只读纪律)。</p>
543
+
544
+ <h3>5. 视觉与实现备注</h3>
545
+ <p>① 间距/字号/圆角/边框复刻自宿主实测 CSS(TodoPanel.module.css:13px/20px 列表、0.5px hairline、radius-lg 浮层、backdrop-filter 菜单底;boot 标签色 #0f1115 / #61666b / #81858c,浅底 #f9fafb)。品牌蓝 #4D6BFE 为 DeepSeek 通行品牌色,实际以宿主运行时 token(--dsw-alias-*)为准。② 深色模式为同 token 的暗色映射,正式实现应直接消费主题变量。③ 本原型零依赖零构建;正式实现为纯前端插件包<b> dsh-todo-progress</b>(R2)+ host 侧插件(R3),不改任何 @deepseek-ai 宿主包源码(AC-R2-8 / AC-R3-8)。④ 展开/收起只在视图层:任务项展开不改 todos 数据,面板收起不丢投影;真实实现里这两者都是本地 UI 状态,不写回会话。⑤ 列表高度<b>不写死</b>:v1.0 已去掉 340px 演示值,列表 <code>flex:1</code> 占满面板剩余高度、内部滚动,窗口变高自动多显示(302px 面板 / 561px 面板体下可视 355px、内容 406px:6 条完整 + 第 7 条半露)。⑥ 状态标签内联在文案流内,顺带修掉 <code>renderR2Panel</code> 生成双层 <code>.tp</code> 的 padding 翻倍问题(文案列 125→192px,列表项 245→273px);⌄ / ⌃ / ⋯ 三个图标按钮用 <code>::after</code> 把命中区补到 24×24,视觉尺寸不变;正文与状态文字统一走 <code>--*-txt</code> 文本安全色(≥4.5:1),品牌蓝只用于填充与描边。</p>
546
+ </section>
547
+ </main>
548
+
549
+ <div class="logbox" id="logbox"></div>
550
+
551
+ <script>
552
+ /* ================= 工具 ================= */
553
+ function el(html){const t=document.createElement('template');t.innerHTML=html.trim();return t.content.firstChild;}
554
+ function toast(msg,acc){const b=document.getElementById('logbox');const n=el('<div class="l'+(acc?' acc':'')+'">'+msg+'</div>');b.appendChild(n);setTimeout(()=>{n.style.transition='opacity .4s';n.style.opacity='0';setTimeout(()=>n.remove(),400);},4200);}
555
+ function esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;');}
556
+
557
+ /* ================= 屏幕切换 ================= */
558
+ document.getElementById('navScreens').addEventListener('click',e=>{const b=e.target.closest('button');if(b)go(b.dataset.s);});
559
+ function go(id){
560
+ document.querySelectorAll('.screens button').forEach(x=>x.classList.toggle('on',x.dataset.s===id));
561
+ document.querySelectorAll('.screen').forEach(x=>x.classList.toggle('on',x.id==='scr-'+id));
562
+ window.scrollTo(0,0);
563
+ }
564
+ document.getElementById('themeBtn').addEventListener('click',()=>{
565
+ const h=document.documentElement;const d=h.getAttribute('data-theme')==='dark';
566
+ h.setAttribute('data-theme',d?'light':'dark');
567
+ document.getElementById('themeBtn').textContent=d?'切换深色':'切换浅色';
568
+ });
569
+
570
+ /* ================= R2 面板 ================= */
571
+ const R2_TODOS=[
572
+ {content:'检查站点 1 首页可用性与 HTTPS 证书',status:'completed'},
573
+ {content:'检查站点 2 首页可用性与 HTTPS 证书',status:'completed'},
574
+ {content:'汇总站点 1-3 接口延迟(P50/P95)',status:'completed'},
575
+ {content:'拉取站点 4 首页快照',status:'in_progress'},
576
+ {content:'拉取站点 5 首页快照',status:'in_progress'},
577
+ {content:'检查站点 4-5 日志异常关键字',status:'pending'},
578
+ {content:'生成巡检报表草稿(含对比昨日)',status:'pending'},
579
+ {content:'把报表发送到「运营值班」群',status:'pending'},
580
+ ];
581
+ const R2_DETAILS=[
582
+ {note:'HTTP 200,证书链完整,首屏 0.42s',out:'reports/site1.png',t:'08:12 → 08:14'},
583
+ {note:'HTTP 200,证书 41 天到期,首屏 0.58s',out:'reports/site2.png',t:'08:14 → 08:16'},
584
+ {note:'P50 213ms / P95 486ms,较昨日 +7%',out:'reports/latency-1104.csv',t:'08:16 → 08:19'},
585
+ {note:'正在等待 SSO 登录态刷新(第 2 次重试)',out:'—',t:'已 1m20s'},
586
+ {note:'并行任务:截图排队中,等待站点 4 释放会话',out:'—',t:'已 0m45s'},
587
+ {note:'关键字:ERROR / Timeout / 5xx',out:'—',t:'待开始'},
588
+ {note:'含昨日同期对比、波动 TOP5',out:'—',t:'待开始'},
589
+ {note:'目标群:运营值班(外部发送需确认)',out:'—',t:'待开始'},
590
+ ];
591
+ function subOf(i,t){
592
+ const d=R2_DETAILS[i]||{note:'—'};
593
+ const st={completed:'已完成 · 可回看产物',in_progress:'进行中 · 模型正在做这一步',pending:'待开始 · 等前面步骤释放'}[t.status]||'';
594
+ return '<div class="kv"><span>状态</span><span>'+st+'</span></div>'
595
+ +'<div class="kv"><span>说明</span><span>'+esc(d.note||'—')+'</span></div>'
596
+ +'<div class="kv"><span>产物</span><span>'+esc(d.out||'—')+'</span></div>'
597
+ +'<div class="kv"><span>时间</span><span>'+esc(d.t||'—')+'</span></div>'
598
+ +'<div class="kv" style="margin-top:3px"><span></span><span style="color:var(--ink-3);opacity:.75">只读视图:改状态请在会话里由模型 todo_write 整表更新</span></div>';
599
+ }
600
+ function derive(todos){
601
+ const total=todos.length;
602
+ const done=todos.filter(t=>t.status==='completed').length;
603
+ const active=todos.filter(t=>t.status==='in_progress').length;
604
+ const pending=total-done-active;
605
+ const firstActive=todos.findIndex(t=>t.status==='in_progress');
606
+ return {total,done,active,pending,firstActive,pct:total?Math.floor(done/total*100):0};
607
+ }
608
+ function tagOf(s,interrupted){
609
+ if(interrupted)return '<span class="tag interrupt">中断</span>';
610
+ if(s==='completed')return '<span class="tag done">完成</span>';
611
+ if(s==='in_progress')return '<span class="tag doing">进行中</span>';
612
+ return '<span class="tag pending">待办</span>';
613
+ }
614
+ function dotOf(s,interrupted){
615
+ if(interrupted)return '<span class="dot interrupt" role="img" aria-label="已中断"></span>';
616
+ if(s==='completed')return '<span class="dot done" role="img" aria-label="已完成"></span>';
617
+ if(s==='in_progress')return '<span class="dot ongoing" role="img" aria-label="进行中"></span>';
618
+ return '<span class="dot idle" role="img" aria-label="待办"></span>';
619
+ }
620
+ const R2_FILTERS=['all','in_progress','pending','completed'];
621
+ const FILTER_LABEL={all:'全部',in_progress:'进行中',pending:'待办',completed:'完成'};
622
+ let r2Filter='all',r2Folded=false,r2Open={};
623
+ function toggleR2Menu(head){
624
+ if(document.querySelector('#r2-panel .tp-menu')){closeR2Menu();return;}
625
+ const host=document.getElementById('r2-panel');
626
+ const holder=host.querySelector('.tp')||host;
627
+ const m=el('<div class="tp-menu">'
628
+ +'<button class="mi" data-m="switch">⇄ 现在换会话 / 新建会话<span class="k">B12</span></button>'
629
+ +'<div class="hint">手动结束当前卷并开新会话;与自动分卷同一套交接(八段摘要 + 待办快照)。</div>'
630
+ +'<div class="sep"></div>'
631
+ +'<button class="mi" data-m="json">⧉ 复制待办快照 JSON<span class="k">Q4</span></button>'
632
+ +'<button class="mi" data-m="legacy">⌃ 兼容:显示输入框上方旧面板<span class="k">R2-08</span></button>'
633
+ +'<button class="mi" data-m="fold">⌄ 收起面板</button>'
634
+ +'</div>');
635
+ holder.appendChild(m);
636
+ m.addEventListener('click',ev=>{
637
+ const b=ev.target.closest('[data-m]');if(!b)return;
638
+ const k=b.dataset.m;
639
+ if(k==='switch')toast('已确认 B12:面板 ⋯ 菜单提供「现在换会话」。点击后按 R3 交接规则开新卷——原型不真实建会话。',true);
640
+ if(k==='json')toast('已复制当前 todos 投影 JSON(原样快照,Q4 已确认)· 长度 '+JSON.stringify(r2Todos).length+' 字符');
641
+ if(k==='legacy'){document.getElementById('r2-legacy').click();}
642
+ if(k==='fold'){r2Folded=true;renderR2Panel(r2State,r2Todos,r2Extra);}
643
+ closeR2Menu();
644
+ });
645
+ }
646
+ function closeR2Menu(){const m=document.querySelector('#r2-panel .tp-menu');if(m)m.remove();}
647
+ document.addEventListener('click',e=>{if(!e.target.closest('.tp-menu')&&!e.target.closest('[data-act="more"]'))closeR2Menu();});
648
+ document.addEventListener('keydown',e=>{
649
+ if(e.key==='Escape')closeR2Menu();
650
+ const a=e.target.closest&&e.target.closest('[data-act]');
651
+ if(a&&(e.key==='Enter'||e.key===' ')){e.preventDefault();a.click();}
652
+ });
653
+
654
+ function renderR2Panel(state,todos,extra){
655
+ const host=document.getElementById('r2-panel');
656
+ const badge=document.getElementById('r2-tab-badge');
657
+ host.onclick=null;closeR2Menu();
658
+ if(state==='default'){
659
+ badge.style.display='none';
660
+ host.innerHTML='<div class="r-ph">已打开「任务进度」tab。模型尚未调用 todo_write,面板按空态渲染前先给一个未展开占位——真实实现中此处直接显示空态引导(切「空态」按钮查看)。<br><br>此屏演示:<b>tab 默认不自动展开</b>(R2-01)。</div>';
661
+ setLegacy('');
662
+ return;
663
+ }
664
+ if(state==='empty'){
665
+ badge.style.display='none';
666
+ host.innerHTML='<div class="tp-empty">'
667
+ +'<svg width="34" height="34" viewBox="0 0 24 24" fill="none"><rect x="4" y="3.5" width="16" height="17" rx="2.5" stroke="currentColor" stroke-width="1.4"/><path d="M8 8.5h8M8 12h8M8 15.5h5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>'
668
+ +'<div style="font-size:13px;color:var(--ink-2)">模型本轮未产生待办</div>'
669
+ +'<div class="g">长任务开始时模型会调用 <code>todo_write</code> 写入待办清单,这里将实时出现进度。<br>(若刚被中断,会显示黄色「未完成即中断」标记,而非本页)</div></div>';
670
+ setLegacy('');
671
+ return;
672
+ }
673
+ const d=derive(todos);
674
+ badge.style.display=d.active>0?'inline-block':'none';
675
+ let html='<div class="tp'+(r2Folded?' folded':'')+'">';
676
+ if(state==='interrupt'){
677
+ html+='<div class="tp-interrupt"><div style="flex:1"><b>检测到未完成即中断。</b><br>上一轮进行到 <b>第 '+(extra&&extra.at?extra.at:4)+' 步</b>「'+esc(extra&&extra.item?extra.item:'拉取站点 4 首页快照')+'」,本轮开始时投影被清空且未收到新的 todo_write。<br><span style="color:var(--ink-3);font-size:11.5px">判定=上一帧见 in_progress ∧ 投影清空 ∧ 本轮无新写入(三条件,缺一不亮)</span><br><button class="btn">查看上一轮待办快照</button></div></div>';
678
+ html+='<div class="tp-done-card" style="padding:10px 0;color:var(--ink-3)">当前投影为空 —— 中断后无活跃清单</div></div>';
679
+ host.innerHTML=html;
680
+ setLegacy('⚠ 第 '+extra.at+' 步未完成即中断');
681
+ return;
682
+ }
683
+ /* doing / done */
684
+ const carry=(extra&&extra.carryChip)?'<div class="tp-carry-chip">进度承自会话 <a href="javascript:void(0)">巡检-1</a> 的待办快照 · 已按原样带入(Q4 已确认)</div>':'';
685
+ const folded=!!r2Folded;
686
+ html+=carry
687
+ +'<div class="tp-head"><div class="row1"><span class="tt">任务进度</span><span class="cnt">完成 '+d.done+' / 共 '+d.total+'</span><span class="pct">'+d.pct+'%</span>'
688
+ +'<span class="cv'+(folded?' fold':'')+'" data-act="fold" tabindex="0" role="button" aria-expanded="'+(folded?'false':'true')+'" title="'+(folded?'展开面板':'收起面板')+'"><svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M2.5 4.25L6 7.75L9.5 4.25" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span>'
689
+ +'<span class="more" data-act="more" tabindex="0" role="button" aria-haspopup="menu" title="更多(B12 手动换会话 / 快照)">⋯</span></div>'
690
+ +'<div class="tp-bar"><i style="width:'+d.pct+'%"></i></div>'
691
+ +'<div class="tp-meta"><span>更新于 8 秒前</span><span>·</span><span>进行中 '+(d.active)+'</span><span>·</span><span>待办 '+d.pending+'</span>'+(carry?'<span>·</span><span style="color:var(--ok)">续接中</span>':'')+'</div>'
692
+ +'<div class="tp-folded-hint">已收起 · 完成 '+d.done+'/'+d.total+'('+d.pct+'%)· 点右侧 ⌄ 展开</div></div>';
693
+ if(state==='doing'&&d.firstActive>=0){
694
+ html+='<div class="tp-cur">当前第 <b>'+extra.stepIx+'</b> 步:'+esc(todos[d.firstActive].content)+(d.active>1?' <span class="muted">(并行 '+d.active+' 项进行中)</span>':'')+'</div>';
695
+ }
696
+ if(state==='doing'){
697
+ html+='<div class="tp-chips">'+R2_FILTERS.map(f=>'<button data-f="'+f+'" class="'+(r2Filter===f?'on':'')+'">'+FILTER_LABEL[f]+' '+(f==='all'?d.total:(f==='in_progress'?d.active:(f==='pending'?d.pending:d.done)))+'</button>').join('')+'</div>';
698
+ }
699
+ if(state==='done'){
700
+ html+='<div class="tp-done-card"><div class="big">✓</div>全部完成('+d.total+'/'+d.total+')· 报表已发送<br><span class="muted" style="font-size:11.5px">投影保留 30 分钟后徽章收起(拍板项 D-1′)</span></div>';
701
+ }
702
+ const list=todos.map((t,i)=>{
703
+ if(r2Filter!=='all'&&t.status!==r2Filter)return '';
704
+ const cur=(state==='doing'&&t.status==='in_progress');
705
+ const open=!!r2Open[i];
706
+ return '<li class="tp-item '+t.status+(cur?' current':'')+(open?' open':'')+'" data-ix="'+i+'">'
707
+ +'<span class="ix">'+(i+1)+'</span>'+dotOf(t.status,false)
708
+ +'<span class="ct">'+esc(t.content)+tagOf(t.status,false)+'</span>'
709
+ +'<span class="exp" data-act="exp" tabindex="0" role="button" aria-expanded="'+(open?'true':'false')+'" title="展开 / 收起该步详情"><svg width="9" height="9" viewBox="0 0 10 10" fill="none"><path d="M3.6 2L6.4 5L3.6 8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg></span>'
710
+ +'<div class="sub">'+subOf(i,t)+'</div></li>';
711
+ }).join('');
712
+ html+='<ul class="tp-list">'+(list||'<li class="tp-item"><span class="ct muted">(当前筛选无条目)</span></li>')+'</ul></div>';
713
+ host.innerHTML=html;
714
+ const chipRow=host.querySelector('.tp-chips');
715
+ if(chipRow)chipRow.addEventListener('click',e=>{const b=e.target.closest('button');if(b){e.stopPropagation();r2Filter=b.dataset.f;renderR2Panel(r2State,r2Todos,r2Extra);}});
716
+ host.onclick=e=>{
717
+ const head=e.target.closest('.tp-head');
718
+ if(e.target.closest('[data-act="more"]')){e.stopPropagation();toggleR2Menu(head);return;}
719
+ if(e.target.closest('[data-act="fold"]')){r2Folded=!r2Folded;renderR2Panel(r2State,r2Todos,r2Extra);return;}
720
+ const item=e.target.closest('.tp-item');
721
+ if(item&&(e.target.closest('[data-act="exp"]')||e.target.closest('.ct'))){
722
+ const ix=+item.dataset.ix;r2Open[ix]=!r2Open[ix];renderR2Panel(r2State,r2Todos,r2Extra);return;
723
+ }
724
+ closeR2Menu();
725
+ };
726
+ setLegacy((d.done>0?'已完成 '+d.done:'')+(d.active>0?' · 进行中 '+d.active:'')+(d.pending>0?' · 待办 '+d.pending:''));
727
+ }
728
+ function setLegacy(pg){document.getElementById('r2-legacy-pg').textContent=pg;}
729
+
730
+ let r2State='default',r2Todos=R2_TODOS,r2Extra={stepIx:4};
731
+ document.querySelectorAll('[data-r2]').forEach(b=>b.addEventListener('click',()=>{
732
+ document.querySelectorAll('[data-r2]').forEach(x=>x.classList.toggle('on',x===b));
733
+ r2State=b.dataset.r2;r2Filter='all';
734
+ const todos=JSON.parse(JSON.stringify(R2_TODOS));
735
+ if(r2State==='done')todos.forEach(t=>t.status='completed');
736
+ r2Todos=todos;
737
+ renderR2Panel(r2State,r2Todos,r2State==='interrupt'?{at:4,item:'拉取站点 4 首页快照'}:{stepIx:4});
738
+ }));
739
+ document.getElementById('r2-stream').addEventListener('click',()=>{
740
+ document.querySelectorAll('[data-r2]').forEach(x=>x.classList.toggle('on',x.dataset.r2==='doing'));
741
+ r2State='doing';r2Filter='all';
742
+ const todos=JSON.parse(JSON.stringify(R2_TODOS));
743
+ /* 回放:从 2 done 起,900ms 一步 */
744
+ let i=0;const seq=[];
745
+ todos[3].status='in_progress';todos[4].status='in_progress';
746
+ seq.push(()=>{todos[3].status='completed';todos[5].status='in_progress';});
747
+ seq.push(()=>{todos[4].status='completed';todos[3].status='completed';todos[6].status='in_progress';});
748
+ seq.push(()=>{todos[5].status='completed';todos[6].status='completed';todos[7].status='in_progress';});
749
+ seq.push(()=>{todos[7].status='completed';});
750
+ const play=()=>{
751
+ if(i>=seq.length){toast('▶ 推帧结束:全部完成(真实实现每帧 ≤2s 到达)',true);return;}
752
+ seq[i++]();
753
+ const doneN=todos.filter(t=>t.status==='completed').length;
754
+ renderR2Panel('doing',todos,{stepIx:todos.findIndex(t=>t.status==='in_progress')+1||8});
755
+ play2=setTimeout(play,900);
756
+ };
757
+ clearTimeout(window.__r2play);window.__r2play=null;
758
+ toast('▶ 模拟 subscription 推帧:每 0.9s 一帧',true);
759
+ play();
760
+ });
761
+ document.getElementById('r2-legacy').addEventListener('click',e=>{
762
+ const d=document.getElementById('r2-legacy-dock');d.classList.toggle('show');
763
+ e.currentTarget.classList.toggle('on',d.classList.contains('show'));
764
+ toast(d.classList.contains('show')?'兼容开关开:旧位置面板与新 tab 同数据源双视图(R2-08)':'兼容开关关(默认)');
765
+ });
766
+
767
+ /* 右侧栏 tab 通用切换(两屏共用交互) */
768
+ document.querySelectorAll('.r-tabs').forEach(tabs=>tabs.addEventListener('click',e=>{
769
+ const b=e.target.closest('button');if(!b||!b.dataset.t)return;
770
+ tabs.querySelectorAll('button').forEach(x=>x.classList.toggle('on',x===b));
771
+ const body=tabs.parentElement.querySelector('.r-body');
772
+ body.querySelectorAll('.r-pane').forEach(p=>p.classList.toggle('on',p.dataset.p===b.dataset.t));
773
+ }));
774
+
775
+ function r3EnhanceCard(){
776
+ const card=document.querySelector('#r3-chat .rollover');if(!card)return;
777
+ card.classList.add('summary-collapsed');
778
+ const toggle=el('<button class="summary-toggle" aria-expanded="false">展开全部</button>');
779
+ toggle.onclick=()=>{const folded=card.classList.toggle('summary-collapsed');toggle.textContent=folded?'展开全部':'收起摘要';toggle.setAttribute('aria-expanded',!folded);};card.appendChild(toggle);
780
+ const details=card.querySelector('details');if(details){const copy=el('<button class="summary-toggle">复制快照</button>');copy.onclick=()=>navigator.clipboard.writeText(details.querySelector('pre').textContent).then(()=>toast('快照已复制')).catch(()=>toast('复制不可用,请手动选择 JSON'));details.appendChild(copy);}
781
+ }
782
+ document.addEventListener('keydown',e=>{if(e.key==='Escape')document.querySelectorAll('#r3-panel .tp-menu').forEach(m=>m.hidden=true);});
783
+ /* ================= R3 场景 ================= */
784
+ const R3_TODOS=[
785
+ {content:'确认 8 站点巡检范围与昨天的对照口径',status:'completed'},
786
+ {content:'巡检脚本改造:支持超时重试与截图留存',status:'completed'},
787
+ {content:'完成站点 1-3 巡检(首页/延迟/日志)',status:'completed'},
788
+ {content:'站点 4 登录态深度巡检(SSO 流程)',status:'in_progress'},
789
+ {content:'站点 5 深度巡检',status:'pending'},
790
+ {content:'汇总 8 站点对比报表(含昨日)',status:'pending'},
791
+ {content:'报表发送到「运营值班」群',status:'pending'},
792
+ {content:'归档截图到 workspace/reports/',status:'pending'},
793
+ ];
794
+ const r3=Object.assign({round:9,rolled:false,view:'s0',carryStep:0,folded:false,legacy:false,filter:'all',sessions:[
795
+ {id:'s0',name:'每日巡检',cluster:'每日巡检',vol:0,round:9,readonly:false,sel:true},
796
+ ]},(()=>{try{return JSON.parse(localStorage.getItem('dsh-r3-demo-v1')||'null')||{}}catch{return {}}})());
797
+ function r3RenderSessions(){
798
+ const host=document.getElementById('r3-sessions');host.innerHTML='';
799
+ r3.sessions.forEach(s=>{
800
+ const tags=[];
801
+ if(s.readonly)tags.push('<span class="badge-mini gray">只读</span>');
802
+ if(s.vol>0)tags.push('<span class="badge-mini">卷 '+s.vol+'/9</span>');
803
+ if(s.sel)tags.push('');
804
+ const it=el('<button class="item'+(s.id===r3.view?' on':'')+'" data-sid="'+s.id+'">'
805
+ +'<span class="nm"><b>'+esc(s.name)+'</b>'+tags.join('')+'</span>'
806
+ +'<small>'+(s.readonly?'已分卷 · 只读 · 停在 第 '+s.round+' 轮':(s.id==='s0'?'第 '+s.round+' 轮 · 阈值 10':(s.id==='s2'?'进行中 · 续接自卷 '+(s.vol-1):'空闲 · 占用名称')))+'</small></button>');
807
+ it.addEventListener('click',()=>r3Switch(s.id));
808
+ host.appendChild(it);
809
+ });
810
+ }
811
+ function r3Head(){
812
+ const h=document.getElementById('r3-chat-head');
813
+ const s=r3.sessions.find(x=>x.id===r3.view);
814
+ if(r3.view==='s0'&&!r3.rolled){h.innerHTML='<span class="t">'+esc(s.name)+'</span><span class="chip">第 '+s.round+' 轮 / 阈值 10</span><span class="chip">口径:一条用户消息 = 1 轮</span>';}
815
+ else if(r3.view==='s0'&&r3.rolled){h.innerHTML='<span class="t">每日巡检-1</span><span class="chip" style="color:var(--warn);border-color:var(--warn)">'+(s.readonly?'归档只读':'归档 · 已恢复写入')+'</span><span class="chip">历史全景保留</span>';}
816
+ else{const prev=r3.sessions.find(x=>x.id==='s0')||{round:10};h.innerHTML='<span class="t">'+esc(s?s.name:'每日巡检-2')+'</span><span class="chip">上轮:巡检-1 第 '+prev.round+' 轮</span><span class="chip">本卷:'+esc(s.name)+' 第 '+s.round+' 轮</span><span class="chip">卷标 '+s.vol+'/9</span>';}
817
+ }
818
+ function r3Chat(){
819
+ const c=document.getElementById('r3-chat');
820
+ if(r3.view==='s0'&&!r3.rolled){
821
+ c.innerHTML=
822
+ '<div class="msg user"><span class="who">用户 · 第 1 轮</span><div class="bub">开始每日巡检:8 个站点,首页可用性 / 接口延迟 / 日志异常,对比昨天,报表发运营值班群。步骤自己拆。</div></div>'
823
+ +'<div class="msg"><span class="who">巡检 Bot</span><div class="bub">已建立 8 步待办清单(右侧栏「任务进度」可见)。当前进行到第 4 步:站点 4 登录态深度巡检。</div></div>'
824
+ +(r3.round>=10?'<div class="msg sys"><div class="bub">第 10 轮回合结束 · 触发自动分卷检查 →(阈值已满足,见左栏命名变化)</div></div>':'')
825
+ +'<div class="msg user"><span class="who">用户 · 第 9 轮</span><div class="bub">站点 3 昨晚发布过,重点看下兼容性。</div></div>';
826
+ c.scrollTop=c.scrollHeight;return;
827
+ }
828
+ if(r3.view==='s0'&&r3.rolled){
829
+ c.innerHTML=
830
+ '<div class="msg user"><span class="who">用户 · 第 10 轮(末轮)</span><div class="bub">跑完这轮先到这。</div></div>'
831
+ +'<div class="msg"><span class="who">巡检 Bot</span><div class="bub">好的。第 4 步「站点 4 登录态深度巡检」仍未完成,已随快照带入「'+(r3.sessions.find(s=>s.id==='s2')||{name:'每日巡检-2'}).name+'」,那边从第 4 步继续。</div></div>'
832
+ +'<div class="msg sys"><div class="bub">本会话已于 12:47 归档为卷 1(只读)。写入需求请用同簇「'+(r3.sessions.find(s=>s.id==='s2')||{name:'每日巡检-2'}).name+'」,或点输入框上方开关恢复写入。</div></div>';
833
+ return;
834
+ }
835
+ /* s2 = 巡检-2 */
836
+ const done4=r3.carryStep>=1;
837
+ c.innerHTML=
838
+ '<div class="rollover">'
839
+ +'<div class="rh"><svg width="15" height="15" viewBox="0 0 16 16" fill="none"><path d="M8 1v14M8 1L3 6M8 1l5 5" stroke="var(--accent)" stroke-width="1.4" stroke-linecap="round" transform="rotate(180 8 8)"/></svg>'
840
+ +'<b>接续摘要</b><span class="sp"></span><span class="src">承自 <a href="javascript:void(0)" onclick="r3Switch(\'s1\')">每日巡检-1</a></span></div>'
841
+ +'<div class="rsec"><div class="k">① 上轮工作摘要</div><div class="v">卷 1 共 10 轮:确认巡检范围与对照口径 → 改造巡检脚本(超时重试+截图留存)→ 完成站点 1-3 巡检。站点 3 昨晚发布过,用户要求重点看兼容性(已查,无回归)。</div></div>'
842
+ +'<div class="rsec"><div class="k">② 当前工作(Current Work)</div><div class="v"><span class="hl-step">第 4 步进行中</span>:站点 4 登录态深度巡检(SSO 流程)'+'。新会话将从这一步继续。</div></div>'
843
+ +'<div class="rsec"><div class="k">③ 关键约束与禁令(原文摘录,不总结)</div><div class="v">「报表发运营值班群」「步骤自己拆」——未识别到其他显式禁令。</div></div>'
844
+ +'<div class="rsec"><div class="k">④ 文件与代码(Files and Code)</div><div class="v"><code>workspace/inspect/probe.mjs</code>(新增 timeout+重试)· <code>workspace/reports/day-1103.md</code>(草稿,未更新)</div></div>'
845
+ +'<div class="rsec"><div class="k">⑤ 未竟后台 job(Pending Jobs)</div><div class="v">job <code>bg-sso-harvest</code>:运行中 · 句柄已随交接写入,标「待收」(B2:不为分卷拆正在跑的 job)。</div></div>'
846
+ +'<div class="rsec"><div class="k">⑥ 待办快照(人读 + 机器可读,与 R2 同一投影源)</div><div class="v">8 项:3 完成 · <b style="color:var(--accent)">1 进行中(第 4 步)</b> · 4 待办。原样快照带入,模型可自行调整(Q4 建议默认)。</div>'
847
+ +'<details><summary>展开快照 JSON(供 -2 直接续跑)</summary><pre>'+esc(JSON.stringify({cluster:'每日巡检',vol:2,from:'每日巡检-1',todos:R3_TODOS},null,1))+'</pre></details></div>'
848
+ +'<div class="rsec"><div class="k">⑦ 簇标识</div><div class="v">clusterId=daily-inspect · seq=2 · 同簇上限 9 卷(B11)</div></div>'
849
+ +'<div class="rsec"><div class="k">⑧ 交接健康</div><div class="v">摘要生成 1 次成功(重试计数 0/2)· 快照逐项比对一致(AC-R3-2)</div></div>'
850
+ +'</div>'
851
+ +(done4
852
+ ?'<div class="msg"><span class="who">巡检 Bot · 续接</span><div class="bub">第 4 步已完成:站点 4 SSO 登录巡检通过。继续第 5 步(站点 5 深度巡检),进度条已更新 4/8。</div></div>'
853
+ :'<div class="msg sys"><div class="bub">续接就绪 · 上方摘要即 -2 首页第一条消息(D-4)。点顶部「在 -2 继续一轮」模拟模型按快照续跑。</div></div>');
854
+ }
855
+ function r3Composer(){
856
+ const w=document.getElementById('r3-composer-wrap');
857
+ if(r3.view==='s0'&&r3.rolled){
858
+ const old=r3.sessions[0],ro=old.readonly;
859
+ w.innerHTML='<div class="ro-banner">'+(ro?'卷 1 已归档,默认只读。历史可回看。':'卷 1 已归档,已允许再次写入。')+'<button class="sw" id="ro-toggle" role="switch" aria-checked="'+(!ro)+'">'+(ro?'允许再次写入':'恢复只读')+'</button></div><div class="composer'+(ro?' disabled':'')+'"><input '+(ro?'disabled aria-disabled="true"':'aria-disabled="false"')+' placeholder="'+(ro?'归档会话:不可输入':'给 Bot 发消息…')+'"><span class="send">↑</span></div>';
860
+ document.getElementById('ro-toggle').onclick=()=>{old.readonly=!old.readonly;r3Switch('s0');};
861
+ return;
862
+ }
863
+ const todos=R3_TODOS.map((t,i)=>({...t,status:r3.view==='s2'?(i<3+r3.carryStep?'completed':i===3+r3.carryStep?'in_progress':'pending'):t.status}));
864
+ w.innerHTML='<div class="legacy-dock'+(r3.legacy?' show':'')+'"><div class="h"><span class="tt">待办</span><span class="pg">与右侧任务进度同源</span></div><ul>'+todos.map((t,i)=>'<li>'+esc((i+1)+'. '+t.content)+' '+tagOf(t.status,false)+'</li>').join('')+'</ul></div><div class="composer"><input placeholder="给 Bot 发消息…(原型示意)"><span class="send">↑</span></div>';
865
+ }
866
+
867
+ function r3Panel(){
868
+ const host=document.getElementById('r3-panel');
869
+ host.onclick=null;
870
+ const folded=!!r3.folded;
871
+ const cv='<span class="cv'+(folded?' fold':'')+'" data-act="fold" tabindex="0" role="button" aria-expanded="'+(folded?'false':'true')+'" title="'+(folded?'展开面板':'收起面板')+'"><svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M2.5 4.25L6 7.75L9.5 4.25" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span>';
872
+ const head=(title,cnt,pct,chip,meta)=>'<div class="tp-head">'+(chip||'')
873
+ +'<div class="row1"><span class="tt">'+title+'</span><span class="cnt">'+cnt+'</span><span class="pct">'+pct+'</span>'+cv+'<button class="more" data-act="r3-more" aria-label="任务进度菜单" aria-expanded="false">⋯</button></div>'
874
+ +'<div class="tp-bar"><i style="width:'+pct+'"></i></div>'
875
+ +'<div class="tp-meta">'+meta+'</div>'
876
+ +'<div class="tp-folded-hint">已收起 · '+cnt+'('+pct+')· 点右侧 ⌄ 展开</div></div>';
877
+ const carryN=(r3.view==='s2'?R3_TODOS.length:0);
878
+ const item=(t,i,cur)=>'<li class="tp-item '+t.status+(cur?' current':'')+'" data-ix="'+i+'">'
879
+ +'<span class="ix">'+(i+1)+'</span>'+dotOf(t.status,false)
880
+ +'<span class="ct">'+esc(t.content)+tagOf(t.status,false)+(i<carryN?'<span class="tag carry">承自 -1</span>':'')+'</span>'
881
+ +'<span class="exp" data-act="exp" tabindex="0" role="button" aria-expanded="false" title="展开 / 收起该步详情"><svg width="9" height="9" viewBox="0 0 10 10" fill="none"><path d="M3.6 2L6.4 5L3.6 8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg></span>'
882
+ +'<div class="sub">'
883
+ +'<div class="kv"><span>状态</span><span>'+({completed:'已完成',in_progress:'进行中 · 模型正在做这一步',pending:'待开始'}[t.status]||'')+'</span></div>'
884
+ +'<div class="kv"><span>来源</span><span>'+(i<carryN?'承自 每日巡检-1 的待办快照':'本卷内新建')+'</span></div>'
885
+ +'<div class="kv" style="margin-top:3px"><span></span><span style="opacity:.75">只读视图:改状态请在会话里由模型 todo_write 整表更新</span></div>'
886
+ +'</div></li>';
887
+ const listOf=(todos,curIx)=>'<ul class="tp-list">'+todos.map((t,i)=>item(t,i,i===curIx)).join('')+'</ul>';
888
+ let inner='<div class="tp'+(folded?' folded':'')+'">';
889
+ if(r3.view==='s0'&&r3.rolled){
890
+ inner+=head('任务进度(末帧快照)','完成 3 / 共 8','37%','','归档于第 10 轮 · 投影随卷 1 冻结 · 只读')+listOf(R3_TODOS,-1);
891
+ }else if(r3.view==='s0'&&!r3.rolled){
892
+ inner+=head('任务进度','完成 3 / 共 8','37%','','更新于 1 分钟前 · 进行中 1 · 待办 4')
893
+ +'<div class="tp-cur">当前第 <b>4</b> 步:站点 4 登录态深度巡检(SSO 流程)</div>'+listOf(R3_TODOS,3);
894
+ }else{
895
+ const step=4+r3.carryStep, done=3+r3.carryStep, pct=Math.floor(done/8*100);
896
+ const todos=R3_TODOS.map((t,i)=>({...t,status:i<done?'completed':(i===done?'in_progress':'pending')}));
897
+ inner+=head('任务进度','完成 '+done+' / 共 8',pct+'%',
898
+ '<div class="tp-carry-chip">进度自动加载自 <a href="javascript:void(0)" onclick="r3Switch(\'s1\')">巡检-1</a> 末帧快照 · 续跑就绪(无需重述任务)</div>',
899
+ '更新于 刚刚 <span>·</span> <span style="color:var(--ok)">进度续接中</span>')
900
+ +'<div class="tp-cur">当前第 <b>'+step+'</b> 步:'+esc(R3_TODOS[done].content)+'</div>'+listOf(todos,done);
901
+ }
902
+ host.innerHTML=inner+'</div>';
903
+ const menu=el('<div class="tp-menu" hidden><button class="mi" data-op="switch">现在换会话</button><button class="mi" data-op="json">复制 JSON 快照</button><button class="mi" data-op="legacy">兼容旧面板:'+(r3.legacy?'开':'关')+'</button><button class="mi" data-op="fold">收起面板</button></div>');
904
+ host.querySelector('.tp').appendChild(menu);
905
+ host.onclick=e=>{
906
+ if(e.target.closest('[data-act="r3-more"]')){menu.hidden=!menu.hidden;e.target.closest('[data-act]').setAttribute('aria-expanded',!menu.hidden);return;}
907
+ const op=e.target.closest('[data-op]')?.dataset.op;
908
+ if(op){menu.hidden=true;if(op==='switch'){if(r3.rolled)toast('原型已展示一次交接。真实手动分卷由插件执行。');else r3Roll(false);}if(op==='json')navigator.clipboard.writeText(JSON.stringify(r3.view==='s2'?R3_TODOS.map((t,i)=>({...t,status:i<3+r3.carryStep?'completed':i===3+r3.carryStep?'in_progress':'pending'})):R3_TODOS,null,2)).then(()=>toast('快照已复制')).catch(()=>toast('复制不可用,请展开摘要 JSON 手动复制'));if(op==='legacy'){r3.legacy=!r3.legacy;r3Composer();r3Panel();}if(op==='fold'){r3.folded=true;r3Panel();}localStorage.setItem('dsh-r3-demo-v1',JSON.stringify(r3));return;}
909
+ if(e.target.closest('[data-act="fold"]')){r3.folded=!r3.folded;localStorage.setItem('dsh-r3-demo-v1',JSON.stringify(r3));r3Panel();return;}
910
+ const li=e.target.closest('.tp-item');
911
+ if(li&&(e.target.closest('[data-act="exp"]')||e.target.closest('.ct'))){li.classList.toggle('open');li.querySelector('.exp').setAttribute('aria-expanded',li.classList.contains('open'));}
912
+ };
913
+ }
914
+ function r3Switch(id){
915
+ if(id==='s1')id='s0';
916
+ r3.view=id;
917
+ localStorage.setItem('dsh-r3-demo-v1',JSON.stringify(r3));
918
+ document.getElementById('r3-carry').disabled=!r3.rolled;
919
+ document.getElementById('r3-view1').disabled=!r3.rolled;
920
+ document.getElementById('r3-view2').disabled=!r3.rolled;
921
+ document.getElementById('r3-round-n').textContent=r3.rolled?'已分卷':r3.sessions[0].round+' / 10';
922
+ r3RenderSessions();r3Head();r3Chat();r3EnhanceCard();r3Composer();r3Panel();
923
+ document.getElementById('r3-view1').classList.toggle('on',id==='s0'&&r3.rolled);
924
+ document.getElementById('r3-view2').classList.toggle('on',id==='s2');
925
+ }
926
+ document.getElementById('r3-view1').addEventListener('click',()=>{if(r3.rolled)r3Switch('s1');});
927
+ document.getElementById('r3-view2').addEventListener('click',()=>{if(r3.rolled)r3Switch('s2');});
928
+ document.getElementById('r3-round').addEventListener('click',()=>{
929
+ const s=r3.sessions[0];
930
+ if(s.round>=10){toast('已在第 10 轮(回合已结束)。点「在 -2 继续一轮」或直接看分卷结果。');return;}
931
+ s.round++;document.getElementById('r3-round-n').textContent=s.round+' / 10';
932
+ r3RenderSessions();r3Head();r3Chat();r3EnhanceCard();
933
+ if(s.round>=10){
934
+ /* 回合结束 → agent/pre-step:触发分卷 */
935
+ setTimeout(()=>r3Roll(false),350);
936
+ } else toast('第 '+s.round+' 轮完成(一条用户消息=1 轮)');
937
+ });
938
+ function r3Roll(conflict){
939
+ if(r3.rolled)return;
940
+ const s=r3.sessions[0];
941
+ s.name='每日巡检-1';s.readonly=true;s.vol=1;s.sel=false;s.round=Math.max(s.round,10);
942
+ const conflictHit=conflict;
943
+ const newName=conflictHit?'每日巡检-3':'每日巡检-2';
944
+ if(conflictHit){r3.sessions.push({id:'sX',name:'每日巡检-2',cluster:'每日巡检',vol:0,round:0,readonly:false,sel:false,note:'名称被外部会话占用(演示前提)'});}
945
+ r3.sessions.push({id:'s2',name:newName,cluster:'每日巡检',vol:2,round:0,readonly:false,sel:true});
946
+ r3.rolled=true;r3.view='s2';
947
+ document.getElementById('r3-round-n').textContent='10 / 10 → 已分卷';
948
+ document.getElementById('r3-carry').disabled=false;
949
+ document.getElementById('r3-view1').disabled=false;document.getElementById('r3-view2').disabled=false;
950
+ r3Switch('s2');
951
+ toast('<b>分卷完成</b>:旧会话改名「每日巡检-1」(转只读),新会话「'+newName+'」已创建并选中(AC-R3-1)',true);
952
+ if(conflictHit)toast('已存在「每日巡检-2」→ 名称顺延为「每日巡检-3」,但<b>簇内卷标仍记真实卷序 卷 2/9</b><span class="muted">(B7 · 已写日志)</span>');
953
+ else toast('八段摘要 + 待办快照已注入新会话首条消息(AC-R3-2)· 第 4 步进行中(AC-R3-3)',true);
954
+ }
955
+ document.getElementById('r3-carry').addEventListener('click',()=>{
956
+ if(!r3.rolled){toast('先跑满 10 轮触发分卷');return;}
957
+ if(r3.carryStep>=1){toast('演示到第 5 步为止:进度已 4/8,后续同构');return;}
958
+ r3.carryStep=1;r3.sessions.find(s=>s.id==='s2').round++;if(r3.view!=='s2')r3.view='s2';localStorage.setItem('dsh-r3-demo-v1',JSON.stringify(r3));
959
+ r3Head();r3Chat();r3EnhanceCard();r3Composer();r3Panel();
960
+ toast('模型在 -2 按快照续跑:第 4 步完成,进度条 4/8,第 5 步进行中',true);
961
+ });
962
+ document.getElementById('r3-conflict').addEventListener('click',()=>{
963
+ if(r3.rolled){toast('本演示已分卷完成。冲突规则:'+ (r3.sessions.some(x=>x.name==='每日巡检-3')?'已触发过(-3 已顺延并写日志)':'未触发;下次分卷若 -2 占位将顺延') +'。');return;}
964
+ r3Roll(true);
965
+ });
966
+ document.getElementById('r3-reset').addEventListener('click',()=>{
967
+ Object.assign(r3,{rolled:false,view:'s0',carryStep:0,folded:false,legacy:false,filter:'all',sessions:[{id:'s0',name:'每日巡检',cluster:'每日巡检',vol:0,round:9,readonly:false,sel:true}]});
968
+ document.getElementById('r3-round-n').textContent='9 / 10';
969
+ document.getElementById('r3-carry').disabled=true;
970
+ document.getElementById('r3-view1').disabled=true;document.getElementById('r3-view2').disabled=true;
971
+ document.getElementById('r3-view1').classList.remove('on');document.getElementById('r3-view2').classList.add('on');
972
+ r3Switch('s0');
973
+ toast('演示已重置:回到第 9/10 轮(阈前一轮)',true);
974
+ });
975
+
976
+ /* 初始渲染 */
977
+ renderR2Panel('default',R2_TODOS,{});
978
+ r3Switch(r3.view);
979
+ </script>
980
+ </body>
981
+ </html>