@stardustlc/dsh-dream 0.5.0 → 0.6.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.
package/lib/client.js CHANGED
@@ -2,11 +2,14 @@
2
2
  // 仓库里没有 src/client.ts,pnpm run build 也不会重写它。改面板请直接改这里,
3
3
  // 改完用 node --check lib/client.js 自检(CI 里也会跑这一步)。
4
4
  //
5
- // 面板做两件事:把梦境日记(dreams.jsonl)画成人能看懂的样子 —— 概览、心境方块、
6
- // 教训榜、按时间倒序的梦卡片;再只读展示 M1 的技术经验(<journalDir>/knowledge/)。
7
- // 数据来自插件自己的同源只读路由 GET /_dsh/dsh-dream/journal 与
8
- // GET /_dsh/dsh-dream/knowledge(由 src/index.ts 挂载);前端不缓存业务数据,刷新即见。
9
- // 经验区块只展示状态 / 条件 / 范围 / 证据摘要 / 最近核验,不展示权重、向量或原始 JSON。
5
+ // 面板做四件事:把梦境日记(dreams.jsonl)画成人能看懂的样子 —— 概览、心境方块、
6
+ // 教训榜、按时间倒序的梦卡片;只读展示技术经验(<journalDir>/knowledge/);
7
+ // 只读展示「待核实」(candidate/disputed/stale + 被扣下的原因)与「写入记录」
8
+ //(<journalDir>/bridge/ 的桥接应用记录)。
9
+ // 数据来自插件自己的同源只读路由 GET /_dsh/dsh-dream/journal、/_dsh/dsh-dream/knowledge
10
+ // 与 /_dsh/dsh-dream/bridge(由 src/index.ts 挂载);前端不缓存业务数据,刷新即见。
11
+ // 经验区块只展示状态 / 条件 / 范围 / 适用与例外 / 证据摘要 / 最近核验;日志截断或坏行会明确提示,
12
+ // 不展示权重、向量或原始 JSON。经验行只提供「复制审阅 / 预览命令」,写操作仍在对话工具里执行。
10
13
  // 隐私纪律:面板只读,不写文件、不上传;「隐私模式」只做显示层模糊,DOM 仍是明文。
11
14
  window.__ModuleLoader__.load({ id: "@stardustlc/dsh-dream", factory: (require) => {
12
15
  var module = { exports: {} }; var exports = module.exports;
@@ -19,8 +22,11 @@ const h = React.createElement;
19
22
  // 相对路径按 document.baseURI 解析:反代挂在 /tools/dsh/ 这类前缀下也不会打偏。
20
23
  const ROUTE = "_dsh/dsh-dream/journal";
21
24
  const KNOWLEDGE_ROUTE = "_dsh/dsh-dream/knowledge";
25
+ const BRIDGE_ROUTE = "_dsh/dsh-dream/bridge";
22
26
  const KNOWLEDGE_LIMIT = 50;
27
+ const BRIDGE_LIMIT = 50;
23
28
  const KNOWLEDGE_STATES = ["candidate", "usable", "disputed", "stale", "rejected"];
29
+ const HELD_STATES = ["candidate", "disputed", "stale"];
24
30
  const DEFAULT_LIMIT = 50;
25
31
  const MORE_LIMIT = 500;
26
32
  const FETCH_TIMEOUT_MS = 15000;
@@ -120,6 +126,40 @@ async function fetchKnowledge() {
120
126
  };
121
127
  }
122
128
 
129
+ /** GET /_dsh/dsh-dream/bridge:只读契约体 { records, stats },无查询参数。 */
130
+ async function fetchBridgeRecords() {
131
+ const init = { credentials: "same-origin" };
132
+ if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") init.signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
133
+ const res = await fetch(routeUrlFor(BRIDGE_ROUTE, ""), init);
134
+ let body = null;
135
+ try { body = await res.json(); } catch (error) { body = null; }
136
+ if (body === null || typeof body !== "object") throw new Error("服务器返回的不是 JSON(HTTP " + String(res.status) + ")");
137
+ if (res.ok !== true || body.ok === false) {
138
+ const failure = new Error(errorMessageOf(body, res.status));
139
+ failure.status = res.status;
140
+ failure.code = body.error !== null && typeof body.error === "object" ? body.error.code : undefined;
141
+ throw failure;
142
+ }
143
+ const data = body.value !== null && typeof body.value === "object" ? body.value : body;
144
+ return {
145
+ records: Array.isArray(data.records) ? data.records : [],
146
+ stats: data.stats !== null && typeof data.stats === "object" ? data.stats : null,
147
+ };
148
+ }
149
+
150
+ /** 复制命令到剪贴板;没有 clipboard / 被拒绝时返回 false,调用方回退为可选中文本。 */
151
+ async function copyText(text) {
152
+ const value = String(text);
153
+ if (value === "") return false;
154
+ try {
155
+ if (typeof navigator !== "undefined" && navigator !== null && navigator.clipboard !== undefined && navigator.clipboard !== null && typeof navigator.clipboard.writeText === "function") {
156
+ await navigator.clipboard.writeText(value);
157
+ return true;
158
+ }
159
+ } catch (error) { /* 权限被拒 / 非安全上下文:回退展示可选中文本 */ }
160
+ return false;
161
+ }
162
+
123
163
  function normalizeDreams(value, limit) {
124
164
  if (!Array.isArray(value) || limit <= 0) return [];
125
165
  const out = [];
@@ -224,15 +264,42 @@ function trimmedText(value) {
224
264
  return typeof value === "string" ? value.trim() : "";
225
265
  }
226
266
 
267
+ /** applicability 数组 → 紧凑适用串(package + versions / platform);坏形状跳过,空数组给 ""。 */
268
+ function applicabilityTextOf(value) {
269
+ if (!Array.isArray(value)) return "";
270
+ const parts = [];
271
+ for (const raw of value) {
272
+ if (raw === null || typeof raw !== "object") continue;
273
+ const pkg = trimmedText(raw.package);
274
+ const versions = trimmedText(raw.versions);
275
+ const platform = trimmedText(raw.platform);
276
+ const main = pkg !== "" ? (versions !== "" ? pkg + " " + versions : pkg) : versions;
277
+ const bits = [];
278
+ if (main !== "") bits.push(main);
279
+ if (platform !== "") bits.push("平台 " + platform);
280
+ if (bits.length > 0) parts.push(bits.join(" · "));
281
+ }
282
+ if (parts.length === 0) return "";
283
+ if (parts.length > 4) return parts.slice(0, 4).join(";") + ";等 " + String(parts.length) + " 条适用范围";
284
+ return parts.join(";");
285
+ }
286
+
227
287
  /**
228
- * 经验视图模型:只投影展示所需字段(title / when / state / scopeLabel /
229
- * evidenceSummary / lastValidatedAt),不碰权重、向量、原始 JSON。
230
- * limit 只截断渲染行数,byState 仍按全量统计。
288
+ * 经验视图模型:只投影展示所需字段(title / when / applicability / exceptions /
289
+ * state / scopeLabel / evidenceSummary / lastValidatedAt),并派生 revision / 扣留原因 /
290
+ * 可桥接标记与复制命令所需的稳定字段;不碰权重、向量、原始 JSON。
291
+ * limit 只截断渲染行数,byState 仍按全量统计;truncated / badLines 原样带给提示行。
231
292
  */
232
293
  function normalizeKnowledge(value, now, limit) {
233
294
  const source = value !== null && typeof value === "object" ? value : {};
234
295
  const cap = typeof limit === "number" && limit > 0 ? limit : KNOWLEDGE_LIMIT;
235
296
  const rawLessons = Array.isArray(source.lessons) ? source.lessons : [];
297
+ const lessonTitles = new Map();
298
+ for (const raw of rawLessons) {
299
+ if (raw !== null && typeof raw === "object" && trimmedText(raw.id) !== "") {
300
+ lessonTitles.set(trimmedText(raw.id), trimmedText(raw.title) || trimmedText(raw.action));
301
+ }
302
+ }
236
303
  const statsSource = source.stats !== null && typeof source.stats === "object" ? source.stats : {};
237
304
  const byState = {};
238
305
  const rows = [];
@@ -248,7 +315,12 @@ function normalizeKnowledge(value, now, limit) {
248
315
  rows.push({
249
316
  id: trimmedText(raw.id),
250
317
  title: title !== "" ? title : action,
318
+ action: action,
251
319
  when: trimmedText(raw.when),
320
+ applicability: applicabilityTextOf(raw.applicability),
321
+ exceptions: Array.isArray(raw.exceptions)
322
+ ? raw.exceptions.filter((item) => typeof item === "string" && item.trim() !== "").map((item) => item.trim()).slice(0, 10)
323
+ : [],
252
324
  state: state,
253
325
  stateLabel: lessonStateLabel(state),
254
326
  stateClass: lessonStateClass(state),
@@ -257,17 +329,28 @@ function normalizeKnowledge(value, now, limit) {
257
329
  lastValidatedAt: lastValidatedAt,
258
330
  lastText: lastValidatedAt === "" ? "尚未核验" : relativeTime(lastValidatedAt, now),
259
331
  lastTitle: lastValidatedAt === "" ? "没有 lastValidatedAt(从未核验)" : formatDate(lastValidatedAt) + (formatTime(lastValidatedAt) === "" ? "" : " " + formatTime(lastValidatedAt)),
332
+ revision: typeof raw.revision === "number" && Number.isFinite(raw.revision) && raw.revision >= 0 ? Math.floor(raw.revision) : 0,
333
+ independentSupportCount: typeof raw.independentSupportCount === "number" && raw.independentSupportCount > 0 ? Math.floor(raw.independentSupportCount) : 0,
334
+ projectId: typeof raw.scope === "object" && raw.scope !== null ? trimmedText(raw.scope.projectId) : "",
335
+ workspaceRoot: typeof raw.scope === "object" && raw.scope !== null ? trimmedText(raw.scope.workspaceRoot) : "",
336
+ conflictIds: Array.isArray(raw.conflictIds) ? raw.conflictIds.filter(function (id) { return typeof id === "string" && id.trim() !== ""; }) : [],
337
+ conflicts: Array.isArray(raw.conflictIds) ? raw.conflictIds.filter(function (id) { return typeof id === "string" && id.trim() !== ""; }).map(function (id) { return { id: id, title: lessonTitles.get(id) || "未加载的经验(" + id + ")" }; }) : [],
338
+ holdReason: trimmedText(raw.holdReason) !== "" ? trimmedText(raw.holdReason) : holdReasonOf(raw),
339
+ holdReasonLabel: trimmedText(raw.holdReasonLabel) !== "" ? trimmedText(raw.holdReasonLabel) : skippedReasonLabel(holdReasonOf(raw)),
340
+ bridgeable: bridgeableOf(raw.state) && trimmedText(raw.id) !== "",
260
341
  candidate: state === "candidate",
261
342
  disputed: state === "disputed",
262
343
  });
263
344
  }
264
345
  const total = typeof statsSource.lessons === "number" && statsSource.lessons >= 0 ? statsSource.lessons : rawLessons.length;
265
346
  const evidenceTotal = typeof statsSource.evidence === "number" && statsSource.evidence >= 0 ? statsSource.evidence : 0;
347
+ const badLinesRaw = statsSource.badLines;
348
+ const badLines = typeof badLinesRaw === "number" && Number.isFinite(badLinesRaw) && badLinesRaw > 0 ? Math.floor(badLinesRaw) : 0;
266
349
  return {
267
350
  rows: rows,
268
351
  total: total,
269
352
  shown: rows.length,
270
- stats: { lessons: total, evidence: evidenceTotal, byState: byState },
353
+ stats: { lessons: total, evidence: evidenceTotal, byState: byState, truncated: statsSource.truncated === true, badLines: badLines },
271
354
  };
272
355
  }
273
356
 
@@ -287,6 +370,18 @@ function knowledgeCountText(stats) {
287
370
  return parts.join(" · ");
288
371
  }
289
372
 
373
+ /** 截断 / 坏行提示:无问题时给 "";确保面板不静默展示不完整的经验集。 */
374
+ function knowledgeNoticeText(stats) {
375
+ const source = stats !== null && typeof stats === "object" ? stats : {};
376
+ const truncated = source.truncated === true;
377
+ const badLines = typeof source.badLines === "number" && Number.isFinite(source.badLines) && source.badLines > 0 ? Math.floor(source.badLines) : 0;
378
+ if (truncated !== true && badLines === 0) return "";
379
+ const parts = [];
380
+ if (truncated === true) parts.push("日志过大,仅回放最近一段事件");
381
+ if (badLines > 0) parts.push("有 " + String(badLines) + " 行损坏已跳过");
382
+ return parts.join(";") + ";events.jsonl 未改动。";
383
+ }
384
+
290
385
  /** 经验区块的独立空态:与「还没有梦境日记」分开,说明经验从哪来。 */
291
386
  function knowledgeEmptyState() {
292
387
  return {
@@ -296,6 +391,180 @@ function knowledgeEmptyState() {
296
391
  };
297
392
  }
298
393
 
394
+ /** 检索层 skipped reason 的中文映射(面板扣留原因文案;与 retrieval.ts 的 reason 常量同名)。 */
395
+ function skippedReasonLabel(reason) {
396
+ switch (trimmedText(reason)) {
397
+ case "state:rejected": return "已驳回:不参与默认检索与注入";
398
+ case "state:stale": return "待复核:条件或依赖可能已过期,默认不注入";
399
+ case "state:disputed": return "冲突未解决:默认不注入,先 resolve-conflict";
400
+ case "candidate-hold": return "候选:可被检索到,但默认不作为任务建议注入";
401
+ case "no-evidence": return "证据不足:没有独立证据,默认不注入";
402
+ case "version-mismatch": return "版本不匹配:当前依赖版本不满足适用范围";
403
+ case "limit": return "超出条数预算,本次未取回";
404
+ case "budget-stop": return "超出字符预算:按排名整条装入,遇到第一条放不下即停";
405
+ case "budget": return "超出字符预算,本次未取回";
406
+ default: return "";
407
+ }
408
+ }
409
+
410
+ /** 浏览态的确定性扣留原因;version-mismatch 需要 query / 包名上下文,不在此列。 */
411
+ function holdReasonOf(lesson) {
412
+ const source = lesson !== null && typeof lesson === "object" ? lesson : {};
413
+ const state = normalizeLessonState(source.state);
414
+ if (state === "disputed") return "state:disputed";
415
+ if (state === "stale") return "state:stale";
416
+ if (state === "rejected") return "state:rejected";
417
+ if (state === "candidate") {
418
+ const support = typeof source.independentSupportCount === "number" && source.independentSupportCount > 0 ? source.independentSupportCount : 0;
419
+ return support === 0 ? "no-evidence" : "candidate-hold";
420
+ }
421
+ return "";
422
+ }
423
+
424
+ /** 面板上的「可桥接」只按契约状态判断:usable;scope / 用户采纳等资格由 dream_bridge 执行时校验。 */
425
+ function bridgeableOf(state) {
426
+ return normalizeLessonState(state) === "usable";
427
+ }
428
+
429
+ /** 复制命令里的默认 action:按状态给最常用的一步,用户可在对话里再改。 */
430
+ function reviewActionFor(state) {
431
+ switch (normalizeLessonState(state)) {
432
+ case "candidate": return "accept";
433
+ case "disputed": return "resolve-conflict";
434
+ case "stale": return "accept";
435
+ case "usable": return "mark-stale";
436
+ case "rejected": return "reopen";
437
+ default: return "";
438
+ }
439
+ }
440
+
441
+ /** 派生 dream_review 复制命令;缺 id 时给 ""(面板不展示不可执行命令)。 */
442
+ function reviewCommandOf(lesson, options) {
443
+ const source = lesson !== null && typeof lesson === "object" ? lesson : {};
444
+ const id = trimmedText(source.id);
445
+ if (id === "" || !Number.isInteger(source.revision) || source.revision < 1) return "";
446
+ const opts = options || {};
447
+ const action = opts.action || reviewActionFor(source.state);
448
+ if (action === "") return "";
449
+ const args = { action: action, lessonId: id, expectedRevision: source.revision };
450
+ if (action === "resolve-conflict") {
451
+ if (!["prefer", "drop", "merge"].includes(opts.resolution) || trimmedText(opts.note) === "" || !Array.isArray(opts.affectedIds) || opts.affectedIds.length === 0) return "";
452
+ args.resolution = opts.resolution;
453
+ args.note = opts.note.trim();
454
+ args.affectedIds = opts.affectedIds;
455
+ }
456
+ return "dream_review " + JSON.stringify(args);
457
+ }
458
+
459
+ /** 派生 dream_bridge preview 复制命令(单条经验);projectId 来自 payload,scopeLabel 只展示。 */
460
+ function bridgePreviewCommandOf(lessonId, projectId, workspaceRoot) {
461
+ const id = trimmedText(lessonId);
462
+ if (id === "") return "";
463
+ const project = trimmedText(projectId);
464
+ const args = { mode: "preview", lessonIds: [id] };
465
+ if (project !== "") args.projectId = project;
466
+ const root = trimmedText(workspaceRoot);
467
+ if (root !== "") args.path = root.replace(/[\\/]+$/, "") + "/AGENTS.md";
468
+ return "dream_bridge " + JSON.stringify(args);
469
+ }
470
+
471
+ function bridgeRollbackCommandOf(row) {
472
+ if (!row || row.rollbackable !== true || trimmedText(row.target) === "") return "";
473
+ const backupId = trimmedText(row.id) || trimmedText(row.backupId);
474
+ if (backupId === "") return "";
475
+ return "dream_bridge " + JSON.stringify({ mode: "rollback", backupId: backupId, path: row.target });
476
+ }
477
+
478
+ /** 哈希只展示前 12 位;空值给 ""。 */
479
+ function shortHash(value) {
480
+ const text = trimmedText(value);
481
+ if (text === "") return "";
482
+ return text.length > 12 ? text.slice(0, 12) + "…" : text;
483
+ }
484
+
485
+ function bridgeActionLabel(action) {
486
+ switch (trimmedText(action)) {
487
+ case "create": return "新建";
488
+ case "replace": return "替换管理块";
489
+ case "append": return "追加管理块";
490
+ case "unchanged": return "未改动";
491
+ case "rollback": return "回滚";
492
+ default: return trimmedText(action) || "未知动作";
493
+ }
494
+ }
495
+
496
+ /** 写入记录视图模型:只投影展示字段,不展示原始 JSON / 管理块正文。 */
497
+ function normalizeBridgeRecords(value, now, limit) {
498
+ const source = value !== null && typeof value === "object" ? value : {};
499
+ const cap = typeof limit === "number" && limit > 0 ? limit : BRIDGE_LIMIT;
500
+ const rawRecords = Array.isArray(source.records) ? source.records : [];
501
+ const statsSource = source.stats !== null && typeof source.stats === "object" ? source.stats : {};
502
+ const rows = [];
503
+ let rollbackableCount = 0;
504
+ for (const raw of rawRecords) {
505
+ if (raw === null || typeof raw !== "object") continue;
506
+ const id = trimmedText(raw.backupId);
507
+ const target = trimmedText(raw.target);
508
+ if (id === "" && target === "") continue;
509
+ const rollbackable = raw.rollbackable === true;
510
+ if (rollbackable) rollbackableCount += 1;
511
+ if (rows.length >= cap) continue;
512
+ const at = trimmedText(raw.at);
513
+ const lessonRefs = [];
514
+ if (Array.isArray(raw.lessons)) {
515
+ for (const item of raw.lessons) {
516
+ if (item !== null && typeof item === "object") {
517
+ const lessonId = trimmedText(item.lessonId);
518
+ if (lessonId === "") continue;
519
+ const revision = typeof item.revision === "number" && Number.isFinite(item.revision) && item.revision >= 0 ? Math.floor(item.revision) : 0;
520
+ lessonRefs.push(lessonId + "@" + String(revision));
521
+ } else if (typeof item === "string" && item.trim() !== "") {
522
+ lessonRefs.push(item.trim());
523
+ }
524
+ }
525
+ }
526
+ const rollbackReason = trimmedText(raw.rollbackReason);
527
+ rows.push({
528
+ id: id,
529
+ at: at,
530
+ atText: at === "" ? "时间未知" : relativeTime(at, now),
531
+ atTitle: at === "" ? "没有记录时间" : formatDate(at) + (formatTime(at) === "" ? "" : " " + formatTime(at)),
532
+ target: target || "目标未记录",
533
+ action: trimmedText(raw.action),
534
+ actionLabel: bridgeActionLabel(raw.action),
535
+ lessons: lessonRefs,
536
+ lessonsText: lessonRefs.length === 0 ? "没有经验引用" : lessonRefs.join("、"),
537
+ beforeShort: shortHash(raw.beforeSha256),
538
+ afterShort: shortHash(raw.afterSha256),
539
+ rollbackable: rollbackable,
540
+ rollbackText: rollbackable ? "可回滚" : ("不可回滚" + (rollbackReason !== "" ? ":" + rollbackReason : "")),
541
+ rollbackReason: rollbackReason,
542
+ });
543
+ }
544
+ const total = typeof statsSource.total === "number" && statsSource.total >= 0 ? statsSource.total : rawRecords.length;
545
+ const statsRollback = typeof statsSource.rollbackable === "number" && statsSource.rollbackable >= 0 ? statsSource.rollbackable : rollbackableCount;
546
+ return {
547
+ rows: rows,
548
+ total: total,
549
+ shown: rows.length,
550
+ rollbackable: statsRollback,
551
+ };
552
+ }
553
+
554
+ /** 「写入记录」的独立空态。 */
555
+ function bridgeEmptyState() {
556
+ return {
557
+ title: "还没有写入记录",
558
+ body: "写入记录来自 dream_bridge 的 preview → apply;面板只读展示目标、时间、经验 revision 与前后哈希,不会写文件。",
559
+ hint: "在可桥接经验行点「复制预览命令」,回到对话里执行;写操作只在工具中完成。",
560
+ };
561
+ }
562
+
563
+ /** 「待核实」的独立空态。 */
564
+ function reviewQueueEmptyState() {
565
+ return "没有待核实的经验 —— 候选、有冲突、待复核的经验会出现在这里,并写明被扣下的原因。";
566
+ }
567
+
299
568
  function spanOf(dreams) {
300
569
  let min = null;
301
570
  let max = null;
@@ -566,6 +835,7 @@ function Btn(props) {
566
835
  onClick: props.onClick,
567
836
  disabled: props.disabled === true,
568
837
  title: props.title,
838
+ "aria-expanded": props["aria-expanded"],
569
839
  "aria-pressed": props.pressed === undefined ? undefined : props.pressed,
570
840
  }, props.children);
571
841
  }
@@ -615,10 +885,69 @@ function LessonCard(props) {
615
885
  h("span", { className: "dshd-lesson-meta", key: "m" }, [
616
886
  h("span", { className: "dshd-times", key: "c" }, "×", h("b", { key: "n" }, String(row.count))),
617
887
  row.lastText === "" ? null : h("span", { className: "dshd-last", key: "l", title: row.lastTitle }, row.lastText),
618
- row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, "桥接候选") : null,
888
+ row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, "常见教训") : null,
619
889
  ]),
620
890
  ]))),
621
- h("div", { className: "dshd-note", key: "note" }, "次数 = 日记里出现过的次数,不代表重要度;出现 ≥3 次标为「桥接候选」。是否写入 AGENTS.md 由 dream_bridge 决定。最近时间取自 stats.lastAt;统计缺失时按本页已加载的梦现算。"),
891
+ h("div", { className: "dshd-note", key: "note" }, "次数仅表示日记里的出现频率;出现 ≥3 次标为「常见教训」。写入项目规则前仍需核验来源并审阅经验。最近时间取自 stats.lastAt;统计缺失时按本页已加载的梦现算。"),
892
+ ]);
893
+ }
894
+
895
+ /** 复制命令按钮:成功给「已复制」,剪贴板不可用时回退为可选中文本。 */
896
+ function CopyCommand(props) {
897
+ const command = typeof props.command === "string" ? props.command : "";
898
+ const [copied, setCopied] = useState(false);
899
+ const [failed, setFailed] = useState(false);
900
+ if (command === "") return null;
901
+ const onCopy = function () {
902
+ copyText(command).then(function (ok) {
903
+ setCopied(ok === true);
904
+ setFailed(ok !== true);
905
+ });
906
+ };
907
+ const label = props.label === undefined ? "复制命令" : props.label;
908
+ return h("span", { className: "dshd-copywrap" }, [
909
+ h(Btn, { key: "b", onClick: onCopy, disabled: props.disabled === true, title: props.title === undefined ? command : props.title }, copied === true ? "已复制" : label),
910
+ failed === true ? h("code", { key: "cmd", className: "dshd-cmd", tabIndex: 0 }, command) : null,
911
+ failed === true ? h("span", { key: "hint", className: "dshd-note" }, "剪贴板不可用:请手动选中上面的命令复制。") : null,
912
+ ]);
913
+ }
914
+
915
+ /** 面板保持只读:先让人看清决策,再生成可直接粘贴进对话的完整命令。 */
916
+ function ReviewControls(props) {
917
+ const row = props.row;
918
+ const [open, setOpen] = useState(false);
919
+ const [action, setAction] = useState(function () { return reviewActionFor(row.state); });
920
+ const [resolution, setResolution] = useState("prefer");
921
+ const [note, setNote] = useState("");
922
+ const [affected, setAffected] = useState(function () { return row.conflictIds[0] || ""; });
923
+ const commands = row.state === "rejected" ? [["reopen", "重新送审"]]
924
+ : row.state === "disputed" ? [["resolve-conflict", "处理冲突"], ["reject", "驳回这条"]]
925
+ : [["accept", "采纳"], ["reject", "驳回"], ["mark-stale", "标记待复核"]];
926
+ const command = reviewCommandOf(row, { action: action, resolution: resolution, note: note, affectedIds: affected === "" ? [] : [affected] });
927
+ return h("div", { className: "dshd-review", onClick: function (event) { event.stopPropagation(); } }, [
928
+ h("div", { className: "dshd-kcmd", key: "buttons" }, [
929
+ h(Btn, { key: "review", onClick: function () { setOpen(!open); }, "aria-expanded": open }, open ? "收起审阅" : "审阅"),
930
+ row.bridgeable === true ? h(CopyCommand, { key: "preview", command: bridgePreviewCommandOf(row.id, row.projectId, row.workspaceRoot), label: "复制预览命令", title: "粘贴到对应项目对话,先查看写入差异" }) : null,
931
+ ]),
932
+ open ? h("div", { className: "dshd-reviewform", key: "form", onKeyDown: function (event) {
933
+ if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); setOpen(false); }
934
+ } }, [
935
+ h("label", { key: "action" }, ["审阅动作", h("select", { "aria-label": "审阅动作", value: action, onChange: function (event) { setAction(event.target.value); } }, commands.map(function (item) { return h("option", { value: item[0], key: item[0] }, item[1]); }))]),
936
+ action === "resolve-conflict" ? h("div", { key: "conflict" }, [
937
+ h("label", { key: "against" }, ["冲突经验", h("select", { "aria-label": "冲突经验", value: affected, onChange: function (event) { setAffected(event.target.value); } }, row.conflicts.map(function (item) { return h("option", { key: item.id, value: item.id, title: item.title }, item.title); }))]),
938
+ h("label", { key: "resolution" }, ["处理方式", h("select", { "aria-label": "处理方式", value: resolution, onChange: function (event) { setResolution(event.target.value); } }, [
939
+ h("option", { key: "prefer", value: "prefer" }, "采用这条,驳回对方"),
940
+ h("option", { key: "drop", value: "drop" }, "放弃这条,保留对方"),
941
+ h("option", { key: "merge", value: "merge" }, "合并到这条,对方留作历史"),
942
+ ])]),
943
+ h("label", { key: "note" }, ["处理依据", h("textarea", { "aria-label": "处理依据", rows: 2, value: note, placeholder: "说明哪个来源支持这次决定…", onChange: function (event) { setNote(event.target.value); } })]),
944
+ ]) : null,
945
+ command === "" ? h("div", { className: "dshd-note", key: "incomplete" }, row.revision < 1 ? "记录缺少版本,请刷新后重试。" : "选择冲突经验并填写处理依据后,才能生成命令。") : h("div", { key: "command" }, [
946
+ h("code", { className: "dshd-cmd", tabIndex: 0, key: "text" }, command),
947
+ h(CopyCommand, { key: "copy", command: command, label: "复制审阅命令" }),
948
+ ]),
949
+ h("div", { className: "dshd-note", key: "help" }, "复制后粘贴到 DSH 对话,请助手执行;完成后刷新。命令带版本校验,记录变化时会拒绝旧命令。"),
950
+ ]) : null,
622
951
  ]);
623
952
  }
624
953
 
@@ -637,21 +966,34 @@ function KnowledgeRow(props) {
637
966
  h("span", { className: "dshd-ktitle", key: "t", title: row.title }, row.title),
638
967
  ]),
639
968
  h("div", { className: "dshd-kmeta", key: "m" }, [
640
- h("span", { className: "dshd-kscope", key: "s", title: "适用范围" }, row.scopeLabel),
641
969
  h("span", { className: "dshd-klast", key: "l", title: row.lastTitle }, "最近核验:" + row.lastText),
642
970
  ]),
643
971
  h("div", { className: "dshd-kbody", key: "body" }, [
644
972
  h("div", { key: "when" }, "何时用:" + (row.when !== "" ? row.when : "未标注适用条件")),
645
- h("div", { key: "evidence" }, "证据:" + row.evidenceSummary),
973
+ h("div", { key: "action", className: "dshd-action" }, "怎么做:" + (row.action || "未标注具体动作")),
974
+ h("details", { key: "details", onClick: function (event) { event.stopPropagation(); } }, [
975
+ h("summary", { key: "summary" }, "适用范围与证据"),
976
+ h("div", { key: "scope" }, "范围:" + row.scopeLabel),
977
+ row.applicability !== "" ? h("div", { key: "applicability" }, "适用:" + row.applicability) : null,
978
+ row.exceptions.length > 0 ? h("div", { key: "exceptions" }, "例外:" + row.exceptions.join(";")) : null,
979
+ h("div", { key: "evidence" }, "证据:" + row.evidenceSummary),
980
+ ]),
981
+ props.showHoldReason === true && row.holdReasonLabel !== "" ? h("div", { key: "hold", className: "dshd-holdreason" }, "为什么被扣下:" + row.holdReasonLabel) : null,
646
982
  ]),
983
+ h(ReviewControls, { row: row, key: row.id + "@" + row.revision }),
647
984
  ]);
648
985
  }
649
986
 
650
987
  function KnowledgeCard(props) {
651
988
  const vm = props.vm;
989
+ const [stateFilter, setStateFilter] = useState("usable");
990
+ const [pageSize, setPageSize] = useState(5);
991
+ const reviewed = vm.rows.filter(function (row) { return row.state === stateFilter; });
992
+ const notice = knowledgeNoticeText(vm === null || vm === undefined ? null : vm.stats);
993
+ const noticeNode = notice === "" ? null : h("div", { className: "dshd-knotice", key: "notice", role: "status" }, notice);
652
994
  const head = function (extra) {
653
995
  return h("div", { className: "dshd-khead", key: "head" }, [
654
- h("h4", { key: "t" }, "经验"),
996
+ h("h4", { key: "t" }, "已审阅经验"),
655
997
  h("span", { className: "dshd-spacer", key: "s" }),
656
998
  extra,
657
999
  ]);
@@ -667,6 +1009,7 @@ function KnowledgeCard(props) {
667
1009
  const empty = knowledgeEmptyState();
668
1010
  return h("section", { className: "dshd-card dshd-kcard" }, [
669
1011
  h("h4", { key: "t" }, "经验"),
1012
+ noticeNode,
670
1013
  h("div", { className: "dshd-kempty", key: "e" }, [
671
1014
  h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
672
1015
  h("p", { className: "dshd-kempty-body", key: "b" }, empty.body),
@@ -678,13 +1021,120 @@ function KnowledgeCard(props) {
678
1021
  head(props.loading === true
679
1022
  ? h("span", { className: "dshd-hint", key: "loading" }, "加载中…")
680
1023
  : h("span", { className: "dshd-hint", key: "count" }, knowledgeCountText(vm.stats))),
681
- h("div", { className: "dshd-krows", key: "rows" }, vm.rows.map(function (row, index) {
1024
+ noticeNode,
1025
+ h("label", { className: "dshd-note", key: "filter" }, ["查看 ", h("select", { "aria-label": "经验状态", value: stateFilter, onChange: function (event) { setStateFilter(event.target.value); setPageSize(5); } }, [
1026
+ h("option", { key: "usable", value: "usable" }, "可用经验"), h("option", { key: "rejected", value: "rejected" }, "已驳回历史"),
1027
+ ])]),
1028
+ reviewed.length === 0 ? h("div", { className: "dshd-hint", key: "none" }, stateFilter === "usable" ? "暂无可用经验;待核实的记录在下方。" : "没有已驳回记录。") : null,
1029
+ h("div", { className: "dshd-krows", key: "rows" }, reviewed.slice(0, pageSize).map(function (row, index) {
682
1030
  return h(KnowledgeRow, { key: row.id !== "" ? row.id : "k" + String(index), row: row, privacy: props.privacy === true });
683
1031
  })),
1032
+ reviewed.length > pageSize ? h(Btn, { key: "next", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多经验") : null,
684
1033
  vm.shown < vm.total
685
1034
  ? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条经验。")
686
1035
  : null,
687
- h("div", { className: "dshd-note", key: "note" }, "只读展示:候选默认不注入任务;采纳、驳回或标记冲突请用 dream_review。"),
1036
+ h("div", { className: "dshd-note", key: "note" }, "采纳记录仍需满足当前任务的平台、版本与项目条件;来源已读不代表结论永远适用。"),
1037
+ ]);
1038
+ }
1039
+
1040
+ /** 「待核实」只读区块:candidate / disputed / stale + 被扣下的原因(复用 KnowledgeRow)。 */
1041
+ function ReviewQueueCard(props) {
1042
+ const vm = props.vm;
1043
+ const [pageSize, setPageSize] = useState(5);
1044
+ const rows = vm !== null && vm !== undefined && Array.isArray(vm.rows)
1045
+ ? vm.rows.filter(function (row) { return HELD_STATES.indexOf(row.state) !== -1; })
1046
+ : [];
1047
+ const head = function (extra) {
1048
+ return h("div", { className: "dshd-khead", key: "head" }, [
1049
+ h("h4", { key: "t" }, "待核实"),
1050
+ h("span", { className: "dshd-spacer", key: "s" }),
1051
+ extra,
1052
+ ]);
1053
+ };
1054
+ if (props.error !== null && props.error !== undefined) {
1055
+ return h("section", { className: "dshd-card dshd-kcard" }, [
1056
+ head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, "重试")),
1057
+ h("div", { className: "dshd-hint", key: "e", role: "alert" }, "读取经验失败:" + (props.error.message === "" ? String(props.error) : props.error.message)),
1058
+ ]);
1059
+ }
1060
+ if (props.loading === true) {
1061
+ return h("section", { className: "dshd-card dshd-kcard" }, [
1062
+ head(h("span", { className: "dshd-hint", key: "l" }, "加载中…")),
1063
+ ]);
1064
+ }
1065
+ return h("section", { className: "dshd-card dshd-kcard" }, [
1066
+ head(rows.length === 0 ? null : h("span", { className: "dshd-hint", key: "c" }, String(rows.length) + " 条待核实")),
1067
+ rows.length === 0
1068
+ ? h("div", { className: "dshd-kempty", key: "empty" }, [
1069
+ h("div", { className: "dshd-kempty-title", key: "t" }, "没有待核实的经验"),
1070
+ h("p", { className: "dshd-kempty-body", key: "b" }, reviewQueueEmptyState()),
1071
+ ])
1072
+ : h("div", { className: "dshd-krows", key: "rows" }, rows.slice(0, pageSize).map(function (row, index) {
1073
+ return h(KnowledgeRow, { key: row.id !== "" ? row.id : "held-" + String(index), row: row, privacy: props.privacy === true, showHoldReason: true });
1074
+ })),
1075
+ rows.length > pageSize ? h(Btn, { key: "more", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多待核实经验") : null,
1076
+ h("div", { className: "dshd-note", key: "note" }, "这些经验默认不会作为任务建议注入;扣留原因来自检索层的 skipped reason。复制审阅命令后到对话里执行,面板不会写文件。"),
1077
+ ]);
1078
+ }
1079
+
1080
+ /** 「写入记录」只读区块:<journalDir>/bridge/ 的桥接应用记录。 */
1081
+ function BridgeLogCard(props) {
1082
+ const vm = props.vm;
1083
+ const head = function (extra) {
1084
+ return h("div", { className: "dshd-khead", key: "head" }, [
1085
+ h("h4", { key: "t" }, "写入记录"),
1086
+ h("span", { className: "dshd-spacer", key: "s" }),
1087
+ extra,
1088
+ ]);
1089
+ };
1090
+ if (props.error !== null && props.error !== undefined) {
1091
+ return h("section", { className: "dshd-card dshd-kcard" }, [
1092
+ head(h(Btn, { key: "retry", onClick: props.onRetry, disabled: props.loading === true }, "重试")),
1093
+ h("div", { className: "dshd-hint", key: "e", role: "alert" }, "读取写入记录失败:" + (props.error.message === "" ? String(props.error) : props.error.message)),
1094
+ ]);
1095
+ }
1096
+ if (props.loading === true) {
1097
+ return h("section", { className: "dshd-card dshd-kcard" }, [
1098
+ head(h("span", { className: "dshd-hint", key: "l" }, "加载中…")),
1099
+ ]);
1100
+ }
1101
+ if (vm.rows.length === 0) {
1102
+ const empty = bridgeEmptyState();
1103
+ return h("section", { className: "dshd-card dshd-kcard" }, [
1104
+ h("h4", { key: "t" }, "写入记录"),
1105
+ h("div", { className: "dshd-kempty", key: "empty" }, [
1106
+ h("div", { className: "dshd-kempty-title", key: "t2" }, empty.title),
1107
+ h("p", { className: "dshd-kempty-body", key: "b" }, empty.body),
1108
+ h("p", { className: "dshd-note", key: "h" }, empty.hint),
1109
+ ]),
1110
+ ]);
1111
+ }
1112
+ return h("section", { className: "dshd-card dshd-kcard" }, [
1113
+ head(h("span", { className: "dshd-hint", key: "c" }, String(vm.total) + " 次写入 · " + String(vm.rollbackable) + " 次可回滚")),
1114
+ h("div", { className: "dshd-krows", key: "rows" }, vm.rows.map(function (row, index) {
1115
+ return h("div", { className: "dshd-bridge", key: row.id !== "" ? row.id : "bridge-" + String(index) }, [
1116
+ h("div", { className: "dshd-bridge-head", key: "h" }, [
1117
+ h("span", { className: "dshd-kbadge", key: "a" }, row.actionLabel),
1118
+ h("span", { className: "dshd-bridge-target", key: "t", title: row.target }, row.target),
1119
+ h("span", { className: "dshd-spacer", key: "s" }),
1120
+ h("span", { className: "dshd-klast", key: "at", title: row.atTitle }, row.atText),
1121
+ ]),
1122
+ h("div", { className: "dshd-bridge-meta", key: "m" }, [
1123
+ h("span", { key: "lessons", title: row.lessonsText }, "经验:" + row.lessonsText),
1124
+ row.beforeShort === "" ? null : h("span", { key: "before" }, "前哈希:" + row.beforeShort),
1125
+ row.afterShort === "" ? null : h("span", { key: "after" }, "后哈希:" + row.afterShort),
1126
+ h("span", { key: "rb", className: row.rollbackable === true ? "dshd-rollback-ok" : "dshd-rollback-no" }, row.rollbackText),
1127
+ ]),
1128
+ h("details", { key: "backup" }, [
1129
+ h("summary", { key: "summary" }, "备份标识与回滚命令"),
1130
+ h("code", { key: "id", className: "dshd-cmd" }, row.id),
1131
+ row.rollbackable === true ? h("code", { key: "command", className: "dshd-cmd", tabIndex: 0 }, bridgeRollbackCommandOf(row)) : null,
1132
+ ]),
1133
+ row.rollbackable === true ? h(CopyCommand, { key: "rollback", label: "复制回滚命令", command: bridgeRollbackCommandOf(row), title: "粘贴到对应项目对话执行;只恢复管理块,保留块外人工内容" }) : null,
1134
+ ]);
1135
+ })),
1136
+ vm.shown < vm.total ? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条记录。") : null,
1137
+ h("div", { className: "dshd-note", key: "note" }, "只读展示:记录来自 dream_bridge 的 apply;回滚请按 dream_bridge 返回的备份标识在对话里执行,面板不会写文件。"),
688
1138
  ]);
689
1139
  }
690
1140
 
@@ -754,6 +1204,7 @@ function Skeleton() {
754
1204
  // ───────────────────────── 主面板 ─────────────────────────
755
1205
 
756
1206
  function DreamPanel() {
1207
+ const [activeTab, setActiveTab] = useState("journal");
757
1208
  const [dreams, setDreams] = useState([]);
758
1209
  const [stats, setStats] = useState(null);
759
1210
  const [loading, setLoading] = useState(true);
@@ -769,6 +1220,9 @@ function DreamPanel() {
769
1220
  const [knowledge, setKnowledge] = useState(null);
770
1221
  const [knowledgeLoading, setKnowledgeLoading] = useState(true);
771
1222
  const [knowledgeError, setKnowledgeError] = useState(null);
1223
+ const [bridge, setBridge] = useState(null);
1224
+ const [bridgeLoading, setBridgeLoading] = useState(true);
1225
+ const [bridgeError, setBridgeError] = useState(null);
772
1226
 
773
1227
  // 搜索防抖:连续打字只发一次请求;回车立即生效。
774
1228
  useEffect(function () {
@@ -813,8 +1267,27 @@ function DreamPanel() {
813
1267
  return function () { alive = false; };
814
1268
  }, [reloadToken]);
815
1269
 
1270
+ // 写入记录独立取数:桥接目录不存在时返回空,不影响其余区块。
1271
+ useEffect(function () {
1272
+ let alive = true;
1273
+ setBridgeLoading(true);
1274
+ setBridgeError(null);
1275
+ fetchBridgeRecords().then(function (payload) {
1276
+ if (alive !== true) return;
1277
+ setBridge(normalizeBridgeRecords(payload, new Date()));
1278
+ setBridgeLoading(false);
1279
+ }).catch(function (failure) {
1280
+ if (alive !== true) return;
1281
+ setBridgeError(failure instanceof Error ? failure : new Error(String(failure)));
1282
+ setBridgeLoading(false);
1283
+ setBridge(null);
1284
+ });
1285
+ return function () { alive = false; };
1286
+ }, [reloadToken]);
1287
+
816
1288
  const vm = viewModelOf(dreams, stats, now);
817
1289
  const knowledgeVm = knowledge === null ? { rows: [], total: 0, shown: 0, stats: null } : knowledge;
1290
+ const bridgeVm = bridge === null ? { rows: [], total: 0, shown: 0, rollbackable: 0 } : bridge;
818
1291
  const pristine = vm.count === 0 && query === "";
819
1292
  const filterCount = filterCountOf(filters);
820
1293
  const visibleDreams = applyFilters(dreams, filters);
@@ -849,6 +1322,22 @@ function DreamPanel() {
849
1322
  privacy: privacy === true,
850
1323
  onRetry: showKnowledge,
851
1324
  });
1325
+ const reviewQueueCard = h(ReviewQueueCard, {
1326
+ key: "review-queue",
1327
+ vm: knowledgeVm,
1328
+ loading: knowledgeLoading === true,
1329
+ error: knowledgeError,
1330
+ privacy: privacy === true,
1331
+ onRetry: showKnowledge,
1332
+ });
1333
+ const showBridge = function () { if (bridgeLoading !== true) setReloadToken(function (value) { return value + 1; }); };
1334
+ const bridgeLogCard = h(BridgeLogCard, {
1335
+ key: "bridge-log",
1336
+ vm: bridgeVm,
1337
+ loading: bridgeLoading === true,
1338
+ error: bridgeError,
1339
+ onRetry: showBridge,
1340
+ });
852
1341
 
853
1342
  // 筛选 popover:点面板外任意处收起;纯 node 冒烟没有事件系统就跳过。
854
1343
  useEffect(function () {
@@ -859,8 +1348,18 @@ function DreamPanel() {
859
1348
  if (pop !== null && typeof pop.contains === "function" && pop.contains(event.target) === true) return;
860
1349
  setFilterOpen(false);
861
1350
  };
1351
+ const onKey = function (event) {
1352
+ if (event.key !== "Escape") return;
1353
+ event.preventDefault();
1354
+ event.stopPropagation();
1355
+ event.stopImmediatePropagation();
1356
+ setFilterOpen(false);
1357
+ const trigger = document.querySelector(".dshd-filter button");
1358
+ if (trigger && typeof trigger.focus === "function") trigger.focus();
1359
+ };
862
1360
  document.addEventListener("mousedown", onDown);
863
- return function () { document.removeEventListener("mousedown", onDown); };
1361
+ document.addEventListener("keydown", onKey, true);
1362
+ return function () { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey, true); };
864
1363
  }, [filterOpen]);
865
1364
 
866
1365
  const searchIcon = h("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", key: "i" }, [
@@ -869,7 +1368,7 @@ function DreamPanel() {
869
1368
  ]);
870
1369
 
871
1370
  const toolbar = h("div", { className: "dshd-toolbar", key: "toolbar" }, [
872
- pristine ? null : h("div", { className: "dshd-search", key: "search" }, [
1371
+ pristine || activeTab !== "journal" ? null : h("div", { className: "dshd-search", key: "search" }, [
873
1372
  searchIcon,
874
1373
  h("input", {
875
1374
  key: "search-input",
@@ -890,10 +1389,10 @@ function DreamPanel() {
890
1389
  }, "×"),
891
1390
  ]),
892
1391
  h("span", { className: "dshd-spacer", key: "spacer" }),
893
- pristine ? null : h("span", { className: "dshd-hint", key: "count" }, loading === true
1392
+ pristine || activeTab !== "journal" ? null : h("span", { className: "dshd-hint", key: "count" }, loading === true
894
1393
  ? "加载中…"
895
1394
  : countTextOf(query, vm.total, vm.count, filterCount, visibleDreams.length)),
896
- showFilterButton === true ? h("div", { className: "dshd-filter", key: "filter" }, [
1395
+ showFilterButton === true && activeTab === "journal" ? h("div", { className: "dshd-filter", key: "filter" }, [
897
1396
  h(Btn, {
898
1397
  key: "btn",
899
1398
  on: filterCount > 0,
@@ -942,16 +1441,17 @@ function DreamPanel() {
942
1441
  ]);
943
1442
 
944
1443
  const body = function () {
945
- if (loading === true && vm.count === 0) return h("div", { key: "boot" }, [h(Skeleton, { key: "skeleton" }), knowledgeCard]);
1444
+ if (activeTab === "knowledge") return h("div", { key: "knowledge-tab" }, [knowledgeCard, reviewQueueCard]);
1445
+ if (activeTab === "bridge") return bridgeLogCard;
1446
+ if (loading === true && vm.count === 0) return h(Skeleton, { key: "skeleton" });
946
1447
  if (vm.count === 0) {
947
1448
  if (error !== null) return h("div", { key: "failed-wrap" }, [
948
1449
  h("div", { className: "dshd-empty", key: "failed" }, [
949
1450
  h("h3", { key: "t" }, "暂时读不到梦境日记"),
950
1451
  h("p", { key: "p" }, "等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。"),
951
1452
  ]),
952
- knowledgeCard,
953
1453
  ]);
954
- return h("div", { key: "empty-wrap" }, [h(EmptyState, { key: "empty", query: query, onClear: query === "" ? null : clearSearch }), knowledgeCard]);
1454
+ return h(EmptyState, { key: "empty", query: query, onClear: query === "" ? null : clearSearch });
955
1455
  }
956
1456
  return h("div", { key: "content" }, [
957
1457
  vm.showTiles
@@ -966,7 +1466,6 @@ function DreamPanel() {
966
1466
  h(MoodCard, { key: "mood", vm: vm }),
967
1467
  h(LessonCard, { key: "lessons", vm: vm }),
968
1468
  ]),
969
- knowledgeCard,
970
1469
  h("div", { className: "dshd-listhead", key: "listhead" }, [
971
1470
  h("h4", { key: "t" }, query === "" && filterCount === 0 ? "梦境时间线" : (query === "" ? "筛选结果" : "搜索结果")),
972
1471
  h("span", { className: "dshd-hint", key: "n" }, listHintOf(query, vm.count, filterCount, visibleDreams.length)),
@@ -986,10 +1485,13 @@ function DreamPanel() {
986
1485
  return h("div", { className: "dshd-root" + (privacy === true ? " dshd-privacy" : ""), key: "root" }, [
987
1486
  h("div", { className: "dshd-shell", key: "shell" }, [
988
1487
  errorBox,
1488
+ h("nav", { className: "dshd-tabs", key: "tabs", "aria-label": "梦境面板视图" }, [["journal", "日记"], ["knowledge", "经验"], ["bridge", "写入记录"]].map(function (item) {
1489
+ return h("button", { key: item[0], type: "button", className: "dshd-chipbtn" + (activeTab === item[0] ? " on" : ""), "aria-pressed": activeTab === item[0], onClick: function () { setFilterOpen(false); setActiveTab(item[0]); } }, item[1]);
1490
+ })),
989
1491
  toolbar,
990
1492
  privacy === true ? h("div", { className: "dshd-note", key: "privacynote", role: "status" }, "隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。") : null,
991
1493
  body(),
992
- h("div", { className: "dshd-footer", key: "footer" }, [
1494
+ activeTab !== "journal" ? null : h("div", { className: "dshd-footer", key: "footer" }, [
993
1495
  h("span", { key: "l" }, footerTextOf(query, limit, vm.total, vm.count)),
994
1496
  h("span", { className: "dshd-spacer", key: "sp" }),
995
1497
  canLoadMore === true ? h(Btn, { key: "more", onClick: showMore, disabled: loading === true }, loading === true ? "加载中…" : "显示更早的梦") : null,
@@ -1012,7 +1514,6 @@ const CSS = [
1012
1514
  ".dshd-root{color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:1.6}",
1013
1515
  ".dshd-shell{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.08));border-radius:14px;padding:14px 16px;container-type:inline-size}",
1014
1516
  ".dshd-sub{color:var(--dsw-alias-label-tertiary,#6b7280);font-size:12px;margin:4px 0 12px}",
1015
- "@container (max-width:620px){.dshd-two{grid-template-columns:1fr}.dshd-toolbar .dshd-hint{display:none}}",
1016
1517
  ".dshd-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:12px}",
1017
1518
  ".dshd-search{position:relative;display:flex;align-items:center;flex:1;min-width:180px}",
1018
1519
  ".dshd-search svg{position:absolute;left:9px;color:var(--dsw-alias-label-tertiary,#6b7280);pointer-events:none}",
@@ -1118,8 +1619,26 @@ const CSS = [
1118
1619
  ".dshd-kempty{padding:14px 10px;text-align:center;color:var(--dsw-alias-label-tertiary,#6b7280)}",
1119
1620
  ".dshd-kempty-title{color:var(--dsw-alias-label-primary,#111);font-weight:600;font-size:13px;margin-bottom:4px}",
1120
1621
  ".dshd-kempty-body{margin:0 auto 5px;max-width:460px;font-size:12px}",
1622
+ ".dshd-knotice{font-size:11px;color:var(--dsw-alias-label-primary,#111);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 14%,transparent);border-radius:8px;padding:5px 8px;margin-bottom:7px}",
1121
1623
  ".dshd-privacy .dshd-kbody{filter:blur(4px)}",
1122
- ".dshd-privacy .dshd-knowledge.dshd-peek .dshd-kbody{filter:none}"
1624
+ ".dshd-privacy .dshd-knowledge.dshd-peek .dshd-kbody{filter:none}",
1625
+ ".dshd-kcmd{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}",
1626
+ ".dshd-tabs{display:flex;gap:4px;margin-bottom:8px;flex-wrap:wrap}",
1627
+ ".dshd-action{font-weight:500;white-space:pre-wrap}",
1628
+ ".dshd-kbody details summary,.dshd-bridge-row details summary{cursor:pointer;color:var(--dsw-alias-label-tertiary,#6b7280);font-size:11px}",
1629
+ ".dshd-reviewform{margin-top:8px;padding:9px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:8px}",
1630
+ ".dshd-reviewform label{display:flex;flex-direction:column;gap:3px;margin-bottom:6px;font-size:12px}",
1631
+ ".dshd-reviewform select,.dshd-reviewform textarea,.dshd-kcard select{background:var(--dsw-alias-bg-layer-1,#fff);color:inherit;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:6px;max-width:100%;padding:4px;font:inherit;box-sizing:border-box}",
1632
+ ".dshd-copywrap{display:inline-flex;flex-direction:column;gap:3px;max-width:100%;align-items:flex-start}",
1633
+ ".dshd-cmd{display:block;font-family:ui-monospace,Consolas,monospace;font-size:11px;white-space:pre-wrap;word-break:break-all;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.04));border-radius:6px;padding:4px 7px;user-select:text;cursor:text;color:inherit;max-width:100%}",
1634
+ ".dshd-holdreason{font-size:11px;color:var(--dsw-alias-state-warn-primary,#f59e0b);margin-top:4px}",
1635
+ ".dshd-bridge{background:var(--dsw-alias-bg-layer-1,#fff);border-radius:9px;padding:8px 10px;border-left:3px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));min-width:0}",
1636
+ ".dshd-bridge-head{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}",
1637
+ ".dshd-bridge-target{font-size:12px;font-weight:600;word-break:break-all;min-width:0}",
1638
+ ".dshd-bridge-meta{display:flex;gap:10px;flex-wrap:wrap;font-size:11px;color:var(--dsw-alias-label-tertiary,#6b7280);margin-top:3px}",
1639
+ ".dshd-rollback-ok{color:var(--dsw-alias-state-success-primary,#16a34a)}",
1640
+ ".dshd-rollback-no{color:var(--dsw-alias-label-tertiary,#6b7280)}",
1641
+ "@container (max-width:620px){.dshd-two{grid-template-columns:1fr}.dshd-toolbar .dshd-hint{display:none}}"
1123
1642
  ].join("");
1124
1643
  /* @ui-css-end */
1125
1644
 
@@ -1163,8 +1682,26 @@ exports.__internals = {
1163
1682
  KNOWLEDGE_ROUTE: KNOWLEDGE_ROUTE,
1164
1683
  KNOWLEDGE_LIMIT: KNOWLEDGE_LIMIT,
1165
1684
  fetchKnowledge: fetchKnowledge,
1685
+ fetchBridgeRecords: fetchBridgeRecords,
1686
+ copyText: copyText,
1687
+ normalizeBridgeRecords: normalizeBridgeRecords,
1688
+ skippedReasonLabel: skippedReasonLabel,
1689
+ holdReasonOf: holdReasonOf,
1690
+ bridgeableOf: bridgeableOf,
1691
+ reviewActionFor: reviewActionFor,
1692
+ reviewCommandOf: reviewCommandOf,
1693
+ bridgePreviewCommandOf: bridgePreviewCommandOf,
1694
+ bridgeRollbackCommandOf: bridgeRollbackCommandOf,
1695
+ shortHash: shortHash,
1696
+ bridgeActionLabel: bridgeActionLabel,
1697
+ bridgeEmptyState: bridgeEmptyState,
1698
+ reviewQueueEmptyState: reviewQueueEmptyState,
1699
+ BRIDGE_ROUTE: BRIDGE_ROUTE,
1700
+ BRIDGE_LIMIT: BRIDGE_LIMIT,
1166
1701
  normalizeKnowledge: normalizeKnowledge,
1167
1702
  normalizeLessonState: normalizeLessonState,
1703
+ applicabilityTextOf: applicabilityTextOf,
1704
+ knowledgeNoticeText: knowledgeNoticeText,
1168
1705
  lessonStateLabel: lessonStateLabel,
1169
1706
  lessonStateClass: lessonStateClass,
1170
1707
  knowledgeCountText: knowledgeCountText,