dsh-multiview 0.1.10 → 0.1.11
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 +15 -0
- package/client.js +60 -5
- package/docs/RELEASE_ACCEPTANCE.md +12 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.11 — 2026-11-07
|
|
4
|
+
|
|
5
|
+
### 新增
|
|
6
|
+
|
|
7
|
+
- **性能模式(可选,默认关闭)**:顶栏「视图」左边的仪表盘图标。开启后非聚焦格暂停实时渲染,只保留标题与「实时渲染已暂停」占位;点击该格立即恢复。
|
|
8
|
+
- 真机(win11-test,DSH 0.2.2-rc.1)四路同时长流式实测:**p95 140ms → 58ms**,帧率 24.4 → 44.1fps,p50 19.3 → 15.8ms(1 格实时 + 3 格暂停)。
|
|
9
|
+
- 根因记录:CPU profile 显示分屏插件自身只占 0.6% 自耗时,大头是被四人份"虚拟化 transcript 逐 chunk 测量/重渲染 + 渲染器非 JS 开销"占据;因此唯一有效杠杆就是减少同时在渲染的格子。
|
|
10
|
+
|
|
11
|
+
## 0.1.10 — 2026-11-07
|
|
12
|
+
|
|
13
|
+
首个通过 **npm Trusted Publishing(OIDC)** 发布的版本:仓库中没有任何 npm token 或 secret,打 tag 即由 GitHub Actions 用 OIDC 换取短期凭据发布,并附带 provenance 签名。
|
|
14
|
+
|
|
15
|
+
- workflow 不再使用 setup-node 的 registry-url:它会写入 _authToken=${NODE_AUTH_TOKEN},空值会让 npm 退回 token 认证而不是走 OIDC 交换。
|
|
16
|
+
- 发布命令为 npm publish --access public --provenance。
|
|
17
|
+
|
|
3
18
|
## 0.1.9 — 2026-11-07
|
|
4
19
|
|
|
5
20
|
### 基础设施
|
package/client.js
CHANGED
|
@@ -11,7 +11,7 @@ const NS = 'multiview';
|
|
|
11
11
|
const MV_PANEL_ID = 'multiview';
|
|
12
12
|
const MV_PANE_SLOT = 'multiview.pane';
|
|
13
13
|
const MV_STORAGE_KEY = 'dsh.multiview.v1';
|
|
14
|
-
const MV_PLUGIN_VERSION = '0.1.
|
|
14
|
+
const MV_PLUGIN_VERSION = '0.1.11';
|
|
15
15
|
const MV_ZH = {
|
|
16
16
|
panel: '分屏',
|
|
17
17
|
title: '分屏',
|
|
@@ -34,6 +34,11 @@ const MV_ZH = {
|
|
|
34
34
|
waiting: '等待你处理',
|
|
35
35
|
unread: '有新结果',
|
|
36
36
|
views: '视图',
|
|
37
|
+
perfMode: '性能模式',
|
|
38
|
+
perfModeOn: '性能模式已开启:只有聚焦的那一格实时渲染',
|
|
39
|
+
perfModeOff: '性能模式已关闭:每一格都实时渲染',
|
|
40
|
+
parkedTitle: '实时渲染已暂停',
|
|
41
|
+
parkedHint: '性能模式下非聚焦格不渲染;点击这一格恢复',
|
|
37
42
|
viewsEmpty: '还没有保存的视图。绑定两个以上会话后会自动记住。',
|
|
38
43
|
viewsCurrent: '当前分屏',
|
|
39
44
|
viewsDelete: '删除视图',
|
|
@@ -79,6 +84,11 @@ const MV_EN = {
|
|
|
79
84
|
waiting: 'Waiting for you',
|
|
80
85
|
unread: 'New result',
|
|
81
86
|
views: 'Views',
|
|
87
|
+
perfMode: 'Performance mode',
|
|
88
|
+
perfModeOn: 'Performance mode on: only the focused pane renders live',
|
|
89
|
+
perfModeOff: 'Performance mode off: every pane renders live',
|
|
90
|
+
parkedTitle: 'Live rendering paused',
|
|
91
|
+
parkedHint: 'Performance mode parks unfocused panes; click this pane to resume',
|
|
82
92
|
viewsEmpty: 'No saved views yet. A split with two or more sessions is remembered automatically.',
|
|
83
93
|
viewsCurrent: 'Current split',
|
|
84
94
|
viewsDelete: 'Delete view',
|
|
@@ -443,7 +453,8 @@ function mvInitialState() {
|
|
|
443
453
|
seeded: false,
|
|
444
454
|
reconciled: false,
|
|
445
455
|
views: [],
|
|
446
|
-
currentViewId: null
|
|
456
|
+
currentViewId: null,
|
|
457
|
+
perfMode: false
|
|
447
458
|
};
|
|
448
459
|
}
|
|
449
460
|
let mvState = mvInitialState();
|
|
@@ -470,7 +481,8 @@ function mvScheduleSave() {
|
|
|
470
481
|
preset: mvState.preset,
|
|
471
482
|
seeded: mvState.seeded,
|
|
472
483
|
views: mvState.views,
|
|
473
|
-
currentViewId: mvState.currentViewId
|
|
484
|
+
currentViewId: mvState.currentViewId,
|
|
485
|
+
perfMode: mvState.perfMode === true
|
|
474
486
|
};
|
|
475
487
|
window.localStorage.setItem(MV_STORAGE_KEY, JSON.stringify(durable));
|
|
476
488
|
} catch (error) {}
|
|
@@ -591,7 +603,8 @@ function mvHydrate() {
|
|
|
591
603
|
seeded: raw.seeded === true,
|
|
592
604
|
reconciled: false,
|
|
593
605
|
views,
|
|
594
|
-
currentViewId
|
|
606
|
+
currentViewId,
|
|
607
|
+
perfMode: raw.perfMode === true
|
|
595
608
|
};
|
|
596
609
|
}
|
|
597
610
|
function mvReset() {
|
|
@@ -802,6 +815,12 @@ function mvDeleteView(viewId) {
|
|
|
802
815
|
};
|
|
803
816
|
});
|
|
804
817
|
}
|
|
818
|
+
function mvTogglePerfMode() {
|
|
819
|
+
mvCommit((current)=>({
|
|
820
|
+
...current,
|
|
821
|
+
perfMode: current.perfMode !== true
|
|
822
|
+
}));
|
|
823
|
+
}
|
|
805
824
|
|
|
806
825
|
const MV_UPDATE_KEY = 'dsh.multiview.update.v1';
|
|
807
826
|
const MV_CHECK_INTERVAL = 24 * 60 * 60 * 1000;
|
|
@@ -1158,6 +1177,11 @@ const MV_CSS = `
|
|
|
1158
1177
|
.mv-icon:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
1159
1178
|
.mv-chip{color:var(--dsw-alias-label-secondary)}
|
|
1160
1179
|
.mv-chip[data-active="true"]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
|
|
1180
|
+
.mv-perf[data-active="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-state-business-primary)}
|
|
1181
|
+
.mv-parked{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;width:100%;height:100%;padding:0;border:none;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer}
|
|
1182
|
+
.mv-parked:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1183
|
+
.mv-parked-title{font-size:13px;color:var(--dsw-alias-label-secondary)}
|
|
1184
|
+
.mv-parked-note{font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
1161
1185
|
@media (prefers-reduced-motion: reduce){*{transition:none !important;animation:none !important}}
|
|
1162
1186
|
`;
|
|
1163
1187
|
function MvGlyph(props) {
|
|
@@ -1202,6 +1226,7 @@ const MV_ICON = {
|
|
|
1202
1226
|
external: 'M6 10L13 3M9 3h4v4',
|
|
1203
1227
|
updateCheck: 'M8 2a6 6 0 1 0 0 12A6 6 0 1 0 8 2zM8 10.7V5.7M5.6 8.1 8 5.6l2.4 2.5',
|
|
1204
1228
|
updateInstall: 'M3 9.5v3c0 .6.45 1 1 1h8c.55 0 1-.4 1-1v-3M8 2.6v6.2M5.4 6.4 8 9l2.6-2.6',
|
|
1229
|
+
perf: 'M2.6 12.2a5.4 5.4 0 1 1 10.8 0M8 12.2 10.5 7.4',
|
|
1205
1230
|
views: 'M8 2.2 13.4 5 8 7.8 2.6 5 8 2.2M2.6 8.2 8 11l5.4-2.8M2.6 11.2 8 14l5.4-2.8'
|
|
1206
1231
|
};
|
|
1207
1232
|
|
|
@@ -1667,6 +1692,9 @@ function PaneFrame(props) {
|
|
|
1667
1692
|
onPick: (id)=>surface.onBind(leaf.id, id),
|
|
1668
1693
|
views: surface.state.views,
|
|
1669
1694
|
currentViewId: surface.state.currentViewId
|
|
1695
|
+
}) : surface.state.perfMode === true && !focused ? h(MvParkedPane, {
|
|
1696
|
+
title,
|
|
1697
|
+
onResume: ()=>surface.onFocus(leaf.id)
|
|
1670
1698
|
}) : h(MvPaneHost, {
|
|
1671
1699
|
sessionId,
|
|
1672
1700
|
SessionProvider: surface.SessionProvider,
|
|
@@ -1680,6 +1708,19 @@ const MvPaneHost = React.memo(function MvPaneHost(props) {
|
|
|
1680
1708
|
renderSlot: props.renderSlot
|
|
1681
1709
|
});
|
|
1682
1710
|
}, (previous, next)=>previous.sessionId === next.sessionId && previous.SessionProvider === next.SessionProvider && previous.renderSlot === next.renderSlot);
|
|
1711
|
+
function MvParkedPane(props) {
|
|
1712
|
+
const { title, onResume } = props;
|
|
1713
|
+
return h('button', {
|
|
1714
|
+
type: 'button',
|
|
1715
|
+
className: 'mv-parked',
|
|
1716
|
+
onClick: onResume,
|
|
1717
|
+
title: T('parkedHint')
|
|
1718
|
+
}, h('span', {
|
|
1719
|
+
className: 'mv-parked-title'
|
|
1720
|
+
}, title), h('span', {
|
|
1721
|
+
className: 'mv-parked-note'
|
|
1722
|
+
}, T('parkedTitle')));
|
|
1723
|
+
}
|
|
1683
1724
|
function renderMvNode(node, surface) {
|
|
1684
1725
|
if (node.kind === 'leaf') return h(PaneFrame, {
|
|
1685
1726
|
key: node.id,
|
|
@@ -1806,6 +1847,20 @@ function MvUpdateDialog(props) {
|
|
|
1806
1847
|
'aria-label': T('updateCheck')
|
|
1807
1848
|
}, body, actions));
|
|
1808
1849
|
}
|
|
1850
|
+
function MvPerfButton() {
|
|
1851
|
+
const state = useMvState();
|
|
1852
|
+
const on = state.perfMode === true;
|
|
1853
|
+
return h('button', {
|
|
1854
|
+
type: 'button',
|
|
1855
|
+
className: 'mv-icon mv-perf',
|
|
1856
|
+
'data-active': on ? 'true' : 'false',
|
|
1857
|
+
title: on ? T('perfModeOn') : T('perfModeOff'),
|
|
1858
|
+
'aria-label': T('perfMode'),
|
|
1859
|
+
onClick: ()=>mvTogglePerfMode()
|
|
1860
|
+
}, h(MvGlyph, {
|
|
1861
|
+
path: MV_ICON.perf
|
|
1862
|
+
}));
|
|
1863
|
+
}
|
|
1809
1864
|
function MvViewsButton(props) {
|
|
1810
1865
|
const { views, currentKey } = props;
|
|
1811
1866
|
const [open, setOpen] = React.useState(false);
|
|
@@ -2059,7 +2114,7 @@ function MultiviewStage(props) {
|
|
|
2059
2114
|
className: 'mv-chip',
|
|
2060
2115
|
'data-active': 'true',
|
|
2061
2116
|
onClick: mvRestore
|
|
2062
|
-
}, T('restore')), h(MvViewsButton, {
|
|
2117
|
+
}, T('restore')), h(MvPerfButton, null), h(MvViewsButton, {
|
|
2063
2118
|
views: state.views ?? [],
|
|
2064
2119
|
currentKey: mvViewKey(state.layout)
|
|
2065
2120
|
}), h(MvUpdateButton, null), h('span', {
|
|
@@ -64,6 +64,15 @@
|
|
|
64
64
|
|
|
65
65
|
> 该 VM 的测试 profile 里没有可用的审批触发条件(无 `danger-full-access` 提权会话),所以审批链路只在 `desktop-kfscv8c-1` 的 0.2.1-rc.5 实例上验证过(S11)。
|
|
66
66
|
|
|
67
|
+
|
|
68
|
+
## 2.2 卸载 / 重装演练(win11-test,2026-11-07)
|
|
69
|
+
|
|
70
|
+
| # | 步骤 | 结果 |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| U1 | 从 profile 删除依赖 + bundle 行 + `node_modules\dsh-multiview`,重启 | 启动图 0 条 multiview;侧栏无「分屏」;无 `.mv-*` 残留样式(`oursLeft: 0`) |
|
|
73
|
+
| U2 | 残留检查 | `localStorage['dsh.multiview.v1']` 仍在(约 1.4KB,文档已声明为唯一残留) |
|
|
74
|
+
| U3 | 重新装回 0.1.10 + 恢复 bundle 行,重启 | 启动图 1 条;「分屏」回来;打开面板 4 格正常、无错误提示 |
|
|
75
|
+
|
|
67
76
|
## 3. 性能(CDP rAF 采样,1530×851)
|
|
68
77
|
|
|
69
78
|
| 场景 | p50 | p95 | 最长帧 | >50ms 长任务 | JS 堆 |
|
|
@@ -72,7 +81,10 @@
|
|
|
72
81
|
| 4 格挂载、空闲 | 16.7ms | 16.8ms | 16.8ms | 0 | 75MB |
|
|
73
82
|
| 4 格挂载、2 路同时流式 | 16.7ms | 16.7ms | 16.8ms | 0 | 85MB |
|
|
74
83
|
| 4 格挂载、4 路**同时长流式**(60s 采样,前 ~40s 四路都在产出) | 16.7ms | **50ms** | **150ms** | **2** | 53MB |
|
|
84
|
+
| 4 格、4 路同时长流式(**win11-test 真机**,0.2.2-rc.1,60s,四路全程在产出) | 19.3ms | **140ms** | **370ms** | **55** | 69MB |
|
|
85
|
+
| 4 格静态内容、脚本化滚动 10s(win11-test 真机) | 15.8ms | 26ms | 85ms | — | — |
|
|
75
86
|
|
|
87
|
+
> **2026-11-07 真机复测结论**:交互(滚动/拖拽/聚焦)在 4 格下仍是 ~59fps、p95 26ms;卡顿只出现在「多格同时长流式」——那是 DSH Chat 自己对每个 chunk 重排 markdown 的成本 ×4,不是分屏框架的开销(`contain: strict` A/B 59.2 vs 58.5fps,属噪声,未采纳)。另:虚拟机窗口失焦时 Chromium 会把 rAF 压到 ~7fps,所以真机绝对帧率受环境影响,只有同一窗口内的对比才有意义。
|
|
76
88
|
> 四路长文同时流式是本插件目前压到的最重场景:平均仍近 60fps,但 p95 升到 50ms、出现 2 个 >50ms 长任务与一次 150ms 长帧。原因是四份虚拟化 transcript 各自按 chunk 重排,属于渲染侧固有成本;未做节流/降级(保持"每格都是真实 Chat"的产品约束)。需要更稳时可后续加"非聚焦格降频"。|
|
|
77
89
|
|
|
78
90
|
采样脚本:`.dev/sampler.js`(`requestAnimationFrame` 间隔分布 + `PerformanceObserver('longtask')` + `performance.memory`)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-multiview",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Multi-view for DeepSeek Harness: 2-4 live conversations side by side, each pane a real Chat, with resize, swap, saved splits and pane focus.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|