@a9i5k4/dsh-auto-memory 2.5.2 → 3.0.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/README.md +171 -1
- package/README.zh-CN.md +171 -1
- package/docs/CONTRIBUTORS.html +471 -0
- package/docs/HANDOFF-CRITERIA.md +92 -0
- package/docs/INTEGRATION-ANALYSIS.md +350 -348
- package/docs/USER-GUIDE.en.md +56 -1
- package/docs/USER-GUIDE.zh-CN.md +57 -2
- package/docs/internal/ACCEPT-35-LIVE.md +143 -0
- package/docs/internal/ACCEPTANCE-20260914.md +90 -0
- package/docs/internal/ARCH-REVIEW-BRIEF.md +411 -0
- package/docs/internal/ARCH-REVIEW-REQUEST.md +201 -0
- package/docs/internal/ARCH-REVIEW-ROUND2.md +169 -0
- package/docs/internal/ARCH-REVIEW-ROUND3.md +206 -0
- package/docs/internal/AUDIT-WB-GRAPH-FULL-20260916.md +314 -0
- package/docs/internal/CONCURRENCY-INVESTIGATION-20260917.md +192 -0
- package/docs/internal/CROSS-SESSION-SEARCH-PATH-DECISION.md +72 -0
- package/docs/internal/CROSS-SESSION-SEARCH-RESEARCH.md +131 -0
- package/docs/internal/DECISIONS-20260914-SESSION.md +269 -0
- package/docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md +219 -0
- package/docs/internal/DIRECTION-CHECK-WB-GRAPH-20260916.md +132 -0
- package/docs/internal/FEEDBACK-TO-DSHAPI-RELAY.md +13 -0
- package/docs/internal/GH-DISCUSSION-5732-COMMENT.md +74 -0
- package/docs/internal/GPT-ACCEPTANCE-PROMPT-20260916.md +352 -0
- package/docs/internal/GPT-REVIEW-PROMPT.md +216 -0
- package/docs/internal/GROUP-WEBHOOK-SETUP.md +33 -0
- package/docs/internal/KICKOFF-P0.md +254 -0
- package/docs/internal/MASTER-PLAN-3.0.md +411 -0
- package/docs/internal/MEMORY-MUTATION-AND-INDEX-DESIGN.md +85 -0
- package/docs/internal/MERGE-CONFLICT-SCAN-20260914.md +222 -0
- package/docs/internal/PENDING-FIXES-20260916.md +289 -0
- package/docs/internal/RAG-KARPATHY-PROGRAM.md +229 -0
- package/docs/internal/REPORT-P0-NIGHTLY.md +212 -0
- package/docs/internal/REPORT-P5-ACCEPTANCE.md +31 -0
- package/docs/internal/REPORT-WB-GRAPH-NIGHTLY.md +153 -0
- package/docs/internal/REVIEW-WB-GRAPH-SELF.md +81 -0
- package/docs/internal/ROADMAP-20260917-WEEK.md +305 -0
- package/docs/internal/ROADMAP.md +106 -0
- package/docs/internal/RUN-P0-NIGHTLY.md +227 -0
- package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +175 -0
- package/docs/internal/S10-GAPS-PLAIN-20260917.md +125 -0
- package/docs/internal/SEMANTIC-ARCHITECTURE-SPEC.md +360 -0
- package/docs/internal/SESSION-FILE-REPAIR-PROTOCOL.md +90 -0
- package/docs/internal/THREE-LAYER-CONTRACT.md +210 -0
- package/docs/internal/TODO-BACKLOG.md +263 -142
- package/docs/internal/TODO-GRAPH.html +715 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260914-v2 +493 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-alsfix +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p1 +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p6a-rev +703 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-wshint +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260916-batch +715 -0
- package/docs/internal/WB-FORMAT-CONVENTION.md +112 -0
- package/docs/internal/WB-GRAPH-DECISIONS-20260914.md +71 -0
- package/docs/internal/reviews/CLAIM-VERIFICATION-20260914.md +56 -0
- package/docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md +787 -0
- package/docs/internal/reviews/REVIEW-gpt6astra-20260914.md +112 -0
- package/docs/internal/reviews/ROUND3-REVIEW-INTEGRATION-20260914.md +230 -0
- package/docs/prompts/M8-3-enable-verify.md +49 -49
- package/lib/acceptance.js +71 -0
- package/lib/activation-host.js +90 -9
- package/lib/activation-inbox.js +25 -7
- package/lib/board-mode.js +30 -0
- package/lib/client.js +880 -68
- package/lib/context-bridge.js +2 -2
- package/lib/context-host.js +70 -6
- package/lib/engine-identity.js +149 -0
- package/lib/engine-switch.js +247 -0
- package/lib/episodic-store.js +11 -10
- package/lib/evidence-store.js +2 -2
- package/lib/fact-store.js +1 -1
- package/lib/fs-retry.js +46 -0
- package/lib/index.js +1987 -153
- package/lib/intent-clean-safe.js +40 -0
- package/lib/intent-clean.js +12 -16
- package/lib/l0-extract.js +263 -149
- package/lib/l0-index-sync.js +195 -0
- package/lib/l0-index.js +349 -239
- package/lib/ledger-criteria.js +142 -0
- package/lib/m7-index-sync-host.js +65 -4
- package/lib/m7-wire.js +3 -3
- package/lib/memory-anchor.js +56 -1
- package/lib/memory-envelope.js +252 -0
- package/lib/memory-hub.js +14 -4
- package/lib/memory-mutation.js +246 -0
- package/lib/memory-writer.js +204 -24
- package/lib/procedure-observation.js +48 -0
- package/lib/procedure-store.js +34 -17
- package/lib/python-setup.js +1 -1
- package/lib/rerank-host.js +160 -0
- package/lib/rules-layer.js +261 -0
- package/lib/semantic-js.js +15 -0
- package/lib/shadow-retrieval.js +3 -3
- package/lib/state-commit.js +245 -0
- package/lib/subagent-gc.js +4 -8
- package/lib/tier-layer-inject.js +650 -0
- package/lib/tier0-catalog.js +693 -0
- package/lib/water-window.js +88 -2
- package/lib/wb-contract.js +495 -0
- package/lib/wb-sidecar.js +839 -0
- package/lib/ws-overview-rank.js +2 -2
- package/package.json +1 -1
- package/python/m7_embedding_v1.py +5 -5
- package/python/worker_semantic_v1.py +17 -6
- package/python/worker_v1.py +38 -4
package/lib/client.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. settings.section — 自动记忆设置页(存储位置、注入、反思风格)
|
|
8
8
|
* Data flows over /api/dsh-auto-memory/* (loopback-only host routes).
|
|
9
9
|
*/
|
|
10
|
-
console.log('[dsh-auto-memory] client
|
|
10
|
+
console.log('[dsh-auto-memory] client v3.0.0 fingerprint: index-self-heal')
|
|
11
11
|
window.__ModuleLoader__.load({
|
|
12
12
|
id: '@a9i5k4/dsh-auto-memory',
|
|
13
13
|
factory: (require) => {
|
|
@@ -15,6 +15,18 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
var exports = module.exports
|
|
16
16
|
var React = require('react')
|
|
17
17
|
var h = React.createElement
|
|
18
|
+
// react-dom 的 createPortal(2026-09-16): 抽屉要浮在整页之上, 必须挂到 body ——
|
|
19
|
+
// 挂在看板容器内的话, 页面往下滚它就被推出视口, 用户「点开的页签看不见」。
|
|
20
|
+
// 这是 dsh-context 的同类做法(它也用 createPortal 渲染居中弹窗)。
|
|
21
|
+
// require 失败时降级为 null ⇒ 抽屉退回容器内渲染(功能不丢, 只是不能浮)。
|
|
22
|
+
var createPortal = null
|
|
23
|
+
try { createPortal = require('react-dom').createPortal } catch (e) { createPortal = null }
|
|
24
|
+
/** 把节点送进 body —— 浮窗必须挂在 body 才能浮在整页之上且不随页面滚动跑出视口。
|
|
25
|
+
* createPortal 不可用时原样返回(退回常规渲染, 功能不丢)。 */
|
|
26
|
+
function kxPortal(node) {
|
|
27
|
+
if (!createPortal || typeof document === 'undefined' || !document.body) return node
|
|
28
|
+
return createPortal(node, document.body)
|
|
29
|
+
}
|
|
18
30
|
var useState = React.useState
|
|
19
31
|
var useEffect = React.useEffect
|
|
20
32
|
var useReducer = React.useReducer
|
|
@@ -252,6 +264,7 @@ window.__ModuleLoader__.load({
|
|
|
252
264
|
fProjectDir: '项目记忆目录', fProjectDirHint: '相对各工作区的目录名(默认 .dsh-memory)。',
|
|
253
265
|
fInject: '注入记忆上下文', fInjectHint: '每次组装提示词时自动注入 <memory_system> 块。',
|
|
254
266
|
fBudget: '注入预算(字符)', fBudgetHint: '记忆块总预算,超出部分截断。默认 1600(≈400-600 token/轮);活跃会话每轮都注入这段背景,调低更省 token,调高保留更多记忆。',
|
|
267
|
+
fExclude: '排除来源(每行一条)', fExcludeHint: '这些来源不再进注入,避免坏记忆反复灌入把结果一路带偏。四种写法:记忆 id(mem_xxx)、整层(log/whiteboard/project/user/reflection)、目录前缀(以 / 或 \\ 结尾)、精确文件路径。与 supersede 互补:supersede 管"被新版替代",这里管"整条来源不可信"。被挡下的条数会在注入里以 [降级] 如实标注。',
|
|
255
268
|
fNoteCap: '项目笔记容量上限(字符)', fNoteCapHint: '项目笔记 MEMORY.md 的容量上限,默认 12000。超出时自动整理:先把较早内容交给模型折叠成要点,失败则退回整条归档(原文进 archive,不丢)——新记忆不会被堵在外面。注意与上面的「注入预算」不是一回事:这个管文件本体大小,注入预算管每轮往上下文塞多少摘要。',
|
|
256
269
|
fUserCap: '用户级记忆容量上限(字符)', fUserCapHint: '用户级 MEMORY.md(跨项目规则)的容量上限,默认 12000。整理方式与项目笔记相同(先折叠成要点,必要时整条归档)。',
|
|
257
270
|
fSnapGap: '快照最小间隔(轮)', fSnapGapHint: '动态记忆快照内容变化后,至少隔 N 轮才重新注入,避免每轮日志微变都追加快照导致历史膨胀。默认 5;0=每轮都尝试(仍受内容变化约束)。',
|
|
@@ -271,6 +284,8 @@ window.__ModuleLoader__.load({
|
|
|
271
284
|
awayTitle: '欢迎回来', awayMsg: '你离开的这段时间,我已经帮你把日志整理好了。记忆窗口已打开,可以看看这段时间的状况。',
|
|
272
285
|
sumTitle: '时段总结',
|
|
273
286
|
fReflect: '每日反思', fReflectHint: '昨天有工作日志时,会话首轮主动呈现昨日反思。',
|
|
287
|
+
fAutoContinue: '自动接续', fAutoContinueHint: '水位达到阈值时弹出确认卡,同意即接续到新会话(沿用工作区/模型/思考档位)。出厂默认关闭(功能仍在测试期);本开关独立于交接白板——关白板不影响水位测量与接续资格。',
|
|
288
|
+
handoffSwitchTitle: '白板与自动接续开关', handoffSwitchHint: '两个开关出厂默认均为关闭(功能仍在测试期);此处与「设置 → 上下文」读写同一对配置键,任一处改动即全局生效。', handoffOn: '交接白板:开', handoffOff: '交接白板:关',
|
|
274
289
|
fHandoff: '交接白板', fHandoffHint: 'PLAN.md 全貌快照 + 四段式交接账本:模型在理解全貌/阶段完成时写入,注入动态快照首位,面板「白板」页签可实时查看,跨上下文窗口续命。',
|
|
275
290
|
fWaterWindow: '水位估计窗口(token)', fWaterWindowHint: '会话消息按官方公式(4 字符≈1 token)估算达到该值即视为上下文将满;0=关闭。默认 65536(128K 窗口的保守半量),按所用模型调整。',
|
|
276
291
|
fWaterAdvisory: '水位交接建议', fWaterAdvisoryHint: '水位越阈时在动态快照注入交接建议(写账本/刷新白板/建议开新窗);无人值守时静默。',
|
|
@@ -284,7 +299,7 @@ window.__ModuleLoader__.load({
|
|
|
284
299
|
fMaintSchedule: '定时 30 天蒸馏', fMaintScheduleHint: '每天到点自动把超过 30 天的旧日志蒸馏归档(等价 memory_maintain);无旧日志时零成本跳过。',
|
|
285
300
|
fMaintScheduleTime: '蒸馏触发时间', fMaintScheduleTimeHint: 'HH:MM,默认 10:00(与固化时间错开)。',
|
|
286
301
|
waterCardTitle: '上下文水位', waterAutoSrc: '自动检测', waterManualSrc: '手动设定', waterOfficialSrc: '官方路由容量', waterFallbackSrc: '回退默认值', waterNotMeasured: '本会话尚未测量(发送一轮后自动更新)', waterMeterOfficial: '官方计量', waterMeterHeuristic: '启发式降级', waterThresholdHint: '水位达到 {t} 时注入交接建议并自动补写账本;计量优先用官方 token-meter(与聊天框 context ring 同源),不可用时降级启发式估算;窗口优先取官方路由容量(request/context 的 contextWindow),其次按当前模型查 settings.yaml contextWindow,也可在下方手动覆盖;触发分母=官方声明窗口(provider 自报过硬限时取较小值),预留输出不参与扣减。',
|
|
287
|
-
autoContTitle: '自动接续', autoContHint: '
|
|
302
|
+
autoContTitle: '自动接续', autoContHint: '开启后:水位达到阈值即弹出确认卡——同意=立即接续,拒绝=本轮跳过(同一边界不再提示),30-40 秒无操作视为挂机自动接续;接续流程为先终止旧会话当前回合(未完成的回答会被打断)→ 让旧 Agent 刷新白板 PLAN 与交接账本并等它真正执行完 → 再建新会话(沿用工作区/模型/思考档位)并注入分层交接材料;触发后默认 30 分钟内不重复。', autoContOn: '自动接续:开', autoContOff: '自动接续:关', autoContThreshold: '阈值', autoContCountdown: '⏳ {s} 秒后自动接续…', autoContCancel: '取消', autoContConfirm: '本会话上下文已用 {p}%(按官方声明窗口计,官方约 80% 才压缩)。现在接续到新会话?点「同意接续」会先终止旧会话当前回合(未完成的回答会被打断),再让旧会话刷新白板与账本,然后建新会话。', autoContAgree: '同意接续', autoContReject: '拒绝', autoContTimeout: '{s} 秒后自动接续(视为挂机)', autoContRejected: '已跳过本次接续(下一个轮次边界再提醒)',
|
|
288
303
|
continueCardTitle: '一键接续', continueCardHint: '写好交接账本→创建新会话→把交接材料作为首条消息预载,并自动切换到新会话继续任务(接续材料含白板节选+最新账本;当前工作区无账本时自动取全局最近一篇)。', continueBtn: '一键接续到新会话', continueBusy: '处理中…',
|
|
289
304
|
fStyle: '反思风格', fStyleHint: '生活化 / 专业性 / 由内容决定。',
|
|
290
305
|
fLocale: '界面语言', fLocaleHint: '默认跟随 DSH 系统语言;也可手动指定中文 / English。', followSystem: '跟随系统语言',
|
|
@@ -403,6 +418,7 @@ window.__ModuleLoader__.load({
|
|
|
403
418
|
fProjectDir: 'Project memory dir', fProjectDirHint: 'Directory name relative to each workspace (default .dsh-memory).',
|
|
404
419
|
fInject: 'Inject memory context', fInjectHint: 'Auto-inject <memory_system> block into every prompt.',
|
|
405
420
|
fBudget: 'Injection budget (chars)', fBudgetHint: 'Total budget for the memory block; excess is truncated. Default 1600 (~400-600 tokens/turn); this background is injected every turn, so lower = fewer tokens, higher = more memory retained.',
|
|
421
|
+
fExclude: 'Excluded sources (one per line)', fExcludeHint: 'These sources never enter injection again, so a bad memory cannot keep steering the result. Four forms: memory id (mem_xxx), whole layer (log/whiteboard/project/user/reflection), directory prefix (ending in / or \\), exact file path. Complements supersede: supersede covers "replaced by a newer version", this covers "this source is untrustworthy". Blocked counts are reported as a [降级] line, never silently.',
|
|
406
422
|
fNoteCap: 'Project note capacity (chars)', fNoteCapHint: 'Capacity cap for the project MEMORY.md, default 12000. When exceeded the framework auto-consolidates: older content is first folded into bullet points by the model, falling back to whole-record archiving (originals go to archive, never lost) — new memories are never blocked. This is NOT the injection budget above: this caps the file itself, the injection budget caps the digest added to each turn.',
|
|
407
423
|
fUserCap: 'User memory capacity (chars)', fUserCapHint: 'Capacity cap for the user-level MEMORY.md (cross-project rules), default 12000. Same consolidation behaviour (fold into points first, whole-record archive as fallback).',
|
|
408
424
|
fSnapGap: 'Snapshot min gap (turns)', fSnapGapHint: 'After the dynamic memory snapshot changes, re-inject at least N turns later, so small per-turn log changes do not append a new snapshot every turn (history bloat). Default 5; 0 = try every turn (still change-gated).',
|
|
@@ -422,6 +438,8 @@ window.__ModuleLoader__.load({
|
|
|
422
438
|
awayTitle: 'Welcome back', awayMsg: 'While you were away, I tidied up your logs. The memory panel is open — take a look at what happened.',
|
|
423
439
|
sumTitle: 'Period summary',
|
|
424
440
|
fReflect: 'Daily reflection', fReflectHint: 'When yesterday has logs, the agent presents the reflection at session start.',
|
|
441
|
+
fAutoContinue: 'Auto continue', fAutoContinueHint: 'Shows a confirm card once the water level passes the threshold; agreeing continues into a new session (workspace/model/reasoning effort are carried over). Off by default (still in testing); independent of the handoff whiteboard — turning the whiteboard off never disables water-level measurement or continue eligibility.',
|
|
442
|
+
handoffSwitchTitle: 'Whiteboard & auto continue switches', handoffSwitchHint: 'Both default to off (still in testing). These are the same config keys as Settings → Context — changing either applies globally.', handoffOn: 'Handoff whiteboard: on', handoffOff: 'Handoff whiteboard: off',
|
|
425
443
|
fHandoff: 'Handoff whiteboard', fHandoffHint: 'PLAN.md snapshot + four-part handoff ledger: written by the model at milestones, injected first in the dynamic snapshot, live in the Whiteboard tab — context that survives windows.',
|
|
426
444
|
fWaterWindow: 'Water-level window (tokens)', fWaterWindowHint: 'Session messages priced with the official meter (4 chars ≈ 1 token); past this, context is considered nearly full; 0 disables. Default 65536 — a conservative half of a 128K window.',
|
|
427
445
|
fWaterAdvisory: 'Water-level handoff advisory', fWaterAdvisoryHint: 'Injects a handoff advisory past the threshold (write ledger / refresh plan / suggest a new window); silent in unattended mode.',
|
|
@@ -435,7 +453,7 @@ window.__ModuleLoader__.load({
|
|
|
435
453
|
fMaintSchedule: 'Scheduled 30-day distill', fMaintScheduleHint: 'Once a day at the set time, distill & archive logs older than 30 days (same flow as memory_maintain); zero cost when nothing to archive.',
|
|
436
454
|
fMaintScheduleTime: 'Distill time', fMaintScheduleTimeHint: 'HH:MM, default 10:00 (staggered from the consolidation time).',
|
|
437
455
|
waterCardTitle: 'Context water level', waterAutoSrc: 'auto-detected', waterManualSrc: 'manual', waterOfficialSrc: 'route capacity', waterFallbackSrc: 'fallback default', waterNotMeasured: 'Not measured for this session yet (updates after the next turn)', waterMeterOfficial: 'official meter', waterMeterHeuristic: 'heuristic fallback', waterThresholdHint: 'Past {t} of the window, the handoff advisory is injected and the skeleton ledger is backfilled; metering prefers the official token-meter (same source as the chat context ring), falling back to the heuristic when unavailable; the window prefers the official route capacity (request/context contextWindow), then the active model in settings.yaml, or your manual override below; the trigger denominator is the official declared window (min with any provider-reported hard limit) — the reserved output budget is not deducted.',
|
|
438
|
-
autoContTitle: 'Auto continue', autoContHint: 'When on: once the water level passes the threshold
|
|
456
|
+
autoContTitle: 'Auto continue', autoContHint: 'When on: a confirm card appears once the water level passes the threshold — Agree = continue now, Decline = skip this boundary (no re-prompt for it), no answer for 30-40s = unattended, continue automatically. The handoff sequence is: stop the old turn first (an unfinished answer will be interrupted) → have the old agent refresh PLAN + ledger and wait until it has really run → then create the new session, which keeps workspace/model/reasoning effort and receives layered handoff material; suppressed for 30 minutes after each run.', autoContOn: 'Auto continue: on', autoContOff: 'Auto continue: off', autoContThreshold: 'Threshold', autoContCountdown: '⏳ Auto-continue in {s}s…', autoContCancel: 'Cancel', autoContConfirm: 'Context at {p}% of the official declared window (official compaction starts ~80%). Continue into a new session now? Agreeing stops the old turn first (an unfinished answer will be interrupted), then the old agent refreshes PLAN + ledger before the new session is created.', autoContAgree: 'Continue', autoContReject: 'Decline', autoContTimeout: 'auto-continue in {s}s (unattended)', autoContRejected: 'Skipped this handoff (will ask again at the next turn boundary)',
|
|
439
457
|
continueCardTitle: 'One-click continue', continueCardHint: 'Writes the handoff ledger → creates a new session → injects the handoff material as the first message → switches to the new session automatically (material = PLAN excerpt + latest ledger; falls back to the most recent global ledger when this workspace has none).', continueBtn: 'Continue in a new session', continueBusy: 'Working…',
|
|
440
458
|
fStyle: 'Reflection style', fStyleHint: 'Life-style / Professional / Auto.',
|
|
441
459
|
fLocale: 'UI language', fLocaleHint: 'Follows the DSH system language by default; you can also pin Chinese / English.', followSystem: 'Follow system language',
|
|
@@ -567,6 +585,27 @@ window.__ModuleLoader__.load({
|
|
|
567
585
|
|
|
568
586
|
// ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
|
|
569
587
|
var CHANGELOG = {
|
|
588
|
+
'3.0.0': { zh: [
|
|
589
|
+
'★ 大版本:底层重建收官。检索、注入、容量、并发四条底层全部重做;白板与看板从「实验特性」变成出厂形态。',
|
|
590
|
+
'★ 白板默认开启(原来默认关)。交接白板 + 四段式交接账本是这一版的招牌能力,出厂关着等于新用户看不到它。想彻底关掉:设置 → 自动化 → 交接白板。',
|
|
591
|
+
'★ 白板默认「新版看板」:结构化索引(条目/标签/线索/版本链)+ 列式泳道视图 + 两个遍历工具(按标签展开 memory_expand_pre / 按 id 回溯 memory_trace_pre)。旧的文字白板完整保留,设置页一键切回(「旧版白板」)。',
|
|
592
|
+
'说明:自动接续**仍然默认关闭**——水位判据还没在你的机器上实测到位,这一版特意不动它,等验证满意再考虑。',
|
|
593
|
+
'修复:语义唤回长期「查不到东西」的真因。语义索引因一次没收尾的同步被永久占住(重启也只能暂时缓解),此后每轮都退化成「关键词 + 常驻目录」。现已修好:索引能建起来,唤回恢复语义检索。',
|
|
594
|
+
'修复:升级弹窗原先只弹 2.1.0 的旧说明(版本号写死),所以你一直看不到新版更新日志。现在会自动展示当前版本的说明。',
|
|
595
|
+
'修复:同时开两个会话时「谁也没法注入」、点进不同工作区时语义模型跟着走——4 处状态被两会话互相覆盖,现已按会话/工作区分开。',
|
|
596
|
+
'新增:自动接续的续航材料更完整;上下文将满时给出的白板/账本提示更稳。',
|
|
597
|
+
'老用户不受影响:你自己设过的开关与阈值一律沿用,只有从没设过的那几项会吃新默认。',
|
|
598
|
+
], en: [
|
|
599
|
+
'★ Major release: the ground-up rebuild is complete. Retrieval, injection, capacity and concurrency were all rebuilt; the whiteboard and kanban are now shipping defaults rather than experiments.',
|
|
600
|
+
'★ Whiteboard is now ON by default (it used to be off). The handoff whiteboard plus four-part ledgers are this release\'s headline feature — off by default meant new users never saw them. To disable entirely: Settings → Automation → Handoff whiteboard.',
|
|
601
|
+
'★ Whiteboard now defaults to the new kanban: a structured index (entries/tags/cues/version chain), a column-swimlane view, and two traversal tools (expand by tag, trace by id). The old text whiteboard is fully retained — switch back with one click in Settings.',
|
|
602
|
+
'Note: auto-continue remains OFF by default — the water-level criteria have not yet been validated on your machine, so this release deliberately leaves it alone.',
|
|
603
|
+
'Fix: the real cause of semantic recall coming up empty for weeks. A sync that never finished had permanently occupied the index slot (restarting only helped temporarily), so every turn degraded to keyword + resident catalog. Fixed: the index now builds, and recall is semantic again.',
|
|
604
|
+
'Fix: the upgrade dialog only ever showed the old 2.1.0 notes (the version was hardcoded), so you never saw the current release notes. It now shows the version you actually upgraded to.',
|
|
605
|
+
'Fix: with two sessions open, neither could inject, and the semantic model followed you between workspaces — four pieces of state were overwriting each other. Now scoped per session/workspace.',
|
|
606
|
+
'New: more complete carry-over material for auto-continue; steadier whiteboard/ledger prompting as context fills up.',
|
|
607
|
+
'Existing users are unaffected: any switch or threshold you set yourself is kept — only never-set keys pick up the new defaults.',
|
|
608
|
+
] },
|
|
570
609
|
'2.5.2': { zh: [
|
|
571
610
|
'修复:开启「无人值守模式」的用户切换工作区失灵(面板恒显示启动目录、日志 0)——防漂移锁此前全局钉死首个工作区,现改为按会话锁定,只防本会话内的目录漂移;未开启该模式的用户行为不变。',
|
|
572
611
|
'修复:面板白板/账本恒显示「最近活跃会话」的内容——现在按你正在查看的会话取数;会话经工作区绑定创建(无 cwd)时也能正确解析;解析不出身份时如实提示「当前会话未绑定工作区」,不再展示错误路径。',
|
|
@@ -895,11 +934,82 @@ window.__ModuleLoader__.load({
|
|
|
895
934
|
else dialogQueue.push(d)
|
|
896
935
|
notifyDialog()
|
|
897
936
|
}
|
|
937
|
+
// issue#40:宿主配置控制自动播放;localStorage 只记录**用户动作**,不得反过来当开关。
|
|
938
|
+
var tourNavigationEpoch = 0
|
|
939
|
+
var welcomeTourConfig = null
|
|
940
|
+
function decodeWelcomeConfigPre(response) {
|
|
941
|
+
if (!response || typeof response !== 'object' || Array.isArray(response)) return null
|
|
942
|
+
var cfg = response.config
|
|
943
|
+
if (!cfg && Object.prototype.hasOwnProperty.call(response, 'welcomeTourEnabled')) cfg = response
|
|
944
|
+
if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) return null
|
|
945
|
+
if (cfg.welcomeTourEnabled !== undefined && typeof cfg.welcomeTourEnabled !== 'boolean') return null
|
|
946
|
+
return cfg
|
|
947
|
+
}
|
|
948
|
+
function welcomeAutoAllowedPre(cfg) {
|
|
949
|
+
if (!cfg || cfg.welcomeTourEnabled === false) return false
|
|
950
|
+
try { if (localStorage.getItem('dsh-auto-memory.tourDismissed') === '1') return false } catch (_) { return false }
|
|
951
|
+
return true
|
|
952
|
+
}
|
|
953
|
+
function dismissWelcomeTourPre(reason) {
|
|
954
|
+
tourNavigationEpoch++
|
|
955
|
+
if (reason === 'user') {
|
|
956
|
+
try { localStorage.setItem('dsh-auto-memory.tourDismissed', '1') } catch (_) {}
|
|
957
|
+
}
|
|
958
|
+
function target(d) { return d && d.kind === 'welcomeTour' && (reason !== 'config' || !d.manual) }
|
|
959
|
+
dialogQueue = dialogQueue.filter(function (d) { return !target(d) })
|
|
960
|
+
if (target(dialogState)) dialogState = dialogQueue.shift() || null
|
|
961
|
+
notifyDialog()
|
|
962
|
+
}
|
|
963
|
+
function rememberWelcomeConfigPre(response) {
|
|
964
|
+
welcomeTourConfig = decodeWelcomeConfigPre(response)
|
|
965
|
+
if (welcomeTourConfig && welcomeTourConfig.welcomeTourEnabled === false) dismissWelcomeTourPre('config')
|
|
966
|
+
return welcomeTourConfig
|
|
967
|
+
}
|
|
968
|
+
// 焦点陷阱:向导内的 Tab 不得逃到背景页面;Esc 关闭;关闭后焦点归还触发元素。
|
|
969
|
+
function installTourFocusPre(root, close, doc) {
|
|
970
|
+
if (!root || !doc) return function () {}
|
|
971
|
+
var previous = doc.activeElement
|
|
972
|
+
var selector = 'button:not([disabled]),a[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
|
|
973
|
+
function focusables() {
|
|
974
|
+
return Array.prototype.filter.call(root.querySelectorAll(selector), function (el) { return el.getClientRects().length > 0 && el.getAttribute('aria-hidden') !== 'true' })
|
|
975
|
+
}
|
|
976
|
+
function onKey(e) {
|
|
977
|
+
if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); close(); return }
|
|
978
|
+
if (e.key !== 'Tab') return
|
|
979
|
+
var items = focusables(), first = items[0], last = items[items.length - 1]
|
|
980
|
+
if (!items.length) { e.preventDefault(); root.focus(); return }
|
|
981
|
+
if (e.shiftKey && (doc.activeElement === first || !root.contains(doc.activeElement))) { e.preventDefault(); last.focus() }
|
|
982
|
+
else if (!e.shiftKey && (doc.activeElement === last || !root.contains(doc.activeElement))) { e.preventDefault(); first.focus() }
|
|
983
|
+
}
|
|
984
|
+
var keyTarget = typeof doc.addEventListener === 'function' ? doc : root
|
|
985
|
+
keyTarget.addEventListener('keydown', onKey, true)
|
|
986
|
+
var initial = focusables()[0] || root
|
|
987
|
+
initial.focus()
|
|
988
|
+
return function () {
|
|
989
|
+
keyTarget.removeEventListener('keydown', onKey, true)
|
|
990
|
+
if (previous && previous.isConnected && typeof previous.focus === 'function' &&
|
|
991
|
+
(root.contains(doc.activeElement) || doc.activeElement === doc.body)) previous.focus()
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function openManualWelcomeTourPre() {
|
|
995
|
+
tourNavigationEpoch++
|
|
996
|
+
openDialog({ kind: 'welcomeTour', manual: true })
|
|
997
|
+
}
|
|
998
|
+
function loadWelcomeConfigPre(timeoutMs) {
|
|
999
|
+
return new Promise(function (resolve) {
|
|
1000
|
+
var finished = false
|
|
1001
|
+
var timer = setTimeout(function () { if (!finished) { finished = true; resolve(null) } }, timeoutMs || 5000)
|
|
1002
|
+
apiGet(API.config).then(function (d) {
|
|
1003
|
+
if (finished) return
|
|
1004
|
+
finished = true; clearTimeout(timer); resolve(rememberWelcomeConfigPre(d))
|
|
1005
|
+
}, function () { if (!finished) { finished = true; clearTimeout(timer); resolve(null) } })
|
|
1006
|
+
})
|
|
1007
|
+
}
|
|
898
1008
|
function closeDialog() { dialogState = dialogQueue.shift() || null; notifyDialog() }
|
|
899
1009
|
function onDialog(fn) { dialogListeners.add(fn); return function () { dialogListeners.delete(fn) } }
|
|
900
1010
|
// 调试/重看入口:控制台 window['dsh-auto-memory.openWelcomeTour']() 随时重开首启向导
|
|
901
1011
|
try {
|
|
902
|
-
window['dsh-auto-memory.openWelcomeTour'] = function () {
|
|
1012
|
+
window['dsh-auto-memory.openWelcomeTour'] = function () { openManualWelcomeTourPre() }
|
|
903
1013
|
} catch (eOpenTour) {}
|
|
904
1014
|
function cmpVersion(a, b) {
|
|
905
1015
|
var pa = String(a).split('.').map(Number), pb = String(b).split('.').map(Number)
|
|
@@ -958,6 +1068,7 @@ window.__ModuleLoader__.load({
|
|
|
958
1068
|
pyCancel: ROUTE_PREFIX + '/python-setup/cancel',
|
|
959
1069
|
pyStatus: ROUTE_PREFIX + '/python-setup/status',
|
|
960
1070
|
handoffState: ROUTE_PREFIX + '/handoff-state',
|
|
1071
|
+
kanbanBoard: ROUTE_PREFIX + '/kanban-board',
|
|
961
1072
|
handoffContinue: ROUTE_PREFIX + '/handoff-continue',
|
|
962
1073
|
handoffPermission: ROUTE_PREFIX + '/handoff-permission',
|
|
963
1074
|
autoContState: ROUTE_PREFIX + '/auto-continue-state',
|
|
@@ -994,11 +1105,30 @@ window.__ModuleLoader__.load({
|
|
|
994
1105
|
*/
|
|
995
1106
|
function saveConfigPatch(patch, hooks) {
|
|
996
1107
|
var h = hooks || {}
|
|
997
|
-
|
|
1108
|
+
// issue#40:保存后立刻用**回传的真实配置**刷新向导闸,而不是等下次启动重新拉取。
|
|
1109
|
+
// 关掉开关时顺手撤下正在显示的向导(否则"点了关闭但窗口还开着"——界面无即时回显)。
|
|
1110
|
+
var p = apiPost(API.config, patch).then(function (d) {
|
|
1111
|
+
rememberWelcomeConfigPre(d)
|
|
1112
|
+
if (patch && typeof patch.welcomeTourEnabled === 'boolean') {
|
|
1113
|
+
welcomeTourConfig = Object.assign({}, welcomeTourConfig || {}, { welcomeTourEnabled: patch.welcomeTourEnabled })
|
|
1114
|
+
if (patch.welcomeTourEnabled === false) dismissWelcomeTourPre('config')
|
|
1115
|
+
}
|
|
1116
|
+
return d
|
|
1117
|
+
})
|
|
998
1118
|
if (h.onSaved || h.onError) p.then(function (d) { if (h.onSaved) h.onSaved(d) }, function (e) { if (h.onError) h.onError(e) })
|
|
999
1119
|
else p.catch(function () {})
|
|
1000
1120
|
return p
|
|
1001
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* 读配置的唯一解包出口(2026-09-14 实测止血):宿主 GET /config **恒返回外壳** `{ config, path }`
|
|
1124
|
+
* (见 lib/index.js:8461),配置键在 `d.config` 上。
|
|
1125
|
+
* 直读外壳的旧写法(`c.autoContinueEnabled`)恒得 undefined ⇒ `undefined === false` 恒假 ⇒ `!(…)` 恒真
|
|
1126
|
+
* ⇒ 开关显示**恒为「开」**,与真实配置无关;更糟的是点击算的是 `!autoCfg.enabled` = `!true` = **false**
|
|
1127
|
+
* ⇒ 该按钮**只能关、永远开不起来**(用户报障原话「我点开关,它并没有真正开关」)。
|
|
1128
|
+
* 纪律:任何 `apiGet(API.config)` 的返回值都必须经这层解包,禁止直读外壳上的配置键。
|
|
1129
|
+
* 回归守卫:tests/smoke/smoke-test-switch-decouple-pre.mjs D5。
|
|
1130
|
+
*/
|
|
1131
|
+
function configOf(d) { return (d && d.config) || {} }
|
|
1002
1132
|
/**
|
|
1003
1133
|
* 语义引擎状态的统一刷新(2026-09-10 第 0 步止血):此前同一段 fetch + 状态写回在界面里逐字重复 10 处,
|
|
1004
1134
|
* 响应形状或失败兜底要改时得挨个改。apply 收合并后的状态对象(已带 loaded:true),onError 可省。
|
|
@@ -1017,7 +1147,7 @@ window.__ModuleLoader__.load({
|
|
|
1017
1147
|
' max-width: calc(100vw - 32px); max-height: calc(100vh - 32px);',
|
|
1018
1148
|
' display: flex; flex-direction: column; overflow: hidden; z-index: 3000; pointer-events: auto;',
|
|
1019
1149
|
' border-radius: 16px; font: 13px/1.55 system-ui, "Segoe UI", sans-serif;',
|
|
1020
|
-
' background: color-mix(in srgb, var(--dsw-alias-bg-
|
|
1150
|
+
' background: color-mix(in srgb, var(--dsw-alias-bg-layer-2, rgba(255,255,255,.86)) 58%, transparent);',
|
|
1021
1151
|
' -webkit-backdrop-filter: blur(28px) saturate(1.55); backdrop-filter: blur(28px) saturate(1.55);',
|
|
1022
1152
|
' border: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 65%, transparent);',
|
|
1023
1153
|
' box-shadow: 0 24px 64px rgba(0,0,0,.22), 0 4px 16px rgba(0,0,0,.10), inset 0 1px 0 rgba(255,255,255,.22);',
|
|
@@ -1065,7 +1195,7 @@ window.__ModuleLoader__.load({
|
|
|
1065
1195
|
'[data-dam-settings-group] h3 { margin: 0; color: var(--dsw-alias-label-primary, #1f2328); font-size: calc(14px * var(--dam-scale)); }',
|
|
1066
1196
|
'[data-dam-settings-group] p { margin: 4px 0 14px; color: var(--dsw-alias-label-secondary, #68717d); font-size: calc(11.5px * var(--dam-scale)); line-height: 1.5; }',
|
|
1067
1197
|
'[data-dam-settings-row] { padding: 10px 0; }',
|
|
1068
|
-
'[data-dam-savebar] { position: sticky; bottom: 0; z-index: 3; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 14px; margin: 6px -16px -16px; background: color-mix(in srgb, var(--dsw-alias-bg-
|
|
1198
|
+
'[data-dam-savebar] { position: sticky; bottom: 0; z-index: 3; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 14px; margin: 6px -16px -16px; background: color-mix(in srgb, var(--dsw-alias-bg-layer-2, rgba(255,255,255,.92)) 74%, transparent); backdrop-filter: blur(16px) saturate(1.4); -webkit-backdrop-filter: blur(16px) saturate(1.4); border-top: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, #c5cbd3) 38%, transparent); border-radius: 0 0 14px 14px; transition: background .25s ease; }',
|
|
1069
1199
|
'[data-dam-savebar] button { opacity: .5; transition: opacity .2s ease, background .2s ease, color .2s ease; }',
|
|
1070
1200
|
'[data-dam-savebar] button[data-dirty="true"] { opacity: 1; background: var(--dam-accent, #1d4ed8); color: #fff; font-weight: 650; box-shadow: 0 3px 12px color-mix(in srgb, var(--dam-accent, #1d4ed8) 35%, transparent); }',
|
|
1071
1201
|
'[data-dam-settings-row] > [data-dam-row] { margin-bottom: 3px; }',
|
|
@@ -1118,13 +1248,13 @@ window.__ModuleLoader__.load({
|
|
|
1118
1248
|
'[data-dam-spinner] { width: 18px; height: 18px; border: 2px solid color-mix(in srgb, var(--dam-accent, #4f7cff) 22%, transparent); border-top-color: var(--dam-accent, #4f7cff); border-radius: 50%; animation: dam-spin .8s linear infinite; }',
|
|
1119
1249
|
'@keyframes dam-spin { to { transform: rotate(360deg); } }',
|
|
1120
1250
|
'@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {',
|
|
1121
|
-
' [data-dam-panel] { background: var(--dsw-alias-bg-
|
|
1251
|
+
' [data-dam-panel] { background: var(--dsw-alias-bg-layer-2, #ffffff); } }',
|
|
1122
1252
|
// ── 首启引导向导(Win11 OOBE × macOS 欢迎 × Liquid Glass) ──
|
|
1123
1253
|
'[data-dam-tour-backdrop] { position: fixed; inset: 0; z-index: 2147482900; display: flex; align-items: center; justify-content: center;',
|
|
1124
1254
|
' background: radial-gradient(ellipse at 50% 42%, rgba(20,30,60,.30), rgba(6,10,22,.44)); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); animation: dam-tour-fade .28s ease both; }',
|
|
1125
1255
|
'@keyframes dam-tour-fade { from { opacity: 0 } to { opacity: 1 } }',
|
|
1126
|
-
'[data-dam-tour] { position: relative; width: min(620px, calc(100vw - 48px)); border-radius: 22px; overflow: hidden; padding-bottom: 4px;',
|
|
1127
|
-
' background: color-mix(in srgb, var(--dsw-alias-bg-
|
|
1256
|
+
'[data-dam-tour] { position: relative; width: min(620px, calc(100vw - 48px)); border-radius: 22px; max-height: calc(100vh - 32px); max-height: calc(100dvh - 32px); overflow-y: auto; overflow-x: hidden; touch-action: pan-y; box-sizing: border-box; padding-bottom: 4px;',
|
|
1257
|
+
' background: color-mix(in srgb, var(--dsw-alias-bg-layer-2, rgba(30,34,46,.9)) 60%, transparent);',
|
|
1128
1258
|
' backdrop-filter: blur(30px) saturate(1.6); -webkit-backdrop-filter: blur(30px) saturate(1.6);',
|
|
1129
1259
|
' border: 1px solid rgba(255,255,255,.5);',
|
|
1130
1260
|
' box-shadow: 0 32px 90px rgba(8,14,38,.5), 0 6px 24px rgba(8,14,38,.28), inset 0 1px 0 rgba(255,255,255,.5), inset 0 -1px 0 rgba(255,255,255,.14);',
|
|
@@ -1343,6 +1473,483 @@ window.__ModuleLoader__.load({
|
|
|
1343
1473
|
h('div', { className: 'dam-content' }, props.children))
|
|
1344
1474
|
}
|
|
1345
1475
|
|
|
1476
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1477
|
+
// 白板看板(2026-09-16「兼并 dsh-graph」) —— 列式泳道可视化。
|
|
1478
|
+
//
|
|
1479
|
+
// 立场: 不挂 dsh-graph 插件(那会往 profile 里加第二个 bundle、第二份存储、第二套工具命名空间),
|
|
1480
|
+
// 而是把它的**可视化形态**长在 auto-memory 自己身上: 数据源是自家的 handoff/index.json,
|
|
1481
|
+
// 渲染在自家的面板里, 由 boardMode 一档切换(legacy 文字白板 / graph 列式看板)。
|
|
1482
|
+
// ⇒ 一份数据、一个插件、一个开关。
|
|
1483
|
+
//
|
|
1484
|
+
// 视觉: 与面板既有液态玻璃令牌一致(--dam-* / --dsw-alias-*), 不引第三方 CSS。
|
|
1485
|
+
// 纯展示组件: 只读 data, 不发起任何写操作(看板改卡留待后续; 先保证「看得见」)。
|
|
1486
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1487
|
+
var KANBAN_TONES = {
|
|
1488
|
+
ok: { bg: 'color-mix(in srgb, #3fa96a 18%, transparent)', fg: '#2f7d4f' },
|
|
1489
|
+
warn: { bg: 'color-mix(in srgb, #d4a94f 20%, transparent)', fg: '#8a6a1f' },
|
|
1490
|
+
unknown: { bg: 'color-mix(in srgb, currentColor 10%, transparent)', fg: 'inherit' },
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1494
|
+
// 看板 v2(2026-09-16 「大刀阔斧」批) —— 缩放 / 布局 / 交互全部补齐。
|
|
1495
|
+
//
|
|
1496
|
+
// v1 的三个致命体验问题(用户实测反馈):
|
|
1497
|
+
// ① 大小不可调: 只有面板全局 4 档 --dam-scale, 列宽 210px 写死不随字号变;
|
|
1498
|
+
// ② 看不见: 6 列 × 210px = 1260px 塞进 440px 面板, 只能看到 2 列且无滚动提示;
|
|
1499
|
+
// ③ 看不全: 预览 maxHeight 3.6em 硬截断, 无法展开。
|
|
1500
|
+
// ⇒ v2: 看板自带字号 ×N 与列宽 ×N(独立于 --dam-scale, localStorage 记忆) + 纵向堆叠默认 +
|
|
1501
|
+
// 泳道可折叠 + 卡片可展开看全文 + 泳道内搜索 + 「最近更新」聚合视图 + 空泳道收起。
|
|
1502
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1503
|
+
var KANBAN_ZOOM_KEY = 'dam-kanban-zoom'
|
|
1504
|
+
var KANBAN_COL_KEY = 'dam-kanban-cols'
|
|
1505
|
+
var KANBAN_LAYOUT_KEY = 'dam-kanban-layout'
|
|
1506
|
+
|
|
1507
|
+
function readLsNum(key, def) {
|
|
1508
|
+
try { var v = parseFloat(localStorage.getItem(key)); return isFinite(v) && v > 0 ? v : def } catch (e) { return def }
|
|
1509
|
+
}
|
|
1510
|
+
function writeLs(key, val) { try { localStorage.setItem(key, String(val)) } catch (e) {} }
|
|
1511
|
+
|
|
1512
|
+
/** 单张卡片: 标题 + 判据徽章 + 预览/全文 + 小节徽章 + 标签 + 来源。可点击展开。 */
|
|
1513
|
+
function KanbanCard(props) {
|
|
1514
|
+
var c = props.card || {}
|
|
1515
|
+
var zoom = Number(props.zoom) || 1
|
|
1516
|
+
var expanded = !!props.expanded
|
|
1517
|
+
var onToggle = props.onToggle
|
|
1518
|
+
var tone = KANBAN_TONES[(c.badge && c.badge.tone) || 'unknown'] || KANBAN_TONES.unknown
|
|
1519
|
+
var body = expanded ? (c.full || c.preview || '') : (c.preview || '')
|
|
1520
|
+
var canExpand = (c.full || '').length > (c.preview || '').length
|
|
1521
|
+
return h('div', {
|
|
1522
|
+
'data-dam-kcard': '',
|
|
1523
|
+
title: c.source + (c.section ? ' §' + c.section : '') + (c.anchored ? ' · 有锚点' : ''),
|
|
1524
|
+
style: {
|
|
1525
|
+
border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.22)) 70%, transparent)',
|
|
1526
|
+
borderRadius: '8px', padding: '7px 9px', marginBottom: '7px',
|
|
1527
|
+
background: 'color-mix(in srgb, currentColor 3%, transparent)',
|
|
1528
|
+
},
|
|
1529
|
+
}, [
|
|
1530
|
+
h('div', { style: { display: 'flex', alignItems: 'flex-start', gap: '6px' } }, [
|
|
1531
|
+
h('div', { style: { flex: '1 1 auto', fontWeight: 600, fontSize: (12.5 * zoom) + 'px', lineHeight: '1.35', wordBreak: 'break-word' } }, c.title || c.short),
|
|
1532
|
+
h('span', {
|
|
1533
|
+
style: { flex: '0 0 auto', fontSize: (10 * zoom) + 'px', padding: '1px 5px', borderRadius: '4px', background: tone.bg, color: tone.fg, whiteSpace: 'nowrap' },
|
|
1534
|
+
}, (c.badge && c.badge.text) || ''),
|
|
1535
|
+
]),
|
|
1536
|
+
// 文档来源(小节卡的关键上下文: 同名的「目标」小节来自哪篇账本)
|
|
1537
|
+
c.docTitle ? h('div', { 'data-dam-hint': '', style: { marginTop: '2px', fontSize: (10 * zoom) + 'px', opacity: .62, wordBreak: 'break-word' } }, c.docTitle) : null,
|
|
1538
|
+
body ? h('div', {
|
|
1539
|
+
'data-dam-hint': '',
|
|
1540
|
+
style: {
|
|
1541
|
+
marginTop: '4px', lineHeight: '1.45', fontSize: (11.5 * zoom) + 'px', whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
|
1542
|
+
maxHeight: expanded ? 'none' : (7.2 * zoom) + 'em', overflow: 'hidden',
|
|
1543
|
+
},
|
|
1544
|
+
}, body) : null,
|
|
1545
|
+
(canExpand || (c.full || '').length > 300) ? h('button', {
|
|
1546
|
+
'data-dam-btn': '', onClick: function (e) { e.stopPropagation(); if (onToggle) onToggle(c.id) },
|
|
1547
|
+
style: { marginTop: '3px', padding: '1px 6px', fontSize: (10 * zoom) + 'px', opacity: .7 },
|
|
1548
|
+
}, expanded ? (locale === 'zh' ? '收起' : 'collapse') : (locale === 'zh' ? '展开全文' : 'expand')) : null,
|
|
1549
|
+
(c.tags && c.tags.length) || (c.sectionTags && c.sectionTags.length) ? h('div', { style: { marginTop: '4px', display: 'flex', flexWrap: 'wrap', gap: '4px' } },
|
|
1550
|
+
(c.tags || []).map(function (tg) {
|
|
1551
|
+
return h('span', { key: 't' + tg, 'data-dam-hint': '', style: { fontSize: (10 * zoom) + 'px', padding: '0 4px', borderRadius: '3px', background: 'color-mix(in srgb, currentColor 8%, transparent)' } }, tg)
|
|
1552
|
+
}).concat((c.sectionTags || []).map(function (tg) {
|
|
1553
|
+
return h('span', { key: 's' + tg, 'data-dam-hint': '', style: { fontSize: (10 * zoom) + 'px', padding: '0 4px', borderRadius: '3px', background: 'color-mix(in srgb, #4f7cff 14%, transparent)', opacity: .85 } }, '§' + tg)
|
|
1554
|
+
}))) : null,
|
|
1555
|
+
h('div', { 'data-dam-hint': '', style: { marginTop: '4px', fontSize: (10 * zoom) + 'px' } },
|
|
1556
|
+
'#' + (c.short || '') + (c.bullets ? ' · ' + c.bullets + (locale === 'zh' ? ' 条' : ' items') : '') + (c.versionCount ? ' · v' + c.versionCount : '')),
|
|
1557
|
+
])
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
/** 看板主体: 布局可切(纵向堆叠/横向泳道) + 缩放 + 折叠 + 搜索 + 聚合视图。 */
|
|
1561
|
+
function KanbanBoard(props) {
|
|
1562
|
+
var kb = props.data || {}
|
|
1563
|
+
var lanes = kb.lanes || []
|
|
1564
|
+
var stats = kb.stats || {}
|
|
1565
|
+
var total = stats.total || 0
|
|
1566
|
+
var zoomPair = useState(function () { return readLsNum(KANBAN_ZOOM_KEY, 1) })
|
|
1567
|
+
var zoom = zoomPair[0], setZoom = zoomPair[1]
|
|
1568
|
+
var colPair = useState(function () { return readLsNum(KANBAN_COL_KEY, 280) })
|
|
1569
|
+
var colW = colPair[0], setColW = colPair[1]
|
|
1570
|
+
var layoutPair = useState(function () {
|
|
1571
|
+
try { return localStorage.getItem(KANBAN_LAYOUT_KEY) || 'stack' } catch (e) { return 'stack' }
|
|
1572
|
+
})
|
|
1573
|
+
var layout = layoutPair[0], setLayout = layoutPair[1]
|
|
1574
|
+
var foldedPair = useState({})
|
|
1575
|
+
var folded = foldedPair[0], setFolded = foldedPair[1]
|
|
1576
|
+
var expPair = useState({})
|
|
1577
|
+
var expanded = expPair[0], setExpanded = expPair[1]
|
|
1578
|
+
var viewPair = useState('lanes') // lanes | recent
|
|
1579
|
+
var view = viewPair[0], setView = viewPair[1]
|
|
1580
|
+
var qPair = useState('')
|
|
1581
|
+
var q = qPair[0], setQ = qPair[1]
|
|
1582
|
+
|
|
1583
|
+
if (!lanes.length || !total) {
|
|
1584
|
+
return h('div', { 'data-dam-hint': '' }, locale === 'zh'
|
|
1585
|
+
? '新版看板已启用,但索引里还没有条目——写一次白板或账本(memory_note)后这里会出现泳道卡片。'
|
|
1586
|
+
: 'Graph board is on, but the index has no entries yet — write a plan or ledger (memory_note) and cards will appear.')
|
|
1587
|
+
}
|
|
1588
|
+
var zh = locale === 'zh'
|
|
1589
|
+
var setZoomBoth = function (d) { var v = Math.min(2.2, Math.max(0.7, Math.round((zoom + d) * 100) / 100)); setZoom(v); writeLs(KANBAN_ZOOM_KEY, v) }
|
|
1590
|
+
var setColBoth = function (d) { var v = Math.min(720, Math.max(180, colW + d)); setColW(v); writeLs(KANBAN_COL_KEY, v) }
|
|
1591
|
+
var toggleLayout = function () { var v = layout === 'stack' ? 'lane' : 'stack'; setLayout(v); writeLs(KANBAN_LAYOUT_KEY, v) }
|
|
1592
|
+
var toggleFold = function (k) { setFolded(Object.assign({}, folded, { [k]: !folded[k] })) }
|
|
1593
|
+
var toggleCard = function (id) { setExpanded(Object.assign({}, expanded, { [id]: !expanded[id] })) }
|
|
1594
|
+
var matchQ = function (c) {
|
|
1595
|
+
if (!q.trim()) return true
|
|
1596
|
+
var s = q.trim().toLowerCase()
|
|
1597
|
+
return (c.title + ' ' + (c.docTitle || '') + ' ' + (c.preview || '') + ' ' + (c.tags || []).join(' ') + ' ' + (c.sectionTags || []).join(' ')).toLowerCase().indexOf(s) >= 0
|
|
1598
|
+
}
|
|
1599
|
+
var isStack = layout === 'stack'
|
|
1600
|
+
var sum = zh
|
|
1601
|
+
? ('共 ' + total + ' 张小节卡' + (stats.omitted ? '(另有 ' + stats.omitted + ' 张已省略)' : '') + ' · 有锚点 ' + (stats.anchored || 0) + ' · 来源 ' + (stats.cardSource === 'section' ? '按小节切分' : '按文件'))
|
|
1602
|
+
: (total + ' section cards' + (stats.omitted ? ' (' + stats.omitted + ' omitted)' : '') + ' · anchored ' + (stats.anchored || 0))
|
|
1603
|
+
|
|
1604
|
+
var btn = function (label, onClick, title) {
|
|
1605
|
+
return h('button', { 'data-dam-btn': '', onClick: onClick, title: title || '', style: { padding: '1px 7px', fontSize: '11px' } }, label)
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// 工具条: 缩放 / 布局 / 视图 / 搜索
|
|
1609
|
+
var bar = h('div', { 'data-dam-kbar': '', style: { display: 'flex', flexWrap: 'wrap', gap: '5px', alignItems: 'center', marginBottom: '8px' } }, [
|
|
1610
|
+
btn('A-', function () { setZoomBoth(-0.1) }, zh ? '缩小字号' : 'smaller'),
|
|
1611
|
+
h('span', { 'data-dam-hint': '', style: { fontSize: '10.5px', minWidth: '36px', textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
|
|
1612
|
+
btn('A+', function () { setZoomBoth(0.1) }, zh ? '放大字号' : 'bigger'),
|
|
1613
|
+
h('span', { style: { width: '6px' } }),
|
|
1614
|
+
btn('W-', function () { setColBoth(-40) }, zh ? '收窄列宽' : 'narrower'),
|
|
1615
|
+
h('span', { 'data-dam-hint': '', style: { fontSize: '10.5px', minWidth: '44px', textAlign: 'center' } }, isStack ? '' : colW + 'px'),
|
|
1616
|
+
btn('W+', function () { setColBoth(40) }, zh ? '加宽列宽' : 'wider'),
|
|
1617
|
+
h('span', { style: { width: '6px' } }),
|
|
1618
|
+
btn(isStack ? (zh ? '纵向' : 'stack') : (zh ? '横向' : 'lane'), toggleLayout, zh ? '切换泳道排布' : 'toggle layout'),
|
|
1619
|
+
btn(view === 'lanes' ? (zh ? '泳道' : 'lanes') : (zh ? '最近' : 'recent'), function () { setView(view === 'lanes' ? 'recent' : 'lanes') }, zh ? '切换视图' : 'toggle view'),
|
|
1620
|
+
h('input', {
|
|
1621
|
+
'data-dam-input': '', value: q, placeholder: zh ? '搜索…' : 'search…',
|
|
1622
|
+
onChange: function (e) { setQ(e.target.value) },
|
|
1623
|
+
style: { flex: '1 1 90px', minWidth: '80px', fontSize: '11px', padding: '2px 6px' },
|
|
1624
|
+
}),
|
|
1625
|
+
])
|
|
1626
|
+
|
|
1627
|
+
var renderCard = function (c, i) {
|
|
1628
|
+
return h(KanbanCard, { key: c.id + '#' + i, card: c, zoom: zoom, expanded: !!expanded[c.id], onToggle: toggleCard })
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// 最近视图(P9): 跨泳道按 mtime 倒序
|
|
1632
|
+
if (view === 'recent') {
|
|
1633
|
+
var rec = (kb.recent || []).filter(matchQ)
|
|
1634
|
+
return h('div', null, [
|
|
1635
|
+
h('div', { 'data-dam-hint': '', style: { marginBottom: '6px' } }, sum),
|
|
1636
|
+
bar,
|
|
1637
|
+
rec.length
|
|
1638
|
+
? h('div', { style: { columnWidth: isStack ? 'auto' : colW + 'px' } }, rec.map(function (c, i) {
|
|
1639
|
+
return h('div', { key: c.id + '#' + i }, [
|
|
1640
|
+
h('div', { 'data-dam-hint': '', style: { fontSize: '10px', marginTop: '4px' } }, (c.laneLabel || '') + ' · ' + (c.source || '')),
|
|
1641
|
+
renderCard(c, i),
|
|
1642
|
+
])
|
|
1643
|
+
}))
|
|
1644
|
+
: h('div', { 'data-dam-hint': '' }, zh ? '无命中' : 'no match'),
|
|
1645
|
+
])
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// 泳道视图: 纵向堆叠(默认, 适配 440px 面板) 或 横向泳道
|
|
1649
|
+
var wrapStyle = isStack
|
|
1650
|
+
? { display: 'block' }
|
|
1651
|
+
: { display: 'flex', gap: '10px', overflowX: 'auto', alignItems: 'flex-start', paddingBottom: '6px' }
|
|
1652
|
+
var laneStyle = isStack
|
|
1653
|
+
? { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.22)) 55%, transparent)', borderRadius: '10px', padding: '8px', background: 'color-mix(in srgb, currentColor 2.5%, transparent)', marginBottom: '9px' }
|
|
1654
|
+
: { flex: '0 0 auto', width: colW + 'px', minWidth: '160px', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.22)) 55%, transparent)', borderRadius: '10px', padding: '8px', background: 'color-mix(in srgb, currentColor 2.5%, transparent)' }
|
|
1655
|
+
|
|
1656
|
+
return h('div', null, [
|
|
1657
|
+
h('div', { 'data-dam-hint': '', style: { marginBottom: '6px', wordBreak: 'break-word' } }, sum),
|
|
1658
|
+
bar,
|
|
1659
|
+
h('div', { 'data-dam-kanban': '', 'data-dam-layout': layout, style: wrapStyle },
|
|
1660
|
+
lanes.map(function (lane) {
|
|
1661
|
+
var cards = (lane.cards || []).filter(matchQ)
|
|
1662
|
+
var isFolded = !!folded[lane.key]
|
|
1663
|
+
var empty = !lane.count
|
|
1664
|
+
// P14: 空泳道默认收起(不占位), 但仍给出可点开的标题
|
|
1665
|
+
return h('div', { key: lane.key, 'data-dam-lane': lane.key, style: Object.assign({}, laneStyle, empty ? { opacity: .55 } : {}) }, [
|
|
1666
|
+
h('div', {
|
|
1667
|
+
style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: isFolded ? '0' : '7px', cursor: 'pointer', gap: '6px' },
|
|
1668
|
+
onClick: function () { toggleFold(lane.key) },
|
|
1669
|
+
}, [
|
|
1670
|
+
h('div', { style: { fontWeight: 700, fontSize: (12 * zoom) + 'px' } },
|
|
1671
|
+
(isFolded ? '▸ ' : '▾ ') + lane.label),
|
|
1672
|
+
h('span', { 'data-dam-hint': '', style: { fontSize: (10.5 * zoom) + 'px', whiteSpace: 'nowrap' } },
|
|
1673
|
+
String(lane.count || 0) + (q.trim() && cards.length !== lane.count ? ' (' + cards.length + ')' : '') + (lane.omitted ? ' +' + lane.omitted : '')),
|
|
1674
|
+
]),
|
|
1675
|
+
isFolded ? null : (cards.length
|
|
1676
|
+
? h('div', isStack ? { style: { columnWidth: colW + 'px', columnGap: '10px' } } : null,
|
|
1677
|
+
cards.map(function (c, i) { return renderCard(c, i) }))
|
|
1678
|
+
: h('div', { 'data-dam-hint': '', style: { padding: '4px 0', fontSize: '11px' } },
|
|
1679
|
+
empty ? (zh ? '(本泳道暂无内容)' : '(empty)') : (zh ? '无命中' : 'no match'))),
|
|
1680
|
+
])
|
|
1681
|
+
})),
|
|
1682
|
+
])
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
1686
|
+
// 整页白板看板(2026-09-16「双承载面」批) —— 注册在 conversation.view 槽位。
|
|
1687
|
+
//
|
|
1688
|
+
// 为什么需要它: 侧边面板固定 440px, 而矩阵式布局(首列行标签 + N 列)需要 ~1000px+。
|
|
1689
|
+
// 把矩阵塞进 440px 就是「大小不可调 / 看着挤」的结构性根因 ⇒ 宽容器另开一个承载面。
|
|
1690
|
+
//
|
|
1691
|
+
// 与面板的关系(两者共存, 非替换):
|
|
1692
|
+
// · 侧边面板 = 常驻入口, 窄, 用**列表视图**(KanbanBoard, 泳道堆叠);
|
|
1693
|
+
// · 整页看板 = 会话页顶栏 Tab, 宽, 用**矩阵视图**(行=日期分组, 列=小节类型)。
|
|
1694
|
+
// 同一份数据(/kanban-board 一个路由同时回 lanes 与 matrix), 按容器宽度选形态。
|
|
1695
|
+
//
|
|
1696
|
+
// 交互: 行折叠 + 卡片点开右侧抽屉(宽屏才放得下全文, 这才是有别于面板的价值)。
|
|
1697
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
1698
|
+
|
|
1699
|
+
// 泳道配色(2026-09-16): 每条泳道一个色相, 让「一眼看出哪列是什么」。
|
|
1700
|
+
// 取值优先走官方令牌(跟随宿主明暗主题), 令牌缺失时回落到可读色值。
|
|
1701
|
+
var KX_LANE_COLOR = {
|
|
1702
|
+
goal: 'var(--dsw-alias-brand-primary, #4c8dff)', // 目标 · 蓝
|
|
1703
|
+
state: 'var(--dsw-alias-state-success-primary, #3aa675)', // 进行中 · 绿
|
|
1704
|
+
deadend: 'var(--dsw-alias-state-error-primary, #d66666)', // 失败与弯路 · 红
|
|
1705
|
+
progress: 'var(--dsw-alias-state-warn-primary, #e0a53a)', // 进度与下一步 · 橙
|
|
1706
|
+
archive: 'var(--dsw-alias-label-tertiary, #8b93a7)', // 版本归档 · 灰蓝
|
|
1707
|
+
misc: 'var(--dsw-alias-label-dimmed, #6b7280)', // 其它 · 暗灰
|
|
1708
|
+
}
|
|
1709
|
+
function kxLaneColor(key) { return KX_LANE_COLOR[key] || KX_LANE_COLOR.misc }
|
|
1710
|
+
|
|
1711
|
+
/** 判据徽章(与面板同口径, 抽出来避免两处漂移)。 */
|
|
1712
|
+
function kxBadge(crit, zh) {
|
|
1713
|
+
var m = {
|
|
1714
|
+
passed: ['✓', zh ? '通过' : 'passed', 'rgba(58,166,117,.20)', '#3aa675'],
|
|
1715
|
+
warned: ['!', zh ? '有警告' : 'warned', 'rgba(224,165,58,.20)', '#e0a53a'],
|
|
1716
|
+
failed: ['✕', zh ? '未通过' : 'failed', 'rgba(214,102,102,.20)', '#d66666'],
|
|
1717
|
+
unknown: ['·', zh ? '未校验' : 'unchecked', 'rgba(128,128,128,.18)', 'inherit'],
|
|
1718
|
+
}
|
|
1719
|
+
var v = m[crit] || m.unknown
|
|
1720
|
+
return h('span', {
|
|
1721
|
+
'data-dam-kx-badge': crit,
|
|
1722
|
+
title: v[1],
|
|
1723
|
+
style: { flexShrink: 0, fontSize: '10px', padding: '0 4px', borderRadius: '3px', background: v[2], color: v[3] },
|
|
1724
|
+
}, v[0])
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/** 矩阵里的紧凑卡(只给全貌, 全文进抽屉)。 */
|
|
1728
|
+
function KanbanMatrixCard(props) {
|
|
1729
|
+
var c = props.card
|
|
1730
|
+
var zh = props.zh
|
|
1731
|
+
// 左侧色条按**泳道**取色(不是按有无 tag): 这样同一列内颜色一致, 跨列一眼可辨。
|
|
1732
|
+
var accent = kxLaneColor(props.laneKey)
|
|
1733
|
+
return h('div', {
|
|
1734
|
+
'data-dam-kx-card': c.id,
|
|
1735
|
+
'data-dam-kx-lane': props.laneKey || '',
|
|
1736
|
+
title: c.title + ' · ' + c.docTitle,
|
|
1737
|
+
onClick: props.onOpen,
|
|
1738
|
+
style: {
|
|
1739
|
+
border: '1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.28))',
|
|
1740
|
+
borderLeft: '3px solid ' + accent,
|
|
1741
|
+
borderRadius: '5px', padding: '6px 8px', marginBottom: '5px', cursor: 'pointer',
|
|
1742
|
+
background: 'var(--dsw-alias-bg-layer-3, rgba(128,128,128,.07))',
|
|
1743
|
+
transition: 'transform .12s ease, box-shadow .12s ease, border-color .12s ease',
|
|
1744
|
+
},
|
|
1745
|
+
onMouseEnter: function (e) {
|
|
1746
|
+
e.currentTarget.style.transform = 'translateY(-1px)'
|
|
1747
|
+
e.currentTarget.style.boxShadow = '0 2px 10px var(--dsw-alias-bg-mask-1, rgba(0,0,0,.18))'
|
|
1748
|
+
e.currentTarget.style.borderColor = accent
|
|
1749
|
+
},
|
|
1750
|
+
onMouseLeave: function (e) {
|
|
1751
|
+
e.currentTarget.style.transform = ''
|
|
1752
|
+
e.currentTarget.style.boxShadow = ''
|
|
1753
|
+
e.currentTarget.style.borderColor = 'var(--dsw-alias-border-l1, rgba(128,128,128,.28))'
|
|
1754
|
+
},
|
|
1755
|
+
}, [
|
|
1756
|
+
h('div', { key: 't', style: { display: 'flex', alignItems: 'center', gap: '5px' } }, [
|
|
1757
|
+
kxBadge(c.criteria, zh),
|
|
1758
|
+
h('span', { key: 'n', style: {
|
|
1759
|
+
flex: 1, minWidth: 0, fontSize: '12.5px', fontWeight: 600,
|
|
1760
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
1761
|
+
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
1762
|
+
} }, c.title),
|
|
1763
|
+
c.bullets ? h('span', { key: 'b', 'data-dam-hint': '', style: { fontSize: '10.5px', flexShrink: 0 } }, '·' + c.bullets) : null,
|
|
1764
|
+
c.anchored ? h('span', { key: 'a', title: '已有锚点', style: { fontSize: '10.5px', flexShrink: 0, color: 'var(--dsw-alias-state-success-primary, #3aa675)' } }, '⚓') : null,
|
|
1765
|
+
]),
|
|
1766
|
+
c.preview ? h('div', { key: 'p', 'data-dam-hint': '', style: {
|
|
1767
|
+
fontSize: '11px', lineHeight: 1.45, marginTop: '4px',
|
|
1768
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
1769
|
+
overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
|
|
1770
|
+
} }, c.preview) : null,
|
|
1771
|
+
])
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
/** 整页矩阵看板: 行=日期分组, 列=小节类型。 */
|
|
1775
|
+
function KanbanView(props) {
|
|
1776
|
+
var st = useState(null), data = st[0], setData = st[1]
|
|
1777
|
+
var st2 = useState(null), drawer = st2[0], setDrawer = st2[1]
|
|
1778
|
+
var st3 = useState({}), folded = st3[0], setFolded = st3[1]
|
|
1779
|
+
var st4 = useState(''), q = st4[0], setQ = st4[1]
|
|
1780
|
+
var st5 = useState(1), zoom = st5[0], setZoom = st5[1]
|
|
1781
|
+
var st6 = useState(0), tick = st6[0], bump = st6[1]
|
|
1782
|
+
var zh = (locale || 'zh').indexOf('zh') === 0
|
|
1783
|
+
useEffect(function () {
|
|
1784
|
+
var alive = true
|
|
1785
|
+
apiGet(API.kanbanBoard, { sessionId: currentSessionIdClient() })
|
|
1786
|
+
.then(function (k) { if (alive) setData(k && k.enabled ? k : null) })
|
|
1787
|
+
.catch(function () { if (alive) setData(null) })
|
|
1788
|
+
return function () { alive = false }
|
|
1789
|
+
}, [tick])
|
|
1790
|
+
|
|
1791
|
+
var mx = data && data.matrix
|
|
1792
|
+
var cols = (mx && mx.columns) || []
|
|
1793
|
+
var rows = (mx && mx.rows) || []
|
|
1794
|
+
var kw = q.trim().toLowerCase()
|
|
1795
|
+
var hit = function (c) { return !kw || String(c.title + ' ' + c.preview + ' ' + c.docTitle).toLowerCase().indexOf(kw) >= 0 }
|
|
1796
|
+
|
|
1797
|
+
var total = (mx && mx.stats && mx.stats.totalCards) || 0
|
|
1798
|
+
var missing = (mx && mx.stats && mx.stats.missingDeadend) || []
|
|
1799
|
+
|
|
1800
|
+
return h('div', { 'data-dam-kanban-view': '', style: {
|
|
1801
|
+
// position:relative 是**必需**的: 抽屉用 absolute 定位, 它的参照系就是这里 ⇒ 抽屉被裁剪在
|
|
1802
|
+
// 看板承载面内, 不会像 fixed 那样越界盖住底部输入框。overflow:hidden 同时兜底裁剪。
|
|
1803
|
+
position: 'relative', height: '100%', overflow: 'hidden', boxSizing: 'border-box',
|
|
1804
|
+
color: 'var(--dsw-alias-label-primary)', fontSize: '12px',
|
|
1805
|
+
} }, [
|
|
1806
|
+
// 内滚动层(与抽屉分离: 抽屉不该跟着内容一起滚)
|
|
1807
|
+
h('div', { 'data-dam-kx-scroll': '', style: { height: '100%', overflow: 'auto', padding: '12px 16px', boxSizing: 'border-box' } }, [
|
|
1808
|
+
// ── 概览条 ──
|
|
1809
|
+
h('div', { key: 'hdr', style: { display: 'flex', alignItems: 'baseline', gap: '10px', flexWrap: 'wrap', marginBottom: '8px' } }, [
|
|
1810
|
+
h('strong', { key: 't', style: { fontSize: '14px' } }, zh ? '白板看板 · 时间矩阵' : 'Whiteboard · time matrix'),
|
|
1811
|
+
h('span', { key: 's', 'data-dam-hint': '' },
|
|
1812
|
+
zh
|
|
1813
|
+
? (String(total) + ' 张小节卡 / ' + String(rows.length) + ' 天 / ' + (mx && mx.stats ? mx.stats.dateFrom + ' → ' + mx.stats.dateTo : ''))
|
|
1814
|
+
: (String(total) + ' cards / ' + String(rows.length) + ' days')),
|
|
1815
|
+
missing.length
|
|
1816
|
+
? h('span', { key: 'm', 'data-dam-kx-warn': '', title: missing.join(', '), style: { color: '#e0a53a' } },
|
|
1817
|
+
zh ? ('⚠ ' + missing.length + ' 天缺「失败与弯路」') : ('⚠ ' + missing.length + ' days missing dead-ends'))
|
|
1818
|
+
: null,
|
|
1819
|
+
]),
|
|
1820
|
+
// ── 工具条 ──
|
|
1821
|
+
h('div', { key: 'bar', style: { display: 'flex', gap: '6px', alignItems: 'center', marginBottom: '8px', flexWrap: 'wrap' } }, [
|
|
1822
|
+
h('input', { key: 'q', 'data-dam-input': '', placeholder: zh ? '搜索标题/正文/来源…' : 'search…', value: q, onChange: function (e) { setQ(e.target.value) }, style: { minWidth: '200px' } }),
|
|
1823
|
+
h('button', { key: 'zo', 'data-dam-btn': '', onClick: function () { setZoom(Math.max(0.8, zoom - 0.1)) } }, 'A-'),
|
|
1824
|
+
h('span', { key: 'zv', 'data-dam-hint': '', style: { minWidth: '38px', textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
|
|
1825
|
+
h('button', { key: 'zi', 'data-dam-btn': '', onClick: function () { setZoom(Math.min(1.6, zoom + 0.1)) } }, 'A+'),
|
|
1826
|
+
h('button', { key: 'rf', 'data-dam-btn': '', onClick: function () { setData(null); bump(function (n) { return n + 1 }) } }, zh ? '刷新' : 'reload'),
|
|
1827
|
+
]),
|
|
1828
|
+
// ── 空态 / 错误态(不静默) ──
|
|
1829
|
+
!data ? h('div', { key: 'ph', 'data-dam-kx-empty': 'nodata', 'data-dam-hint': '' },
|
|
1830
|
+
zh ? '看板未启用或加载失败(需 boardMode=graph 且 handoff 已开启)。' : 'Board unavailable.') : null,
|
|
1831
|
+
data && !mx ? h('div', { key: 'nm', 'data-dam-kx-empty': 'nomatrix', 'data-dam-hint': '' },
|
|
1832
|
+
zh ? '本次载荷缺矩阵视图。' : 'No matrix in payload.') : null,
|
|
1833
|
+
data && mx && !rows.length ? h('div', { key: 'nr', 'data-dam-kx-empty': 'norows', 'data-dam-hint': '' },
|
|
1834
|
+
zh ? '白板里还没有账本文档。' : 'No ledger docs yet.') : null,
|
|
1835
|
+
// ── 矩阵本体 ──
|
|
1836
|
+
data && mx && rows.length ? h('div', {
|
|
1837
|
+
key: 'grid',
|
|
1838
|
+
style: {
|
|
1839
|
+
display: 'grid',
|
|
1840
|
+
// 首列 170px 行标签(日期要放得下) + N 列自适应
|
|
1841
|
+
gridTemplateColumns: '170px repeat(' + cols.length + ', minmax(190px, 1fr))',
|
|
1842
|
+
gap: '6px',
|
|
1843
|
+
fontSize: (12 * zoom) + 'px',
|
|
1844
|
+
},
|
|
1845
|
+
}, [].concat(
|
|
1846
|
+
// 表头: 与内容区用底色 + 下边框拉开层次, sticky 吸顶
|
|
1847
|
+
[h('div', { key: 'hc0', 'data-dam-kx-head': 'row', style: {
|
|
1848
|
+
fontWeight: 700, padding: '8px 6px', position: 'sticky', top: 0, zIndex: 3,
|
|
1849
|
+
fontSize: '11.5px', letterSpacing: '.03em', textTransform: 'uppercase',
|
|
1850
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
1851
|
+
background: 'var(--dsw-alias-bg-layer-1)',
|
|
1852
|
+
borderBottom: '2px solid var(--dsw-alias-border-l2, rgba(128,128,128,.45))',
|
|
1853
|
+
} }, zh ? '日期' : 'Date')],
|
|
1854
|
+
cols.map(function (c) {
|
|
1855
|
+
return h('div', { key: 'hc-' + c.key, 'data-dam-kx-head': c.key, style: {
|
|
1856
|
+
fontWeight: 700, padding: '8px 6px', display: 'flex', gap: '6px', alignItems: 'center',
|
|
1857
|
+
position: 'sticky', top: 0, zIndex: 3,
|
|
1858
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
1859
|
+
background: 'var(--dsw-alias-bg-layer-1)',
|
|
1860
|
+
borderBottom: '2px solid ' + kxLaneColor(c.key),
|
|
1861
|
+
} }, [
|
|
1862
|
+
// 顶部色点 = 该列身份, 与卡片左条同色 ⇒ 列↔卡片的归属一眼可辨
|
|
1863
|
+
h('span', { key: 'dot', 'data-dam-kx-dot': c.key, style: {
|
|
1864
|
+
width: '8px', height: '8px', borderRadius: '50%', flexShrink: 0,
|
|
1865
|
+
background: kxLaneColor(c.key), boxShadow: '0 0 0 2px ' + kxLaneColor(c.key) + '33',
|
|
1866
|
+
} }),
|
|
1867
|
+
h('span', { key: 'l', style: { flex: 1, minWidth: 0 } }, c.label),
|
|
1868
|
+
h('span', { key: 'n', 'data-dam-hint': '', style: { fontSize: '11px', color: 'var(--dsw-alias-label-tertiary)' } }, String(c.count || 0)),
|
|
1869
|
+
])
|
|
1870
|
+
}),
|
|
1871
|
+
// 数据行
|
|
1872
|
+
rows.reduce(function (acc, r) {
|
|
1873
|
+
var isFolded = !!folded[r.key]
|
|
1874
|
+
acc.push(h('div', {
|
|
1875
|
+
key: 'rl-' + r.key,
|
|
1876
|
+
'data-dam-kx-row': r.key,
|
|
1877
|
+
onClick: function () { setFolded(function (f) { var n = Object.assign({}, f); n[r.key] = !n[r.key]; return n }) },
|
|
1878
|
+
style: {
|
|
1879
|
+
padding: '8px 6px', cursor: 'pointer', userSelect: 'none',
|
|
1880
|
+
fontWeight: 700, fontSize: '12.5px',
|
|
1881
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
1882
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
1883
|
+
borderRadius: '5px',
|
|
1884
|
+
borderTop: '1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.28))',
|
|
1885
|
+
},
|
|
1886
|
+
title: zh ? '点击折叠/展开该日' : 'toggle',
|
|
1887
|
+
}, (isFolded ? '▸ ' : '▾ ') + r.label + ' (' + String(r.docs) + ')'))
|
|
1888
|
+
cols.forEach(function (c) {
|
|
1889
|
+
var cells = ((r.cells || {})[c.key] || []).filter(hit)
|
|
1890
|
+
acc.push(h('div', {
|
|
1891
|
+
key: 'rc-' + r.key + '-' + c.key,
|
|
1892
|
+
'data-dam-kx-cell': c.key,
|
|
1893
|
+
style: {
|
|
1894
|
+
padding: '3px 2px', minWidth: 0,
|
|
1895
|
+
borderTop: '1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.18))',
|
|
1896
|
+
// 极淡的泳道底色(color-mix 把主色压到 ~5%): 让整列连成一片而不抢内容
|
|
1897
|
+
background: 'color-mix(in srgb, ' + kxLaneColor(c.key) + ' 5%, transparent)',
|
|
1898
|
+
},
|
|
1899
|
+
}, isFolded ? null : (cells.length
|
|
1900
|
+
? cells.map(function (c2) { return h(KanbanMatrixCard, { key: c2.id, card: c2, zh: zh, laneKey: c.key, onOpen: function () { setDrawer(c2) } }) })
|
|
1901
|
+
: h('div', { 'data-dam-hint': '', style: { fontSize: '10px', padding: '2px 4px', opacity: .5 } }, kw ? '—' : ''))))
|
|
1902
|
+
})
|
|
1903
|
+
return acc
|
|
1904
|
+
}, [])
|
|
1905
|
+
)) : null,
|
|
1906
|
+
]), // ← 闭合内滚动层(data-dam-kx-scroll)
|
|
1907
|
+
// ── 右侧浮窗(portal 到 body) ──
|
|
1908
|
+
// ★ 2026-09-16 二次修正: 上一版改成容器内 absolute 后, **页面往下滚它就被推出视口**
|
|
1909
|
+
// (用户实测「在页面下部分没办法看到点开的页签」)。absolute 的参照系是容器, 容器会滚。
|
|
1910
|
+
// 正解 = createPortal 挂到 document.body + position:fixed ⇒ 参照系是视口, 永远可见,
|
|
1911
|
+
// 且因为脱离容器, 不会被容器的 overflow 裁剪。底部留 96px 安全边距避开输入区。
|
|
1912
|
+
drawer ? kxPortal(h('div', {
|
|
1913
|
+
key: 'dr',
|
|
1914
|
+
'data-dam-kx-drawer': drawer.id,
|
|
1915
|
+
style: {
|
|
1916
|
+
position: 'fixed', top: '52px', right: '16px', bottom: '96px',
|
|
1917
|
+
width: 'min(560px, 42vw)', zIndex: 2147483000,
|
|
1918
|
+
display: 'flex', flexDirection: 'column',
|
|
1919
|
+
background: 'var(--dsw-alias-bg-layer-2, rgba(24,24,28,.97))', backdropFilter: 'blur(16px)',
|
|
1920
|
+
border: '1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.35))',
|
|
1921
|
+
borderRadius: '10px',
|
|
1922
|
+
boxShadow: '0 12px 40px var(--dsw-alias-bg-mask-1, rgba(0,0,0,.35))',
|
|
1923
|
+
color: 'var(--dsw-alias-label-primary)', fontSize: '12.5px', lineHeight: 1.6,
|
|
1924
|
+
overflow: 'hidden',
|
|
1925
|
+
},
|
|
1926
|
+
}, [
|
|
1927
|
+
h('div', { key: 'h', style: { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' } }, [
|
|
1928
|
+
kxBadge(drawer.criteria, zh),
|
|
1929
|
+
h('strong', { key: 't', style: { flex: 1, fontSize: '14px' } }, drawer.title),
|
|
1930
|
+
h('button', { key: 'x', 'data-dam-btn': '', onClick: function () { setDrawer(null) } }, '✕'),
|
|
1931
|
+
]),
|
|
1932
|
+
h('div', { key: 'meta', 'data-dam-hint': '', style: { marginBottom: '8px', display: 'flex', gap: '8px', flexWrap: 'wrap' } }, [
|
|
1933
|
+
h('span', { key: 'd' }, drawer.docTitle),
|
|
1934
|
+
h('span', { key: 's' }, drawer.source),
|
|
1935
|
+
h('span', { key: 'i' }, '#' + drawer.short),
|
|
1936
|
+
drawer.anchored ? h('span', { key: 'a', title: drawer.anchorId }, '⚓ ' + String(drawer.anchorId).slice(0, 14)) : null,
|
|
1937
|
+
]),
|
|
1938
|
+
(drawer.sectionTags && drawer.sectionTags.length)
|
|
1939
|
+
? h('div', { key: 'sg', style: { display: 'flex', gap: '4px', flexWrap: 'wrap', marginBottom: '8px' } },
|
|
1940
|
+
drawer.sectionTags.map(function (tg, i) { return h('span', { key: i, 'data-dam-chip': '', style: { fontSize: '10px', padding: '1px 5px', borderRadius: '3px', background: 'rgba(76,141,255,.18)' } }, tg) }))
|
|
1941
|
+
: null,
|
|
1942
|
+
h('pre', { key: 'body', 'data-dam-kx-full': drawer.id, style: { whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontFamily: 'inherit', margin: 0, fontSize: '12px' } }, drawer.full || drawer.preview || ''),
|
|
1943
|
+
].map(function (node, i) {
|
|
1944
|
+
// 头部固定、正文滚动: 把内容各自包一层; 正文那层给 flex:1 + overflow:auto
|
|
1945
|
+
if (i === 0) return h('div', { key: 'headwrap', style: { padding: '12px 14px 8px', flexShrink: 0 } }, node)
|
|
1946
|
+
if (i === 1) return h('div', { key: 'metawrap', style: { padding: '0 14px', flexShrink: 0 } }, node)
|
|
1947
|
+
if (i === 2) return node ? h('div', { key: 'tagwrap', style: { padding: '0 14px', flexShrink: 0 } }, node) : null
|
|
1948
|
+
return h('div', { key: 'bodywrap', style: { padding: '8px 14px 14px', flex: 1, overflow: 'auto', minHeight: 0 } }, node)
|
|
1949
|
+
}))) : null,
|
|
1950
|
+
])
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1346
1953
|
function Loading(props) {
|
|
1347
1954
|
var label = props && props.label ? props.label : t('loading')
|
|
1348
1955
|
return h('div', { 'data-dam-loading': '' }, h('span', { 'data-dam-spinner': '', 'aria-hidden': 'true' }), h('span', null, label))
|
|
@@ -1736,7 +2343,7 @@ window.__ModuleLoader__.load({
|
|
|
1736
2343
|
}
|
|
1737
2344
|
// 美术规格对齐 artifacts/m7-live-pre/ui-assets/semantic-tier-ui.html 组件③
|
|
1738
2345
|
var card = { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(255,255,255,.16)) 55%, transparent)', borderRadius: '12px', padding: '10px 12px', marginBottom: '9px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent)', boxShadow: '0 4px 14px rgba(0,0,0,.12), inset 0 1px 0 rgba(255,255,255,.14)' }
|
|
1739
|
-
var badge = function (txt, bg, fg) { return h('span', { style: { fontSize: 'calc(10.5px * var(--dam-scale))', fontWeight: 700, padding: '2px 8px', borderRadius: '6px', background: bg, color: fg || 'var(--dsw-alias-
|
|
2346
|
+
var badge = function (txt, bg, fg) { return h('span', { style: { fontSize: 'calc(10.5px * var(--dam-scale))', fontWeight: 700, padding: '2px 8px', borderRadius: '6px', background: bg, color: fg || 'var(--dsw-alias-label-primary, inherit)', letterSpacing: '.02em' } }, txt) }
|
|
1740
2347
|
var decBg = { emit: ['rgba(47,164,106,.24)', '#7fdcb0'], prefetch: ['rgba(196,138,42,.2)', '#e8c584'], suppress: ['rgba(128,128,128,.16)', 'rgba(255,255,255,.65)'] }
|
|
1741
2348
|
var laneBg = { explicit: ['rgba(111,155,255,.24)', '#b9ceff'], proactive: ['rgba(160,120,255,.22)', '#d4c2ff'] }
|
|
1742
2349
|
var reasonChip = function (txt) { return h('span', { style: { fontFamily: 'ui-monospace, Consolas, monospace', fontSize: 'calc(10px * var(--dam-scale))', padding: '2px 7px', borderRadius: '5px', background: 'rgba(255,214,150,.1)', color: '#ffd9a1' } }, txt) }
|
|
@@ -1774,7 +2381,7 @@ window.__ModuleLoader__.load({
|
|
|
1774
2381
|
index[k].push(row)
|
|
1775
2382
|
})
|
|
1776
2383
|
var card = { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(255,255,255,.16)) 55%, transparent)', borderRadius: '12px', padding: '10px 12px', marginBottom: '9px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent)', boxShadow: '0 4px 14px rgba(0,0,0,.12), inset 0 1px 0 rgba(255,255,255,.14)' }
|
|
1777
|
-
var badge = function (txt, bg, fg) { return h('span', { style: { fontSize: 'calc(10.5px * var(--dam-scale))', fontWeight: 700, padding: '2px 8px', borderRadius: '6px', background: bg, color: fg || 'var(--dsw-alias-
|
|
2384
|
+
var badge = function (txt, bg, fg) { return h('span', { style: { fontSize: 'calc(10.5px * var(--dam-scale))', fontWeight: 700, padding: '2px 8px', borderRadius: '6px', background: bg, color: fg || 'var(--dsw-alias-label-primary, inherit)', letterSpacing: '.02em' } }, txt) }
|
|
1778
2385
|
var decBg = { emit: ['rgba(47,164,106,.24)', '#7fdcb0'], prefetch: ['rgba(196,138,42,.2)', '#e8c584'], suppress: ['rgba(128,128,128,.16)', 'rgba(255,255,255,.65)'] }
|
|
1779
2386
|
var laneBg = { explicit: ['rgba(111,155,255,.24)', '#b9ceff'], proactive: ['rgba(160,120,255,.22)', '#d4c2ff'] }
|
|
1780
2387
|
var reasonChip = function (txt) { return h('span', { style: { fontFamily: 'ui-monospace, Consolas, monospace', fontSize: 'calc(10px * var(--dam-scale))', padding: '2px 7px', borderRadius: '5px', background: 'rgba(255,214,150,.1)', color: '#ffd9a1' } }, txt) }
|
|
@@ -1909,7 +2516,9 @@ window.__ModuleLoader__.load({
|
|
|
1909
2516
|
body: JSON.stringify({ action: action, procedureId: procedureId, v: v }) })
|
|
1910
2517
|
.then(function (r) { return r.json() })
|
|
1911
2518
|
.then(function (j) {
|
|
1912
|
-
|
|
2519
|
+
// issue #30:附带 reasonCodes —— 否则"点了晋升但没晋级"只显示 keep,看不出原因
|
|
2520
|
+
// (如 observation-only / diversity-below-3 / no-success-criteria)。
|
|
2521
|
+
setActMsg(action + ': ' + (j && (j.decision || j.reason || (j.ok === false ? (j.reason || 'rejected') : 'ok')) || 'done') + (j && Array.isArray(j.reasonCodes) && j.reasonCodes.length ? ' (' + j.reasonCodes.join(', ') + ')' : ''))
|
|
1913
2522
|
setRefresh(function (x) { return x + 1 })
|
|
1914
2523
|
})
|
|
1915
2524
|
.catch(function (e) { setActMsg(action + ' failed: ' + String(e && e.message)) })
|
|
@@ -1942,16 +2551,18 @@ window.__ModuleLoader__.load({
|
|
|
1942
2551
|
pipeline.map(function (p) {
|
|
1943
2552
|
var ev = p.evidence || {}
|
|
1944
2553
|
return h('div', { 'data-dam-content': '', key: p.procedureId, style: { padding: '5px 0', borderBottom: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(255,255,255,.16)) 40%, transparent)' } },
|
|
1945
|
-
h('div', null, h('b', null, p.title), h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } }, '[' + p.stage + ']' + (p.pinned ? ' 📌' : '') + (p.riskLevel === 'high' ? ' ⚠' : '')),
|
|
2554
|
+
h('div', null, h('b', null, p.title), h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } }, '[' + p.stage + ']' + (p.observationOnly ? (locale === 'zh' ? ' · 观察(不参与自动晋升)' : ' · observation-only') : '') + (p.pinned ? ' 📌' : '') + (p.riskLevel === 'high' ? ' ⚠' : '')),
|
|
1946
2555
|
h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } }, (locale === 'zh' ? '成功 ' : 'succ ') + (ev.success || 0) + ' · ' + (locale === 'zh' ? '会话 ' : 'sess ') + (ev.sessions || 0))),
|
|
1947
2556
|
h('div', { style: { display: 'flex', gap: '6px', marginTop: '5px' } },
|
|
1948
|
-
|
|
1949
|
-
|
|
2557
|
+
// issue #30:纯 episode 观察行**结构上**不可能晋升(无 successCriteria),
|
|
2558
|
+
// 继续显示"晋升"按钮只会让人反复点击却看不到变化 ⇒ 直接隐藏并打观察标。
|
|
2559
|
+
!p.observationOnly && h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px' }, onClick: function () { hubAct('promote', p.procedureId) } }, locale === 'zh' ? '晋升' : 'promote'),
|
|
2560
|
+
!p.observationOnly && p.stage === 'validated' && h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px' }, onClick: function () { hubAct('activate', p.procedureId) } }, locale === 'zh' ? '直接激活' : 'activate'),
|
|
1950
2561
|
h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', opacity: .75 }, onClick: function () { hubAct('deprecate', p.procedureId) } }, locale === 'zh' ? '弃用' : 'deprecate'),
|
|
1951
2562
|
h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', opacity: .75 }, onClick: function () { hubAct('pin', p.procedureId, !p.pinned) } }, p.pinned ? (locale === 'zh' ? '取消置顶' : 'unpin') : (locale === 'zh' ? '置顶' : 'pin'))))
|
|
1952
2563
|
})))
|
|
1953
2564
|
}
|
|
1954
|
-
if (actMsg) rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: 'var(--dsw-alias-warn, #e8c584)' } }, actMsg))
|
|
2565
|
+
if (actMsg) rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: 'var(--dsw-alias-state-warn-primary, #e8c584)' } }, actMsg))
|
|
1955
2566
|
// 事实层
|
|
1956
2567
|
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px' } }, t('hubFacts')))
|
|
1957
2568
|
var factList = (facts && facts.recent) || []
|
|
@@ -1960,7 +2571,7 @@ window.__ModuleLoader__.load({
|
|
|
1960
2571
|
return h('div', { 'data-dam-content': '', key: f.factId, style: { padding: '3px 0' } }, f.subject + ' · ' + f.predicate + (f.object ? ' · ' + f.object : ''))
|
|
1961
2572
|
}) : h('div', { 'data-dam-content': '' }, t('hubFactsEmpty'))))
|
|
1962
2573
|
if (facts && facts.pendingConflicts && facts.pendingConflicts.length) {
|
|
1963
|
-
rows.push(h('div', { 'data-dam-hint': '', style: { color: 'var(--dsw-alias-warn, #e8c584)' } }, t('hubConflicts') + ': ' + facts.pendingConflicts.length))
|
|
2574
|
+
rows.push(h('div', { 'data-dam-hint': '', style: { color: 'var(--dsw-alias-state-warn-primary, #e8c584)' } }, t('hubConflicts') + ': ' + facts.pendingConflicts.length))
|
|
1964
2575
|
}
|
|
1965
2576
|
// 经历层
|
|
1966
2577
|
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px' } }, t('hubEpisodic')))
|
|
@@ -2027,7 +2638,7 @@ window.__ModuleLoader__.load({
|
|
|
2027
2638
|
var counts = data.counts || { total: 0, ok: 0, stale: 0, unrepairable: 0 }
|
|
2028
2639
|
var rows = []
|
|
2029
2640
|
if (data.indexEnabled === false) {
|
|
2030
|
-
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: 'var(--dsw-alias-warn, #e8c584)' } },
|
|
2641
|
+
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: 'var(--dsw-alias-state-warn-primary, #e8c584)' } },
|
|
2031
2642
|
locale === 'zh' ? '⚠ 记忆锚定索引未启用:语料健康不做 sidecar 比对(无 stale 可修)。如需「修复 stale / 删除记忆」,请到 设置 → 自动记忆引擎 开启「记忆锚定索引」。此提示不影响记忆读写与检索。' : '⚠ Memory anchor index disabled: corpus health performs no sidecar comparison (nothing stale). To use repair/delete, enable "Memory anchor index" under Settings → Semantic engine. Read/write/recall are unaffected.'))
|
|
2032
2643
|
}
|
|
2033
2644
|
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '8px' } }, t('storageScanHint')))
|
|
@@ -2056,7 +2667,7 @@ window.__ModuleLoader__.load({
|
|
|
2056
2667
|
if (!(delFile && delId)) return
|
|
2057
2668
|
act('delete', { filePath: delFile, memoryId: delId }, function () { setDelId('') })
|
|
2058
2669
|
} }, locale === 'zh' ? '删除' : 'delete')))))
|
|
2059
|
-
if (msg) rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: 'var(--dsw-alias-warn, #e8c584)' } }, msg))
|
|
2670
|
+
if (msg) rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: 'var(--dsw-alias-state-warn-primary, #e8c584)' } }, msg))
|
|
2060
2671
|
var audit = (data.audit || []).slice(-4)
|
|
2061
2672
|
if (audit.length) {
|
|
2062
2673
|
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px', opacity: .7 } },
|
|
@@ -2156,7 +2767,7 @@ window.__ModuleLoader__.load({
|
|
|
2156
2767
|
var t0 = Date.now()
|
|
2157
2768
|
var timeoutMs = 90000
|
|
2158
2769
|
try {
|
|
2159
|
-
var cfg = await apiGet(API.config)
|
|
2770
|
+
var cfg = configOf(await apiGet(API.config))
|
|
2160
2771
|
var sec = cfg && Number(cfg.autoContinueRefreshTimeoutSeconds)
|
|
2161
2772
|
if (sec >= 15 && sec <= 600) timeoutMs = sec * 1000
|
|
2162
2773
|
} catch (eC) {}
|
|
@@ -2227,7 +2838,7 @@ window.__ModuleLoader__.load({
|
|
|
2227
2838
|
autoState.suppressUntil = Date.now() + 5 * 60 * 1000
|
|
2228
2839
|
try {
|
|
2229
2840
|
var cfg = null
|
|
2230
|
-
try { cfg = await apiGet(API.config) } catch (eCfg) {}
|
|
2841
|
+
try { cfg = configOf(await apiGet(API.config)) } catch (eCfg) {}
|
|
2231
2842
|
var ritual = !(cfg && cfg.autoContinueRefreshRitual === false) && !(opts && opts.skipRefresh)
|
|
2232
2843
|
if (ritual) { try { await refreshOldSession(onMsg) } catch (eRf) {} }
|
|
2233
2844
|
onMsg(locale === 'zh' ? '正在构造交接材料(含旧会话转写)…' : 'Preparing handoff material (incl. prev-session transcript)…')
|
|
@@ -2251,6 +2862,10 @@ window.__ModuleLoader__.load({
|
|
|
2251
2862
|
var openPair = useState(null)
|
|
2252
2863
|
var open = openPair[0]
|
|
2253
2864
|
var setOpen = openPair[1]
|
|
2865
|
+
// 白板看板数据(2026-09-16 兼并 dsh-graph): graph 档有值 → 渲染列式泳道; legacy 档恒 null。
|
|
2866
|
+
var kbDataPair = useState(null)
|
|
2867
|
+
var kbData = kbDataPair[0]
|
|
2868
|
+
var setKbData = kbDataPair[1]
|
|
2254
2869
|
var contentPair = useState('')
|
|
2255
2870
|
var content = contentPair[0]
|
|
2256
2871
|
var setContent = contentPair[1]
|
|
@@ -2262,16 +2877,46 @@ window.__ModuleLoader__.load({
|
|
|
2262
2877
|
var contMsg = contMsgPair[0]
|
|
2263
2878
|
var setContMsg = contMsgPair[1]
|
|
2264
2879
|
// M-CM6-C 自动接续(①轮次边界触发 + ②确认卡由全局 AutoContinueHost 执行;这里只读/写开关与阈值)
|
|
2880
|
+
// 2026-09-14 解耦:白板页也读写这两个开关,且与设置页共用**同一对配置键**
|
|
2881
|
+
// (autoContinueEnabled / handoffEnabled)——两处不可能各说各话。
|
|
2882
|
+
// 注:两个键出厂默认均为 false(功能仍在测试期),默认关、界面可开。
|
|
2265
2883
|
var autoPair = useState(null)
|
|
2266
2884
|
var autoCfg = autoPair[0]
|
|
2267
2885
|
var setAutoCfg = autoPair[1]
|
|
2886
|
+
var hbPair = useState(false)
|
|
2887
|
+
var handoffOn = hbPair[0]
|
|
2888
|
+
var setHandoffOn = hbPair[1]
|
|
2889
|
+
// WB-GRAPH boardMode 本地镜像(2026-09-16): 接续面板按钮即时翻面用; 初值从配置取, 保存成功后本地翻面。
|
|
2890
|
+
var boardModePair = useState('legacy')
|
|
2891
|
+
var boardModeLocal = boardModePair[0]
|
|
2892
|
+
var setBoardModeLocal = boardModePair[1]
|
|
2893
|
+
// 白板开关改动后必须重取白板数据(host 的 handoffPanelData 按 handoffEnabled 决定 enabled),
|
|
2894
|
+
// 否则「刚打开白板」页面上仍是旧的 enabled:false。
|
|
2895
|
+
var reloadPair = useState(0)
|
|
2896
|
+
var reloadKey = reloadPair[0]
|
|
2897
|
+
var setReload = reloadPair[1]
|
|
2268
2898
|
useEffect(function () {
|
|
2269
|
-
apiGet(API.config).then(function (
|
|
2270
|
-
|
|
2899
|
+
apiGet(API.config).then(function (d) {
|
|
2900
|
+
// 解包外壳(见 configOf):旧实现直读 `c.autoContinueEnabled`,d 其实是 `{ config, path }`
|
|
2901
|
+
// ⇒ 两个开关**显示恒为「开」**,且点击只会写 false(唯一可写方向是「关」)。
|
|
2902
|
+
var c = configOf(d)
|
|
2903
|
+
setAutoCfg({ enabled: !(c.autoContinueEnabled === false), threshold: Number(c.autoContinueThreshold) || 0.75 })
|
|
2904
|
+
setHandoffOn(!(c.handoffEnabled === false))
|
|
2905
|
+
setBoardModeLocal(c.boardMode === 'graph' ? 'graph' : 'legacy')
|
|
2271
2906
|
}).catch(function () { setAutoCfg({ enabled: false, threshold: 0.7 }) })
|
|
2272
2907
|
}, [])
|
|
2908
|
+
// 2026-09-14 修复:patch 里是**配置键**(autoContinueEnabled / autoContinueThreshold),而本地状态用的是
|
|
2909
|
+
// enabled / threshold。旧实现直接把 patch 并进本地状态 ⇒ 写的是 autoCfg.autoContinueEnabled,而按钮读的是
|
|
2910
|
+
// autoCfg.enabled ⇒ **按钮永远不回弹**:点了看似没反应,再点还是按同一个旧值计算(用户观感「自动接续无法开关」)。
|
|
2911
|
+
// 阈值输入同病:输入框读 autoCfg.threshold,旧实现写 autoCfg.autoContinueThreshold ⇒ 显示也不更新。
|
|
2912
|
+
// 注意:写入配置本身一直是成功的(宿主端单键合并),坏的只是本地回显 —— 所以"看起来没开关"与"其实已写盘"并存。
|
|
2273
2913
|
var autoSave = function (patch) {
|
|
2274
|
-
setAutoCfg(function (p) {
|
|
2914
|
+
setAutoCfg(function (p) {
|
|
2915
|
+
var next = Object.assign({}, p)
|
|
2916
|
+
if (patch.autoContinueEnabled !== undefined) next.enabled = patch.autoContinueEnabled
|
|
2917
|
+
if (patch.autoContinueThreshold !== undefined) next.threshold = patch.autoContinueThreshold
|
|
2918
|
+
return next
|
|
2919
|
+
})
|
|
2275
2920
|
saveConfigPatch(patch)
|
|
2276
2921
|
}
|
|
2277
2922
|
async function oneClickContinue() {
|
|
@@ -2289,8 +2934,14 @@ window.__ModuleLoader__.load({
|
|
|
2289
2934
|
var alive = true
|
|
2290
2935
|
setData(null)
|
|
2291
2936
|
apiGet(API.handoffState, { sessionId: sid }).then(function (d) { if (alive) setData(d) }).catch(function () {})
|
|
2937
|
+
// 白板看板(2026-09-16 兼并 dsh-graph): 与白板数据同源同批拉取。
|
|
2938
|
+
// legacy 档宿主返回 { enabled:false } ⇒ 保持 kbData=null, 看板块不渲染(逐字节不变)。
|
|
2939
|
+
apiGet(API.kanbanBoard, { sessionId: sid }).then(function (k) {
|
|
2940
|
+
if (!alive) return
|
|
2941
|
+
setKbData(k && k.enabled ? k : null)
|
|
2942
|
+
}).catch(function () { if (alive) setKbData(null) })
|
|
2292
2943
|
return function () { alive = false }
|
|
2293
|
-
}, [sid])
|
|
2944
|
+
}, [sid, reloadKey])
|
|
2294
2945
|
// 切换会话即刷新水位卡:订阅官方会话列表快照,current 变化时重取(2026-09-08)
|
|
2295
2946
|
useEffect(function () {
|
|
2296
2947
|
var unsub = null
|
|
@@ -2323,7 +2974,25 @@ window.__ModuleLoader__.load({
|
|
|
2323
2974
|
return name
|
|
2324
2975
|
}
|
|
2325
2976
|
if (!data) return h(Loading)
|
|
2326
|
-
|
|
2977
|
+
// 开关卡(2026-09-14 解耦):**先渲染开关,再谈白板内容** —— 旧实现在 !data.enabled 处直接 return,
|
|
2978
|
+
// 而白板开关默认关 ⇒ 白板页连开关都看不到(默认关 + 无处可开 = 死锁)。
|
|
2979
|
+
// 两个按钮与设置页共用同一对配置键,写盘路径也是既有机制(saveConfigPatch / autoSave)。
|
|
2980
|
+
var switchCard = h(Card, { title: t('handoffSwitchTitle') }, h('div', { 'data-dam-content': '' }, [
|
|
2981
|
+
h('div', { 'data-dam-hint': '', style: { marginBottom: '6px' } }, t('handoffSwitchHint')),
|
|
2982
|
+
h('div', { 'data-dam-row': '' }, [
|
|
2983
|
+
h('button', { 'data-dam-btn': '', 'data-dam-key': 'handoffEnabled', onClick: function () { var v = !handoffOn; setHandoffOn(v); saveConfigPatch({ handoffEnabled: v }, { onSaved: function () { setReload(function (n) { return n + 1 }) } }) } }, handoffOn ? t('handoffOn') : t('handoffOff')),
|
|
2984
|
+
// WB-GRAPH 白板模式快捷切换(接续面板, 2026-09-16): 与设置页同一配置键 boardMode, 点击翻面+提示需重启。
|
|
2985
|
+
// 用 reloadKey 作重渲染源(切换成功后 force reload), 避免引入新的状态耦合。
|
|
2986
|
+
h('button', { 'data-dam-btn': '', 'data-dam-key': 'boardMode', key: 'bm-' + reloadKey, style: { marginLeft: '8px' }, onClick: function () {
|
|
2987
|
+
var next = boardModeLocal === 'graph' ? 'legacy' : 'graph'
|
|
2988
|
+
setBoardModeLocal(next)
|
|
2989
|
+
saveConfigPatch({ boardMode: next }, { onSaved: function () { setReload(function (n) { return n + 1 }); window.alert(locale === 'zh' ? (next === 'graph' ? '已切到新版看板(dsh-graph)。重启 dsh web 后生效。' : '已切回旧版白板。重启 dsh web 后生效。') : (next === 'graph' ? 'Switched to graph board. Restart dsh web to take effect.' : 'Switched back to legacy board. Restart dsh web to take effect.')) } })
|
|
2990
|
+
} }, locale === 'zh' ? (boardModeLocal === 'graph' ? '看板版·点击回旧版' : '旧版白板·点击切看板') : (boardModeLocal === 'graph' ? 'Graph board · back to legacy' : 'Legacy board · switch to graph')),
|
|
2991
|
+
autoCfg ? h('button', { 'data-dam-btn': '', 'data-dam-key': 'autoContinueEnabled', style: { marginLeft: '8px' }, onClick: function () { autoSave({ autoContinueEnabled: !autoCfg.enabled }) } }, autoCfg.enabled ? t('autoContOn') : t('autoContOff')) : null,
|
|
2992
|
+
autoCfg ? h('label', { 'data-dam-hint': '', style: { marginLeft: '8px' } }, t('autoContThreshold') + ' ', h('input', { 'data-dam-input': '', type: 'number', min: 0.5, max: 0.95, step: 0.05, value: String(autoCfg.threshold), onChange: function (e) { var v = Number(e.target.value); if (v >= 0.5 && v <= 0.95) autoSave({ autoContinueThreshold: v }) } })) : null,
|
|
2993
|
+
]),
|
|
2994
|
+
]))
|
|
2995
|
+
if (!data.enabled) return h('div', null, [switchCard, h(Card, { title: t('planTitle') }, h('div', { 'data-dam-hint': '' }, t('planDisabled')))])
|
|
2327
2996
|
var wl = data.waterLevel || {}
|
|
2328
2997
|
// 2026-09-13(工作区切换修复):宿主对当前会话解析不出工作区身份时(wsBound:false),如实提示
|
|
2329
2998
|
// 而不是渲染另一个会话/启动目录下的白板内容(旧全局单值行为的残余观感)。
|
|
@@ -2333,6 +3002,48 @@ window.__ModuleLoader__.load({
|
|
|
2333
3002
|
} else {
|
|
2334
3003
|
rows.push(h(Card, { title: t('planTitle') }, h('div', { 'data-dam-content': '' }, data.plan || t('planEmpty'))))
|
|
2335
3004
|
}
|
|
3005
|
+
// ── 白板看板(2026-09-16 兼并 dsh-graph): graph 档在白板页顶部渲染列式泳道 ──
|
|
3006
|
+
// 数据来自 /kanban-board(自家 sidecar 投影); legacy 档 kb 为 null ⇒ 整块不渲染,
|
|
3007
|
+
// 页面与旧版逐字节相同。看板置顶: 它才是「一眼看全貌」的那一层, 文字白板退居其次。
|
|
3008
|
+
if (kbData) {
|
|
3009
|
+
rows.unshift(h(Card, { title: (locale === 'zh' ? '白板看板 · 全流程可视化' : 'Whiteboard kanban · full flow') }, h('div', { 'data-dam-content': '' }, h(KanbanBoard, { data: kbData }))))
|
|
3010
|
+
}
|
|
3011
|
+
// ── P2-5 前端(2026-09-16): 新版看板的结构化视图(tag 导航 + 段视图)──────────
|
|
3012
|
+
// 后端 handoffPanelData 已在 graph 档返回 structured(by_tag 倒排 + 按 kind 分组的条目摘要)。
|
|
3013
|
+
// legacy 档 structured 为 null ⇒ 本卡**完全不渲染**, 页面与旧版逐字节相同(开关解耦纪律)。
|
|
3014
|
+
//
|
|
3015
|
+
// v2(2026-09-16 晚): **看板可视化在场时隐藏这两张文字卡** —— 用户实测反馈「就是没有图」:
|
|
3016
|
+
// 它们与真看板内容重复(tag 导航的 0 个标签、段视图的条目流水), 同屏出现会让人以为
|
|
3017
|
+
// 这就是「新版看板」的全部。真看板(kbData)在场时, 文字卡退位; 其缺失时仍作兜底显示,
|
|
3018
|
+
// 保证「结构化数据始终可达」(不删功能, 只是不再与图争位)。
|
|
3019
|
+
if (data.structured && !kbData) {
|
|
3020
|
+
var st = data.structured
|
|
3021
|
+
var tagKids = []
|
|
3022
|
+
tagKids.push(h('div', { 'data-dam-hint': '', style: { marginBottom: '6px' } },
|
|
3023
|
+
(locale === 'zh' ? '结构化索引: ' : 'Structured index: ') + st.total + (locale === 'zh' ? ' 个条目 · ' : ' entries · ') + (st.tags || []).length + (locale === 'zh' ? ' 个标签' : ' tags') + (st.rebuiltAt ? ' · ' + String(st.rebuiltAt).slice(0, 19).replace('T', ' ') : '')))
|
|
3024
|
+
if ((st.tags || []).length) {
|
|
3025
|
+
tagKids.push(h('div', { 'data-dam-row': '', style: { flexWrap: 'wrap' } }, st.tags.map(function (tg) {
|
|
3026
|
+
return h('button', { 'data-dam-btn': '', key: 'tg-' + tg.tag, title: tg.tag, style: { marginRight: '6px', marginBottom: '6px' } },
|
|
3027
|
+
tg.tag + ' ×' + tg.count)
|
|
3028
|
+
})))
|
|
3029
|
+
tagKids.push(h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } },
|
|
3030
|
+
locale === 'zh' ? '标签来自账本/白板行内标记(tag:xxx / type:xxx / topic:xxx)。模型侧可用 memory_expand_pre(tag) 按标签展开条目、memory_trace_pre(id) 回溯来源与版本链。' : 'Tags come from inline markers (tag:xxx / type:xxx / topic:xxx). The model can use memory_expand_pre(tag) to expand by tag and memory_trace_pre(id) to trace origin and version chain.'))
|
|
3031
|
+
} else {
|
|
3032
|
+
tagKids.push(h('div', { 'data-dam-muted': '' }, locale === 'zh' ? '尚无标签——在账本/白板里写 tag:xxx 后重新写入即出现。' : 'No tags yet — write tag:xxx in a ledger/plan and re-save.'))
|
|
3033
|
+
}
|
|
3034
|
+
rows.push(h(Card, { title: locale === 'zh' ? '新版看板 · 标签导航' : 'Graph board · tag navigation' }, h('div', { 'data-dam-content': '' }, tagKids)))
|
|
3035
|
+
var secKids = []
|
|
3036
|
+
;(st.sections || []).forEach(function (sc) {
|
|
3037
|
+
if (!sc.count) return
|
|
3038
|
+
secKids.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', fontWeight: 600 } }, sc.kind + ' · ' + sc.count))
|
|
3039
|
+
;(sc.items || []).forEach(function (it) {
|
|
3040
|
+
secKids.push(h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', marginLeft: '8px' } },
|
|
3041
|
+
h('span', { 'data-dam-hint': '' }, String(it.id || '').slice(4, 12) + ' '),
|
|
3042
|
+
h('span', { title: it.source || '' }, (it.section ? '§' + it.section + ' ' : '') + String(it.preview || '').slice(0, 90))))
|
|
3043
|
+
})
|
|
3044
|
+
})
|
|
3045
|
+
if (secKids.length) rows.push(h(Card, { title: locale === 'zh' ? '新版看板 · 段视图' : 'Graph board · sections' }, h('div', { 'data-dam-content': '' }, secKids)))
|
|
3046
|
+
}
|
|
2336
3047
|
if (wl.window > 0) {
|
|
2337
3048
|
// 2.2.4:百分比显示真实水位(旧版截断到 150% 会把「窗口算错」伪装成「刚好超一点」);进度条宽度仍按 100% 截断。
|
|
2338
3049
|
var rawPct = Math.round((wl.ratio || 0) * 100)
|
|
@@ -2352,13 +3063,8 @@ window.__ModuleLoader__.load({
|
|
|
2352
3063
|
])))
|
|
2353
3064
|
}
|
|
2354
3065
|
if (autoCfg && wl.window > 0) {
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
h('div', { 'data-dam-row': '' },
|
|
2358
|
-
h('button', { 'data-dam-btn': '', onClick: function () { autoSave({ autoContinueEnabled: !autoCfg.enabled }) } }, autoCfg.enabled ? t('autoContOn') : t('autoContOff')),
|
|
2359
|
-
h('label', { 'data-dam-hint': '', style: { marginLeft: '8px' } }, t('autoContThreshold') + ' ',
|
|
2360
|
-
h('input', { 'data-dam-input': '', type: 'number', min: 0.5, max: 0.95, step: 0.05, value: String(autoCfg.threshold), style: { width: '64px' }, onChange: function (e) { var v = Number(e.target.value); if (v >= 0.5 && v <= 0.95) autoSave({ autoContinueThreshold: v }) } }))),
|
|
2361
|
-
])))
|
|
3066
|
+
// 2026-09-14:开关与阈值统一收进顶部常显的开关卡(同一配置键只应有一个入口),此处只留说明。
|
|
3067
|
+
rows.unshift(h(Card, { title: t('autoContTitle') }, h('div', { 'data-dam-content': '' }, h('div', { 'data-dam-hint': '' }, t('autoContHint')))))
|
|
2362
3068
|
}
|
|
2363
3069
|
if (open) {
|
|
2364
3070
|
rows.unshift(h(Card, { title: t('planFileTitle') + ' · ' + open }, h('div', { 'data-dam-content': '' }, content || t('empty'))))
|
|
@@ -2379,7 +3085,9 @@ window.__ModuleLoader__.load({
|
|
|
2379
3085
|
contMsg ? h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } }, contMsg) : null,
|
|
2380
3086
|
])))
|
|
2381
3087
|
}
|
|
2382
|
-
|
|
3088
|
+
// 2026-09-14 修复:开关卡此前只在「未启用」分支被返回 ⇒ 白板一旦开启,白板页就再也看不到开关(开得了、关不掉)。
|
|
3089
|
+
// 开关卡必须在**两种**状态下都渲染 —— 它是这个页面上唯一的开关入口。
|
|
3090
|
+
return h('div', null, [switchCard].concat(rows))
|
|
2383
3091
|
}
|
|
2384
3092
|
|
|
2385
3093
|
function ReflectionsTab() {
|
|
@@ -2549,13 +3257,13 @@ window.__ModuleLoader__.load({
|
|
|
2549
3257
|
h('g', null, children.map(function (n, i) {
|
|
2550
3258
|
return h('g', { key: 'branch-' + i },
|
|
2551
3259
|
h('line', { x1: n.center.x, y1: n.center.y + 38, x2: n.x, y2: n.y - 22, stroke: '#8b949e', strokeOpacity: .48, strokeWidth: 1.2 }),
|
|
2552
|
-
h('rect', { x: n.x - (compact ? 82 : 92), y: n.y - 22, width: compact ? 164 : 184, height: 44, rx: 10, fill: 'color-mix(in srgb, var(--dsw-alias-bg-
|
|
3260
|
+
h('rect', { x: n.x - (compact ? 82 : 92), y: n.y - 22, width: compact ? 164 : 184, height: 44, rx: 10, fill: 'color-mix(in srgb, var(--dsw-alias-bg-layer-2, #fff) 92%, transparent)', stroke: 'color-mix(in srgb, var(--dam-accent, #1d4ed8) 42%, #8b949e)', strokeOpacity: .8 }),
|
|
2553
3261
|
h('text', { x: n.x, y: n.y - 5, textAnchor: 'middle', fontSize: compact ? 9.5 : 10.5, fontWeight: 650, fill: 'currentColor' }, String(n.item && n.item.label || n.item || '').slice(0, 22)),
|
|
2554
3262
|
h('text', { x: n.x, y: n.y + 10, textAnchor: 'middle', fontSize: 8, fill: '#7d8793' }, String(n.item && n.item.detail || '').slice(0, 26)))
|
|
2555
3263
|
})),
|
|
2556
3264
|
h('g', null, centers.map(function (c) {
|
|
2557
3265
|
return h('g', { key: c.ws.path, 'data-dam-graph-node': '', onPointerDown: function (ev) { ev.stopPropagation() }, onClick: function () { onSelect(c.ws) } },
|
|
2558
|
-
h('rect', { x: c.x - 100, y: c.y - 38, width: 200, height: 76, rx: 16, fill: 'color-mix(in srgb, var(--dam-accent, #1d4ed8) 22%, var(--dsw-alias-bg-
|
|
3266
|
+
h('rect', { x: c.x - 100, y: c.y - 38, width: 200, height: 76, rx: 16, fill: 'color-mix(in srgb, var(--dam-accent, #1d4ed8) 22%, var(--dsw-alias-bg-layer-2, #fff))', stroke: 'var(--dam-accent, #1d4ed8)', strokeWidth: 2 }),
|
|
2559
3267
|
h('text', { x: c.x, y: c.y - 5, textAnchor: 'middle', fontSize: compact ? 10.5 : 12, fontWeight: 650, fill: 'currentColor' }, h('tspan', { x: c.x, dy: 0 }, String(c.ws.name || '').slice(0, 12)), h('tspan', { x: c.x, dy: 13 }, String(c.ws.name || '').slice(12, 24))),
|
|
2560
3268
|
h('text', { x: c.x, y: c.y + 15, textAnchor: 'middle', fontSize: 8, fill: '#7d8793' }, (c.ws.logCount || 0) + ' ' + t('logEntries')))
|
|
2561
3269
|
}))))
|
|
@@ -2748,7 +3456,7 @@ window.__ModuleLoader__.load({
|
|
|
2748
3456
|
h('div', { style: { minHeight: '34px', flex: 1, borderLeft: '2px solid color-mix(in srgb, var(--dam-accent, #1d4ed8) 24%, transparent)', paddingLeft: '9px' } }, hourEvents.length ? hourEvents.map(function (en) { var qs = QUADRANT_STYLE[en.quadrant] || QUADRANT_STYLE['未分类']; return h('div', { key: en.time + en.title, 'data-dam-calendar-event': '', onClick: function (ev) { ev.stopPropagation(); toggleDone(en) }, style: { marginBottom: '4px', padding: '4px 7px', borderRadius: 6, background: 'color-mix(in srgb, ' + qs.color + ' 18%, transparent)', color: qs.color, textDecoration: en.done ? 'line-through' : 'none' } }, (en.time || '') + ' ' + en.title + (en.note ? ' · ' + en.note : '')) }) : h('span', { 'data-dam-hint': '' }, locale === 'zh' ? '点击添加事件' : 'Click to add event'))))
|
|
2749
3457
|
})(hourSlot)
|
|
2750
3458
|
}
|
|
2751
|
-
rows.push(h('div', { 'data-dam-calendar-modal': '', style: { position: 'absolute', inset: 0, zIndex: 10, overflow: 'auto', borderRadius: '16px', background: 'color-mix(in srgb, var(--dsw-alias-bg-
|
|
3459
|
+
rows.push(h('div', { 'data-dam-calendar-modal': '', style: { position: 'absolute', inset: 0, zIndex: 10, overflow: 'auto', borderRadius: '16px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-2, rgba(255,255,255,.9)) 78%, transparent)', backdropFilter: 'blur(20px) saturate(1.4)', WebkitBackdropFilter: 'blur(20px) saturate(1.4)', padding: '16px' } }, h('div', { 'data-dam-row': '' }, h('div', { style: { fontWeight: 700, flex: 1 } }, dayView + ' · ' + (locale === 'zh' ? '当天安排' : 'Day schedule')), h('button', { 'data-dam-btn': '', onClick: function () { setDayView(null) } }, '✕')), timeline))
|
|
2752
3460
|
}
|
|
2753
3461
|
// 添加/编辑浮层(液态玻璃)
|
|
2754
3462
|
if (draft) {
|
|
@@ -2756,7 +3464,7 @@ window.__ModuleLoader__.load({
|
|
|
2756
3464
|
'data-dam-calendar-modal': '',
|
|
2757
3465
|
style: {
|
|
2758
3466
|
position: 'absolute', inset: 0, zIndex: 10, borderRadius: '16px',
|
|
2759
|
-
background: 'color-mix(in srgb, var(--dsw-alias-bg-
|
|
3467
|
+
background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-2, rgba(255,255,255,.9)) 70%, transparent)',
|
|
2760
3468
|
backdropFilter: 'blur(20px) saturate(1.4)', WebkitBackdropFilter: 'blur(20px) saturate(1.4)',
|
|
2761
3469
|
display: 'flex', flexDirection: 'column', padding: '18px', gap: '10px',
|
|
2762
3470
|
border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 60%, transparent)',
|
|
@@ -3007,7 +3715,7 @@ window.__ModuleLoader__.load({
|
|
|
3007
3715
|
return function () { alive = false }
|
|
3008
3716
|
}, [])
|
|
3009
3717
|
// 可读性兜底(@ProperSAMA PR#12,适配版):DSH Desktop 增强模式 + 透明/Mica 窗口材质下,
|
|
3010
|
-
// 主题令牌 --dsw-alias-bg-
|
|
3718
|
+
// 主题令牌 --dsw-alias-bg-layer-2 本身就是半透明的,面板文字几乎不可读。判别信号用
|
|
3011
3719
|
// 「令牌自身的 alpha」(增强模式显著低于普通模式的 0.86-0.9):< 0.65 时把面板背景提升到
|
|
3012
3720
|
// 0.96(保留色相)并弱化顶部高光。普通模式令牌不透明 → 不触发,液态玻璃观感零变化。
|
|
3013
3721
|
useEffect(function () {
|
|
@@ -3016,7 +3724,7 @@ window.__ModuleLoader__.load({
|
|
|
3016
3724
|
if (!el) return
|
|
3017
3725
|
try {
|
|
3018
3726
|
var cs = getComputedStyle(el)
|
|
3019
|
-
var token = cs.getPropertyValue('--dsw-alias-bg-
|
|
3727
|
+
var token = cs.getPropertyValue('--dsw-alias-bg-layer-2')
|
|
3020
3728
|
var col = parseCssColor((token || '').trim() || cs.backgroundColor)
|
|
3021
3729
|
if (col && col.a < 0.65) {
|
|
3022
3730
|
el.style.background = 'rgba(' + col.r + ', ' + col.g + ', ' + col.b + ', 0.96)'
|
|
@@ -3243,20 +3951,26 @@ window.__ModuleLoader__.load({
|
|
|
3243
3951
|
if (ph === 'downloading' || ph === 'verifying' || ph === 'done' || ph === 'error') return undefined
|
|
3244
3952
|
apiPost(API.semanticDownload, { action: 'start', mirror: 'auto' }).catch(function () {})
|
|
3245
3953
|
}, [tourShowing, tourStep, wizSt.ready])
|
|
3954
|
+
// issue#40:向导展示期间装焦点陷阱(移动端浮层吞点击/Esc 关不掉的关键一环)。
|
|
3955
|
+
useEffect(function () {
|
|
3956
|
+
if (!dialogState || dialogState.kind !== 'welcomeTour') return undefined
|
|
3957
|
+
var root = document.querySelector('[data-dam-tour]')
|
|
3958
|
+
return installTourFocusPre(root, function () { dismissWelcomeTourPre('user') }, document)
|
|
3959
|
+
}, [dialogState ? dialogState.kind : null])
|
|
3246
3960
|
if (!dialogState) return null
|
|
3247
3961
|
// 左下角小卡片(记忆按钮上方),高透明毛玻璃,不遮全屏
|
|
3248
3962
|
var overlay = { position: 'fixed', left: '10px', bottom: '64px', zIndex: 2147483000, width: 'min(360px, calc(100vw - 20px))', maxHeight: 'min(46vh, 430px)', display: 'flex', flexDirection: 'column' }
|
|
3249
3963
|
var box = {
|
|
3250
3964
|
overflow: 'auto', borderRadius: '14px', padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: '6px',
|
|
3251
|
-
background: 'color-mix(in srgb, var(--dsw-alias-bg-
|
|
3965
|
+
background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-2, rgba(24,26,32,.9)) 58%, transparent)',
|
|
3252
3966
|
backdropFilter: 'blur(14px) saturate(1.3)', WebkitBackdropFilter: 'blur(14px) saturate(1.3)',
|
|
3253
3967
|
border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 45%, transparent)',
|
|
3254
3968
|
boxShadow: '0 8px 28px rgba(0,0,0,.18)',
|
|
3255
3969
|
}
|
|
3256
|
-
var head = { fontSize: 'calc(13px * var(--dam-scale))', fontWeight: 700, color: 'var(--dsw-alias-
|
|
3970
|
+
var head = { fontSize: 'calc(13px * var(--dam-scale))', fontWeight: 700, color: 'var(--dsw-alias-label-primary, inherit)' }
|
|
3257
3971
|
var sub = { fontSize: 'calc(11px * var(--dam-scale))', opacity: .7 }
|
|
3258
3972
|
var item = { fontSize: 'calc(11px * var(--dam-scale))', lineHeight: 1.55, padding: '1px 0 1px 16px', position: 'relative' }
|
|
3259
|
-
var close = { alignSelf: 'flex-end', padding: '4px 16px', borderRadius: '8px', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 45%, transparent)', background: 'color-mix(in srgb, var(--dsw-alias-brand-primary, #4f7cff) 12%, transparent)', color: 'var(--dsw-alias-
|
|
3973
|
+
var close = { alignSelf: 'flex-end', padding: '4px 16px', borderRadius: '8px', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 45%, transparent)', background: 'color-mix(in srgb, var(--dsw-alias-brand-primary, #4f7cff) 12%, transparent)', color: 'var(--dsw-alias-label-primary, inherit)', cursor: 'pointer', fontSize: 'calc(11.5px * var(--dam-scale))' }
|
|
3260
3974
|
var dot = { position: 'absolute', left: '0', top: '10px', width: '7px', height: '7px', borderRadius: '50%', background: 'var(--dsw-alias-brand-primary, #4f7cff)' }
|
|
3261
3975
|
// 通用开场舞台必须在 first/notice/update 三个分支之前完成赋值;var 只提升声明、不提升赋值。
|
|
3262
3976
|
var skipUpdateIntro = function () { setUpdateSkip(true) }
|
|
@@ -3425,11 +4139,9 @@ window.__ModuleLoader__.load({
|
|
|
3425
4139
|
})
|
|
3426
4140
|
} catch (eChain) {}
|
|
3427
4141
|
}
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
else finishTour()
|
|
3432
|
-
}
|
|
4142
|
+
// issue#40:"关闭"就是关闭 —— 旧实现把 ✕ 变成"先跳到最后一步再点",手机端体感即"关不掉"。
|
|
4143
|
+
// 现在 ✕ 直接撤下向导并记录**用户动作**(tourDismissed),此后不再自动播放。
|
|
4144
|
+
var closeOrRemind = function () { dismissWelcomeTourPre('user') }
|
|
3433
4145
|
var step = TOUR_STEPS[Math.min(tourStep, TOUR_STEPS.length - 1)]
|
|
3434
4146
|
var isLast = tourStep >= TOUR_STEPS.length - 1
|
|
3435
4147
|
// 每步只生成当前图形所需 DOM;避免旧实现一次创建 22 个 span、非当前零件塌成 0/2px。
|
|
@@ -3456,7 +4168,7 @@ window.__ModuleLoader__.load({
|
|
|
3456
4168
|
el.style.setProperty('--dam-mx', (((e.clientX - r.left) / r.width) * 100).toFixed(1) + '%')
|
|
3457
4169
|
el.style.setProperty('--dam-my', (((e.clientY - r.top) / r.height) * 100).toFixed(1) + '%')
|
|
3458
4170
|
} },
|
|
3459
|
-
h('div', { 'data-dam-tour': '' },
|
|
4171
|
+
h('div', { 'data-dam-tour': '', role: 'dialog', 'aria-modal': true, tabIndex: -1, 'aria-label': locale === 'zh' ? '欢迎向导' : 'Welcome tour' },
|
|
3460
4172
|
h('div', { 'data-dam-tour-glare': '' }),
|
|
3461
4173
|
h('button', { 'data-dam-tour-close': '', title: t('close'), onClick: closeOrRemind }, '✕'),
|
|
3462
4174
|
h('div', { 'data-dam-tour-orb-wrap': '', key: 'orb' + tourStep, 'data-step': String(tourStep), 'data-art': step.art || 'bubble' },
|
|
@@ -3540,7 +4252,7 @@ window.__ModuleLoader__.load({
|
|
|
3540
4252
|
var nTitle = zh ? (n.title || '') : (n.titleEn || n.title || '')
|
|
3541
4253
|
var nMsg = zh ? (n.message || '') : (n.messageEn || n.message || '')
|
|
3542
4254
|
var isUrgent = n.level === 'urgent'
|
|
3543
|
-
var accent = isUrgent ? 'var(--dsw-alias-
|
|
4255
|
+
var accent = isUrgent ? 'var(--dsw-alias-state-error-primary, #e5534b)' : 'var(--dsw-alias-brand-primary, #4f7cff)'
|
|
3544
4256
|
var noticeButton = h('button', { 'data-dam-btn': '', style: close, onClick: function () {
|
|
3545
4257
|
try {
|
|
3546
4258
|
var arr = []
|
|
@@ -3687,7 +4399,7 @@ window.__ModuleLoader__.load({
|
|
|
3687
4399
|
// 带上当前会话 id:宿主据此只在「属于本窗口的」armed 上弹确认卡(2026-09-10)
|
|
3688
4400
|
var sidQ = ''
|
|
3689
4401
|
try { sidQ = String(currentSessionIdClient() || '') } catch (eS) {}
|
|
3690
|
-
apiGet(API.config).then(function (
|
|
4402
|
+
apiGet(API.config).then(function (d) { if (alive) setAcCfg(configOf(d)) }).catch(function () {})
|
|
3691
4403
|
apiGet(API.autoContState, sidQ ? { sessionId: sidQ } : {}).then(function (s) { if (alive && s) setAcState(s) }).catch(function () {})
|
|
3692
4404
|
}
|
|
3693
4405
|
load()
|
|
@@ -3880,12 +4592,12 @@ window.__ModuleLoader__.load({
|
|
|
3880
4592
|
if (mdlLoading) kids.push(h('div', { 'data-dam-hint': '' }, locale === 'zh' ? '正在检测可用模型…' : 'Detecting models…'))
|
|
3881
4593
|
if (mdlErr) kids.push(h('div', { 'data-dam-error': '' }, mdlErr))
|
|
3882
4594
|
if (!mdlLoading && !mdlErr && mdlData) {
|
|
3883
|
-
kids.push(h('button', { key: '__default__', 'data-dam-btn': '', style: { display: 'block', width: '100%', textAlign: 'left', padding: '4px 6px', opacity: cfg.subagentModel ? 1 : 0.75 }, onClick: function () {
|
|
4595
|
+
kids.push(h('button', { key: '__default__', 'data-dam-btn': '', style: { display: 'block', width: '100%', textAlign: 'left', padding: '4px 6px', opacity: cfg.subagentModel ? 1 : 0.75 }, onClick: function () { setMany({ subagentModel: '', subagentProvider: '' }); setMdlOpen(false) } },
|
|
3884
4596
|
locale === 'zh' ? '跟随路由默认(留空)' : 'Follow routing default (empty)'))
|
|
3885
4597
|
;(mdlData.providers || []).forEach(function (p) {
|
|
3886
4598
|
var modelBtns = (p.models || []).length
|
|
3887
4599
|
? p.models.map(function (m) {
|
|
3888
|
-
return h('button', { key: p.id + '/' + m.id, 'data-dam-btn': '', style: { display: 'block', width: '100%', textAlign: 'left', padding: '3px 6px', fontWeight: cfg.subagentModel === m.id ? 700 : 400 }, onClick: function () {
|
|
4600
|
+
return h('button', { key: p.id + '/' + m.id, 'data-dam-btn': '', style: { display: 'block', width: '100%', textAlign: 'left', padding: '3px 6px', fontWeight: cfg.subagentModel === m.id ? 700 : 400 }, onClick: function () { setMany({ subagentModel: m.id, subagentProvider: p.id }); setMdlOpen(false) } },
|
|
3889
4601
|
m.id + (m.name && m.name !== m.id ? ' · ' + m.name : '') + (cfg.subagentModel === m.id ? ' ✓' : ''))
|
|
3890
4602
|
})
|
|
3891
4603
|
: [h('div', { key: 'none', 'data-dam-hint': '' }, locale === 'zh' ? '(该 provider 未列出模型)' : '(no models advertised)')]
|
|
@@ -3899,7 +4611,7 @@ window.__ModuleLoader__.load({
|
|
|
3899
4611
|
if (!(mdlData.providers || []).length && !(mdlData.failures || []).length) {
|
|
3900
4612
|
kids.push(h('div', { 'data-dam-hint': '' }, locale === 'zh' ? '未检测到 provider;可直接在下方手动输入。' : 'No providers detected; use manual input below.'))
|
|
3901
4613
|
}
|
|
3902
|
-
kids.push(h('input', { key: '__manual__', 'data-dam-input': '', style: { marginTop: '6px', width: '100%' }, value: cfg.subagentModel || '', placeholder: locale === 'zh' ? '手动输入(可选,provider 跟随路由默认)' : 'manual entry (optional, provider follows routing default)', onChange: function (e) {
|
|
4614
|
+
kids.push(h('input', { key: '__manual__', 'data-dam-input': '', style: { marginTop: '6px', width: '100%' }, value: cfg.subagentModel || '', placeholder: locale === 'zh' ? '手动输入(可选,provider 跟随路由默认)' : 'manual entry (optional, provider follows routing default)', onChange: function (e) { setMany({ subagentModel: String(e.target.value || '').trim(), subagentProvider: '' }) } }))
|
|
3903
4615
|
}
|
|
3904
4616
|
// 思考强度(DSH 0.1.5 agentOptions.reasoningEffort):与模型成对下发给子代理;留空=跟随模型默认。
|
|
3905
4617
|
// 合法值按 DeepSeek 适配器口径 off|low|high|max(默认 high)——服务端同样做白名单过滤。
|
|
@@ -3985,7 +4697,15 @@ window.__ModuleLoader__.load({
|
|
|
3985
4697
|
return function () { clearInterval(iv) }
|
|
3986
4698
|
}, [sem && sem.download && sem.download.phase])
|
|
3987
4699
|
if (!cfg) return err ? h('div', { 'data-dam-error': '' }, err) : h(Loading)
|
|
3988
|
-
|
|
4700
|
+
// issue #52:旧实现 `var next = Object.assign({}, cfg); next[key] = value` 读的是**本次渲染的闭包快照**。
|
|
4701
|
+
// 同一个事件里连续调 `set('subagentModel', m); set('subagentProvider', p)` 时,React 批处理两次
|
|
4702
|
+
// 更新,而第二次仍基于同一个旧 cfg ⇒ 把第一次写进去的值**覆盖回旧值**。
|
|
4703
|
+
// 典型症状:subagentModel 存过一个后来被删除的模型后,无论换模型/清空/手输都无法覆盖,
|
|
4704
|
+
// 插件持续按已删除模型派子代理(issue #52)。
|
|
4705
|
+
// 修法:改用函数式更新,基于**最新** prev 派生 next,成对字段不再互相覆盖。
|
|
4706
|
+
function set(key, value) { setCfg(function (prev) { var next = Object.assign({}, prev); next[key] = value; return next }); setDirty(true) }
|
|
4707
|
+
/** 原子更新多个字段(issue #52 建议口径):一次函数式更新写入成对字段,避免先后覆盖。 */
|
|
4708
|
+
function setMany(patch) { setCfg(function (prev) { return Object.assign({}, prev, patch) }); setDirty(true) }
|
|
3989
4709
|
function checkUpdate() {
|
|
3990
4710
|
if (checkingUpdate) return
|
|
3991
4711
|
setCheckingUpdate(true)
|
|
@@ -4004,10 +4724,20 @@ window.__ModuleLoader__.load({
|
|
|
4004
4724
|
function save() {
|
|
4005
4725
|
if (busy) return
|
|
4006
4726
|
setBusy(true); setMsg(''); setErr('')
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4727
|
+
// 2026-09-14 修「跨入口互相覆盖」:旧实现把整份 cfg 快照 POST,而宿主端语义是 { ...this.config, ...patch } 合并
|
|
4728
|
+
// ⇒ 只要本页加载之后又从别的入口(白板页开关 / 向导 / 接续页)改过任何键,这里一保存就会把它们**回滚**成
|
|
4729
|
+
// 加载时的旧值(用户观感:「打开白板,原有设置被覆盖」)。改为先取回宿主当前配置,只提交与远端不同的键。
|
|
4730
|
+
apiGet(API.config).then(function (d0) {
|
|
4731
|
+
var remote = (d0 && d0.config) || {}
|
|
4732
|
+
var patch = {}
|
|
4733
|
+
Object.keys(cfg).forEach(function (k) {
|
|
4734
|
+
if (JSON.stringify(cfg[k]) !== JSON.stringify(remote[k])) patch[k] = cfg[k]
|
|
4735
|
+
})
|
|
4736
|
+
saveConfigPatch(patch, {
|
|
4737
|
+
onSaved: function (d) { setCfg(d.config); setDirty(false); setMsg(t('saved') + (d.migrated ? ' ' + d.migrated : '')); setBusy(false); if (d && d.config && d.config.locale) applyLocalePref(d.config.locale); refreshSem(setSem) },
|
|
4738
|
+
onError: function (e) { setErr(e.message); setBusy(false) }
|
|
4739
|
+
})
|
|
4740
|
+
}).catch(function (e) { setErr(e.message); setBusy(false) })
|
|
4011
4741
|
}
|
|
4012
4742
|
function field(label, control, hint) {
|
|
4013
4743
|
return h('div', { 'data-dam-settings-row': '' },
|
|
@@ -4291,6 +5021,18 @@ window.__ModuleLoader__.load({
|
|
|
4291
5021
|
: null]),
|
|
4292
5022
|
section('injection', sectionLabels.injection, [field(t('fInject'), h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), t('fInjectHint')),
|
|
4293
5023
|
field(t('fBudget'), h('input', { 'data-dam-input': '', type: 'number', min: 400, value: cfg.injectBudgetChars, onChange: function (e) { set('injectBudgetChars', Number(e.target.value) || 2400) } }), t('fBudgetHint')),
|
|
5024
|
+
// 群反馈第 4 条:排除来源(每行一条)。textarea 便于粘贴多行路径;
|
|
5025
|
+
// 空行/纯空白在写回时被过滤,避免"看起来配了其实没配"。
|
|
5026
|
+
field(t('fExclude'), h('textarea', {
|
|
5027
|
+
'data-dam-input': '', rows: 3,
|
|
5028
|
+
style: { width: '100%', resize: 'vertical', fontFamily: 'inherit' },
|
|
5029
|
+
value: (Array.isArray(cfg.injectExcludeSources) ? cfg.injectExcludeSources : []).join('\n'),
|
|
5030
|
+
placeholder: 'mem_0123456789abcdef0123456789abcdef\nD:\\ws\\.dsh-memory\\archive\\\nlog',
|
|
5031
|
+
onChange: function (e) {
|
|
5032
|
+
var lines = String(e.target.value || '').split('\n').map(function (x) { return x.trim() }).filter(Boolean)
|
|
5033
|
+
set('injectExcludeSources', lines)
|
|
5034
|
+
},
|
|
5035
|
+
}), t('fExcludeHint')),
|
|
4294
5036
|
field(t('fNoteCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.noteCapacityChars === undefined ? 12000 : cfg.noteCapacityChars, onChange: function (e) { set('noteCapacityChars', Number(e.target.value) || 12000) } }), t('fNoteCapHint')),
|
|
4295
5037
|
field(t('fUserCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.userCapacityChars === undefined ? 12000 : cfg.userCapacityChars, onChange: function (e) { set('userCapacityChars', Number(e.target.value) || 12000) } }), t('fUserCapHint')),
|
|
4296
5038
|
field(t('fDays'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 14, value: cfg.recentDaysInjected, onChange: function (e) { set('recentDaysInjected', Number(e.target.value) || 1) } }), t('fDaysHint')),
|
|
@@ -4332,13 +5074,37 @@ window.__ModuleLoader__.load({
|
|
|
4332
5074
|
locale === 'zh' ? '用于时段总结、问候语、自动沉淀、蒸馏等 subagent 功能;模型与思考强度(off/low/high/max)都经 DSH 的 agentOptions 下发,留空跟随路由与模型默认。保存后生效。' : 'For scheduled summaries, greetings, auto-consolidation and distillation subagents; model and reasoning effort (off/low/high/max) are delivered via DSH agentOptions. Empty follows routing and model defaults. Applies after saving.'),
|
|
4333
5075
|
mdlOpen ? buildModelDrawer() : null,
|
|
4334
5076
|
]),
|
|
4335
|
-
|
|
5077
|
+
// 2026-09-14 解耦:交接白板与自动接续各自独立成项(旧实现只在白板页有接续按钮,且 enabled 还被
|
|
5078
|
+
// handoffEnabled 二次与运算)。两者出厂默认均为 false(功能仍在测试期),这里与白板页读写完全相同的配置键。
|
|
5079
|
+
section('context', sectionLabels.context, [field(t('fHandoff'), h('input', { type: 'checkbox', 'data-dam-key': 'handoffEnabled', checked: cfg.handoffEnabled !== false, onChange: function (e) { set('handoffEnabled', e.target.checked) } }), t('fHandoffHint')),
|
|
5080
|
+
field(t('fAutoContinue'), h('input', { type: 'checkbox', 'data-dam-key': 'autoContinueEnabled', checked: cfg.autoContinueEnabled !== false, onChange: function (e) { set('autoContinueEnabled', e.target.checked) } }), t('fAutoContinueHint')),
|
|
4336
5081
|
field(t('fHandoffPlan'), h('input', { 'data-dam-input': '', type: 'number', min: 200, value: cfg.handoffPlanChars === undefined ? 1200 : cfg.handoffPlanChars, onChange: function (e) { set('handoffPlanChars', Number(e.target.value) || 1200) } }), t('fHandoffPlanHint')),
|
|
4337
5082
|
field(t('fHandoffLedger'), h('input', { 'data-dam-input': '', type: 'number', min: 100, value: cfg.handoffLedgerChars === undefined ? 800 : cfg.handoffLedgerChars, onChange: function (e) { set('handoffLedgerChars', Number(e.target.value) || 800) } }), t('fHandoffLedgerHint')),
|
|
4338
5083
|
field(t('fWaterWindow'), h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.waterLevelWindowTokens === undefined ? 65536 : cfg.waterLevelWindowTokens, onChange: function (e) { set('waterLevelWindowTokens', Number(e.target.value) || 0) } }), t('fWaterWindowHint')),
|
|
4339
5084
|
field(t('fWaterThreshold'), h('input', { 'data-dam-input': '', type: 'number', min: 0.1, max: 1.5, step: 0.05, value: cfg.waterLevelThreshold === undefined ? 0.75 : cfg.waterLevelThreshold, onChange: function (e) { set('waterLevelThreshold', Number(e.target.value) || 0.75) } }), t('fWaterThresholdHint')),
|
|
4340
5085
|
field(t('fWaterAdvisory'), h('input', { type: 'checkbox', checked: cfg.waterLevelAdvisory !== false, onChange: function (e) { set('waterLevelAdvisory', e.target.checked) } }), t('fWaterAdvisoryHint')),
|
|
4341
5086
|
field(t('fWaterAuto'), h('input', { type: 'checkbox', checked: cfg.waterLevelAutoHandoff !== false, onChange: function (e) { set('waterLevelAutoHandoff', e.target.checked) } }), t('fWaterAutoHint')),
|
|
5087
|
+
// WB-GRAPH 白板线一键切换(2026-09-16, board_mode_v1): 旧版白板(默认) / 新版看板(dsh-graph vendor
|
|
5088
|
+
// + sidecar + 遍历工具, 工具数 14→16)。开关解耦: 只管白板线; 切换需重启 dsh web 生效(工具注册在启动期)。
|
|
5089
|
+
field(locale === 'zh' ? '白板模式(旧版白板 / 新版看板)' : 'Board mode (legacy / graph)', (function () {
|
|
5090
|
+
var cur = cfg.boardMode === 'graph' ? 'graph' : 'legacy'
|
|
5091
|
+
// ★2026-09-16 修 BUG-3(设置页切换不落盘): 旧实现 `set('boardMode', m)` 只改**本地 React 态**
|
|
5092
|
+
// 然后 reload —— 配置从未写到服务端, 刷新后仍是旧值(界面回跳原档), 用户会判定"开关坏了"。
|
|
5093
|
+
// 正解: 走与接续面板同一个写入路径 `saveConfigPatch(API.config, patch)`(见 :999 定义),
|
|
5094
|
+
// 写盘成功后再 reload; 失败则在界面提示, 不静默假装成功。
|
|
5095
|
+
// 同时满足用户硬性偏好: 开关类改动必须**即时回显**(写盘后立即 reload 呈现新档)。
|
|
5096
|
+
function pick(m) {
|
|
5097
|
+
set('boardMode', m) // 先本地回显(即时反馈)
|
|
5098
|
+
saveConfigPatch({ boardMode: m }, {
|
|
5099
|
+
onSaved: function () { window.setTimeout(function () { window.location.reload() }, 350) },
|
|
5100
|
+
onError: function (e) { setErr(String((e && e.message) || e || 'save failed')) },
|
|
5101
|
+
})
|
|
5102
|
+
}
|
|
5103
|
+
return h('div', { 'data-dam-row': '' },
|
|
5104
|
+
h('button', { 'data-dam-btn': '', 'data-dam-key': 'boardMode', style: { fontWeight: cur === 'legacy' ? 700 : 400, opacity: cur === 'legacy' ? 1 : 0.65 }, onClick: function () { if (cur !== 'legacy') pick('legacy') } }, locale === 'zh' ? '旧版白板' : 'Legacy board'),
|
|
5105
|
+
h('button', { 'data-dam-btn': '', 'data-dam-key': 'boardModeGraph', style: { marginLeft: '6px', fontWeight: cur === 'graph' ? 700 : 400, opacity: cur === 'graph' ? 1 : 0.65 }, onClick: function () { if (cur !== 'graph') pick('graph') } }, locale === 'zh' ? '新版看板(dsh-graph)' : 'Graph board (dsh-graph)'))
|
|
5106
|
+
})(),
|
|
5107
|
+
locale === 'zh' ? '一键切换白板形态。默认「旧版白板」=一切行为不变;切到「新版看板」后启用 dsh-graph 看板、白板结构化索引(handoff/index.json)与两个遍历工具(memory_expand_pre / memory_trace_pre,工具数 14→16)。切换后需重启 dsh web 生效;再点回旧版即完全回滚。' : 'One-click switch for the board form. Default "Legacy board" keeps everything unchanged; "Graph board" enables the dsh-graph kanban, the structured handoff index (handoff/index.json) and two traversal tools (memory_expand_pre / memory_trace_pre, tool count 14→16). Restart dsh web after switching; switch back to fully roll back.'),
|
|
4342
5108
|
field(locale === 'zh' ? '子代理痕迹回收' : 'Subagent trace recycle', h('input', { type: 'checkbox', checked: cfg.subagentGcEnabled !== false, onChange: function (e) { set('subagentGcEnabled', e.target.checked) } }),
|
|
4343
5109
|
locale === 'zh' ? '本插件的一次性子代理(自动沉淀 / 时段总结 / 问候 / 蒸馏)每跑一次都会在 ~/.dsh/sessions 留下一个持久化会话;数量上千后会话列表会明显变慢。开启后任务一结束就把这些痕迹移入 ~/.dsh/subagent-gc-backup 备份(可回滚),不影响子代理结果。默认开。' : 'Every one-shot subagent this plugin spawns (auto-consolidation, scheduled summary, greeting, distillation) leaves a persisted session under ~/.dsh/sessions; hundreds of them slow the session list down. When on, each trace is moved into ~/.dsh/subagent-gc-backup right after the task ends (reversible), never affecting the subagent result. On by default.'),
|
|
4344
5110
|
field(locale === 'zh' ? '兜底回收保留天数' : 'Fallback recycle keep days', h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.subagentGcKeepDays === undefined ? 3 : cfg.subagentGcKeepDays, onChange: function (e) { set('subagentGcKeepDays', Number(e.target.value) || 0) } }),
|
|
@@ -4460,7 +5226,8 @@ window.__ModuleLoader__.load({
|
|
|
4460
5226
|
// v0.1.38:旧 first 卡已删除 → 全新安装直接完整欢迎向导;老用户按 seen/向导完成态补大更新。
|
|
4461
5227
|
// 抽成具名函数便于状态机单测(startup-dispatch)。
|
|
4462
5228
|
var MAJOR_TOUR_KEY = 'dsh-auto-memory.majorTourV130'
|
|
4463
|
-
function dispatchStartupDialog(d) {
|
|
5229
|
+
function dispatchStartupDialog(d, cfg) {
|
|
5230
|
+
var allowTour = welcomeAutoAllowedPre(cfg)
|
|
4464
5231
|
if (d && d.current) {
|
|
4465
5232
|
try {
|
|
4466
5233
|
var seen = localStorage.getItem('dsh-auto-memory.seenVersion')
|
|
@@ -4469,28 +5236,31 @@ window.__ModuleLoader__.load({
|
|
|
4469
5236
|
var firstDone = localStorage.getItem('dsh-auto-memory.firstRunDone')
|
|
4470
5237
|
// 0.1.38:旧的 first 欢迎卡已删除(内容被 welcomeTour 完整覆盖,且小卡形态无 ✕ 关闭)。
|
|
4471
5238
|
// 全新安装直接完整欢迎向导(分步功能开关+引擎检测/下载引导)。
|
|
5239
|
+
// issue#40:三个自动弹出分支都必须过 allowTour 配置闸 —— 否则 welcomeTourEnabled=false 形同虚设。
|
|
4472
5240
|
var freshInstall = !seen && !firstDone && !wizDone
|
|
4473
5241
|
var isExisting = !!seen || !!firstDone || !!wizDone
|
|
4474
|
-
if (freshInstall) {
|
|
5242
|
+
if (allowTour && freshInstall) {
|
|
4475
5243
|
openDialog({ kind: 'welcomeTour' })
|
|
4476
5244
|
try { localStorage.setItem(MAJOR_TOUR_KEY, '1') } catch (eF) {}
|
|
4477
5245
|
return
|
|
4478
5246
|
}
|
|
4479
5247
|
// 老用户(有使用痕迹)seen < 0.1.30(大更新前版本)或从未看过向导 → 补一次完整欢迎向导
|
|
4480
5248
|
// (含引擎下载引导;0.1.29→0.1.34 直升即使曾看过旧版也重放,确保引导不漏)
|
|
4481
|
-
if (isExisting && !wizDone && (!seen || cmpVersion(seen, '0.1.30') < 0)) {
|
|
5249
|
+
if (allowTour && isExisting && !wizDone && (!seen || cmpVersion(seen, '0.1.30') < 0)) {
|
|
4482
5250
|
openDialog({ kind: 'welcomeTour' })
|
|
4483
5251
|
try { localStorage.setItem(MAJOR_TOUR_KEY, '1') } catch (eM) {}
|
|
4484
5252
|
return
|
|
4485
5253
|
}
|
|
4486
|
-
if (majorPending && isExisting && !wizDone) {
|
|
5254
|
+
if (allowTour && majorPending && isExisting && !wizDone) {
|
|
4487
5255
|
openDialog({ kind: 'welcomeTour' })
|
|
4488
5256
|
try { localStorage.setItem(MAJOR_TOUR_KEY, '1') } catch (eM) {}
|
|
4489
5257
|
return
|
|
4490
5258
|
}
|
|
4491
5259
|
if (seen && seen !== d.current) {
|
|
4492
|
-
//
|
|
4493
|
-
|
|
5260
|
+
// ★2026-09-17 修复:bigKey 原是硬编码 '2.1.0' ⇒ 老用户升级到 3.0.0 时弹的仍是 2.1.0 的
|
|
5261
|
+
// 说明卡,本版 changelog 根本送达不了(违反「用户读得到的弹窗 changelog」要求)。
|
|
5262
|
+
// 改为**动态取当前版本**:只要 CHANGELOG 里有本版条目就弹本版,以后每个大版本自动正确。
|
|
5263
|
+
var bigKey = (CHANGELOG[d.current] ? d.current : '2.1.0')
|
|
4494
5264
|
if (CHANGELOG[bigKey]) {
|
|
4495
5265
|
openDialog({ kind: 'update', versions: [{ version: bigKey, items: CHANGELOG[bigKey] }], currentVersion: d.current })
|
|
4496
5266
|
try { localStorage.setItem('dsh-auto-memory.seenVersion', d.current) } catch (e3b) {}
|
|
@@ -4507,7 +5277,39 @@ window.__ModuleLoader__.load({
|
|
|
4507
5277
|
} catch (e) {}
|
|
4508
5278
|
}
|
|
4509
5279
|
}
|
|
4510
|
-
|
|
5280
|
+
// issue#40:不得与 update-check 抢跑 —— 配置未知时(Null)一律**不自动播放**,等配置到达再分发。
|
|
5281
|
+
var startupEpoch = tourNavigationEpoch
|
|
5282
|
+
Promise.all([
|
|
5283
|
+
apiGet(API.updateCheck).catch(function () { return null }),
|
|
5284
|
+
loadWelcomeConfigPre(5000),
|
|
5285
|
+
]).then(function (pair) {
|
|
5286
|
+
// 配置闸的清理动作会推进 epoch;此时仍要送达版本通知,但向导必须保持关闭。
|
|
5287
|
+
var cfg = pair[1]
|
|
5288
|
+
if (tourNavigationEpoch !== startupEpoch) cfg = { welcomeTourEnabled: false }
|
|
5289
|
+
dispatchStartupDialog(pair[0], cfg)
|
|
5290
|
+
}).catch(function () {})
|
|
5291
|
+
// 会话切换 / 浏览器前进后退 都视为"用户已离开向导"⇒ 撤下,避免浮层跨页面残留(移动端体感:关不掉)。
|
|
5292
|
+
if (typeof window['dsh-auto-memory.disposeTourNavigation'] === 'function') window['dsh-auto-memory.disposeTourNavigation']()
|
|
5293
|
+
var lastTourSession = currentSessionIdClient()
|
|
5294
|
+
var tourSessionDispose = null
|
|
5295
|
+
function tourNavigated() { dismissWelcomeTourPre('navigation') }
|
|
5296
|
+
try {
|
|
5297
|
+
if (sessions && sessions.list && typeof sessions.list.subscribe === 'function') {
|
|
5298
|
+
tourSessionDispose = sessions.list.subscribe(function () {
|
|
5299
|
+
var sid = currentSessionIdClient()
|
|
5300
|
+
if (sid !== lastTourSession) { lastTourSession = sid; dismissWelcomeTourPre('navigation') }
|
|
5301
|
+
})
|
|
5302
|
+
}
|
|
5303
|
+
window.addEventListener('popstate', tourNavigated)
|
|
5304
|
+
window.addEventListener('hashchange', tourNavigated)
|
|
5305
|
+
} catch (_) {}
|
|
5306
|
+
window['dsh-auto-memory.disposeTourNavigation'] = function () {
|
|
5307
|
+
if (typeof tourSessionDispose === 'function') tourSessionDispose()
|
|
5308
|
+
window.removeEventListener('popstate', tourNavigated)
|
|
5309
|
+
window.removeEventListener('hashchange', tourNavigated)
|
|
5310
|
+
tourSessionDispose = null
|
|
5311
|
+
}
|
|
5312
|
+
if (typeof ctx !== 'undefined' && ctx && typeof ctx.effect === 'function') ctx.effect(function () { return window['dsh-auto-memory.disposeTourNavigation'] }, 'auto-memory:tour-navigation')
|
|
4511
5313
|
|
|
4512
5314
|
// 动态通知(发布者→用户:重大 bug 提醒,不依赖发版):启动检查 + 每小时刷新;按 id 去重,urgent 优先
|
|
4513
5315
|
function checkNotices() {
|
|
@@ -4590,6 +5392,16 @@ window.__ModuleLoader__.load({
|
|
|
4590
5392
|
surfaceDisposers.push(slots.inject('settings.section', function () {
|
|
4591
5393
|
return slots.register({ name: 'settings.section', id: 'auto-memory', order: 25, label: t('autoMemory') + ' (pre)' }, function (props) { return h(SettingsPage, { close: props && props.close }) })
|
|
4592
5394
|
}))
|
|
5395
|
+
// 2026-09-16「双承载面」: 会话页顶栏再挂一个**整页白板看板**(与侧边面板共存)。
|
|
5396
|
+
// 面板 440px 装不下矩阵, 整页 ~1000px+ 可以 ⇒ 两者按容器宽度各用其形态。
|
|
5397
|
+
// 与 boardMode 闸门解耦: 组件内部自己取数, 非 graph 档拿到 enabled:false 就显示提示,
|
|
5398
|
+
// 不改变 legacy 档下顶栏是否出现该 Tab 的行为(那由宿主/开关层决定, 本处不加判断)。
|
|
5399
|
+
surfaceDisposers.push(slots.inject('conversation.view', function () {
|
|
5400
|
+
return slots.register(
|
|
5401
|
+
{ name: 'conversation.view', id: 'auto-memory-kanban', order: 80, label: function () { return locale === 'zh' ? '白板看板' : 'Whiteboard' } },
|
|
5402
|
+
function (props) { return h(KanbanView, props || {}) },
|
|
5403
|
+
)
|
|
5404
|
+
}))
|
|
4593
5405
|
} catch (e) {
|
|
4594
5406
|
console.warn('[dsh-auto-memory] slot registration failed', e)
|
|
4595
5407
|
}
|