@mzzsfy/dsh-settings-nav-icons 0.3.0 → 0.4.1

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
@@ -38,7 +38,7 @@ DeepSeek Harness 纯前端插件:设置弹窗左侧导航与 dsh-market 插件
38
38
 
39
39
  - **外部声明**:其他插件运行时注册自己的 label/插件名 → 图标,优先级最高(见下节)。
40
40
  - **内置映射**:官方分区与无法改源的第三方分区。
41
- - **关键词**:插件名/分区名整词命中语义关键词得主题图(git→分支、im→机器人、search→放大镜、usage→图表等 19 组规则);整词边界匹配,`im` 不误伤 `important`。
41
+ - **关键词**:插件名/分区名整词命中语义关键词得主题图(git→分支、im→机器人、search→放大镜、usage→图表、wallet→钱包、dash→表盘等 21 组规则);整词边界匹配,`im` 不误伤 `important`。
42
42
  - **备用池**:星芒/层叠/标签/网格四个中性图形,按名称稳定哈希取一个——同一名称永远同一图形,重渲染不闪动。dsh-market 目录内全部插件(含下载量前 1000)至此都有图标。
43
43
 
44
44
  ## 图标声明机制(其他插件接入)
@@ -54,12 +54,12 @@ window.__navicIcons.register({ '消息通知': 'bell' })
54
54
  ```
55
55
 
56
56
  - 键:分区显示文本(设置导航)或插件名(dsh-market 卡片)。两域共用一张表,同名时以先命中者生效,键请取不易与官方分区撞名的插件名。官方双语分区按等价组解析:任一语言 label 作键,两种语言下均命中(消除语言切换的声明键漂移)。
57
- - 值:内置 glyph 名(`tune/theme/bot/market/cube/mcp/shield/cards/plan/bell/wrench/archive/spark/layers/tag/grid/git/search/term/chart/code/doc/db/flow/globe/lock/image/zap`)或完整 16×16 `<svg>` 字符串。svg 字符串过安全门:完整开标签(大小写不敏感,拒绝 `<svgx` 残串)且单根闭合(首个 `</svg>` 后不得再有内容,堵尾缀活动 HTML);不带 `on*` 事件属性(`\b` 前界堵斜杠分隔绕过)、不带 `<script>`/`<style>`(内联样式全文档生效且 @import 可外联)/`<foreignObject>`/SMIL 动画(`<animate>`/`<set>` 等)载体;不带 `href`/`xlink:href` 及 `attributeName="href"` 注入(16×16 静态图标无合法引用/动画场景,外联请求一并封死);不带 `javascript:`(纵深);长度 ≤4096 字符;glyph 名查表经 `typeof` 收口,原型链成员不可能被注入。
57
+ - 值:内置 glyph 名(`tune/theme/bot/market/cube/mcp/shield/cards/plan/bell/wrench/archive/spark/layers/tag/grid/git/search/term/chart/code/doc/db/flow/globe/lock/image/zap/wallet/gauge`)或完整 16×16 `<svg>` 字符串。svg 字符串过安全门:完整开标签(大小写不敏感,拒绝 `<svgx` 残串)且单根闭合(首个 `</svg>` 后不得再有内容,堵尾缀活动 HTML);不带 `on*` 事件属性(`\b` 前界堵斜杠分隔绕过)、不带 `<script>`/`<style>`(内联样式全文档生效且 @import 可外联)/`<foreignObject>`/SMIL 动画(`<animate>`/`<set>` 等)载体;不带 `href`/`xlink:href` 及 `attributeName="href"` 注入(16×16 静态图标无合法引用/动画场景,外联请求一并封死);不带 `javascript:`(纵深);长度 ≤4096 字符;glyph 名查表经 `typeof` 收口,原型链成员不可能被注入。
58
58
  - 声明值经归一化后写入注册表;同值重复注册幂等短路;非法值(未知 glyph/被安全门拒绝/非字符串)撤销该键声明,该分区回到内置映射或关键词/哈希默认管线,已改写的 nav svg 内容就地重写。
59
59
  - 声明持久化在 `window.__navicIconDeclarations`:本插件 client 半区热重载会重跑工厂而生产者不重发注册,持久层让重装实例恢复声明,页面刷新随 window 释放。
60
60
  - 污染面收敛在 `window.__navicIcons` 单一命名空间,插件卸载时移除 API、取消已排定的重绘、队列恢复数组形态——卸载后生产者按上方「方式二」入队等待下一实例,不再驱动 DOM 改写;重载页面后全部还原为官方图标。
61
61
 
62
- 本仓库自有插件的分区图标即全部走此机制:usage-panel(账号余额 → plan)、turn-notify(消息通知 → bell)、maintain(版本与运维 → wrench)、session-manager(会话归档 → archive);四包的注册样板由本包测试做契约锁定。
62
+ 本仓库全部插件包(含本包自画像与仅 host 半区的包)都走此机制显式声明,不依赖关键词/哈希推导;键为「分区 label + 市场短名」双形态:usage-panel(账号余额、dsh-usage-panel → wallet)、usage-dash(使用统计/Usage、dsh-usage-dash → gauge)、turn-notify(消息通知、dsh-turn-notify → bell)、maintain(版本与运维、dsh-maintain → wrench)、session-manager(会话归档、dsh-session-manager → archive)、rs-workflow(若水工作流、dsh-rs-workflow → flow)、cron-board(dsh-cron-board → plan)、think-expand(dsh-think-expand → spark)、model-capability-editor(dsh-model-capability-editor → cube)、auto-trust-all(dsh-auto-trust-all → shield)、llm-pi-gateway(dsh-llm-pi-gateway → globe)、settings-nav-icons(dsh-settings-nav-icons → tune);仅 host 半区的 auto-trust-all 与 llm-pi-gateway 为声明专设最小 client 半区。注册样板由本包测试做契约锁定。
63
63
 
64
64
  ## 内置映射表
65
65
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mzzsfy/dsh-settings-nav-icons",
3
3
  "description": "设置弹窗导航分区与插件市场卡片图标替换:按分区显示文本把回退齿轮换成专属图形,市场卡片头像槽换成插件图标,DOM 观察实现,卸载后重载页面恢复官方图标",
4
- "version": "0.3.0",
4
+ "version": "0.4.1",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "exports": {
package/src/client.js CHANGED
@@ -3,11 +3,21 @@
3
3
  // 背景见 README「实现边界」:settings.* 子槽声明权被原版条目占用,
4
4
  // 接管 Shell 不可行,DOM 观察是唯一不依赖官方契约变更的路径。
5
5
 
6
+ // 本插件市场短名自画像:经同一套声明机制注册(先于 load,渲染端同文件就绪)
7
+ if (typeof window !== 'undefined') {
8
+ const NAV_ICON = { 'dsh-settings-nav-icons': 'tune' }
9
+ if (window.__navicIcons !== undefined) window.__navicIcons.register(NAV_ICON)
10
+ else if (Array.isArray(window.__navicIconQueue)) window.__navicIconQueue.push(NAV_ICON)
11
+ else window.__navicIconQueue = [NAV_ICON]
12
+ }
13
+
6
14
  window.__ModuleLoader__.load({
7
15
  id: '@mzzsfy/dsh-settings-nav-icons',
8
16
  factory(require) {
9
17
  // ---- 官方 DOM 字面量标识(设置弹窗导航,CSS Modules 哈希前缀稳定) ----
10
18
 
19
+ // 注册 id 单点定义:样式 data-plugin 标记与装载归属同源,防字面量双写漂移
20
+ const PLUGIN_ID = '@mzzsfy/dsh-settings-nav-icons'
11
21
  const SELECTOR_CELL = 'button.VOzbGW_navCell'
12
22
  const SELECTOR_LABEL = '.VOzbGW_navLabel'
13
23
  const ATTR_MARK = 'data-navic'
@@ -260,6 +270,8 @@ window.__ModuleLoader__.load({
260
270
 
261
271
  function installOverlayStyle() {
262
272
  overlayStyle = document.createElement('style')
273
+ // 自带 data-plugin:缺失时宿主 claimStyles 会把它归属给后续材质化插件,其 HMR 重建即误删
274
+ overlayStyle.setAttribute('data-plugin', PLUGIN_ID)
263
275
  overlayStyle.textContent = OVERLAY_CSS
264
276
  document.head.appendChild(overlayStyle)
265
277
  if (slot !== null) slot.overlayStyle = overlayStyle
@@ -387,6 +399,13 @@ window.__ModuleLoader__.load({
387
399
  rectOf('2.5', '3', '11', '10', '1.2') +
388
400
  circleOf('5.9', '6.1', '1') + pathOf('m3.6 12 3-3 2.3 2.3 1.7-1.7 1.9 1.9'))
389
401
  const ZAP = svgOf(pathOf('M8.9 1.8 3.8 8.9h3.5L7 14.2l5.2-7.1H8.6z'))
402
+ const WALLET = svgOf(
403
+ rectOf('2.5', '3.5', '11', '9.5', '1.5') +
404
+ pathOf('M13.5 7.4h-2.1a1.6 1.6 0 0 0 0 3.2h2.1'))
405
+ const GAUGE = svgOf(
406
+ pathOf('M2.5 12a5.5 5.5 0 1 1 11 0') +
407
+ pathOf('M8 12 10.8 9.2') +
408
+ pathOf('M2.5 12h11'))
390
409
  const FALLBACK = [SPARK, LAYERS, TAG, GRID]
391
410
 
392
411
  // 内置 glyph 名称表:声明值可用名称引用内置图形,避免跨插件复制 svg。
@@ -396,6 +415,7 @@ window.__ModuleLoader__.load({
396
415
  archive: ARCHIVE, spark: SPARK, layers: LAYERS, tag: TAG, grid: GRID,
397
416
  git: GIT, search: SEARCH, term: TERM, chart: CHART, code: CODE, doc: DOC,
398
417
  db: DB, flow: FLOW, globe: GLOBE, lock: LOCK, image: IMAGE, zap: ZAP,
418
+ wallet: WALLET, gauge: GAUGE,
399
419
  }
400
420
 
401
421
  // 声明值解析与安全门:svg 须完整开标签(大小写不敏感)且单根闭合(首个 </svg>
@@ -437,7 +457,9 @@ window.__ModuleLoader__.load({
437
457
  ['doc|note|file|markdown|wiki', DOC],
438
458
  ['database|sqlite|redis|cache|db', DB],
439
459
  ['session|archive|history', ARCHIVE],
460
+ ['dash|dashboard|gauge', GAUGE],
440
461
  ['usage|stat|meter|monitor|radar|metric', CHART],
462
+ ['wallet|balance|money|coin|fund', WALLET],
441
463
  ['flow|workflow|pipeline|task|job|queue', FLOW],
442
464
  ['auth|login|pass|secret|token|lock|guard|crypt', SHIELD],
443
465
  ['mcp', MCP],
@@ -807,6 +829,8 @@ window.__ModuleLoader__.load({
807
829
 
808
830
  function installScrollStyle() {
809
831
  scrollStyle = document.createElement('style')
832
+ // 自带 data-plugin:缺失时宿主 claimStyles 会把它归属给后续材质化插件,其 HMR 重建即误删
833
+ scrollStyle.setAttribute('data-plugin', PLUGIN_ID)
810
834
  scrollStyle.textContent = NAVLIST_SCROLL_CSS
811
835
  document.head.appendChild(scrollStyle)
812
836
  if (slot !== null) slot.scrollStyle = scrollStyle
package/src/logic.mjs CHANGED
@@ -109,6 +109,13 @@ export const IMAGE = svgOf(
109
109
  rectOf('2.5', '3', '11', '10', '1.2') +
110
110
  circleOf('5.9', '6.1', '1') + pathOf('m3.6 12 3-3 2.3 2.3 1.7-1.7 1.9 1.9'))
111
111
  export const ZAP = svgOf(pathOf('M8.9 1.8 3.8 8.9h3.5L7 14.2l5.2-7.1H8.6z'))
112
+ export const WALLET = svgOf(
113
+ rectOf('2.5', '3.5', '11', '9.5', '1.5') +
114
+ pathOf('M13.5 7.4h-2.1a1.6 1.6 0 0 0 0 3.2h2.1'))
115
+ export const GAUGE = svgOf(
116
+ pathOf('M2.5 12a5.5 5.5 0 1 1 11 0') +
117
+ pathOf('M8 12 10.8 9.2') +
118
+ pathOf('M2.5 12h11'))
112
119
 
113
120
  export const FALLBACK = [SPARK, LAYERS, TAG, GRID]
114
121
 
@@ -119,6 +126,7 @@ export const GLYPHS = {
119
126
  archive: ARCHIVE, spark: SPARK, layers: LAYERS, tag: TAG, grid: GRID,
120
127
  git: GIT, search: SEARCH, term: TERM, chart: CHART, code: CODE, doc: DOC,
121
128
  db: DB, flow: FLOW, globe: GLOBE, lock: LOCK, image: IMAGE, zap: ZAP,
129
+ wallet: WALLET, gauge: GAUGE,
122
130
  }
123
131
 
124
132
  // 声明值解析与安全门:svg 须完整开标签(大小写不敏感)且单根闭合(首个 </svg>
@@ -183,7 +191,9 @@ export const NAME_RULES = [
183
191
  ['doc|note|file|markdown|wiki', DOC],
184
192
  ['database|sqlite|redis|cache|db', DB],
185
193
  ['session|archive|history', ARCHIVE],
194
+ ['dash|dashboard|gauge', GAUGE],
186
195
  ['usage|stat|meter|monitor|radar|metric', CHART],
196
+ ['wallet|balance|money|coin|fund', WALLET],
187
197
  ['flow|workflow|pipeline|task|job|queue', FLOW],
188
198
  ['auth|login|pass|secret|token|lock|guard|crypt', SHIELD],
189
199
  ['mcp', MCP],
@@ -143,6 +143,8 @@ function fakeDocument() {
143
143
  value: '',
144
144
  placeholder: '',
145
145
  innerHTML: '',
146
+ attrs: {},
147
+ setAttribute(name, value) { el.attrs[name] = value },
146
148
  appendChild(child) { el.children.push(child) },
147
149
  focus() { el.focused = true },
148
150
  addEventListener(type, fn) { el.handlers[type] = fn },
@@ -250,6 +252,7 @@ test('启动样式:覆盖浮层样式与滚动样式注入 head,卸载移除,重
250
252
  assert.equal(doc.createdStyles.length, 2, '恰两次注入(浮层+滚动)')
251
253
  assert.ok(doc.createdStyles.some((el) => el.textContent.includes('sni-ov')), '浮层样式随启动注入')
252
254
  assert.ok(doc.createdStyles.some((el) => el.textContent === NAVLIST_SCROLL_CSS), '滚动规则与实现同源')
255
+ assert.ok(doc.createdStyles.every((el) => el.attrs['data-plugin'] === '@mzzsfy/dsh-settings-nav-icons'), '样式自带 data-plugin,防宿主 claimStyles 误归属后随他插件 HMR 整批误删')
253
256
  assert.equal(doc.head.children.length, 2, '样式挂载在 head')
254
257
  assert.deepEqual(doc.events, ['style', 'style', 'observe'], '样式注入先于观察器挂载')
255
258
  unload()
@@ -300,11 +303,12 @@ test('启动样式代际:HMR 不卸载重评估由新实例代拆,旧形槽跨
300
303
  test('registerIcons 输入校验:数组与空值拒收', () => {
301
304
  const { win, doc, raf } = loadClient()
302
305
  const unload = boot(win.__loaded, doc)
306
+ raf.flush() // 排空启动期自声明的重贴,后续断言只看本次入参的增量
303
307
  try {
304
308
  win.__navicIcons.register(['bell'])
305
309
  win.__navicIcons.register(null)
306
310
  win.__navicIcons.register('bell')
307
- assert.deepEqual(Object.keys(win.__navicIconDeclarations ?? {}), [], '非法入参不写入注册表')
311
+ assert.deepEqual(Object.keys(win.__navicIconDeclarations ?? {}), ['dsh-settings-nav-icons'], '非法入参不写入注册表(仅启动期自声明在场)')
308
312
  assert.equal(raf.size(), 0, '非法入参不触发重贴')
309
313
  assert.equal(doc.marked.length, 0)
310
314
  } finally {
@@ -402,6 +406,7 @@ test('队列时序:畸形条目不中断排空,同键后者覆盖,排空先于
402
406
  test('卸载:__navicIcons 删除,rAF 取消,异常不外抛', () => {
403
407
  const { win, doc, raf } = loadClient()
404
408
  const unload = boot(win.__loaded, doc)
409
+ raf.flush() // 排空启动期自声明的重贴,断言只看本次注册的增量
405
410
  win.__navicIcons.register({ bell: 'bell' })
406
411
  assert.equal(raf.size(), 1, '重贴已排定')
407
412
  unload()
@@ -536,6 +541,7 @@ test('头像槽换名:重贴清行内旧注入(父容器范围),外来兄弟不
536
541
  test('rAF 合批:同一帧内多次 register 只排一帧,flush 后重新排定', () => {
537
542
  const { win, doc, raf } = loadClient()
538
543
  const unload = boot(win.__loaded, doc)
544
+ raf.flush() // 排空启动期自声明的重贴,断言只看本次注册的增量
539
545
  try {
540
546
  win.__navicIcons.register({ a: 'bell' })
541
547
  win.__navicIcons.register({ b: 'wrench' })
@@ -567,6 +573,7 @@ test('replacePass 逐项异常隔离:单项失败不中断其余', () => {
567
573
  test('onMutations 过滤:元素级变更唤醒,文本节点与域外文本不唤醒', () => {
568
574
  const { win, doc, raf, observers } = loadClient()
569
575
  const unload = boot(win.__loaded, doc)
576
+ raf.flush() // 排空启动期自声明的重贴,断言只看本次变更的增量
570
577
  try {
571
578
  const observer = observers[observers.length - 1]
572
579
  const inCellText = { parentElement: { closest(sel) { return sel.includes('navCell') ? {} : null } } }
@@ -740,10 +747,12 @@ test('用户覆盖:非 nav 单元格右键不弹浮层', () => {
740
747
  }
741
748
  })
742
749
 
743
- // 声明侧契约:四个生产者包的注册样板(先探测入口、数组态入队),改契约须同步五处。
750
+ // 声明侧契约:全部生产者包的注册样板(先探测入口、数组态入队),改契约须同步全部生产者。
744
751
  // 包脱离 monorepo 布局(发布态)时跳过:无兄弟包源码可读。
745
- test('生产者样板契约:四包注册走 __navicIcons/__navicIconQueue', () => {
746
- const producers = ['dsh-session-manager', 'dsh-usage-panel', 'dsh-turn-notify', 'dsh-maintain']
752
+ test('生产者样板契约:全包注册走 __navicIcons/__navicIconQueue', () => {
753
+ const producers = ['dsh-session-manager', 'dsh-usage-panel', 'dsh-turn-notify', 'dsh-maintain', 'dsh-rs-workflow',
754
+ 'dsh-usage-dash', 'dsh-cron-board', 'dsh-think-expand', 'dsh-model-capability-editor', 'dsh-settings-nav-icons',
755
+ 'dsh-auto-trust-all', 'dsh-llm-pi-gateway']
747
756
  let checked = 0
748
757
  for (const pkg of producers) {
749
758
  const clientPath = join(PKG_ROOT, '..', pkg, 'src', 'client.js')
@@ -274,7 +274,7 @@ function defineScenarios(prefix, L) {
274
274
  })
275
275
 
276
276
  test(prefix + '关键词规则整词边界与跨规则优先级', () => {
277
- const { themedIcon, FALLBACK } = L
277
+ const { themedIcon, FALLBACK, GLYPHS } = L
278
278
  assert.equal(themedIcon('important'), FALLBACK[poolIndexOf('important')], 'im 不命中 important')
279
279
  assert.equal(themedIcon('dbtool'), FALLBACK[poolIndexOf('dbtool')], 'db 不命中 dbtool')
280
280
  assert.equal(themedIcon('my db tool'), themedIcon('db'), '独立词 db 命中')
@@ -283,6 +283,13 @@ function defineScenarios(prefix, L) {
283
283
  // 顺序即优先级,具体语义在前:双规则同打名取靠前者
284
284
  assert.equal(themedIcon('search-bot'), themedIcon('search'), 'search 先于 bot')
285
285
  assert.equal(themedIcon('git-notify'), themedIcon('git'), 'git 先于 notify')
286
+ // 余额语义:wallet 系关键词命中钱包图,plan 系关键词仍命中时钟
287
+ assert.equal(themedIcon('account-balance'), GLYPHS.wallet, 'balance 命中钱包')
288
+ assert.equal(themedIcon('my-wallet-tool'), GLYPHS.wallet, 'wallet 命中钱包')
289
+ assert.equal(themedIcon('cron-board'), GLYPHS.plan, 'cron 仍命中时钟')
290
+ // 仪表盘语义:dash 系关键词命中表盘图
291
+ assert.equal(themedIcon('usage-dash'), GLYPHS.gauge, 'dash 命中表盘')
292
+ assert.equal(themedIcon('my-dashboard'), GLYPHS.gauge, 'dashboard 命中表盘')
286
293
  })
287
294
 
288
295
  test(prefix + '别名等价:双语分区任一语言键命中,组外键不受影响', () => {
@@ -419,8 +426,8 @@ test('两份实现 NAME_RULES 全量同源(含标志位)', () => {
419
426
 
420
427
  test('README 契约数量锁定', () => {
421
428
  // README 声明的内置表规模,扩表须同步改 README
422
- assert.equal(Object.keys(logic.GLYPHS).length, 28)
423
- assert.equal(logic.NAME_RULES.length, 19)
429
+ assert.equal(Object.keys(logic.GLYPHS).length, 30)
430
+ assert.equal(logic.NAME_RULES.length, 21)
424
431
  })
425
432
 
426
433
  test('LOGIC 段与 logic.mjs 决策函数逐函数源码一致(归一化注释与空白)', () => {