@stardustlc/dsh-dream 0.5.1 → 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,12 +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 挂载);前端不缓存业务数据,刷新即见。
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 挂载);前端不缓存业务数据,刷新即见。
9
11
  // 经验区块只展示状态 / 条件 / 范围 / 适用与例外 / 证据摘要 / 最近核验;日志截断或坏行会明确提示,
10
- // 不展示权重、向量或原始 JSON。
12
+ // 不展示权重、向量或原始 JSON。经验行只提供「复制审阅 / 预览命令」,写操作仍在对话工具里执行。
11
13
  // 隐私纪律:面板只读,不写文件、不上传;「隐私模式」只做显示层模糊,DOM 仍是明文。
12
14
  window.__ModuleLoader__.load({ id: "@stardustlc/dsh-dream", factory: (require) => {
13
15
  var module = { exports: {} }; var exports = module.exports;
@@ -20,8 +22,11 @@ const h = React.createElement;
20
22
  // 相对路径按 document.baseURI 解析:反代挂在 /tools/dsh/ 这类前缀下也不会打偏。
21
23
  const ROUTE = "_dsh/dsh-dream/journal";
22
24
  const KNOWLEDGE_ROUTE = "_dsh/dsh-dream/knowledge";
25
+ const BRIDGE_ROUTE = "_dsh/dsh-dream/bridge";
23
26
  const KNOWLEDGE_LIMIT = 50;
27
+ const BRIDGE_LIMIT = 50;
24
28
  const KNOWLEDGE_STATES = ["candidate", "usable", "disputed", "stale", "rejected"];
29
+ const HELD_STATES = ["candidate", "disputed", "stale"];
25
30
  const DEFAULT_LIMIT = 50;
26
31
  const MORE_LIMIT = 500;
27
32
  const FETCH_TIMEOUT_MS = 15000;
@@ -121,6 +126,40 @@ async function fetchKnowledge() {
121
126
  };
122
127
  }
123
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
+
124
163
  function normalizeDreams(value, limit) {
125
164
  if (!Array.isArray(value) || limit <= 0) return [];
126
165
  const out = [];
@@ -247,13 +286,20 @@ function applicabilityTextOf(value) {
247
286
 
248
287
  /**
249
288
  * 经验视图模型:只投影展示所需字段(title / when / applicability / exceptions /
250
- * state / scopeLabel / evidenceSummary / lastValidatedAt),不碰权重、向量、原始 JSON。
289
+ * state / scopeLabel / evidenceSummary / lastValidatedAt),并派生 revision / 扣留原因 /
290
+ * 可桥接标记与复制命令所需的稳定字段;不碰权重、向量、原始 JSON。
251
291
  * limit 只截断渲染行数,byState 仍按全量统计;truncated / badLines 原样带给提示行。
252
292
  */
253
293
  function normalizeKnowledge(value, now, limit) {
254
294
  const source = value !== null && typeof value === "object" ? value : {};
255
295
  const cap = typeof limit === "number" && limit > 0 ? limit : KNOWLEDGE_LIMIT;
256
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
+ }
257
303
  const statsSource = source.stats !== null && typeof source.stats === "object" ? source.stats : {};
258
304
  const byState = {};
259
305
  const rows = [];
@@ -269,6 +315,7 @@ function normalizeKnowledge(value, now, limit) {
269
315
  rows.push({
270
316
  id: trimmedText(raw.id),
271
317
  title: title !== "" ? title : action,
318
+ action: action,
272
319
  when: trimmedText(raw.when),
273
320
  applicability: applicabilityTextOf(raw.applicability),
274
321
  exceptions: Array.isArray(raw.exceptions)
@@ -282,6 +329,15 @@ function normalizeKnowledge(value, now, limit) {
282
329
  lastValidatedAt: lastValidatedAt,
283
330
  lastText: lastValidatedAt === "" ? "尚未核验" : relativeTime(lastValidatedAt, now),
284
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) !== "",
285
341
  candidate: state === "candidate",
286
342
  disputed: state === "disputed",
287
343
  });
@@ -335,6 +391,180 @@ function knowledgeEmptyState() {
335
391
  };
336
392
  }
337
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
+
338
568
  function spanOf(dreams) {
339
569
  let min = null;
340
570
  let max = null;
@@ -605,6 +835,7 @@ function Btn(props) {
605
835
  onClick: props.onClick,
606
836
  disabled: props.disabled === true,
607
837
  title: props.title,
838
+ "aria-expanded": props["aria-expanded"],
608
839
  "aria-pressed": props.pressed === undefined ? undefined : props.pressed,
609
840
  }, props.children);
610
841
  }
@@ -654,10 +885,69 @@ function LessonCard(props) {
654
885
  h("span", { className: "dshd-lesson-meta", key: "m" }, [
655
886
  h("span", { className: "dshd-times", key: "c" }, "×", h("b", { key: "n" }, String(row.count))),
656
887
  row.lastText === "" ? null : h("span", { className: "dshd-last", key: "l", title: row.lastTitle }, row.lastText),
657
- row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, "桥接候选") : null,
888
+ row.candidate ? h("span", { className: "dshd-badge", key: "badge" }, "常见教训") : null,
658
889
  ]),
659
890
  ]))),
660
- 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,
661
951
  ]);
662
952
  }
663
953
 
@@ -676,25 +966,34 @@ function KnowledgeRow(props) {
676
966
  h("span", { className: "dshd-ktitle", key: "t", title: row.title }, row.title),
677
967
  ]),
678
968
  h("div", { className: "dshd-kmeta", key: "m" }, [
679
- h("span", { className: "dshd-kscope", key: "s", title: "适用范围" }, row.scopeLabel),
680
969
  h("span", { className: "dshd-klast", key: "l", title: row.lastTitle }, "最近核验:" + row.lastText),
681
970
  ]),
682
971
  h("div", { className: "dshd-kbody", key: "body" }, [
683
972
  h("div", { key: "when" }, "何时用:" + (row.when !== "" ? row.when : "未标注适用条件")),
684
- row.applicability !== "" ? h("div", { key: "applicability", title: row.applicability }, "适用:" + row.applicability) : null,
685
- row.exceptions.length > 0 ? h("div", { key: "exceptions", title: row.exceptions.join(";") }, "例外:" + row.exceptions.join(";")) : null,
686
- 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,
687
982
  ]),
983
+ h(ReviewControls, { row: row, key: row.id + "@" + row.revision }),
688
984
  ]);
689
985
  }
690
986
 
691
987
  function KnowledgeCard(props) {
692
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; });
693
992
  const notice = knowledgeNoticeText(vm === null || vm === undefined ? null : vm.stats);
694
993
  const noticeNode = notice === "" ? null : h("div", { className: "dshd-knotice", key: "notice", role: "status" }, notice);
695
994
  const head = function (extra) {
696
995
  return h("div", { className: "dshd-khead", key: "head" }, [
697
- h("h4", { key: "t" }, "经验"),
996
+ h("h4", { key: "t" }, "已审阅经验"),
698
997
  h("span", { className: "dshd-spacer", key: "s" }),
699
998
  extra,
700
999
  ]);
@@ -723,13 +1022,119 @@ function KnowledgeCard(props) {
723
1022
  ? h("span", { className: "dshd-hint", key: "loading" }, "加载中…")
724
1023
  : h("span", { className: "dshd-hint", key: "count" }, knowledgeCountText(vm.stats))),
725
1024
  noticeNode,
726
- h("div", { className: "dshd-krows", key: "rows" }, vm.rows.map(function (row, index) {
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) {
727
1030
  return h(KnowledgeRow, { key: row.id !== "" ? row.id : "k" + String(index), row: row, privacy: props.privacy === true });
728
1031
  })),
1032
+ reviewed.length > pageSize ? h(Btn, { key: "next", onClick: function () { setPageSize(pageSize + 5); } }, "显示更多经验") : null,
729
1033
  vm.shown < vm.total
730
1034
  ? h("div", { className: "dshd-note", key: "more" }, "只显示最近 " + String(vm.shown) + " / " + String(vm.total) + " 条经验。")
731
1035
  : null,
732
- 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 返回的备份标识在对话里执行,面板不会写文件。"),
733
1138
  ]);
734
1139
  }
735
1140
 
@@ -799,6 +1204,7 @@ function Skeleton() {
799
1204
  // ───────────────────────── 主面板 ─────────────────────────
800
1205
 
801
1206
  function DreamPanel() {
1207
+ const [activeTab, setActiveTab] = useState("journal");
802
1208
  const [dreams, setDreams] = useState([]);
803
1209
  const [stats, setStats] = useState(null);
804
1210
  const [loading, setLoading] = useState(true);
@@ -814,6 +1220,9 @@ function DreamPanel() {
814
1220
  const [knowledge, setKnowledge] = useState(null);
815
1221
  const [knowledgeLoading, setKnowledgeLoading] = useState(true);
816
1222
  const [knowledgeError, setKnowledgeError] = useState(null);
1223
+ const [bridge, setBridge] = useState(null);
1224
+ const [bridgeLoading, setBridgeLoading] = useState(true);
1225
+ const [bridgeError, setBridgeError] = useState(null);
817
1226
 
818
1227
  // 搜索防抖:连续打字只发一次请求;回车立即生效。
819
1228
  useEffect(function () {
@@ -858,8 +1267,27 @@ function DreamPanel() {
858
1267
  return function () { alive = false; };
859
1268
  }, [reloadToken]);
860
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
+
861
1288
  const vm = viewModelOf(dreams, stats, now);
862
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;
863
1291
  const pristine = vm.count === 0 && query === "";
864
1292
  const filterCount = filterCountOf(filters);
865
1293
  const visibleDreams = applyFilters(dreams, filters);
@@ -894,6 +1322,22 @@ function DreamPanel() {
894
1322
  privacy: privacy === true,
895
1323
  onRetry: showKnowledge,
896
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
+ });
897
1341
 
898
1342
  // 筛选 popover:点面板外任意处收起;纯 node 冒烟没有事件系统就跳过。
899
1343
  useEffect(function () {
@@ -904,8 +1348,18 @@ function DreamPanel() {
904
1348
  if (pop !== null && typeof pop.contains === "function" && pop.contains(event.target) === true) return;
905
1349
  setFilterOpen(false);
906
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
+ };
907
1360
  document.addEventListener("mousedown", onDown);
908
- return function () { document.removeEventListener("mousedown", onDown); };
1361
+ document.addEventListener("keydown", onKey, true);
1362
+ return function () { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey, true); };
909
1363
  }, [filterOpen]);
910
1364
 
911
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" }, [
@@ -914,7 +1368,7 @@ function DreamPanel() {
914
1368
  ]);
915
1369
 
916
1370
  const toolbar = h("div", { className: "dshd-toolbar", key: "toolbar" }, [
917
- pristine ? null : h("div", { className: "dshd-search", key: "search" }, [
1371
+ pristine || activeTab !== "journal" ? null : h("div", { className: "dshd-search", key: "search" }, [
918
1372
  searchIcon,
919
1373
  h("input", {
920
1374
  key: "search-input",
@@ -935,10 +1389,10 @@ function DreamPanel() {
935
1389
  }, "×"),
936
1390
  ]),
937
1391
  h("span", { className: "dshd-spacer", key: "spacer" }),
938
- 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
939
1393
  ? "加载中…"
940
1394
  : countTextOf(query, vm.total, vm.count, filterCount, visibleDreams.length)),
941
- showFilterButton === true ? h("div", { className: "dshd-filter", key: "filter" }, [
1395
+ showFilterButton === true && activeTab === "journal" ? h("div", { className: "dshd-filter", key: "filter" }, [
942
1396
  h(Btn, {
943
1397
  key: "btn",
944
1398
  on: filterCount > 0,
@@ -987,16 +1441,17 @@ function DreamPanel() {
987
1441
  ]);
988
1442
 
989
1443
  const body = function () {
990
- 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" });
991
1447
  if (vm.count === 0) {
992
1448
  if (error !== null) return h("div", { key: "failed-wrap" }, [
993
1449
  h("div", { className: "dshd-empty", key: "failed" }, [
994
1450
  h("h3", { key: "t" }, "暂时读不到梦境日记"),
995
1451
  h("p", { key: "p" }, "等宿主路由就绪后点上面的「重试」;如果一直失败,看一眼 DSH 日志里有没有 [dsh-dream] 告警。"),
996
1452
  ]),
997
- knowledgeCard,
998
1453
  ]);
999
- 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 });
1000
1455
  }
1001
1456
  return h("div", { key: "content" }, [
1002
1457
  vm.showTiles
@@ -1011,7 +1466,6 @@ function DreamPanel() {
1011
1466
  h(MoodCard, { key: "mood", vm: vm }),
1012
1467
  h(LessonCard, { key: "lessons", vm: vm }),
1013
1468
  ]),
1014
- knowledgeCard,
1015
1469
  h("div", { className: "dshd-listhead", key: "listhead" }, [
1016
1470
  h("h4", { key: "t" }, query === "" && filterCount === 0 ? "梦境时间线" : (query === "" ? "筛选结果" : "搜索结果")),
1017
1471
  h("span", { className: "dshd-hint", key: "n" }, listHintOf(query, vm.count, filterCount, visibleDreams.length)),
@@ -1031,10 +1485,13 @@ function DreamPanel() {
1031
1485
  return h("div", { className: "dshd-root" + (privacy === true ? " dshd-privacy" : ""), key: "root" }, [
1032
1486
  h("div", { className: "dshd-shell", key: "shell" }, [
1033
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
+ })),
1034
1491
  toolbar,
1035
1492
  privacy === true ? h("div", { className: "dshd-note", key: "privacynote", role: "status" }, "隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。") : null,
1036
1493
  body(),
1037
- h("div", { className: "dshd-footer", key: "footer" }, [
1494
+ activeTab !== "journal" ? null : h("div", { className: "dshd-footer", key: "footer" }, [
1038
1495
  h("span", { key: "l" }, footerTextOf(query, limit, vm.total, vm.count)),
1039
1496
  h("span", { className: "dshd-spacer", key: "sp" }),
1040
1497
  canLoadMore === true ? h(Btn, { key: "more", onClick: showMore, disabled: loading === true }, loading === true ? "加载中…" : "显示更早的梦") : null,
@@ -1057,7 +1514,6 @@ const CSS = [
1057
1514
  ".dshd-root{color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:1.6}",
1058
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}",
1059
1516
  ".dshd-sub{color:var(--dsw-alias-label-tertiary,#6b7280);font-size:12px;margin:4px 0 12px}",
1060
- "@container (max-width:620px){.dshd-two{grid-template-columns:1fr}.dshd-toolbar .dshd-hint{display:none}}",
1061
1517
  ".dshd-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:12px}",
1062
1518
  ".dshd-search{position:relative;display:flex;align-items:center;flex:1;min-width:180px}",
1063
1519
  ".dshd-search svg{position:absolute;left:9px;color:var(--dsw-alias-label-tertiary,#6b7280);pointer-events:none}",
@@ -1165,7 +1621,24 @@ const CSS = [
1165
1621
  ".dshd-kempty-body{margin:0 auto 5px;max-width:460px;font-size:12px}",
1166
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}",
1167
1623
  ".dshd-privacy .dshd-kbody{filter:blur(4px)}",
1168
- ".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}}"
1169
1642
  ].join("");
1170
1643
  /* @ui-css-end */
1171
1644
 
@@ -1209,6 +1682,22 @@ exports.__internals = {
1209
1682
  KNOWLEDGE_ROUTE: KNOWLEDGE_ROUTE,
1210
1683
  KNOWLEDGE_LIMIT: KNOWLEDGE_LIMIT,
1211
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,
1212
1701
  normalizeKnowledge: normalizeKnowledge,
1213
1702
  normalizeLessonState: normalizeLessonState,
1214
1703
  applicabilityTextOf: applicabilityTextOf,