dsh-mosaic 1.1.1 → 1.1.3
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/CHANGELOG.md +20 -0
- package/README.md +6 -3
- package/SKILL.md +1 -1
- package/lib/client.js +67 -26
- package/lib/validate.js +14 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@
|
|
|
4
4
|
> 兼容性分两处表达:`package.json` 的 `engines.dsh` 给机器可读的范围,README「安装」一节给「插件版本 ↔ 实测 Harness 版本」对照表;每次在真实 Harness 上验证后更新这两处。
|
|
5
5
|
> `0.2.1` 及各 `0.2.1-<标签>` 是改规则前的历史版本,保留用于追溯。
|
|
6
6
|
|
|
7
|
+
## 1.1.3
|
|
8
|
+
|
|
9
|
+
- 设置页的导航图标改用插件自己的 mark(内联 SVG,按面板请求的边长渲染),不再和 General 共用同一个齿轮。注册走 `settings.section.icon`:Harness `0.2.1-rc.5` 还没有这个插槽,注册保持静默,升级到声明它的版本后自动生效。
|
|
10
|
+
- 内部:`useDiagramId` 改名 `useInstanceId`,它现在同时给示意图 marker 和图标渐变生成实例 id。
|
|
11
|
+
|
|
12
|
+
## 1.1.2
|
|
13
|
+
|
|
14
|
+
### 修复
|
|
15
|
+
|
|
16
|
+
- 示意图标签里的 `(`、`)`、`[`、`]`、`{`、`}`、`|` 会让 Mermaid 提前结束标签,`get_current_lock_id()` 这类标签直接解析失败。流程图节点与边标签现在统一加引号;`state`、`er` 等已加引号的语法不受影响。
|
|
17
|
+
- 标签里的双引号原本转义成 `#quot;`,但 Mermaid 12 不解析该实体,会原样显示成 `"`;改为单引号,正常显示。
|
|
18
|
+
- `gantt` 的任务名与分组名遇到 `:` 会被当作标签分隔符,静默丢掉后半段;现在换成全角 `:`。
|
|
19
|
+
- `warnings=` 补上 flowchart 的边数与 sequence 的消息数检查——README 与 SKILL 早就写了这两个预算,代码此前只检查节点和生命线。
|
|
20
|
+
|
|
21
|
+
### 清理
|
|
22
|
+
|
|
23
|
+
- 删除 `validate.js` 里两个无人调用的函数(`looksLikeSpec`、`takeString`)。
|
|
24
|
+
- 删除三条没有任何组件使用的 CSS(`.mosaic-chart-title`、`.mosaic-donut-label`、`.mosaic-error`)。
|
|
25
|
+
- 测试面收窄到测试真正用到的导出。
|
|
26
|
+
|
|
7
27
|
## 1.1.1
|
|
8
28
|
|
|
9
29
|
发布流程修正,运行时代码与 `1.1.0` 相同。
|
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ $DSH_HOME/logs/dsh-mosaic/YYYY-MM-DD.jsonl # 未设置 DSH_HOME 时是 ~/.dsh/
|
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
- 一行一个 JSON 对象,字段固定:`ts`、`level`(debug/info/warn/error)、`event`,其余按事件附带(如 `tool`、`ms`、`status`、`route`、`source`、`reason`)。
|
|
92
|
-
- 记录什么:激活/停用、Prompt 的读取与保存(来源、字符数、是否落盘)、工具执行(名称、结果状态、耗时)、路由请求(方法、状态码、耗时)、技能被读取、以及浏览器侧上报的失败(`client.` 前缀:围栏解析失败、Mermaid
|
|
92
|
+
- 记录什么:激活/停用、Prompt 的读取与保存(来源、字符数、是否落盘)、工具执行(名称、结果状态、耗时)、路由请求(方法、状态码、耗时)、技能被读取、以及浏览器侧上报的失败(`client.` 前缀:围栏解析失败、Mermaid 失败、示意图导出失败、Prompt 读写失败、Widget 动作被限流/超限丢弃)。
|
|
93
93
|
- 不记录什么:Prompt 正文、组件 JSON、任何文本字段的内容。字段名为 `prompt`/`text`/`code`/`content`/`secret`/`token`/`password`/`apiKey` 时只写字符数;其它字符串超过 300 字符会被截断。
|
|
94
94
|
- 体积:单文件超过 2 MB 滚动为 `<日期>.1.jsonl`;超过 7 天的文件在下次启动时删除;文件权限 `0600`。写不进去时不会影响插件,只在宿主日志里告警一次。
|
|
95
95
|
- 浏览器侧的失败通过同源 `POST /dsh-mosaic/log` 上报(请求体上限 4 KB,非本机权威或跨站一律 403,客户端每分钟最多 20 条);接口在日志落盘之后才回 204,看到成功就是真的写进去了。
|
|
@@ -108,13 +108,14 @@ grep -c '"event":"tool.failed"' "$DSH_HOME"/logs/dsh-mosaic/*.jsonl
|
|
|
108
108
|
|
|
109
109
|
## 已知边界
|
|
110
110
|
|
|
111
|
-
- 本插件的 npm 版本独立递增(当前 `1.
|
|
111
|
+
- 本插件的 npm 版本独立递增(当前 `1.1.3`),不再与 Harness 版本号对齐;兼容范围由 `package.json` 的 `engines.dsh` 声明,最近一次实测的 Harness 版本见「安装」一节。
|
|
112
112
|
- 围栏渲染靠扫描页面里的代码块实现:0.2.1 全系列(实测 rc.3 与 rc.5,master 同样)都没有给插件开放「渲染 fenced code block」的扩展点。工具通道 `render_view` + `tool.call.toolview` 是原生做法,围栏是它的补充。
|
|
113
113
|
- 从 0.2.1 起代码块由 Shiki 渲染,`<code>` 上**没有** `language-*` 类;识别靠「代码块位于 assistant 行内 + 正文能解析成合法视图 spec」这条路径,用户消息、工具结果、推理块里的代码块都被排除。
|
|
114
114
|
- `diagram` 与 `chart` 自带缩放/平移:卡片右上角有 `−`/`+`/`⟲`/`⤢`,放大后按住图面拖动即可平移,`Ctrl/⌘ + 滚轮`缩放,双击复位;示意图按原始尺寸渲染,不会被压缩到卡片宽度。触屏上图表区域只拦横向拖动(`touch-action: pan-y`),手指上下划仍然滚动聊天页面。
|
|
115
115
|
- `widget` 里调用 `window.mosaic.action()` 只有**真实用户手势**之后才会把动作发回模型:Widget 自己也在判断 `navigator.userActivation`,但决定权在宿主——消息的 nonce 就写在 Widget 自己的文档里,模型代码能读出来直接伪造,所以父页面独立再校验一次自己文档的激活状态。确实需要自行提交的 Widget 要在节点上写 `actions: "programmatic"`;无论哪种,整个会话共享「每分钟 10 次、单次 payload 16 KB(按 UTF-8 字节算)」的额度,新开一个 Widget 不会重置。浏览器若不提供 `navigator.userActivation`,非 `programmatic` 的 Widget 会被按「没有手势」处理,按钮不会回模型。
|
|
116
116
|
- `widget` 是 iframe,拿不到宿主的 `ctx.locale`,因此内部文案由调用方自己写;主题靠宿主令牌值传入。围栏是独立的 React root,切换外观不会触发它重渲染,所以 widget 会观察 `body[data-ds-dark-theme]` 这一个外观属性并在变化时重发令牌——这是插件唯一读取的宿主属性。
|
|
117
117
|
- Mermaid 资产走包内私有路由 `/dsh-mosaic/assets`;没有 Web 服务时 diagram 回落到内置 SVG 布局。
|
|
118
|
+
- 设置页的导航图标走 `settings.section.icon` 插槽。Harness `0.2.1-rc.5` 的导航图标是写死的映射,没有这个插槽,Mosaic 会落到默认齿轮;升级到声明该插槽的版本(`0.2.2-alpha.3` 起)后自动显示自己的 mark,插件不用再改。
|
|
118
119
|
- 自有路由(`/prompt`、`/log`)先验 **Host 必须是本机权威**(loopback、监听地址,或 `config.trustedHosts` 里声明的),再验 `Sec-Fetch-Site` 与 `Origin`。只比 Origin 和 Host 挡不住 DNS rebinding:被 rebinding 的页面这两者都等于攻击者域名,看起来完全同源。宿主把 Web 服务绑到非 loopback 地址时,请把对应主机名写进 `config.trustedHosts`,否则这些路由会拒绝请求。
|
|
119
120
|
- bundle patch 只插入 Mosaic 自己这一行,不改动任何其他插件。若你还装了别的围栏类插件,两者可以对同一段围栏各显一套,按需在 profile 里关掉其中一个即可。
|
|
120
121
|
- 持久化只用「Host 数据目录里的一个 JSON 文件」,没有走 DSH 的 storageDomain。实测(rc.5):`storageDomain` 可用,但记录校验只在**重新打开**时执行(`put` 不校验);`single` 布局遇到版本变化会直接拒绝打开,`compatibleVersions` 也不适用。对一份 Prompt 来说这些能力换不来收益,反而多一个依赖和三条失败分支。
|
|
@@ -154,10 +155,12 @@ dsh plugin --profile desktop add link:/absolute/path/to/dsh-mosaic
|
|
|
154
155
|
|
|
155
156
|
安装后重启或刷新 DSH Web,新会话即可使用。bundle patch 只插入本插件一行,不会关闭或覆盖别人的插件。
|
|
156
157
|
|
|
157
|
-
**兼容性**:npm 版本独立于 DeepSeek Harness 版本,`1.
|
|
158
|
+
**兼容性**:npm 版本独立于 DeepSeek Harness 版本,`1.1.x` 对应下方实测的 Harness 范围;声明式范围见 `package.json` 的 `engines.dsh`(当前 `>=0.2.1-rc.1 <0.3.0-0`)。
|
|
158
159
|
|
|
159
160
|
| 插件版本 | 实测通过的 DeepSeek Harness | 说明 |
|
|
160
161
|
| --- | --- | --- |
|
|
162
|
+
| `1.1.3` | `0.2.1-rc.5` | 设置页改用插件自己的图标;需要 Harness 声明 `settings.section.icon`,rc.5 尚未提供 |
|
|
163
|
+
| `1.1.2` | `0.2.1-rc.5` | 修复示意图标签里的括号/方括号/花括号导致解析失败;补上边与消息的可读预算 |
|
|
161
164
|
| `1.1.1` | `0.2.1-rc.5` | 运行时代码与 `1.1.0` 相同;仅修正发布工作流的无效输入 |
|
|
162
165
|
| `1.1.0` | `0.2.1-rc.5` | 13 种示意图、PNG/SVG 导出、`warnings=` 可读规模提示、布局修正 |
|
|
163
166
|
| `1.0.0` | `0.2.1-rc.5`(同时复核 rc.3 行为) | 首个稳定版;含 Widget 沙箱、路由权威、Prompt 并发等修正 |
|
package/SKILL.md
CHANGED
|
@@ -117,7 +117,7 @@ A single `diagram` node fragment (this is not a complete fence; wrap it in `{"it
|
|
|
117
117
|
],"legend":[{"label":"命中路径","tone":"accent"}]}
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
`flowchart`, `sequence`, `state`, `er`, `gantt`, and `mindmap` render through a lazy-loaded Mermaid engine; `timeline`, `layers`, `quadrant`, `tree`, `funnel`, `venn`, and `swimlane` are drawn by Mosaic itself. Every diagram card
|
|
120
|
+
`flowchart`, `sequence`, `state`, `er`, `gantt`, and `mindmap` render through a lazy-loaded Mermaid engine; `timeline`, `layers`, `quadrant`, `tree`, `funnel`, `venn`, and `swimlane` are drawn by Mosaic itself. Every diagram card exports `svg` and `png` from its header; `drawio` appears for the kinds it can express as vertices and edges (`flowchart`, `sequence`, `timeline`, `layers`, `state`).
|
|
121
121
|
|
|
122
122
|
### Media and links
|
|
123
123
|
|
package/lib/client.js
CHANGED
|
@@ -340,14 +340,12 @@ window.__ModuleLoader__.load({
|
|
|
340
340
|
.mosaic-diff-new{background:color-mix(in srgb,var(--m-success) 7%,var(--m-bg))}
|
|
341
341
|
.mosaic-chart{width:100%;height:auto;display:block;overflow:visible}
|
|
342
342
|
.mosaic-chart text{font-size:10px;fill:var(--m-muted)}
|
|
343
|
-
.mosaic-chart .mosaic-chart-title{font-size:12px;font-weight:600;fill:var(--m-text)}
|
|
344
343
|
.mosaic-chart .mosaic-axis{stroke:var(--m-border);stroke-width:1}
|
|
345
344
|
.mosaic-chart .mosaic-axis-label{font-size:9px;fill:var(--m-muted);font-variant-numeric:tabular-nums}
|
|
346
345
|
.mosaic-chart .mosaic-grid-line{stroke:var(--m-border);stroke-width:1;opacity:.45}
|
|
347
346
|
.mosaic-chart .mosaic-line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round}
|
|
348
347
|
.mosaic-chart .mosaic-dot{stroke:var(--m-bg);stroke-width:1.5}
|
|
349
348
|
.mosaic-chart .mosaic-bar{shape-rendering:geometricPrecision}
|
|
350
|
-
.mosaic-chart .mosaic-donut-label{font-size:11px;fill:var(--m-text)}
|
|
351
349
|
.mosaic-diagram-wrap{max-width:100%;overflow:hidden;padding-bottom:2px}
|
|
352
350
|
.mosaic-diagram{display:block;width:auto;max-width:none;height:auto;margin:0 auto;overflow:visible}
|
|
353
351
|
.mosaic-mermaid{max-width:100%;padding:2px 0}
|
|
@@ -482,7 +480,6 @@ window.__ModuleLoader__.load({
|
|
|
482
480
|
.mosaic-overlay-body[data-figure="true"]{width:min(1440px,97vw);overflow:hidden}
|
|
483
481
|
.mosaic-overlay-image{display:block;max-width:100%;max-height:76vh;margin:0 auto;border-radius:10px;object-fit:contain}
|
|
484
482
|
.mosaic-diagnostic{margin:0 0 6px;padding:6px 10px;border:1px solid color-mix(in srgb,var(--m-danger) 40%,transparent);border-radius:7px;background:color-mix(in srgb,var(--m-danger) 10%,transparent);color:var(--m-danger);font-size:12px;line-height:1.5;white-space:pre-wrap}
|
|
485
|
-
.mosaic-error{margin:8px 0;padding:8px 10px;border:1px solid color-mix(in srgb,var(--m-danger) 40%,transparent);border-radius:8px;background:color-mix(in srgb,var(--m-danger) 8%,var(--m-bg));color:var(--m-danger);font-size:12px}
|
|
486
483
|
.mosaic-skeleton{display:flex;flex-direction:column;gap:8px;padding:10px 0}
|
|
487
484
|
.mosaic-skeleton span{display:block;height:10px;border-radius:5px;background:var(--m-bg2);animation:mosaic-pulse 1.2s ease-in-out infinite}
|
|
488
485
|
.mosaic-skeleton span:nth-child(2){width:70%}
|
|
@@ -600,6 +597,33 @@ window.__ModuleLoader__.load({
|
|
|
600
597
|
const PROMPT_URL = '/dsh-mosaic/prompt'
|
|
601
598
|
const PROMPT_LIMIT = 128 * 1024
|
|
602
599
|
|
|
600
|
+
/** The plugin's own mark: four tiles on the brand gradient. */
|
|
601
|
+
function MosaicMark({ size = 16 }) {
|
|
602
|
+
const gradientId = useInstanceId('mosaic-mark')
|
|
603
|
+
return h('svg', {
|
|
604
|
+
width: size,
|
|
605
|
+
height: size,
|
|
606
|
+
viewBox: '0 0 64 64',
|
|
607
|
+
'aria-hidden': 'true',
|
|
608
|
+
focusable: 'false',
|
|
609
|
+
style: { display: 'block' },
|
|
610
|
+
},
|
|
611
|
+
h('defs', null,
|
|
612
|
+
h('linearGradient', { id: gradientId, x1: '0', y1: '0', x2: '1', y2: '1' },
|
|
613
|
+
h('stop', { offset: '0', stopColor: '#1d4ed8' }),
|
|
614
|
+
h('stop', { offset: '1', stopColor: '#0f766e' }))),
|
|
615
|
+
h('rect', { x: 2, y: 2, width: 60, height: 60, rx: 14, fill: `url(#${gradientId})` }),
|
|
616
|
+
h('rect', { x: 10, y: 10, width: 20, height: 20, rx: 5, fill: '#ffffff', fillOpacity: 0.94 }),
|
|
617
|
+
h('rect', { x: 34, y: 10, width: 20, height: 20, rx: 5, fill: '#ffffff', fillOpacity: 0.72 }),
|
|
618
|
+
h('rect', { x: 10, y: 34, width: 20, height: 20, rx: 5, fill: '#ffffff', fillOpacity: 0.72 }),
|
|
619
|
+
h('rect', { x: 34, y: 34, width: 20, height: 20, rx: 5, fill: '#ffffff', fillOpacity: 0.46 }))
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** The settings nav glyph, rendered at the edge the shell asks for. */
|
|
623
|
+
function SettingsSectionIcon({ size }) {
|
|
624
|
+
return h(MosaicMark, { size: typeof size === 'number' && size > 0 ? size : 16 })
|
|
625
|
+
}
|
|
626
|
+
|
|
603
627
|
/**
|
|
604
628
|
* The model prompt, editable in place. The Host owns the value and persists it, so
|
|
605
629
|
* the same text is used by every browser and survives a restart; this page only
|
|
@@ -1528,7 +1552,9 @@ window.__ModuleLoader__.load({
|
|
|
1528
1552
|
|
|
1529
1553
|
function SpacerNode() {
|
|
1530
1554
|
return h('div', { className: 'mosaic-spacer' })
|
|
1531
|
-
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function numberValue(value) {
|
|
1532
1558
|
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
1533
1559
|
const text = String(value ?? '').trim().replace(/[,$£€¥%]/g, '')
|
|
1534
1560
|
if (text === '') return null
|
|
@@ -2480,11 +2506,19 @@ window.__ModuleLoader__.load({
|
|
|
2480
2506
|
function mermaidLabel(value) {
|
|
2481
2507
|
return String(value ?? '')
|
|
2482
2508
|
.replace(/\s+/g, ' ')
|
|
2483
|
-
.replace(/"/g, '
|
|
2484
|
-
.replace(/\|/g, '#124;')
|
|
2509
|
+
.replace(/"/g, "'")
|
|
2485
2510
|
.trim()
|
|
2486
2511
|
}
|
|
2487
2512
|
|
|
2513
|
+
/**
|
|
2514
|
+
* A quoted Mermaid label. Unquoted labels end at `()`, `[]`, `{}` or `|`,
|
|
2515
|
+
* so a label like `get_current_lock_id()` fails to parse; quoting is the
|
|
2516
|
+
* only form that survives them.
|
|
2517
|
+
*/
|
|
2518
|
+
function mermaidQuoted(value) {
|
|
2519
|
+
return `"${mermaidLabel(value)}"`
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2488
2522
|
/** Parse a token colour into `[r, g, b]`; `undefined` when it is not a plain colour. */
|
|
2489
2523
|
function parseColor(value) {
|
|
2490
2524
|
const text = String(value ?? '').trim()
|
|
@@ -2601,6 +2635,11 @@ window.__ModuleLoader__.load({
|
|
|
2601
2635
|
return lines.join('\n')
|
|
2602
2636
|
}
|
|
2603
2637
|
|
|
2638
|
+
/** A gantt name ends at the first `:`, so a literal one would truncate it. */
|
|
2639
|
+
function ganttLabel(value) {
|
|
2640
|
+
return mermaidLabel(value).replace(/:/g, '\uff1a')
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2604
2643
|
function ganttMermaid(node) {
|
|
2605
2644
|
const lines = ['gantt', ' dateFormat YYYY-MM-DD', ' axisFormat %m-%d']
|
|
2606
2645
|
let section
|
|
@@ -2608,10 +2647,10 @@ window.__ModuleLoader__.load({
|
|
|
2608
2647
|
for (const task of Array.isArray(node.tasks) ? node.tasks : []) {
|
|
2609
2648
|
if (task.section && task.section !== section) {
|
|
2610
2649
|
section = task.section
|
|
2611
|
-
lines.push(` section ${
|
|
2650
|
+
lines.push(` section ${ganttLabel(section)}`)
|
|
2612
2651
|
}
|
|
2613
2652
|
index += 1
|
|
2614
|
-
lines.push(` ${
|
|
2653
|
+
lines.push(` ${ganttLabel(task.name)} :t${index}, ${task.start}, ${task.end}`)
|
|
2615
2654
|
}
|
|
2616
2655
|
return lines.join('\n')
|
|
2617
2656
|
}
|
|
@@ -2659,8 +2698,7 @@ window.__ModuleLoader__.load({
|
|
|
2659
2698
|
const usedTones = new Set()
|
|
2660
2699
|
const lines = [`flowchart ${node.direction === 'LR' ? 'LR' : 'TB'}`]
|
|
2661
2700
|
nodes.forEach((entry) => {
|
|
2662
|
-
const
|
|
2663
|
-
const withSub = entry.sub ? `${label}<br/>${mermaidLabel(entry.sub)}` : label
|
|
2701
|
+
const withSub = mermaidQuoted(entry.sub ? `${entry.label}<br/>${entry.sub}` : entry.label)
|
|
2664
2702
|
const id = idOf.get(entry.id)
|
|
2665
2703
|
if (entry.shape === 'diamond') lines.push(` ${id}{${withSub}}`)
|
|
2666
2704
|
else if (entry.shape === 'round') lines.push(` ${id}(${withSub})`)
|
|
@@ -2676,7 +2714,7 @@ window.__ModuleLoader__.load({
|
|
|
2676
2714
|
const to = idOf.get(edge.to)
|
|
2677
2715
|
if (!from || !to) return
|
|
2678
2716
|
const arrow = edge.kind === 'dashed' ? '-.->' : edge.kind === 'accent' ? '==>' : '-->'
|
|
2679
|
-
const label = edge.label ? `|${
|
|
2717
|
+
const label = edge.label ? `|${mermaidQuoted(edge.label)}|` : ''
|
|
2680
2718
|
lines.push(` ${from} ${arrow}${label} ${to}`)
|
|
2681
2719
|
})
|
|
2682
2720
|
if (baseStyle !== undefined) lines.push(` classDef mosaic_base ${baseStyle}`)
|
|
@@ -3129,15 +3167,15 @@ window.__ModuleLoader__.load({
|
|
|
3129
3167
|
}
|
|
3130
3168
|
|
|
3131
3169
|
/**
|
|
3132
|
-
* A stable id per component instance.
|
|
3133
|
-
*
|
|
3170
|
+
* A stable id per component instance. SVG ids live in the document, so two
|
|
3171
|
+
* instances on one page must not share `#mosaic-arrow` or a gradient.
|
|
3134
3172
|
*/
|
|
3135
|
-
let
|
|
3136
|
-
function
|
|
3173
|
+
let instanceIds = 0
|
|
3174
|
+
function useInstanceId(prefix) {
|
|
3137
3175
|
const ref = useRef(null)
|
|
3138
3176
|
if (ref.current === null) {
|
|
3139
|
-
|
|
3140
|
-
ref.current = `${prefix}-${
|
|
3177
|
+
instanceIds += 1
|
|
3178
|
+
ref.current = `${prefix}-${instanceIds}`
|
|
3141
3179
|
}
|
|
3142
3180
|
return ref.current
|
|
3143
3181
|
}
|
|
@@ -3368,8 +3406,8 @@ window.__ModuleLoader__.load({
|
|
|
3368
3406
|
}
|
|
3369
3407
|
|
|
3370
3408
|
function FlowchartDiagram({ node }) {
|
|
3371
|
-
const arrowId =
|
|
3372
|
-
const accentArrowId =
|
|
3409
|
+
const arrowId = useInstanceId('mosaic-arrow')
|
|
3410
|
+
const accentArrowId = useInstanceId('mosaic-arrow-accent')
|
|
3373
3411
|
const layout = useMemo(() => {
|
|
3374
3412
|
const nodes = Array.isArray(node.nodes) ? node.nodes : []
|
|
3375
3413
|
const edges = Array.isArray(node.edges) ? node.edges : []
|
|
@@ -3553,9 +3591,9 @@ window.__ModuleLoader__.load({
|
|
|
3553
3591
|
}
|
|
3554
3592
|
|
|
3555
3593
|
function SequenceDiagram({ node }) {
|
|
3556
|
-
const syncId =
|
|
3557
|
-
const returnId =
|
|
3558
|
-
const asyncId =
|
|
3594
|
+
const syncId = useInstanceId('mosaic-seq-arrow')
|
|
3595
|
+
const returnId = useInstanceId('mosaic-seq-arrow-muted')
|
|
3596
|
+
const asyncId = useInstanceId('mosaic-seq-arrow-accent')
|
|
3559
3597
|
const layout = useMemo(() => {
|
|
3560
3598
|
const participants = Array.isArray(node.participants) ? node.participants : []
|
|
3561
3599
|
const messages = Array.isArray(node.messages) ? node.messages : []
|
|
@@ -3788,7 +3826,7 @@ window.__ModuleLoader__.load({
|
|
|
3788
3826
|
function SwimlaneDiagram({ node }) {
|
|
3789
3827
|
const lanes = Array.isArray(node.lanes) ? node.lanes : []
|
|
3790
3828
|
const steps = Array.isArray(node.steps) ? node.steps : []
|
|
3791
|
-
const arrowId =
|
|
3829
|
+
const arrowId = useInstanceId('mosaic-swim-arrow')
|
|
3792
3830
|
const layout = useMemo(() => layoutSwimlane(lanes, steps), [node.lanes, node.steps])
|
|
3793
3831
|
const display = diagramDisplaySize(layout.width, layout.height)
|
|
3794
3832
|
const arrows = []
|
|
@@ -4378,6 +4416,12 @@ window.__ModuleLoader__.load({
|
|
|
4378
4416
|
label: () => translate('settingsTitle'),
|
|
4379
4417
|
locale: NS,
|
|
4380
4418
|
}, MosaicSettings))
|
|
4419
|
+
// Hosts older than the `settings.section.icon` seat never declare it, so this
|
|
4420
|
+
// stays dormant and the shell keeps its default gear for our section.
|
|
4421
|
+
ctx.slots.inject('settings.section.icon', () => ctx.slots.register({
|
|
4422
|
+
name: 'settings.section.icon',
|
|
4423
|
+
id: 'mosaic',
|
|
4424
|
+
}, SettingsSectionIcon))
|
|
4381
4425
|
ctx.effect(() => installFenceRenderer(ctx, sendAction), 'dsh-mosaic: fence')
|
|
4382
4426
|
console.info('[mosaic] client active; fence=dsh-view')
|
|
4383
4427
|
}
|
|
@@ -4386,7 +4430,6 @@ window.__ModuleLoader__.load({
|
|
|
4386
4430
|
// `inject`, so this extra key is inert at runtime.
|
|
4387
4431
|
const __test = {
|
|
4388
4432
|
widgetDocument,
|
|
4389
|
-
widgetParts,
|
|
4390
4433
|
WIDGET_CSP,
|
|
4391
4434
|
widgetBridge,
|
|
4392
4435
|
textWidth,
|
|
@@ -4397,8 +4440,6 @@ window.__ModuleLoader__.load({
|
|
|
4397
4440
|
layoutVenn,
|
|
4398
4441
|
layoutSwimlane,
|
|
4399
4442
|
toMermaid,
|
|
4400
|
-
diagrams: { QuadrantDiagram, TreeDiagram, FunnelDiagram, VennDiagram, SwimlaneDiagram, DiagramLegend },
|
|
4401
|
-
inlineSvgMarkup,
|
|
4402
4443
|
parseView,
|
|
4403
4444
|
}
|
|
4404
4445
|
|
package/lib/validate.js
CHANGED
|
@@ -74,10 +74,6 @@ function asBoolean(value) {
|
|
|
74
74
|
return typeof value === 'boolean' ? value : undefined
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function looksLikeSpec(value) {
|
|
78
|
-
return isRecord(value) && Array.isArray(value.items)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
77
|
function looksLikeNode(value) {
|
|
82
78
|
return isRecord(value) && typeof value.type === 'string'
|
|
83
79
|
}
|
|
@@ -103,12 +99,6 @@ function fail(state, path, message) {
|
|
|
103
99
|
state.errors.push(`${path}: ${message}`)
|
|
104
100
|
}
|
|
105
101
|
|
|
106
|
-
function takeString(state, path, value, cap = LIMITS.maxString) {
|
|
107
|
-
const result = asString(value, cap)
|
|
108
|
-
if (result === undefined) fail(state, path, 'must be a string')
|
|
109
|
-
return result
|
|
110
|
-
}
|
|
111
|
-
|
|
112
102
|
function takeOptionalNumber(state, path, value, min, max) {
|
|
113
103
|
if (value === undefined) return undefined
|
|
114
104
|
const result = asNumber(value, min, max)
|
|
@@ -1362,13 +1352,19 @@ const READABLE = {
|
|
|
1362
1352
|
|
|
1363
1353
|
/** Counts per diagram kind that a reader can take in without a guide. */
|
|
1364
1354
|
const DIAGRAM_READABLE = {
|
|
1365
|
-
flowchart:
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1355
|
+
flowchart: [
|
|
1356
|
+
{ limit: 9, count: (node) => node.nodes.length, noun: 'nodes' },
|
|
1357
|
+
{ limit: 12, count: (node) => node.edges.length, noun: 'edges' },
|
|
1358
|
+
],
|
|
1359
|
+
state: [{ limit: 9, count: (node) => node.nodes.length, noun: 'states' }],
|
|
1360
|
+
sequence: [
|
|
1361
|
+
{ limit: 5, count: (node) => node.participants.length, noun: 'lifelines' },
|
|
1362
|
+
{ limit: 12, count: (node) => node.messages.length, noun: 'messages' },
|
|
1363
|
+
],
|
|
1364
|
+
timeline: [{ limit: 7, count: (node) => node.items.length, noun: 'items' }],
|
|
1365
|
+
layers: [{ limit: 6, count: (node) => node.layers.length, noun: 'layers' }],
|
|
1366
|
+
funnel: [{ limit: 6, count: (node) => node.items.length, noun: 'steps' }],
|
|
1367
|
+
swimlane: [{ limit: 6, count: (node) => node.steps.length, noun: 'steps' }],
|
|
1372
1368
|
}
|
|
1373
1369
|
|
|
1374
1370
|
const REMOTE_ASSET_RE = /<(?:script|img|iframe|link|video|audio|source)[^>]+(?:src|href)\s*=\s*["']?(?:https?:)?\/\//iu
|
|
@@ -1421,8 +1417,7 @@ export function lintView(spec) {
|
|
|
1421
1417
|
if (Array.isArray(node.nodes)) {
|
|
1422
1418
|
accents += node.nodes.filter((entry) => entry?.tone === 'accent').length
|
|
1423
1419
|
}
|
|
1424
|
-
const budget
|
|
1425
|
-
if (budget !== undefined) {
|
|
1420
|
+
for (const budget of DIAGRAM_READABLE[node.kind] ?? []) {
|
|
1426
1421
|
const count = budget.count(node)
|
|
1427
1422
|
if (count > budget.limit) {
|
|
1428
1423
|
add(`${path}: ${count} ${budget.noun}; keep ${budget.limit} or fewer, or split`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mosaic",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lightweight visual answers for DeepSeek Harness: one compact visual style, an editable model prompt, dsh-view components, Mermaid diagrams, a sandboxed HTML/JS escape hatch, local-first interactions, and link-only media cards.",
|
|
6
6
|
"author": "imwhatiam",
|