dsh-notes-plugin 0.1.0 → 0.1.2

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 CHANGED
@@ -1,12 +1,20 @@
1
- # dsh-notes
1
+ <div align="center">
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/dsh-notes.svg)](https://www.npmjs.com/package/dsh-notes)
4
- [![npm downloads](https://img.shields.io/npm/dw/dsh-notes.svg)](https://www.npmjs.com/package/dsh-notes)
5
- [![node](https://img.shields.io/node/v/dsh-notes.svg)](https://www.npmjs.com/package/dsh-notes)
6
- [![license](https://img.shields.io/npm/l/dsh-notes.svg)](https://github.com/PPawnsir/dsh-notes-plugin/blob/main/LICENSE)
3
+ # 📝 dsh-notes-plugin
4
+
5
+ **把 Agent 会话里「聊完就丢」的决策与约定,沉淀成本地 Markdown 笔记**
6
+
7
+ 自动注入系统提示 · 可派发待办给活跃会话 · 选区一键摘录 · 纯本地 Markdown 不上传
8
+
9
+ [![npm version](https://img.shields.io/npm/v/dsh-notes-plugin.svg)](https://www.npmjs.com/package/dsh-notes-plugin)
10
+ [![npm downloads](https://img.shields.io/npm/dw/dsh-notes-plugin.svg)](https://www.npmjs.com/package/dsh-notes-plugin)
11
+ [![node](https://img.shields.io/node/v/dsh-notes-plugin.svg)](https://www.npmjs.com/package/dsh-notes-plugin)
12
+ [![license](https://img.shields.io/npm/l/dsh-notes-plugin.svg)](https://github.com/PPawnsir/dsh-notes-plugin/blob/main/LICENSE)
7
13
  ![category](https://img.shields.io/badge/awesome--dsh--plugin-workflow-blue)
8
14
 
9
- DeepSeek Harness 会话感知本地笔记 —— 把会话里的决策与约定沉淀成本地 Markdown 笔记,自动注入系统提示、可派发给活跃会话执行。
15
+ <img src="./docs/screenshot-panel.png" alt="dsh-notes-plugin 笔记面板" width="820">
16
+
17
+ </div>
10
18
 
11
19
  ## 解决什么问题
12
20
 
@@ -27,7 +35,7 @@ dsh-notes 把这件事变成可积累的本地资产:
27
35
  > 宿主要求:Node ≥ 22;DSH ≥ `0.1.5-rc.1`(已通过 `peerDependencies` 声明,含预发布分支的版本范围见 package.json)
28
36
 
29
37
  ```sh
30
- dsh plugin --profile web add dsh-notes
38
+ dsh plugin --profile web add dsh-notes-plugin
31
39
  ```
32
40
 
33
41
  重启 DSH 后生效:会话头部出现「**智能笔记**」按钮(✎,带计数徽标),点击打开/关闭面板;桌面角落另有可拖拽的悬浮气泡入口。
@@ -35,8 +43,8 @@ dsh plugin --profile web add dsh-notes
35
43
  ## 升级 / 卸载
36
44
 
37
45
  ```sh
38
- dsh plugin --profile web add dsh-notes@latest # 升级(重启 DSH)
39
- dsh plugin --profile web remove dsh-notes # 卸载(不删数据)
46
+ dsh plugin --profile web add dsh-notes-plugin@latest # 升级(重启 DSH)
47
+ dsh plugin --profile web remove dsh-notes-plugin # 卸载(不删数据)
40
48
  ```
41
49
 
42
50
  ## 功能清单
package/index.mjs CHANGED
@@ -21,9 +21,12 @@ import fsNode from 'node:fs'
21
21
  import { fileURLToPath } from 'node:url'
22
22
 
23
23
  export const name = 'dsh-notes-plugin'
24
- // 硬依赖:fs(笔记读写)+ sandboxPolicy(写策略)+ webServer(静态包 RPC 路由)+ tools(静态包工具注册)。
25
- // 注意:harness 是动态插件的全局 Builtin,静态包里不存在(PACKAGING.md)——静态包必须 inject webServer/tools 走 ctx 服务通道。
26
- export const inject = ['fs', 'sandboxPolicy', 'webServer', 'tools']
24
+ // 硬依赖:fs(笔记读写)+ sandboxPolicy(写策略)+ webServer(静态包 RPC 路由)+ tools(静态包工具注册)
25
+ // + agents/workspaceRegistry/sessionPersistence/sessionQuery/sessionTitle(派发与注入的会话列表数据源——
26
+ // 这些服务注册时机晚于基础服务,不声明 inject 时 apply 先于它们执行,ctx.get 拿到 undefined,会话列表永远为空)。
27
+ // harness 是动态插件的全局 Builtin,静态包里不存在(PACKAGING.md)——静态包必须 inject webServer/tools 走 ctx 服务通道。
28
+ // llm / agentDefaultModel / systemPrompt 为可选增强(自动分类/约定注入),保持 ctx.get + 守卫降级,不进 inject。
29
+ export const inject = ['fs', 'sandboxPolicy', 'webServer', 'tools', 'agents', 'workspaceRegistry', 'sessionPersistence', 'sessionQuery', 'sessionTitle']
27
30
 
28
31
  // ---- 路径锚点(模块级常量,import 时求值,无副作用)----
29
32
  const PKG_DIR = path.dirname(fileURLToPath(import.meta.url)) // packages/dsh-notes
@@ -290,6 +293,37 @@ export function apply(ctx) {
290
293
  }
291
294
  }
292
295
 
296
+ // 由 sessionId 推导工作区名:live 走 agents 的 header.cwd,非 live 走 persistence/sessionQuery 快照。
297
+ // 旧笔记(agents 未就绪期创建)workspace 为空时用于兜底补全——否则“本工作区”注入范围因严格匹配永不命中。
298
+ async function _wsOfSession(sid) {
299
+ if (!sid) return ''
300
+ try {
301
+ const a = agents && agents.get ? agents.get(sid) : undefined
302
+ let cwd = (a && a.session && a.session.header && a.session.header.cwd) || ''
303
+ if (!cwd && sessionPersistence && typeof sessionPersistence.list === 'function') {
304
+ let snaps = []
305
+ try { snaps = sessionPersistence.list() || [] } catch (e) { snaps = [] }
306
+ if (snaps && typeof snaps.then === 'function') { try { snaps = await snaps } catch (e2) { snaps = [] } }
307
+ for (const s of (snaps || [])) {
308
+ const h = s && s.header ? s.header : s
309
+ if (h && h.id === sid) { cwd = h.cwd || ''; break }
310
+ }
311
+ }
312
+ // 兜底二:sessionQuery.readTitleSnapshots(_activeSessions 同款,已验证在当前 DSH 可用)
313
+ if (!cwd && sessionQuery && typeof sessionQuery.readTitleSnapshots === 'function') {
314
+ try {
315
+ const rs = await sessionQuery.readTitleSnapshots([sid])
316
+ for (const r of (rs || [])) {
317
+ const v = r && r.status === 'fulfilled' ? r.value : (r && r.value)
318
+ const hd = v && v.session ? v.session : null
319
+ if (hd && hd.cwd) { cwd = hd.cwd; break }
320
+ }
321
+ } catch (e) {}
322
+ }
323
+ return cwd ? basename(cwd) : ''
324
+ } catch (e) { return '' }
325
+ }
326
+
293
327
  async function _create(title, body, tags, topic, extra) {
294
328
  const id = genId()
295
329
  const now = new Date().toISOString()
@@ -368,6 +402,11 @@ export function apply(ctx) {
368
402
  if (injectTo !== undefined) note.injectTo = injectTo
369
403
  if (body !== undefined) note.body = body
370
404
  note.updatedAt = new Date().toISOString()
405
+ // 兜底补全:约定笔记 workspace 为空(agents 未就绪期创建的存量)时按来源会话推导填入,
406
+ // 否则“本工作区”注入范围在严格匹配下永不命中
407
+ if (!note.workspace && note.sessionId) {
408
+ try { note.workspace = await _wsOfSession(note.sessionId) } catch (e) {}
409
+ }
371
410
  await persistNote(note)
372
411
  return { id, kind: note.kind, status: note.status }
373
412
  }
@@ -742,12 +781,13 @@ export function apply(ctx) {
742
781
  const targets = n.injectTo || []
743
782
  let hit = false
744
783
  if (targets.length === 0) {
745
- // 默认:当前工作区(workspace 为空视为全局约定)
746
- hit = !n.workspace || !ws || n.workspace === ws
784
+ // 默认:当前工作区。严格匹配:ws 有值时要求笔记 workspace 一致(旧笔记无 workspace 视为记录时未知,
785
+ // 仅在 cwd 不可得的会话兜底注入——修掉"选定工作区注入后切到别的会话仍注入")
786
+ hit = ws ? n.workspace === ws : !n.workspace
747
787
  } else {
748
788
  for (const t of targets) {
749
789
  if (t === 'global') { hit = true; break }
750
- if (t === 'workspace') { if (!n.workspace || !ws || n.workspace === ws) { hit = true; break } }
790
+ if (t === 'workspace') { if (ws ? n.workspace === ws : !n.workspace) { hit = true; break } }
751
791
  else if (t === curSid) { hit = true; break }
752
792
  }
753
793
  }
@@ -1141,6 +1181,37 @@ export function apply(ctx) {
1141
1181
  }
1142
1182
  let migrationDone = migrateLegacyNotes()
1143
1183
 
1184
+ // 存量一次性修补:agents 未就绪期创建的笔记 workspace 为空,导致“本工作区”注入范围严格匹配后永不命中。
1185
+ // 启动时按来源会话推导补填一次(只补空值)。注意:不用 _list()(它 await migrationDone,会与本补全死锁),
1186
+ // 直接走底层遍历;也不挂进 migrationDone 链——_list 只需等 legacy 迁移,补全异步自跑即可。
1187
+ const legacyDone = migrationDone
1188
+ async function fixLegacyWorkspaces() {
1189
+ try { await legacyDone } catch (e) {}
1190
+ try {
1191
+ const dirTarget = await fs.resolve(NOTES_DIR)
1192
+ const info = await fs.stat(dirTarget)
1193
+ if (!info) return { fixed: 0 }
1194
+ const entries = await fs.listDir(dirTarget)
1195
+ let fixed = 0
1196
+ let skipped = 0
1197
+ for (const entry of entries) {
1198
+ if (!entry.name || !entry.name.endsWith('.md')) continue
1199
+ const id = entry.name.replace(/\.md$/, '')
1200
+ try {
1201
+ const n = await loadNote(id)
1202
+ if (n.deleted || n.workspace) continue
1203
+ const ws = await _wsOfSession(n.sessionId)
1204
+ if (!ws) { skipped++; console.log('notes: workspace backfill skip ' + id + ' (sid=' + (n.sessionId || 'none') + ', 推导不到 cwd)') ; continue }
1205
+ await persistNote(Object.assign({}, n, { workspace: ws }))
1206
+ fixed++
1207
+ } catch (e) { skipped++; console.error('notes: workspace backfill item failed', id, e) }
1208
+ }
1209
+ console.log('notes: workspace backfill done, fixed=' + fixed + ' skipped=' + skipped)
1210
+ return { fixed: fixed }
1211
+ } catch (e) { console.error('notes: workspace backfill error', e); return { fixed: 0, error: String(e && e.message || e) } }
1212
+ }
1213
+ fixLegacyWorkspaces()
1214
+
1144
1215
  ctx.effect(() => () => {
1145
1216
  for (const d of disposers) { try { d() } catch (e) {} }
1146
1217
  })
package/lib/client.js CHANGED
@@ -9,7 +9,7 @@
9
9
  // · 样式 :fetch notes-css + document.createElement('style') 注入(doc 级,进程单例)
10
10
  // (动态插件的 styles 服务在静态包中不存在)
11
11
  // · 定时器 :动态插件的 ctx.interval 快捷方式不存在,用 ctx.get('timer') + ctx.effect
12
- // · inject :只声明硬依赖 slots;timer/sessions/workspaces 全部 ctx.get + 守卫
12
+ // · inject :声明全部服务(slots/timer/sessions/workspaces),保证就绪后才 apply
13
13
  //
14
14
  // 要改 client 行为:改开发版 client-impl.js,然后 `node scripts/build-dist.cjs` 重新生成。
15
15
  window.__ModuleLoader__.load({
@@ -60,6 +60,86 @@ window.__ModuleLoader__.load({
60
60
  const notify = () => listeners.forEach(fn => fn({ panelOpen }))
61
61
  const notifyNotesChanged = () => noteRefreshListeners.forEach(fn => fn())
62
62
  const e = React.createElement
63
+ // ===== Markdown 预览渲染器(内联手写,零外部依赖;所有插值先 HTML 转义)=====
64
+ // 安全红线:esc() 先把 & < > " ' 转为实体,绝不用 innerHTML 直插原文;代码块内容同样转义
65
+ function esc(s) {
66
+ return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\x22/g, '&quot;').replace(/\x27/g, '&#39;')
67
+ }
68
+ function renderMarkdown(md) {
69
+ var src = String(md || '')
70
+ if (!src.trim()) return ''
71
+ var lines = src.replace(/\r\n/g, '\n').split('\n')
72
+ var out = []
73
+ var i = 0
74
+ var inUl = false, inOl = false
75
+ function closeLists() { if (inUl) { out.push('</ul>'); inUl = false } if (inOl) { out.push('</ol>'); inOl = false } }
76
+ // 行内格式:行内代码 → 粗体 → 斜体 → 链接(仅 http/https)。esc 已在入口执行
77
+ function inline(s) {
78
+ var t = esc(s)
79
+ t = t.replace(/\x60([^\x60]+)\x60/g, function (m, c) { return '<code>' + c + '</code>' })
80
+ t = t.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
81
+ t = t.replace(/\*([^*]+)\*/g, '<em>$1</em>')
82
+ t = t.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
83
+ return t
84
+ }
85
+ while (i < lines.length) {
86
+ var line = lines[i]
87
+ // 围栏代码块
88
+ if (/^\x60\x60\x60/.test(line)) {
89
+ closeLists()
90
+ var lang = line.replace(/^\x60\x60\x60/, '').trim()
91
+ var codeLines = []
92
+ i++
93
+ while (i < lines.length && !/^\x60\x60\x60/.test(lines[i])) { codeLines.push(lines[i]); i++ }
94
+ i++
95
+ out.push('<pre class="dsh-notes-preview-code"' + (lang ? ' data-lang="' + esc(lang) + '"' : '') + '><code>' + esc(codeLines.join('\n')) + '</code></pre>')
96
+ continue
97
+ }
98
+ // 标题 # ~ ###
99
+ var hm = line.match(/^(#{1,3})\s+(.*)$/)
100
+ if (hm) {
101
+ closeLists()
102
+ var lvl = hm[1].length
103
+ out.push('<h' + lvl + ' class="dsh-notes-preview-h' + lvl + '">' + inline(hm[2]) + '</h' + lvl + '>')
104
+ i++
105
+ continue
106
+ }
107
+ // 引用块
108
+ if (/^>\s?/.test(line)) {
109
+ closeLists()
110
+ var quoteLines = []
111
+ while (i < lines.length && /^>\s?/.test(lines[i])) { quoteLines.push(lines[i].replace(/^>\s?/, '')); i++ }
112
+ out.push('<blockquote class="dsh-notes-preview-quote">' + inline(quoteLines.join(' ')) + '</blockquote>')
113
+ continue
114
+ }
115
+ // 无序列表
116
+ if (/^[-*+]\s+/.test(line)) {
117
+ if (inOl) { out.push('</ol>'); inOl = false }
118
+ if (!inUl) { out.push('<ul class="dsh-notes-preview-ul">'); inUl = true }
119
+ out.push('<li>' + inline(line.replace(/^[-*+]\s+/, '')) + '</li>')
120
+ i++
121
+ continue
122
+ }
123
+ // 有序列表
124
+ if (/^\d+\.\s+/.test(line)) {
125
+ if (inUl) { out.push('</ul>'); inUl = false }
126
+ if (!inOl) { out.push('<ol class="dsh-notes-preview-ol">'); inOl = true }
127
+ out.push('<li>' + inline(line.replace(/^\d+\.\s+/, '')) + '</li>')
128
+ i++
129
+ continue
130
+ }
131
+ // 空行
132
+ if (line.trim() === '') { closeLists(); i++; continue }
133
+ // 段落(连续非空非特殊行合并)
134
+ closeLists()
135
+ var paraLines = []
136
+ while (i < lines.length && lines[i].trim() !== '' && !/^(#{1,3}\s|\x60\x60\x60|>\s?|[-*+]\s|\d+\.\s)/.test(lines[i])) { paraLines.push(lines[i]); i++ }
137
+ out.push('<p class="dsh-notes-preview-p">' + inline(paraLines.join(' ')) + '</p>')
138
+ }
139
+ closeLists()
140
+ return out.join('\n')
141
+ }
142
+ // ===== end Markdown 预览渲染器 =====
63
143
  // 性能自检计数器:浏览器控制台执行 JSON.stringify(window.__dshNotesPerf) 可取数诊断
64
144
  const now = (typeof performance !== 'undefined' && performance.now) ? () => performance.now() : () => Date.now()
65
145
  const perf = { selChange: 0, selCollapsedSkip: 0, selChangeMs: 0, selShowEval: 0, selShowMs: 0, mousemoveTracked: 0, hostCall: 0, hostCallMs: 0, panelRender: 0, selRender: 0, hdrRender: 0, longTasks: 0, longTaskMs: 0, worstTaskMs: 0 }
@@ -224,10 +304,13 @@ window.__ModuleLoader__.load({
224
304
  const [dispatching, setDispatching] = React.useState(false)
225
305
  const [dispatchMode, setDispatchMode] = React.useState('existing') // existing=派发到活跃会话 / new=新建会话派发
226
306
  const [dispatchInstr, setDispatchInstr] = React.useState('')
307
+ const [previewMode, setPreviewMode] = React.useState(false) // 编辑/预览双态:默认编辑(textarea 行为不变)
308
+ const [ctxMenu, setCtxMenu] = React.useState(null) // 列表项右键菜单:{ x, y, note }(面板内坐标)或 null
227
309
  const [dispatchWsId, setDispatchWsId] = React.useState('') // new 模式:选中的工作区 id
228
310
  const [dispatchSessWs, setDispatchSessWs] = React.useState('') // existing 模式:选中的工作区名
229
311
  const [dispatchSessId, setDispatchSessId] = React.useState('') // existing 模式:选中的会话 id
230
312
  const [wsList, setWsList] = React.useState([])
313
+ const [dispatchHistoryOpen, setDispatchHistoryOpen] = React.useState(false) // 派发历史折叠态:默认折叠,点标题行展开
231
314
  const keepQuickRef = React.useRef(false)
232
315
  const capRef = React.useRef(null)
233
316
  const timersRef = React.useRef([])
@@ -246,6 +329,7 @@ window.__ModuleLoader__.load({
246
329
  // T2 顶栏压缩:展开态镜像到 ref(keydown 闭包挂一次,需读最新值避免过期)
247
330
  const searchOpenRef = React.useRef(false)
248
331
  const capOpenRef = React.useRef(false)
332
+ const ctxMenuRef = React.useRef(null) // 右键菜单镜像(keydown 闭包读最新值)
249
333
  // 自动保存:编辑字段的最新值 ref(debounce 回调读 ref 而非闭包 state,避免过期)
250
334
  const edTitleRef = React.useRef('')
251
335
  const edTopicRef = React.useRef('')
@@ -297,6 +381,7 @@ window.__ModuleLoader__.load({
297
381
  // T2 顶栏压缩:展开态同步到 ref(keydown 闭包读 ref 避免过期)
298
382
  React.useEffect(() => { searchOpenRef.current = searchOpen }, [searchOpen])
299
383
  React.useEffect(() => { capOpenRef.current = capOpen }, [capOpen])
384
+ React.useEffect(() => { ctxMenuRef.current = ctxMenu }, [ctxMenu])
300
385
  // T2 顶栏压缩:展开时自动聚焦(Ctrl+K/Ctrl+N 改为 setSearchOpen(true)/setCapOpen(true),由此 effect 完成聚焦)
301
386
  React.useEffect(() => { if (searchOpen && searchInputRef.current) searchInputRef.current.focus() }, [searchOpen])
302
387
  React.useEffect(() => {
@@ -316,8 +401,9 @@ window.__ModuleLoader__.load({
316
401
  const mod = ev.ctrlKey || ev.metaKey
317
402
  if (mod && (ev.key === 'k' || ev.key === 'K')) { ev.preventDefault(); setSearchOpen(true); return }
318
403
  if (mod && (ev.key === 'n' || ev.key === 'N')) { ev.preventDefault(); setCapOpen(true); return }
319
- if (ev.key === 'Escape') { ev.preventDefault(); if (capOpenRef.current) { setCapOpen(false); return } if (searchOpenRef.current) { setSearchOpen(false); return } closeRef.current(); return }
404
+ if (ev.key === 'Escape') { ev.preventDefault(); if (ctxMenuRef.current) { setCtxMenu(null); return } if (capOpenRef.current) { setCapOpen(false); return } if (searchOpenRef.current) { setSearchOpen(false); return } closeRef.current(); return }
320
405
  if (inField) return
406
+ if (ctxMenuRef.current) return // 右键菜单打开时暂停列表导航/打开
321
407
  const ids = pagedIdsRef.current
322
408
  if (!ids.length) return
323
409
  if (ev.key === 'j' || ev.key === 'ArrowDown') { ev.preventDefault(); moveFocusRef.current(ids, 1) }
@@ -416,6 +502,33 @@ window.__ModuleLoader__.load({
416
502
  await loadNotes(true); notifyNotesChanged()
417
503
  } catch (err) { setError(String(err.message || err)) }
418
504
  }
505
+ // 列表项右键菜单:替代悬浮 × 按钮(打开即选中目标笔记,操作上下文明确)
506
+ function openCtxMenu(ev, n) {
507
+ ev.preventDefault(); ev.stopPropagation()
508
+ const floatEl = ev.currentTarget.closest('.dsh-notes-floating')
509
+ const rect = floatEl ? floatEl.getBoundingClientRect() : { left: 0, top: 0, width: 600, height: 500 }
510
+ const mw = 160, mh = 116
511
+ let x = ev.clientX - rect.left, y = ev.clientY - rect.top
512
+ x = Math.max(4, Math.min(x, rect.width - mw - 4))
513
+ y = Math.max(4, Math.min(y, rect.height - mh - 4))
514
+ setCtxMenu({ x: x, y: y, note: n })
515
+ selectNote(n)
516
+ }
517
+ async function ctxSetStatus(n, status) {
518
+ setCtxMenu(null); setError('')
519
+ try {
520
+ const res = await rpc('notes-update', { id: n.id, status: status })
521
+ if (res && res.error) { setError(res.error); return }
522
+ await loadNotes(true); notifyNotesChanged()
523
+ } catch (err) { setError(String(err.message || err)) }
524
+ }
525
+ // 点击菜单外部关闭(Esc 在全局 keydown 里处理)
526
+ React.useEffect(() => {
527
+ if (!ctxMenu) return
528
+ const onDown = (ev) => { if (!(ev.target && ev.target.closest && ev.target.closest('.dsh-notes-ctxmenu'))) setCtxMenu(null) }
529
+ document.addEventListener('mousedown', onDown)
530
+ return () => document.removeEventListener('mousedown', onDown)
531
+ }, [ctxMenu])
419
532
  async function pickTopic(id, topic) {
420
533
  setTopicPickFor(null); setError('')
421
534
  try {
@@ -563,13 +676,12 @@ window.__ModuleLoader__.load({
563
676
  if (n.topic && n.topic !== '分类中') metaEls.push(e('span', { className: 'dsh-note-meta-topic' }, n.topic))
564
677
  if (n.sessionId) metaEls.push(e('span', { className: 'dsh-note-meta-sess' }, '会话 ' + shortSid(n.sessionId)))
565
678
  if (visTags.length) metaEls.push(e('span', { className: 'dsh-note-meta-tags' }, visTags.join(' · ')))
566
- return e('div', { key: n.id, className: 'dsh-note-item' + (selected === n.id ? ' selected' : '') + (flashId === n.id ? ' flash' : '') + (focusId === n.id ? ' focused' : '') + statusCls, onClick: () => selectNote(n), 'data-tooltip': n.title },
679
+ return e('div', { key: n.id, className: 'dsh-note-item' + (selected === n.id ? ' selected' : '') + (flashId === n.id ? ' flash' : '') + (focusId === n.id ? ' focused' : '') + statusCls, onClick: () => selectNote(n), onContextMenu: (ev) => openCtxMenu(ev, n), 'data-tooltip': n.title },
567
680
  e('div', { className: 'dsh-note-row' },
568
681
  e('span', { className: 'dsh-note-kind-ic dsh-note-kind-' + (n.kind || 'note') }, kindIc),
569
682
  e('span', { className: 'dsh-note-title' }, e('span', { className: 'dsh-note-title-text' }, highlight(n.title, q))),
570
683
  e('span', { className: 'dsh-note-date' }, n.updatedAt ? n.updatedAt.slice(0, 10) : '')),
571
- metaEls.length ? e('div', { className: 'dsh-note-meta' }, metaEls) : null,
572
- e('button', { className: 'dsh-note-delete dsh-nt', onClick: (ev) => { ev.stopPropagation(); doDelete(n.id) }, 'data-tooltip': '删除' }, '×'))
684
+ metaEls.length ? e('div', { className: 'dsh-note-meta' }, metaEls) : null)
573
685
  }
574
686
  let listContent
575
687
  if (loading && notes.length === 0) listContent = e('div', { className: 'dsh-notes-loading' }, '加载中...')
@@ -649,7 +761,7 @@ window.__ModuleLoader__.load({
649
761
  e('div', { className: 'dsh-notes-ed-head' },
650
762
  e('input', { className: 'dsh-notes-editor-title', placeholder: '标题', value: edTitle, onChange: (ev) => { setEdTitle(ev.target.value); triggerAutoSave() } }),
651
763
  e('div', { className: 'dsh-notes-ed-meta' },
652
- e('select', { className: 'dsh-notes-ed-select', value: edKind, onChange: (ev) => { setEdKind(ev.target.value); triggerAutoSave() }, 'data-tooltip': '笔记类型' },
764
+ e('select', { className: 'dsh-notes-ed-select dsh-notes-ed-kind-chip dsh-notes-ed-kind-' + edKind, value: edKind, onChange: (ev) => { setEdKind(ev.target.value); triggerAutoSave() }, 'data-tooltip': '笔记类型' },
653
765
  e('option', { value: 'note' }, '笔记'),
654
766
  e('option', { value: 'decision' }, '决策'),
655
767
  e('option', { value: 'todo' }, '待办'),
@@ -661,9 +773,14 @@ window.__ModuleLoader__.load({
661
773
  e('option', { value: 'resolved' }, '已解决'),
662
774
  e('option', { value: 'superseded' }, '已取代')),
663
775
  e('input', { className: 'dsh-notes-ed-topic', placeholder: '主题', value: edTopic, onChange: (ev) => { setEdTopic(ev.target.value); triggerAutoSave() }, 'data-tooltip': '主题' }),
664
- e('input', { className: 'dsh-notes-ed-topic', placeholder: '标签,逗号分隔', value: edTags, onChange: (ev) => { setEdTags(ev.target.value); triggerAutoSave() }, 'data-tooltip': '标签(convention 表示工作区约定)' }),
776
+ e('input', { className: 'dsh-notes-ed-tags-input', placeholder: '标签,逗号分隔', value: edTags, onChange: (ev) => { setEdTags(ev.target.value); triggerAutoSave() }, 'data-tooltip': '标签(convention 表示工作区约定)' }),
777
+ (edTags || '').split(/[,,;;]/).map(function (s) { return s.trim() }).filter(Boolean).length
778
+ ? e('div', { className: 'dsh-notes-ed-tags-chips' },
779
+ (edTags || '').split(/[,,;;]/).map(function (s) { return s.trim() }).filter(Boolean)
780
+ .map(function (tag, i) { return e('span', { key: 'tag-' + i, className: 'dsh-notes-ed-tag-chip' }, tag) }))
781
+ : null,
665
782
  e('div', { className: 'dsh-notes-ed-meta-right' },
666
- e('button', { className: 'dsh-notes-ed-action dsh-nt', onClick: (ev) => { ev.stopPropagation(); openDispatch() }, 'data-tooltip': '派发待办到会话(可补充具体要求)' }, dispatching ? '…' : '▶ 派发'),
783
+ e('button', { className: 'dsh-notes-ed-action dsh-notes-dispatch-btn dsh-nt', onClick: (ev) => { ev.stopPropagation(); openDispatch() }, 'data-tooltip': '派发待办到会话(可补充具体要求)' }, dispatching ? '…' : '▶ 派发'),
667
784
  notes.find(n => n.id === selected) && notes.find(n => n.id === selected).sessionId ? e('button', { className: 'dsh-notes-ed-action dsh-nt', onClick: () => jumpToSession(notes.find(n => n.id === selected).sessionId), 'data-tooltip': '跳转到来源会话' }, '↗ 会话') : null,
668
785
  e('button', { className: 'dsh-notes-ed-action' + (edStatus === 'pinned' ? ' on' : ''), onClick: () => { setEdStatus(edStatus === 'pinned' ? 'active' : 'pinned'); triggerAutoSave() }, 'data-tooltip': edStatus === 'pinned' ? '取消置顶' : '置顶' }, '📌'),
669
786
  e('button', { className: 'dsh-notes-ed-action danger', onClick: () => doDelete(selected), 'data-tooltip': '删除(软删除,可恢复)' }, '🗑'))),
@@ -683,17 +800,21 @@ window.__ModuleLoader__.load({
683
800
  ' ' + s.name)))))
684
801
  : null)
685
802
  : null)),
686
- curDispatches.length ? e('div', { className: 'dsh-notes-dispatch-history' },
687
- e('div', { className: 'dsh-notes-dispatch-history-t' }, '▶ 派发历史(' + curDispatches.length + ')'),
688
- curDispatches.map((d, origIdx) => ({ d: d, origIdx: origIdx })).reverse().map(({ d, origIdx }) => e('div', { key: origIdx, className: 'dsh-notes-dispatch-rec' + (d.done ? ' done' : '') },
803
+ curDispatches.length ? e('div', { className: 'dsh-notes-dispatch-history' + (dispatchHistoryOpen ? ' open' : ' collapsed') },
804
+ e('div', { className: 'dsh-notes-dispatch-history-t', onClick: () => setDispatchHistoryOpen(!dispatchHistoryOpen), role: 'button', 'aria-expanded': dispatchHistoryOpen ? 'true' : 'false' },
805
+ (dispatchHistoryOpen ? '▼ ' : '▶ ') + '派发历史(' + curDispatches.length + ')'),
806
+ dispatchHistoryOpen ? curDispatches.map((d, origIdx) => ({ d: d, origIdx: origIdx })).reverse().map(({ d, origIdx }) => e('div', { key: origIdx, className: 'dsh-notes-dispatch-rec' + (d.done ? ' done' : '') },
689
807
  e('div', { className: 'dsh-notes-dispatch-rec-top' },
690
- e('span', { className: 'dsh-notes-dispatch-rec-t' }, (d.done ? '✓ ' : '● ') + (d.sessionName || d.sessionId)),
808
+ e('span', { className: 'dsh-notes-dispatch-rec-t' }, d.done ? '✓ ' + (d.sessionName || d.sessionId) : [e('span', { key: 'dot', className: 'dsh-notes-dispatch-dot' }), ' ' + (d.sessionName || d.sessionId)]),
691
809
  e('span', { className: 'dsh-notes-dispatch-rec-m' }, (d.done ? '已完成 · ' : '待处理 · ') + (d.mode === 'new' ? '新会话' : (d.workspace || '已有会话')) + (d.at ? ' · ' + String(d.at).slice(5, 16).replace('T', ' ') : ''))),
692
810
  d.instruction ? e('div', { className: 'dsh-notes-dispatch-rec-i' }, '要求:' + d.instruction) : null,
693
- !d.done ? e('button', { className: 'dsh-notes-dispatch-done-btn', onClick: () => doDispatchDone(origIdx) }, '标记完成') : null)))
811
+ !d.done ? e('button', { className: 'dsh-notes-dispatch-done-btn', onClick: () => doDispatchDone(origIdx) }, '标记完成') : null)) : null)
694
812
  : null,
695
- e('textarea', { className: 'dsh-notes-editor-body', placeholder: '开始记录…(支持 Markdown)', value: edBody, onChange: (ev) => { setEdBody(ev.target.value); triggerAutoSave() } }),
813
+ previewMode
814
+ ? e('div', { className: 'dsh-notes-preview-container', dangerouslySetInnerHTML: { __html: renderMarkdown(edBody) } })
815
+ : e('textarea', { className: 'dsh-notes-editor-body', placeholder: '开始记录…(支持 Markdown)', value: edBody, onChange: (ev) => { setEdBody(ev.target.value); triggerAutoSave() } }),
696
816
  e('div', { className: 'dsh-notes-ed-foot' },
817
+ e('button', { className: 'dsh-notes-preview-toggle dsh-nt' + (previewMode ? ' on' : ''), onClick: () => setPreviewMode(!previewMode), 'data-tooltip': previewMode ? '切换到编辑模式' : '预览 Markdown 渲染' }, previewMode ? '✎ 编辑' : '👁 预览'),
697
818
  e('span', { className: 'dsh-notes-ed-saved' + (savedAt ? ' show' : '') }, savedAt ? '已自动保存 ' + new Date(savedAt).toTimeString().slice(0, 5) : ''),
698
819
  e('span', null, (edBody || '').length + ' 字')))
699
820
  : e('div', { className: 'dsh-notes-editor-empty' },
@@ -727,6 +848,12 @@ window.__ModuleLoader__.load({
727
848
  e('div', { className: 'dsh-notes-dispatch-actions' },
728
849
  e('button', { className: 'dsh-notes-dispatch-cancel', onClick: () => setDispatchOpen(false) }, '取消'),
729
850
  e('button', { className: 'dsh-notes-dispatch-ok', onClick: doDispatchConfirm, disabled: dispatching }, dispatching ? '派发中…' : '派发'))))
851
+ : null,
852
+ // 列表项右键菜单(替代悬浮 ×):置顶/已解决/删除
853
+ ctxMenu ? e('div', { className: 'dsh-notes-ctxmenu', style: { left: ctxMenu.x + 'px', top: ctxMenu.y + 'px' } },
854
+ e('button', { className: 'dsh-notes-ctxmenu-item', onClick: () => ctxSetStatus(ctxMenu.note, ctxMenu.note.status === 'pinned' ? 'active' : 'pinned') }, '📌 ' + (ctxMenu.note.status === 'pinned' ? '取消置顶' : '置顶')),
855
+ e('button', { className: 'dsh-notes-ctxmenu-item', onClick: () => ctxSetStatus(ctxMenu.note, ctxMenu.note.status === 'resolved' ? 'active' : 'resolved') }, '✓ ' + (ctxMenu.note.status === 'resolved' ? '重开' : '标记已解决')),
856
+ e('button', { className: 'dsh-notes-ctxmenu-item danger', onClick: () => { setCtxMenu(null); doDelete(ctxMenu.note.id) } }, '🗑 删除'))
730
857
  : null)
731
858
  }
732
859
  slots.register({ name: 'shell.overlay', id: 'dsh-notes-panel', order: 200 }, (props) => e(FloatingPanel, props))
@@ -857,10 +984,37 @@ window.__ModuleLoader__.load({
857
984
  } catch (err) { setToast('记录失败:' + String(err.message || err)) }
858
985
  }
859
986
  function cancel() { visibleRef.current = false; setBtn(null); setInstrText(''); if (window.getSelection()) window.getSelection().removeAllRanges() }
987
+ // 复制选区文本到剪贴板:优先 navigator.clipboard,不可用/失败时降级 execCommand;不关浮层不清选区
988
+ function fallbackCopy(text) {
989
+ try {
990
+ var ta = document.createElement('textarea')
991
+ ta.value = text
992
+ ta.style.position = 'fixed'
993
+ ta.style.opacity = '0'
994
+ ta.style.pointerEvents = 'none'
995
+ document.body.appendChild(ta)
996
+ ta.select()
997
+ document.execCommand('copy')
998
+ document.body.removeChild(ta)
999
+ setToast('已复制选区')
1000
+ } catch (err) { setToast('复制失败') }
1001
+ }
1002
+ function copySelection() {
1003
+ var text = selTextRef.current
1004
+ if (!text) { setToast('无选区可复制'); return }
1005
+ try {
1006
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1007
+ navigator.clipboard.writeText(text).then(function () { setToast('已复制选区') }, function () { fallbackCopy(text) })
1008
+ return
1009
+ }
1010
+ } catch (err) {}
1011
+ fallbackCopy(text)
1012
+ }
860
1013
  return e('div', null, btn ? e('div', { className: 'dsh-notes-instruct-box', style: { left: btn.x + 'px', top: btn.y + 'px' } },
861
1014
  e('div', { className: 'dsh-notes-instruct-preview' }, previewText(selTextRef.current)),
862
1015
  e('input', { ref: instrRef, className: 'dsh-notes-instruct-input', type: 'text', placeholder: '可补充:打标签/引导标题/定类型/设为约定…直接回车则仅记录', value: instrText, onChange: function (ev) { setInstrText(ev.target.value) }, onKeyDown: function (ev) { if (ev.key === 'Enter') { ev.preventDefault(); submit() } else if (ev.key === 'Escape') { ev.preventDefault(); cancel() } } }),
863
1016
  e('div', { className: 'dsh-notes-instruct-actions' },
1017
+ e('button', { className: 'dsh-notes-instruct-btn', onClick: copySelection }, '复制'),
864
1018
  e('button', { className: 'dsh-notes-instruct-btn primary', onClick: submit }, '记录'),
865
1019
  e('button', { className: 'dsh-notes-instruct-btn', onClick: cancel }, '取消')
866
1020
  )
@@ -873,9 +1027,8 @@ window.__ModuleLoader__.load({
873
1027
  console.log('notes plugin: client ready')
874
1028
  }
875
1029
 
876
- // 动态版 inject 为 ['timer','sessions','workspaces'];静态包按 PACKAGING.md 第 4 节保守处理:
877
- // 只声明硬依赖 slots(没它就完全没有 UI),其余服务在 apply 内 ctx.get + 守卫,
878
- // 避免服务未就绪时插件永远不启动。
1030
+ // inject 声明 apply 用到的全部服务(slots/timer/sessions/workspaces),
1031
+ // 保证 Cordis 在服务就绪后才激活 apply;apply 内仍保留 ctx.get + 存在性守卫做双保险。
879
1032
  module.exports = { name: 'dsh-notes-plugin', inject: ['slots', 'timer', 'sessions', 'workspaces'], apply: apply }
880
1033
  return module.exports
881
1034
  }
package/lib/styles.css CHANGED
@@ -6,18 +6,20 @@
6
6
  .dsh-notes-floating,.dsh-notes-fab{
7
7
  /* 背景 */
8
8
  --nbg:var(--dsw-alias-bg-overlay, #ffffff); /* 面板主背景 */
9
- --nsf:var(--dsw-alias-bg-layer-1, #fbfbfd); /* 列表区背景(极浅灰) */
10
- --nbg-hover:var(--dsw-alias-bg-layer-2, #f2f2f7); /* 列表项 hover */
11
- --nbg-sel:var(--dsw-alias-bg-layer-2, #e5e5ea); /* 列表项选中(macOS 选中灰) */
9
+ --nsf:rgba(128,128,128,.05); /* 列表区背景(次级面)——color-mix 兜底(明暗面板均可见) */
10
+ --nbg-hover:rgba(128,128,128,.08); /* 列表项 hover(比底色深一档)——color-mix 兜底 */
11
+ --nbg-sel:rgba(128,128,128,.12); /* 列表项选中(macOS 选中灰)——color-mix 兜底 */
12
12
  --nbd:var(--dsw-alias-border-l1, #e5e5ea); /* 边框/分隔线 */
13
13
  --nbd-soft:var(--dsw-alias-border-l1, #ececf0); /* 更柔和的分隔 */
14
14
  /* 文字 */
15
15
  --ntx:var(--dsw-alias-label-primary, #1c1c1e); /* 主文字 */
16
+ --ntx-soft:var(--ntx); /* 正文柔化版(88% 主色,color-mix 覆盖;兜底保持主色) */
16
17
  --nt2:var(--dsw-alias-label-secondary, #6c6c70); /* 次要文字(时间/来源) */
17
18
  --nt3:var(--dsw-alias-label-secondary, #aeaeb2); /* 弱化(placeholder/图标) */
18
19
  /* 强调 */
19
20
  --nacc:var(--dsw-alias-brand-primary, #0a84ff); /* 蓝(交互/选中) */
20
21
  --nacc-soft:rgba(10,132,255,.12);
22
+ --nwarn:#ff9500; /* 待处理/警告(橙) */
21
23
  /* kind 强调色(灰阶为主,kind 用低调色;深浅通用,保留原值) */
22
24
  --nkind-note:#8e8e93;
23
25
  --nkind-decision:#0a84ff;
@@ -27,12 +29,27 @@
27
29
  /* 尺寸 */
28
30
  --nr-sm:6px; --nr-md:8px; --nr-lg:10px;
29
31
  /* 字体 */
30
- --nfont:-apple-system,BlinkMacSystemFont,"SF Pro Text","PingFang SC","Microsoft YaHei",system-ui,sans-serif;
31
- --nmono:"SF Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
32
+ --nfont:-apple-system,BlinkMacSystemFont,"Segoe UI Variable Text","Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei UI","Microsoft YaHei","Noto Sans SC","Source Han Sans SC",system-ui,sans-serif; /* YaHei UI 优先于 YaHei(UI 优化版字距松/笔画清爽);Win11 可变字体优先 */
33
+ --nmono:"SF Mono",ui-monospace,"Cascadia Code","Cascadia Mono",SFMono-Regular,Menlo,Consolas,monospace; /* Win11 Cascadia 优先于 Consolas */
32
34
  /* 阴影(极淡,仅面板) */
33
35
  --nshadow:0 18px 50px rgba(0,0,0,.12),0 2px 8px rgba(0,0,0,.06);
34
36
  }
35
37
 
38
+ /* color-mix 增强:从主文字色(--ntx)派生半透明灰,亮色呈浅灰、暗色呈浅白,随 DSH 主题自动切换。
39
+ 修复 --dsw-alias-bg-layer-2/3 在亮色主题解析为 #fff 导致列表分栏/胶囊/选中态与面板底色撞色。
40
+ color-mix 不可用时回退到上方中性 rgba 灰(不依赖 layer token,明暗面板均可见)。 */
41
+ @supports (color: color-mix(in srgb, #fff, transparent)){
42
+ .dsh-notes-floating,.dsh-notes-fab{
43
+ --nsf:color-mix(in srgb, var(--ntx) 5%, transparent);
44
+ --nbg-hover:color-mix(in srgb, var(--ntx) 8%, transparent);
45
+ --nbg-sel:color-mix(in srgb, var(--ntx) 12%, transparent);
46
+ --ntx-soft:color-mix(in srgb, var(--ntx) 88%, transparent);
47
+ }
48
+ .dsh-notes-instruct-box{
49
+ --nbg-hover:color-mix(in srgb, var(--ntx) 8%, transparent);
50
+ }
51
+ }
52
+
36
53
  /* ===== tooltip(作用域隔离) ===== */
37
54
  .dsh-nt{position:relative}
38
55
  .dsh-nt[data-tooltip]::after{content:attr(data-tooltip);position:fixed;background:rgba(28,28,30,.95);color:#fff;padding:5px 9px;border-radius:6px;font-size:11px;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .12s;z-index:2147483646;font-family:var(--nfont)}
@@ -88,14 +105,14 @@
88
105
  .dsh-notes-kinds{flex:1;display:flex;align-items:center;gap:6px;overflow-x:auto;padding:0;border-bottom:none}
89
106
  .dsh-notes-kind-chip{background:transparent;border:1px solid var(--nbd);border-radius:13px;color:var(--nt2);font-size:11px;padding:3px 11px;cursor:pointer;transition:all .15s;white-space:nowrap;font-weight:500}
90
107
  .dsh-notes-kind-chip:hover{border-color:var(--nt3);color:var(--ntx)}
91
- .dsh-notes-kind-chip.on{background:var(--ntx);border-color:var(--ntx);color:var(--nbg)}
108
+ .dsh-notes-kind-chip.on{background:var(--nbg-sel);border-color:transparent;color:var(--ntx);font-weight:600} /* 柔和灰底深字(贴 DSH 侧边栏选中风),不再黑底白字 */
92
109
  .dsh-notes-pin-toggle{margin-left:auto;background:transparent;border:1px solid var(--nbd);border-radius:13px;color:var(--nt2);font-size:11px;padding:3px 11px;cursor:pointer;transition:all .15s;white-space:nowrap;font-weight:500;flex-shrink:0}
93
110
  .dsh-notes-pin-toggle:hover{border-color:var(--nt3);color:var(--ntx)}
94
- .dsh-notes-pin-toggle.on{background:var(--nacc-soft);border-color:var(--nacc);color:var(--nacc)}
111
+ .dsh-notes-pin-toggle.on{background:var(--nbg-sel);border-color:transparent;color:var(--ntx);font-weight:600} /* 与 kind-chip.on 同款柔和灰底 */
95
112
 
96
113
  /* ===== 列表区(两列布局) ===== */
97
114
  .dsh-notes-content{display:flex;flex:1;overflow:hidden;min-height:0;position:relative}
98
- .dsh-notes-divider{width:5px;cursor:col-resize;flex-shrink:0;background:transparent;transition:background .15s;position:relative;z-index:5}
115
+ .dsh-notes-divider{width:5px;cursor:col-resize;flex-shrink:0;background:var(--nbd);transition:background .15s;position:relative;z-index:5}
99
116
  .dsh-notes-list{overflow:hidden;flex-shrink:0;display:flex;flex-direction:column;background:var(--nsf);min-width:180px;max-width:400px;border-right:1px solid var(--nbd-soft)}
100
117
  .dsh-notes-list-items{flex:1;overflow-y:auto;padding:2px 5px}
101
118
  .dsh-notes-list-items::-webkit-scrollbar{width:8px}
@@ -109,7 +126,6 @@
109
126
  /* ===== 笔记列表项(Apple Notes:0 圆角、无阴影、纯背景选中) ===== */
110
127
  .dsh-note-item{padding:5px 9px;margin:0;border-radius:var(--nr-md);cursor:pointer;transition:background .12s;position:relative;background:transparent;box-shadow:none;user-select:none}
111
128
  .dsh-note-item:hover{background:var(--nbg-hover)}
112
- .dsh-note-item:hover .dsh-note-delete{opacity:1}
113
129
  .dsh-note-item.selected{background:var(--nbg-sel)}
114
130
  .dsh-note-item.focused{background:var(--nbg-hover)}
115
131
  .dsh-note-item.pinned{}
@@ -133,15 +149,14 @@
133
149
  .dsh-note-meta-sess{font-size:10px;color:var(--nt3);white-space:nowrap;flex-shrink:0}
134
150
  .dsh-note-meta-tags{font-size:10px;color:var(--nt3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}
135
151
  .dsh-note-preview{display:none} /* Apple Notes 默认不显示预览,保持列表极简 */
136
- .dsh-note-delete{position:absolute;right:6px;top:8px;background:var(--nbg);border:1px solid var(--nbd);color:var(--nt3);font-size:11px;cursor:pointer;padding:3px 7px;border-radius:6px;opacity:0;transition:all .12s;line-height:1;font-weight:500}
137
- .dsh-note-delete:hover{color:#ff3b30;border-color:rgba(255,59,48,.4)}
152
+ /* 列表项删除已迁入右键菜单(dsh-notes-ctxmenu),不再用悬浮 × 按钮 */
138
153
  .dsh-note-jump{display:none} /* 跳转并入 meta 里的会话,不再单独按钮 */
139
154
  .dsh-note-topic-picker{display:none} /* 主题改到详情区编辑,列表不再内联选择器 */
140
155
 
141
156
  /* ===== 详情区(阅读 + 编辑) ===== */
142
157
  .dsh-notes-editor{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden;background:var(--nbg)}
143
158
  .dsh-notes-editor-empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--nt3);gap:12px;padding:30px;text-align:center;font-size:13px;background:var(--nbg)}
144
- .dsh-notes-editor-empty-ic{font-size:34px;opacity:.5;line-height:1}
159
+ .dsh-notes-editor-empty-ic{font-size:30px;opacity:.55;line-height:1;display:inline-flex;align-items:center;justify-content:center;width:60px;height:60px;border-radius:50%;background:var(--nbg-hover);margin-bottom:4px}
145
160
  .dsh-notes-editor-empty-t{font-size:15px;font-weight:600;color:var(--nt2)}
146
161
  .dsh-notes-editor-empty-s{font-size:12px;color:var(--nt3)}
147
162
 
@@ -157,6 +172,20 @@
157
172
  .dsh-notes-ed-topic{background:transparent;border:1px solid transparent;border-radius:6px;color:var(--nacc);font-size:12px;padding:4px 8px;font-family:var(--nfont);outline:none;max-width:140px;transition:border-color .15s,background .15s}
158
173
  .dsh-notes-ed-topic:hover{background:var(--nbg-hover)}
159
174
  .dsh-notes-ed-topic:focus{border-color:var(--nacc);background:var(--nbg)}
175
+ /* 详情区 kind tint chip(低饱和 tint 底 + 同系深色字,明暗均可读) */
176
+ .dsh-notes-ed-kind-chip{border-radius:13px;font-weight:600;border:1px solid transparent;padding:4px 10px}
177
+ .dsh-notes-ed-kind-chip.dsh-notes-ed-kind-note{background:rgba(10,132,255,.13);color:#0a84ff}
178
+ .dsh-notes-ed-kind-chip.dsh-notes-ed-kind-decision{background:rgba(191,90,242,.13);color:#bf5af2}
179
+ .dsh-notes-ed-kind-chip.dsh-notes-ed-kind-todo{background:rgba(255,149,0,.15);color:#ff9500}
180
+ .dsh-notes-ed-kind-chip.dsh-notes-ed-kind-link{background:rgba(90,200,250,.16);color:#0a9fc4}
181
+ .dsh-notes-ed-kind-chip.dsh-notes-ed-kind-quote{background:rgba(255,55,95,.13);color:#ff375f}
182
+ /* 详情区标签输入(灰色小胶囊形态) */
183
+ .dsh-notes-ed-tags-input{background:var(--nbg-hover);border:1px solid transparent;border-radius:11px;color:var(--nt2);font-size:11px;padding:3px 10px;font-family:var(--nfont);outline:none;max-width:160px;transition:border-color .15s,background .15s}
184
+ .dsh-notes-ed-tags-input:hover{background:var(--nbg-sel)}
185
+ .dsh-notes-ed-tags-input:focus{border-color:var(--nacc);background:var(--nbg);color:var(--ntx)}
186
+ /* 详情区标签 chip 行(灰色小胶囊,与列表项同款) */
187
+ .dsh-notes-ed-tags-chips{display:flex;flex-wrap:wrap;gap:5px;align-items:center}
188
+ .dsh-notes-ed-tag-chip{background:var(--nbg-hover);border-radius:10px;color:var(--nt2);font-size:10px;padding:2px 9px;font-weight:500;white-space:nowrap;line-height:1.5}
160
189
  .dsh-notes-ed-inject{background:transparent;border:1px solid var(--nbd);border-radius:6px;color:var(--nt2);font-size:11px;padding:4px 8px;font-family:var(--nmono);outline:none;width:120px}
161
190
  .dsh-notes-ed-inject:focus{border-color:var(--nacc);color:var(--ntx)}
162
191
  .dsh-notes-ed-meta-right{margin-left:auto;display:flex;gap:4px;align-items:center}
@@ -174,7 +203,7 @@
174
203
  /* 范围浮层触发按钮 */
175
204
  .dsh-notes-ed-scope-wrap{position:relative;flex:1;min-width:0}
176
205
  .dsh-notes-scope-trigger{background:transparent;border:1px solid var(--nbd);border-radius:6px;color:var(--ntx);font-size:12px;padding:4px 10px;cursor:pointer;transition:border-color .15s;font-family:var(--nfont);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}
177
- .dsh-notes-scope-trigger:hover{border-color:var(--nacc)}
206
+ .dsh-notes-scope-trigger:hover{border-color:var(--nt3)}
178
207
  .dsh-notes-scope-caret{color:var(--nt3);font-size:10px}
179
208
  /* 逐级浮层面板 */
180
209
  .dsh-notes-scope-panel{position:absolute;top:calc(100% + 4px);left:0;min-width:230px;max-width:300px;max-height:280px;overflow-y:auto;background:var(--nbg);border:1px solid var(--nbd);border-radius:10px;box-shadow:var(--nshadow);padding:6px;z-index:30;font-family:var(--nfont)}
@@ -189,7 +218,7 @@
189
218
  .dsh-notes-dispatch-mask{position:fixed;inset:0;background:rgba(0,0,0,.28);z-index:60;display:flex;align-items:center;justify-content:center}
190
219
  .dsh-notes-dispatch-modal{width:340px;max-width:90%;background:var(--nbg);border:1px solid var(--nbd);border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,.3);padding:16px;font-family:var(--nfont);max-height:80vh;overflow-y:auto}
191
220
  .dsh-notes-dispatch-modal-t{font-size:14px;font-weight:700;color:var(--ntx);margin-bottom:10px}
192
- .dsh-notes-dispatch-todo{background:var(--nbg-soft);border:1px solid var(--nbd-soft);border-radius:8px;padding:9px 11px;margin-bottom:10px}
221
+ .dsh-notes-dispatch-todo{background:var(--nsf);border:1px solid var(--nbd-soft);border-radius:8px;padding:9px 11px;margin-bottom:10px}
193
222
  .dsh-notes-dispatch-todo-t{font-size:12px;font-weight:600;color:var(--ntx);margin-bottom:3px}
194
223
  .dsh-notes-dispatch-todo-b{font-size:11px;color:var(--nt3);line-height:1.5;max-height:60px;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}
195
224
  .dsh-notes-dispatch-instr{width:100%;box-sizing:border-box;background:var(--nbg);border:1px solid var(--nbd);border-radius:8px;color:var(--ntx);font-size:12px;padding:8px 10px;resize:vertical;font-family:var(--nfont);margin-bottom:10px;outline:none}
@@ -205,36 +234,74 @@
205
234
  .dsh-notes-dispatch-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}
206
235
  .dsh-notes-dispatch-cancel{background:transparent;border:1px solid var(--nbd);border-radius:7px;color:var(--nt2);font-size:12px;padding:6px 16px;cursor:pointer;font-family:var(--nfont)}
207
236
  .dsh-notes-dispatch-cancel:hover{border-color:var(--nt3);color:var(--ntx)}
208
- .dsh-notes-dispatch-ok{background:var(--nacc);border:none;border-radius:7px;color:#fff;font-size:12px;font-weight:600;padding:6px 16px;cursor:pointer;font-family:var(--nfont)}
237
+ .dsh-notes-dispatch-ok{background:var(--nacc);border:none;border-radius:7px;color:var(--dsw-alias-label-primary-foreground,#fff);font-size:12px;font-weight:600;padding:6px 16px;cursor:pointer;font-family:var(--nfont)}
209
238
  .dsh-notes-dispatch-ok:hover{filter:brightness(1.08)}
210
239
  .dsh-notes-dispatch-ok:disabled{opacity:.5;cursor:not-allowed}
211
240
  /* ===== 派发历史(详情区) ===== */
212
- .dsh-notes-dispatch-history{margin-top:10px;padding-top:10px;border-top:1px dashed var(--nbd-soft)}
213
- .dsh-notes-dispatch-history-t{font-size:11px;font-weight:600;color:var(--nt2);margin-bottom:6px}
214
- .dsh-notes-dispatch-rec{padding:5px 8px;border-radius:6px;background:var(--nbg-soft);margin-bottom:4px}
241
+ /* 派发历史(可折叠:默认 collapsed 只占一行标题;点击标题行切换 open/collapsed,不挤压正文) */
242
+ .dsh-notes-dispatch-history{margin:10px 20px 10px;padding-top:10px;border-top:1px dashed var(--nbd-soft)} /* 左右 20px 与正文(ed-body/preview padding)对齐 */
243
+ .dsh-notes-dispatch-history-t{font-size:11px;font-weight:600;color:var(--nt2);margin-bottom:6px;cursor:pointer;user-select:none;display:flex;align-items:center;gap:5px;transition:color .15s}
244
+ .dsh-notes-dispatch-history-t:hover{color:var(--ntx)}
245
+ .dsh-notes-dispatch-history.collapsed{margin-bottom:6px}
246
+ .dsh-notes-dispatch-history.collapsed .dsh-notes-dispatch-history-t{margin-bottom:0}
247
+ .dsh-notes-dispatch-rec{padding:5px 8px;border-radius:6px;background:var(--nsf);border:1px solid var(--nbd-soft);margin-bottom:4px}
215
248
  .dsh-notes-dispatch-rec-top{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
216
249
  .dsh-notes-dispatch-rec-t{font-size:11px;font-weight:600;color:var(--nacc)}
217
250
  .dsh-notes-dispatch-rec-m{font-size:10px;color:var(--nt3);flex-shrink:0}
218
251
  .dsh-notes-dispatch-rec-i{font-size:10px;color:var(--nt2);margin-top:2px;line-height:1.4}
219
252
  .dsh-notes-dispatch-rec.done{opacity:.55}
220
253
  .dsh-notes-dispatch-rec.done .dsh-notes-dispatch-rec-t{color:var(--nt3)}
221
- .dsh-notes-dispatch-done-btn{margin-top:4px;background:transparent;border:1px solid var(--nbd);border-radius:5px;color:var(--nkind-note);font-size:10px;padding:1px 8px;cursor:pointer;font-family:var(--nfont);transition:all .15s}
222
- .dsh-notes-dispatch-done-btn:hover{border-color:var(--nkind-note);background:rgba(52,199,89,.1)}
254
+ .dsh-notes-dispatch-done-btn{margin-top:4px;background:transparent;border:none;border-radius:0;color:var(--nacc);font-size:10px;padding:1px 4px;cursor:pointer;font-family:var(--nfont);transition:color .15s;text-decoration:none}
255
+ .dsh-notes-dispatch-done-btn:hover{text-decoration:underline}
256
+ /* 派发待处理状态点(橙色圆点) */
257
+ .dsh-notes-dispatch-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--nwarn,#ff9500);margin-right:5px;vertical-align:middle;flex-shrink:0}
223
258
 
224
259
  /* 详情操作按钮(右上角 icon) */
225
260
  .dsh-notes-ed-action{background:transparent;border:1px solid var(--nbd);border-radius:6px;color:var(--nt2);font-size:12px;cursor:pointer;padding:4px 10px;line-height:1;transition:all .15s;font-family:var(--nfont)}
226
261
  .dsh-notes-ed-action:hover{background:var(--nbg-hover);color:var(--ntx)}
227
262
  .dsh-notes-ed-action.on{background:var(--nacc-soft);border-color:var(--nacc);color:var(--nacc)}
228
263
  .dsh-notes-ed-action.danger:hover{color:#ff3b30;border-color:rgba(255,59,48,.4);background:rgba(255,59,48,.06)}
264
+ /* 派发主按钮:主题色描边 + 浅色底(hover 加深)。
265
+ 须置于 .dsh-notes-ed-action 基础规则之后——派发按钮同时带 ed-action 与 dispatch-btn 两个类,
266
+ 同优先级(0-1-0)下后定义者胜,否则 ed-action 的透明底/灰字会覆盖派发按钮主样式。 */
267
+ .dsh-notes-dispatch-btn{background:var(--nacc-soft);border:1px solid var(--nacc);color:var(--nacc);font-weight:600}
268
+ .dsh-notes-dispatch-btn:hover{background:var(--nacc);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--nacc)}
229
269
 
230
270
  /* 正文(无边框、宽行高、自动保存) */
231
- .dsh-notes-editor-body{flex:1;min-height:0;background:transparent;border:none;outline:none;color:var(--ntx);padding:14px 20px 20px;font-size:14px;font-family:var(--nfont);line-height:1.65;resize:none;margin:0;box-sizing:border-box;letter-spacing:.1px}
271
+ .dsh-notes-editor-body{flex:1;min-height:0;background:transparent;border:none;outline:none;color:var(--ntx-soft);caret-color:var(--nacc);padding:18px 24px 24px;font-size:14px;font-family:var(--nfont);line-height:1.78;resize:none;margin:0;box-sizing:border-box;letter-spacing:.2px;text-rendering:optimizeLegibility;font-synthesis-weight:none}
232
272
  .dsh-notes-editor-body::placeholder{color:var(--nt3)}
233
273
  .dsh-notes-editor-body.mono{font-family:var(--nmono);font-size:13px;line-height:1.6}
234
274
 
275
+ /* ===== Markdown 预览态(dsh-notes-preview-* 前缀,走既有 token 体系)===== */
276
+ .dsh-notes-preview-container{flex:1;min-height:0;overflow-y:auto;padding:18px 24px 24px;font-size:14px;font-family:var(--nfont);line-height:1.78;color:var(--ntx-soft);letter-spacing:.2px;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-synthesis-weight:none}
277
+ .dsh-notes-preview-container::-webkit-scrollbar{width:8px}
278
+ .dsh-notes-preview-container::-webkit-scrollbar-thumb{background:var(--nbd);border-radius:4px}
279
+ .dsh-notes-preview-container::-webkit-scrollbar-thumb:hover{background:var(--nt3)}
280
+ .dsh-notes-preview-container>:first-child{margin-top:0}
281
+ .dsh-notes-preview-container>:last-child{margin-bottom:0}
282
+ .dsh-notes-preview-p{margin:0 0 14px;word-break:break-word}
283
+ .dsh-notes-preview-h1{font-size:19px;font-weight:700;margin:24px 0 12px;color:var(--ntx);letter-spacing:.3px}
284
+ .dsh-notes-preview-h2{font-size:16.5px;font-weight:600;margin:20px 0 10px;color:var(--ntx);letter-spacing:.2px}
285
+ .dsh-notes-preview-h3{font-size:14.5px;font-weight:600;margin:16px 0 8px;color:var(--ntx)}
286
+ .dsh-notes-preview-code{background:var(--nbg-hover,var(--nsf));border:1px solid var(--nbd-soft);border-radius:var(--nr-md);padding:14px 16px;margin:0 0 14px;overflow-x:auto;font-family:var(--nmono);font-size:13px;line-height:1.65}
287
+ .dsh-notes-preview-code code{background:none;border:none;padding:0;font-family:inherit;font-size:inherit;color:var(--ntx)}
288
+ .dsh-notes-preview-container code{background:var(--nbg-hover,var(--nsf));border:1px solid var(--nbd-soft);border-radius:4px;padding:1px 6px;font-family:var(--nmono);font-size:12.5px;color:var(--ntx)}
289
+ .dsh-notes-preview-container pre code{background:none;border:none;padding:0;font-size:inherit}
290
+ .dsh-notes-preview-ul,.dsh-notes-preview-ol{margin:0 0 14px;padding-left:24px}
291
+ .dsh-notes-preview-ul li,.dsh-notes-preview-ol li{margin-bottom:5px;word-break:break-word}
292
+ .dsh-notes-preview-quote{margin:0 0 14px;padding:10px 16px;border-left:3px solid var(--nbd);background:var(--nbg-hover,var(--nsf));border-radius:0 var(--nr-sm) var(--nr-sm) 0;color:var(--nt2)} /* 中文排版忌斜体:去 italic,左边框用中性色 */
293
+ .dsh-notes-preview-container a{color:var(--nacc);text-decoration:none;border-bottom:1px solid transparent;transition:border-color .15s}
294
+ .dsh-notes-preview-container a:hover{border-bottom-color:var(--nacc)}
295
+ .dsh-notes-preview-container strong{font-weight:600;color:var(--ntx)}
296
+ .dsh-notes-preview-container em{font-style:italic}
297
+ /* 预览/编辑切换按钮(详情底部状态条左侧) */
298
+ .dsh-notes-preview-toggle{background:transparent;border:1px solid var(--nbd);border-radius:var(--nr-sm);color:var(--nt2);font-size:11px;padding:3px 10px;cursor:pointer;transition:all .15s;font-family:var(--nfont);line-height:1.5;white-space:nowrap;flex-shrink:0}
299
+ .dsh-notes-preview-toggle:hover{border-color:var(--nacc);color:var(--nacc);background:var(--nacc-soft)}
300
+ .dsh-notes-preview-toggle.on{background:var(--nacc-soft);border-color:var(--nacc);color:var(--nacc);font-weight:600}
301
+
235
302
  /* 详情底部:轻量状态条(保存提示 + 字数) */
236
- .dsh-notes-ed-foot{display:flex;align-items:center;justify-content:space-between;padding:6px 20px 10px;font-size:11px;color:var(--nt3);flex-shrink:0;border-top:1px solid var(--nbd-soft)}
237
- .dsh-notes-ed-saved{opacity:0;transition:opacity .2s;color:var(--nkind-todo)}
303
+ .dsh-notes-ed-foot{display:flex;align-items:center;justify-content:space-between;padding:8px 24px 12px;font-size:11px;color:var(--nt3);flex-shrink:0;border-top:1px solid var(--nbd-soft)}
304
+ .dsh-notes-ed-saved{opacity:0;transition:opacity .2s;color:var(--nt3)} /* 保存提示收敛为三级灰,不再绿色跳眼 */
238
305
  .dsh-notes-ed-saved.show{opacity:1}
239
306
 
240
307
  /* ===== 状态与反馈 ===== */
@@ -244,7 +311,7 @@
244
311
  .dsh-notes-empty-ic{font-size:34px;opacity:.4;line-height:1;margin-bottom:4px}
245
312
  .dsh-notes-empty-t{font-size:14px;font-weight:600;color:var(--nt2)}
246
313
  .dsh-notes-empty-s{font-size:12px;color:var(--nt3);line-height:1.6;max-width:220px}
247
- .dsh-notes-empty-btn{margin-top:10px;background:var(--nacc);color:#fff;border:none;border-radius:var(--nr-md);padding:7px 18px;font-size:12px;font-weight:500;cursor:pointer;transition:filter .15s;font-family:var(--nfont)}
314
+ .dsh-notes-empty-btn{margin-top:10px;background:var(--nacc);color:var(--dsw-alias-label-primary-foreground,#fff);border:none;border-radius:var(--nr-md);padding:7px 18px;font-size:12px;font-weight:500;cursor:pointer;transition:filter .15s;font-family:var(--nfont)}
248
315
  .dsh-notes-empty-btn:hover{filter:brightness(1.06)}
249
316
  /* 搜索高亮(琥珀色,深浅通用;明暗随 DSH 主题,不再用系统级 prefers-color-scheme) */
250
317
  .dsh-notes-mark{background:rgba(255,213,79,.35);color:inherit;border-radius:2px;padding:0 1px}
@@ -264,7 +331,7 @@
264
331
 
265
332
  /* ===== 选区快速记录浮动按钮 ===== */
266
333
  /* 注意:此按钮 position:fixed 且不在 .dsh-notes-floating 内,拿不到面板的设计令牌变量,必须用具体颜色值 */
267
- .dsh-notes-sel-btn{position:fixed;z-index:2147483646;background:#0a84ff;color:#fff;border:none;border-radius:8px;padding:7px 14px;font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 4px 16px rgba(10,132,255,.5),0 1px 3px rgba(0,0,0,.2);line-height:1;white-space:nowrap;transition:filter .15s,transform .1s;font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",system-ui,sans-serif}
334
+ .dsh-notes-sel-btn{position:fixed;z-index:2147483646;background:#0a84ff;color:var(--dsw-alias-label-primary-foreground,#fff);border:none;border-radius:8px;padding:7px 14px;font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 4px 16px rgba(10,132,255,.5),0 1px 3px rgba(0,0,0,.2);line-height:1;white-space:nowrap;transition:filter .15s,transform .1s;font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",system-ui,sans-serif}
268
335
  .dsh-notes-sel-btn:hover{filter:brightness(1.08);transform:translateY(-1px)}
269
336
 
270
337
  /* ===== T3 选区指令框(划选后弹出:预览 + 备注输入 + 记录/取消) ===== */
@@ -276,7 +343,7 @@
276
343
  --nt2:var(--dsw-alias-label-secondary,#6c6c70);
277
344
  --nt3:var(--dsw-alias-label-secondary,#aeaeb2);
278
345
  --nacc:var(--dsw-alias-brand-primary,#0a84ff);
279
- --nbg-hover:var(--dsw-alias-bg-layer-2,#f2f2f7);
346
+ --nbg-hover:rgba(128,128,128,.08); /* color-mix 兜底(避免亮色下与面板底色撞色) */
280
347
  --nr-md:8px;
281
348
  --nfont:-apple-system,BlinkMacSystemFont,"SF Pro Text","PingFang SC","Microsoft YaHei",system-ui,sans-serif;
282
349
  position:fixed;z-index:2147483646;width:320px;background:var(--nbg);border:1px solid var(--nbd);border-radius:12px;
@@ -289,14 +356,14 @@
289
356
  .dsh-notes-instruct-actions{display:flex;gap:8px;padding:8px 11px;justify-content:flex-end}
290
357
  .dsh-notes-instruct-btn{background:transparent;border:1px solid var(--nbd);border-radius:var(--nr-md);color:var(--nt2);padding:6px 14px;cursor:pointer;font-size:12px;font-weight:500;font-family:var(--nfont);line-height:1;transition:all .15s}
291
358
  .dsh-notes-instruct-btn:hover{border-color:var(--nt3);color:var(--ntx)}
292
- .dsh-notes-instruct-btn.primary{background:var(--nacc);color:#fff;border-color:var(--nacc)}
293
- .dsh-notes-instruct-btn.primary:hover{filter:brightness(1.06);color:#fff}
359
+ .dsh-notes-instruct-btn.primary{background:var(--nacc);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--nacc)}
360
+ .dsh-notes-instruct-btn.primary:hover{filter:brightness(0.9);color:var(--dsw-alias-label-primary-foreground,#fff)}
294
361
 
295
362
  /* ===== 旧类名兼容(已废弃,保留避免渲染期报错;后续清理 client 引用) ===== */
296
363
  .dsh-notes-capture{padding:10px 14px;border-bottom:1px solid var(--nbd-soft);display:flex;gap:8px;background:var(--nbg);flex-shrink:0;align-items:flex-end}
297
364
  .dsh-notes-capture-input{flex:1;min-width:0;background:var(--nbg-hover);border:1px solid transparent;border-radius:var(--nr-md);color:var(--ntx);padding:8px 11px;font-size:13px;resize:none;height:36px;min-height:36px;max-height:110px;font-family:var(--nfont);box-sizing:border-box;transition:border-color .15s,background .15s;line-height:1.5;outline:none}
298
365
  .dsh-notes-capture-input:focus{border-color:var(--nacc);background:var(--nbg)}
299
- .dsh-notes-capture-btn{background:var(--nacc);color:#fff;border:none;border-radius:var(--nr-md);padding:0 15px;height:36px;cursor:pointer;font-size:13px;font-weight:500;white-space:nowrap;flex-shrink:0;transition:filter .15s}
366
+ .dsh-notes-capture-btn{background:var(--nacc);color:var(--dsw-alias-label-primary-foreground,#fff);border:none;border-radius:var(--nr-md);padding:0 15px;height:36px;cursor:pointer;font-size:13px;font-weight:500;white-space:nowrap;flex-shrink:0;transition:filter .15s}
300
367
  .dsh-notes-capture-btn:hover:not(:disabled){filter:brightness(1.06)}
301
368
  .dsh-notes-capture-btn:disabled{opacity:.4;cursor:default}
302
369
  .dsh-notes-capture-btn.saved{background:#34c759}
@@ -306,7 +373,14 @@
306
373
  .dsh-notes-editor-topic{flex:1;min-width:0}
307
374
  .dsh-notes-editor-tags{flex:1.5;min-width:0}
308
375
  .dsh-notes-editor-actions{padding:0 14px 14px;display:flex;gap:8px;flex-shrink:0}
309
- .dsh-notes-save-btn{background:var(--nacc);color:#fff;border:none;border-radius:var(--nr-md);padding:8px 16px;cursor:pointer;font-size:13px;font-weight:500;transition:filter .15s}
376
+ .dsh-notes-save-btn{background:var(--nacc);color:var(--dsw-alias-label-primary-foreground,#fff);border:none;border-radius:var(--nr-md);padding:8px 16px;cursor:pointer;font-size:13px;font-weight:500;transition:filter .15s}
310
377
  .dsh-notes-save-btn:hover{filter:brightness(1.06)}
311
378
  .dsh-notes-delete-btn{background:transparent;color:var(--nt3);border:1px solid var(--nbd);border-radius:var(--nr-md);padding:8px 16px;cursor:pointer;font-size:13px;font-weight:500;transition:all .15s}
312
379
  .dsh-notes-delete-btn:hover{color:#ff3b30;border-color:rgba(255,59,48,.4)}
380
+
381
+ /* 列表项右键菜单(替代悬浮 ×):面板风格、点击外部/Esc 关闭 */
382
+ .dsh-notes-ctxmenu{position:absolute;z-index:60;min-width:150px;background:var(--nbg);border:1px solid var(--nbd);border-radius:var(--nr-md);box-shadow:0 6px 24px rgba(0,0,0,.14);padding:4px;display:flex;flex-direction:column;gap:1px}
383
+ .dsh-notes-ctxmenu-item{display:flex;align-items:center;gap:7px;background:transparent;border:none;color:var(--ntx);font-size:12px;text-align:left;padding:6px 10px;border-radius:6px;cursor:pointer;transition:background .1s;font-family:var(--nfont)}
384
+ .dsh-notes-ctxmenu-item:hover{background:var(--nbg-hover)}
385
+ .dsh-notes-ctxmenu-item.danger{color:var(--ntx)}
386
+ .dsh-notes-ctxmenu-item.danger:hover{color:#ff3b30;background:rgba(255,59,48,.08)}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-notes-plugin",
3
3
  "description": "DeepSeek Harness 会话感知本地笔记:把会话里的决策与约定沉淀成本地 Markdown 笔记,自动注入系统提示、可派发给活跃会话,解决「聊完就丢」的上下文断层",
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
7
7
  "scripts": {