dsh-chat-background 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.en.md +11 -9
- package/README.md +9 -8
- package/client.js +478 -125
- package/docs/ARCHITECTURE.md +8 -0
- package/docs/CONVENTIONS.md +2 -0
- package/docs/PUBLISHING.md +14 -10
- package/docs/TESTING.md +13 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [SemVer](https://semver.org/lang/zh-CN/);版本号与 `package.json`、`client.js` banner 保持一致。
|
|
4
4
|
|
|
5
|
+
## [1.1.0] - 2026-11-04
|
|
6
|
+
|
|
7
|
+
阅读体验版本:对话内容按轮次分区显示,设置页控件在不同屏幕/缩放下保持稳定。
|
|
8
|
+
|
|
9
|
+
### 新增
|
|
10
|
+
|
|
11
|
+
- **按轮次绘制的对话遮罩**:每一轮对话(用户消息 + 对应回复)单独一张遮罩卡,卡片宽度 = 阅读列宽 + 两侧各 24px 留白,随阅读列拖拽手柄自动变化;轮次之间露出壁纸,不再整列铺一层遮罩。
|
|
12
|
+
- **舒适轮次间距**:把 DSH 的轮次布局间距从原生 12px 调到 28px(写回宿主的 `--dsh-chat-flow-gap`),配合卡片上边 6px 出血,卡片间保留约 14px 壁纸缝,用户消息气泡完整包在遮罩内。
|
|
13
|
+
|
|
14
|
+
### 变更
|
|
15
|
+
|
|
16
|
+
- **主窗口遮罩层**现在控制“每轮对话卡片”的透明度,而不是整块主窗口;0 为无遮罩,100 为不透明卡片。
|
|
17
|
+
- **设置页控件模块化**:所有设置行改为两列 grid,右侧控件组保持自身宽度并右对齐;轮换设置使用固定的 caption/输入/数值槽位;下拉框、按钮、颜色块固定尺寸,不再随语言、字体缩放或 DPI 漂移。
|
|
18
|
+
- **滑块**改用尺寸变量推导 thumb 偏移,替代手调像素值;窄屏(≤640px)控件堆叠到说明下方。
|
|
19
|
+
|
|
20
|
+
### 修复
|
|
21
|
+
|
|
22
|
+
- 切换窗口后主窗口/右侧栏偶发变白:通过稳定的槽位标记清除 app 画在布局 frame、主窗口列和右侧栏上的不透明底色,并在标题栏标记变化、窗口重新获得焦点或恢复可见时重新测量。
|
|
23
|
+
- 设置页滑块圆点在部分缩放/DPI 下浮到轨道上方、控件互相挤压错位。
|
|
24
|
+
- 对话遮罩层被宿主的 flow 间距下推 6px,导致用户消息气泡顶部露在遮罩外。
|
|
25
|
+
- npm 安装说明与实际发布状态不一致。
|
|
26
|
+
|
|
27
|
+
### 安装与兼容
|
|
28
|
+
|
|
29
|
+
- DSH 桌面端 `>=0.2.1-rc.3`;Node `^22.19.0 || >=24.0.0`。
|
|
30
|
+
- 支持从 GitHub、npm 或手动安装到 profile,详见 [README](README.md#安装)。
|
|
31
|
+
|
|
5
32
|
## [1.0.0] - 2026-11-02
|
|
6
33
|
|
|
7
34
|
首个公开版本。
|
package/README.en.md
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
## What it does
|
|
13
13
|
|
|
14
|
-
Pick a few pictures, put them in the background folder
|
|
15
|
-
or rotate through a group of favourites; tune the
|
|
16
|
-
text stays readable. The plugin also adds a bar under
|
|
14
|
+
Pick a few pictures, put them in the background folder (default `~/.dsh/backgrounds/`, created automatically if
|
|
15
|
+
missing), and the chat window gets a wallpaper. Keep one image fixed or rotate through a group of favourites; tune the
|
|
16
|
+
left sidebar, main window and right sidebar scrims separately so text stays readable. The plugin also adds a bar under
|
|
17
|
+
the input, a task progress bar and a total token usage line.
|
|
17
18
|
|
|
18
19
|
## Features
|
|
19
20
|
|
|
@@ -44,14 +45,14 @@ dsh plugin --profile desktop add github:LittleLemonJam/dsh-chat-background
|
|
|
44
45
|
Fully quit and reopen DSH afterwards — the host half loads at boot; refreshing the page is not enough.
|
|
45
46
|
Check **Plugins → Installed** to confirm `dsh-chat-background` is enabled.
|
|
46
47
|
|
|
47
|
-
### From npm
|
|
48
|
-
|
|
49
|
-
> The first release is distributed through GitHub; use the command below after the npm package is published.
|
|
48
|
+
### From npm
|
|
50
49
|
|
|
51
50
|
```bash
|
|
52
51
|
dsh plugin --profile desktop add dsh-chat-background
|
|
53
52
|
```
|
|
54
53
|
|
|
54
|
+
The npm package tracks the GitHub release (currently `1.1.0`); either channel works.
|
|
55
|
+
|
|
55
56
|
### Manual install
|
|
56
57
|
|
|
57
58
|
1. Copy `package.json`, `index.js`, `host/`, `client.js`, `cordis.patch.yml`, `locale/` and `icon.svg` into
|
|
@@ -68,10 +69,11 @@ dsh plugin --profile desktop add dsh-chat-background
|
|
|
68
69
|
|
|
69
70
|
4. Restart DSH.
|
|
70
71
|
|
|
71
|
-
###
|
|
72
|
+
### Where pictures go
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
**
|
|
74
|
+
Pictures live in `~/.dsh/backgrounds/` (`C:\Users\<you>\.dsh\backgrounds\` on Windows; set `DSH_BACKGROUNDS_DIR` to use
|
|
75
|
+
another folder). **You do not need to create the folder first** — opening the settings page or pressing **Open folder**
|
|
76
|
+
creates it automatically. Drop images in, then press **Rescan** under **Settings → Page settings → Background**.
|
|
75
77
|
|
|
76
78
|
## Usage
|
|
77
79
|
|
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
## 简介
|
|
12
12
|
|
|
13
|
-
这是一个本地运行的 DSH
|
|
14
|
-
|
|
13
|
+
这是一个本地运行的 DSH 插件。选几张喜欢的图片放进背景文件夹(默认 `~/.dsh/backgrounds/`,不存在会自动创建),
|
|
14
|
+
就能让聊天界面拥有自己的壁纸:可以固定一张,也可以按间隔自动轮换;左侧栏、主窗口、右侧栏的遮罩深浅可以分别调,界面文字仍然清楚可读。
|
|
15
15
|
另外还附带输入栏背景条、任务进度条和全会话 Token 用量等小工具。
|
|
16
16
|
|
|
17
17
|
## 功能
|
|
@@ -43,14 +43,14 @@ dsh plugin --profile desktop add github:LittleLemonJam/dsh-chat-background
|
|
|
43
43
|
安装后**完全退出并重新打开** DSH 桌面端(宿主半在启动时加载,只刷新页面不够)。
|
|
44
44
|
可以在 **插件 → 已安装** 里确认 `dsh-chat-background` 处于启用状态。
|
|
45
45
|
|
|
46
|
-
### 从 npm
|
|
47
|
-
|
|
48
|
-
> 当前首发渠道是 GitHub;npm 包发布后可使用下面的命令。
|
|
46
|
+
### 从 npm 安装
|
|
49
47
|
|
|
50
48
|
```bash
|
|
51
49
|
dsh plugin --profile desktop add dsh-chat-background
|
|
52
50
|
```
|
|
53
51
|
|
|
52
|
+
npm 包与 GitHub 发布同步(当前 `1.1.0`),两条安装方式任选其一即可。
|
|
53
|
+
|
|
54
54
|
### 手动安装
|
|
55
55
|
|
|
56
56
|
1. 把仓库(或 `npm pack` 解压后)的 `package.json`、`index.js`、`host/`、`client.js`、`cordis.patch.yml`、
|
|
@@ -68,10 +68,11 @@ dsh plugin --profile desktop add dsh-chat-background
|
|
|
68
68
|
|
|
69
69
|
4. 重启 DSH。
|
|
70
70
|
|
|
71
|
-
###
|
|
71
|
+
### 背景图放在哪里
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
图片固定放在 `~/.dsh/backgrounds/`(Windows 为 `C:\Users\<用户名>\.dsh\backgrounds\`;也可以用环境变量
|
|
74
|
+
`DSH_BACKGROUNDS_DIR` 指向其他目录)。**目录不存在不用手动创建**:第一次打开设置页或点「打开文件夹」时插件会自动建好。
|
|
75
|
+
把图片放进去后,在 **设置 → 页面设置 → 背景** 点「重新扫描」即可看到。
|
|
75
76
|
|
|
76
77
|
## 使用
|
|
77
78
|
|
package/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-chat-background v1.
|
|
2
|
+
* dsh-chat-background v1.1.0
|
|
3
3
|
* Chat background image, sidebar veil, composer strip, task progress bar and all-session token total for the DeepSeek Harness (DSH) Web GUI.
|
|
4
4
|
* @author LittleLemonJam
|
|
5
5
|
* @license MIT
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
var RIGHT_COLUMN_ATTRIBUTE = "data-dsh-chat-background-right";
|
|
41
41
|
var RIGHT_PANEL_ATTRIBUTE = "data-dsh-chat-background-right-panel";
|
|
42
42
|
var RIGHT_PANEL_FULL_ATTRIBUTE = "data-dsh-chat-background-right-panel-full";
|
|
43
|
+
var READ_COLUMN_ATTRIBUTE = "data-dsh-chat-background-read-column";
|
|
43
44
|
var LAYOUT_FRAME_ATTRIBUTE = "data-dsh-chat-background-frame";
|
|
44
45
|
var FRAME_ANIMATING_ATTRIBUTE = "data-dsh-chat-background-frame-animating";
|
|
45
46
|
var RIGHT_FULLSCREEN_ATTRIBUTE = "data-dsh-chat-background-right-fullscreen";
|
|
@@ -94,7 +95,7 @@
|
|
|
94
95
|
}
|
|
95
96
|
function diagnosticsSnapshot() {
|
|
96
97
|
return {
|
|
97
|
-
version: true ? "1.
|
|
98
|
+
version: true ? "1.1.0" : "unknown",
|
|
98
99
|
updatedAt: Date.now(),
|
|
99
100
|
...state
|
|
100
101
|
};
|
|
@@ -762,24 +763,273 @@
|
|
|
762
763
|
return element;
|
|
763
764
|
}
|
|
764
765
|
|
|
766
|
+
// src/lib/turn-masks.js
|
|
767
|
+
function sidePadding(gap, padding, minGap) {
|
|
768
|
+
if (!Number.isFinite(gap)) return padding;
|
|
769
|
+
if (gap <= 0) return 0;
|
|
770
|
+
return Math.max(0, Math.min(padding, (gap - minGap) / 2));
|
|
771
|
+
}
|
|
772
|
+
function computeTurnCards(ranges, options = {}) {
|
|
773
|
+
const padding = options.padding ?? 10;
|
|
774
|
+
const minGap = options.minGap ?? 6;
|
|
775
|
+
return ranges.map((range, index) => {
|
|
776
|
+
const previous = ranges[index - 1];
|
|
777
|
+
const next = ranges[index + 1];
|
|
778
|
+
const gapAbove = previous === void 0 ? Infinity : range.top - previous.bottom;
|
|
779
|
+
const gapBelow = next === void 0 ? Infinity : next.top - range.bottom;
|
|
780
|
+
const padTop = sidePadding(gapAbove, padding, minGap);
|
|
781
|
+
const padBottom = sidePadding(gapBelow, padding, minGap);
|
|
782
|
+
return {
|
|
783
|
+
key: range.key,
|
|
784
|
+
top: range.top - padTop,
|
|
785
|
+
height: Math.max(0, range.bottom - range.top + padTop + padBottom)
|
|
786
|
+
};
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/turn-masks.js
|
|
791
|
+
var FLOW_SELECTOR = '[data-slot="conversation.chat.flow"]';
|
|
792
|
+
var ITEM_SELECTOR = "[data-chat-turn]";
|
|
793
|
+
var LAYER_CLASS = "dshcb-turn-scrims";
|
|
794
|
+
var SCRIM_CLASS = "dshcb-turn-scrim";
|
|
795
|
+
var PADDING = 10;
|
|
796
|
+
var TOP_BLEED = 6;
|
|
797
|
+
var MIN_GAP = 20;
|
|
798
|
+
var SIDE_PADDING = 24;
|
|
799
|
+
var TURN_GAP = 28;
|
|
800
|
+
var GAP_PROPERTY = "--dsh-chat-flow-gap";
|
|
801
|
+
var column = null;
|
|
802
|
+
var layer = null;
|
|
803
|
+
var resizeObserver = null;
|
|
804
|
+
var frame = 0;
|
|
805
|
+
var scrims = /* @__PURE__ */ new Map();
|
|
806
|
+
var applied = /* @__PURE__ */ new Map();
|
|
807
|
+
var boundaries = /* @__PURE__ */ new Map();
|
|
808
|
+
var lastReport = "";
|
|
809
|
+
function findFlow() {
|
|
810
|
+
for (const flow of document.querySelectorAll(FLOW_SELECTOR)) {
|
|
811
|
+
const items = flow.querySelectorAll(ITEM_SELECTOR);
|
|
812
|
+
if (items.length === 0) continue;
|
|
813
|
+
if (flow.getClientRects().length === 0 && getComputedStyle(flow).display !== "contents") continue;
|
|
814
|
+
let visible = false;
|
|
815
|
+
for (const item of items) {
|
|
816
|
+
const rect = item.getBoundingClientRect();
|
|
817
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
818
|
+
visible = true;
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (!visible) continue;
|
|
823
|
+
return flow;
|
|
824
|
+
}
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
function findColumn(flow) {
|
|
828
|
+
let element = flow;
|
|
829
|
+
while (element !== null && element !== document.body) {
|
|
830
|
+
const style = getComputedStyle(element);
|
|
831
|
+
if (style.position === "fixed") return null;
|
|
832
|
+
if (style.display !== "contents" && element.clientWidth > 0 && element.clientHeight > 0) return element;
|
|
833
|
+
element = element.parentElement;
|
|
834
|
+
}
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
function disposeLayer() {
|
|
838
|
+
resizeObserver?.disconnect();
|
|
839
|
+
resizeObserver = null;
|
|
840
|
+
layer?.remove();
|
|
841
|
+
layer = null;
|
|
842
|
+
if (column !== null) column.removeAttribute(READ_COLUMN_ATTRIBUTE);
|
|
843
|
+
column = null;
|
|
844
|
+
for (const [element] of boundaries) element.style.removeProperty(GAP_PROPERTY);
|
|
845
|
+
boundaries.clear();
|
|
846
|
+
scrims.clear();
|
|
847
|
+
applied.clear();
|
|
848
|
+
}
|
|
849
|
+
function ensureLayer(nextColumn) {
|
|
850
|
+
if (column !== nextColumn) {
|
|
851
|
+
disposeLayer();
|
|
852
|
+
column = nextColumn;
|
|
853
|
+
column.setAttribute(READ_COLUMN_ATTRIBUTE, "");
|
|
854
|
+
}
|
|
855
|
+
if (layer === null || !layer.isConnected) {
|
|
856
|
+
layer = document.createElement("div");
|
|
857
|
+
layer.className = LAYER_CLASS;
|
|
858
|
+
layer.setAttribute("aria-hidden", "true");
|
|
859
|
+
column.appendChild(layer);
|
|
860
|
+
}
|
|
861
|
+
if (resizeObserver === null && typeof ResizeObserver !== "undefined" && column !== null) {
|
|
862
|
+
resizeObserver = new ResizeObserver(() => {
|
|
863
|
+
scheduleTurnMaskUpdate();
|
|
864
|
+
});
|
|
865
|
+
resizeObserver.observe(column);
|
|
866
|
+
}
|
|
867
|
+
if (layer.parentElement !== column) column.appendChild(layer);
|
|
868
|
+
return layer;
|
|
869
|
+
}
|
|
870
|
+
function measureTurns(flow, originTop) {
|
|
871
|
+
const turns = [];
|
|
872
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
873
|
+
for (const item of flow.querySelectorAll(ITEM_SELECTOR)) {
|
|
874
|
+
if (item.closest("[hidden]") !== null) continue;
|
|
875
|
+
const itemRect = item.getBoundingClientRect();
|
|
876
|
+
if (itemRect.width <= 0 || itemRect.height <= 0) continue;
|
|
877
|
+
const turn = item.getAttribute("data-chat-turn") ?? "";
|
|
878
|
+
if (turn === "") continue;
|
|
879
|
+
const top = itemRect.top - originTop;
|
|
880
|
+
const bottom = itemRect.bottom - originTop;
|
|
881
|
+
const existing = byKey.get(turn);
|
|
882
|
+
if (existing === void 0) {
|
|
883
|
+
const group = { key: turn, top, bottom, first: item };
|
|
884
|
+
byKey.set(turn, group);
|
|
885
|
+
turns.push(group);
|
|
886
|
+
} else {
|
|
887
|
+
if (top < existing.top) existing.top = top;
|
|
888
|
+
if (bottom > existing.bottom) existing.bottom = bottom;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
turns.sort((left, right) => left.top - right.top);
|
|
892
|
+
return turns;
|
|
893
|
+
}
|
|
894
|
+
function syncTurnGaps(turns) {
|
|
895
|
+
const desired = /* @__PURE__ */ new Map();
|
|
896
|
+
for (let index = 1; index < turns.length; index += 1) {
|
|
897
|
+
const first = turns[index].first;
|
|
898
|
+
if (first !== null) desired.set(first, TURN_GAP + "px");
|
|
899
|
+
}
|
|
900
|
+
let changed = false;
|
|
901
|
+
for (const [element] of boundaries) {
|
|
902
|
+
if (element.isConnected && desired.has(element)) continue;
|
|
903
|
+
element.style.removeProperty(GAP_PROPERTY);
|
|
904
|
+
boundaries.delete(element);
|
|
905
|
+
changed = true;
|
|
906
|
+
}
|
|
907
|
+
for (const [element, value] of desired) {
|
|
908
|
+
if (boundaries.get(element) === value) continue;
|
|
909
|
+
element.style.setProperty(GAP_PROPERTY, value);
|
|
910
|
+
boundaries.set(element, value);
|
|
911
|
+
changed = true;
|
|
912
|
+
}
|
|
913
|
+
return changed;
|
|
914
|
+
}
|
|
915
|
+
function refresh() {
|
|
916
|
+
frame = 0;
|
|
917
|
+
if (!document.documentElement.hasAttribute(ACTIVE_ATTRIBUTE)) {
|
|
918
|
+
disposeLayer();
|
|
919
|
+
recordTurnMaskDiagnostics({ active: false });
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const flow = findFlow();
|
|
923
|
+
const nextColumn = flow === null ? null : findColumn(flow);
|
|
924
|
+
if (nextColumn === null) {
|
|
925
|
+
disposeLayer();
|
|
926
|
+
recordTurnMaskDiagnostics({ active: true, column: false, turns: 0 });
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const cardLayer = ensureLayer(nextColumn);
|
|
930
|
+
const style = getComputedStyle(nextColumn);
|
|
931
|
+
const paddingLeft = Number.parseFloat(style.paddingLeft) || 0;
|
|
932
|
+
const paddingRight = Number.parseFloat(style.paddingRight) || 0;
|
|
933
|
+
const borderLeft = Number.parseFloat(style.borderLeftWidth) || 0;
|
|
934
|
+
const borderTop = Number.parseFloat(style.borderTopWidth) || 0;
|
|
935
|
+
const rect = nextColumn.getBoundingClientRect();
|
|
936
|
+
const scrollTop = nextColumn.scrollTop || 0;
|
|
937
|
+
const scrollLeft = nextColumn.scrollLeft || 0;
|
|
938
|
+
const originTop = rect.top + borderTop - scrollTop;
|
|
939
|
+
const originLeft = rect.left + borderLeft - scrollLeft;
|
|
940
|
+
const contentWidth = Math.max(0, nextColumn.clientWidth - paddingLeft - paddingRight);
|
|
941
|
+
let turns = measureTurns(flow, originTop);
|
|
942
|
+
if (syncTurnGaps(turns)) turns = measureTurns(flow, originTop);
|
|
943
|
+
const cards = computeTurnCards(turns, { padding: PADDING, minGap: MIN_GAP });
|
|
944
|
+
const cardLeft = paddingLeft - SIDE_PADDING;
|
|
945
|
+
const cardWidth = contentWidth + SIDE_PADDING * 2;
|
|
946
|
+
cardLayer.style.left = cardLeft + "px";
|
|
947
|
+
cardLayer.style.width = cardWidth + "px";
|
|
948
|
+
const live = /* @__PURE__ */ new Set();
|
|
949
|
+
for (const card of cards) {
|
|
950
|
+
const rawTop = Math.round(card.top) - TOP_BLEED;
|
|
951
|
+
const top = Math.max(0, rawTop);
|
|
952
|
+
const height = Math.max(0, Math.round(card.height) + TOP_BLEED - (top - rawTop));
|
|
953
|
+
const key = card.key;
|
|
954
|
+
live.add(key);
|
|
955
|
+
let element = scrims.get(key);
|
|
956
|
+
if (element === void 0) {
|
|
957
|
+
element = document.createElement("div");
|
|
958
|
+
element.className = SCRIM_CLASS;
|
|
959
|
+
element.setAttribute("data-turn", key);
|
|
960
|
+
cardLayer.appendChild(element);
|
|
961
|
+
scrims.set(key, element);
|
|
962
|
+
applied.delete(key);
|
|
963
|
+
}
|
|
964
|
+
const previous = applied.get(key);
|
|
965
|
+
if (previous === void 0 || previous.top !== top || previous.height !== height) {
|
|
966
|
+
element.style.top = top + "px";
|
|
967
|
+
element.style.height = height + "px";
|
|
968
|
+
applied.set(key, { top, height });
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
for (const [key, element] of scrims) {
|
|
972
|
+
if (live.has(key)) continue;
|
|
973
|
+
element.remove();
|
|
974
|
+
scrims.delete(key);
|
|
975
|
+
applied.delete(key);
|
|
976
|
+
}
|
|
977
|
+
recordTurnMaskDiagnostics({
|
|
978
|
+
active: true,
|
|
979
|
+
column: true,
|
|
980
|
+
turns: cards.length,
|
|
981
|
+
width: Math.round(cardWidth),
|
|
982
|
+
textWidth: Math.round(contentWidth)
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
function recordTurnMaskDiagnostics(value) {
|
|
986
|
+
const next = JSON.stringify(value);
|
|
987
|
+
if (next === lastReport) return;
|
|
988
|
+
lastReport = next;
|
|
989
|
+
recordDiagnostics({ turnMasks: value });
|
|
990
|
+
}
|
|
991
|
+
function scheduleTurnMaskUpdate() {
|
|
992
|
+
if (frame !== 0) return;
|
|
993
|
+
if (typeof window.requestAnimationFrame !== "function") {
|
|
994
|
+
refresh();
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
frame = window.requestAnimationFrame(refresh);
|
|
998
|
+
}
|
|
999
|
+
function updateTurnMasks() {
|
|
1000
|
+
if (frame !== 0) {
|
|
1001
|
+
window.cancelAnimationFrame(frame);
|
|
1002
|
+
frame = 0;
|
|
1003
|
+
}
|
|
1004
|
+
refresh();
|
|
1005
|
+
}
|
|
1006
|
+
function teardownTurnMasks() {
|
|
1007
|
+
if (frame !== 0) {
|
|
1008
|
+
window.cancelAnimationFrame(frame);
|
|
1009
|
+
frame = 0;
|
|
1010
|
+
}
|
|
1011
|
+
disposeLayer();
|
|
1012
|
+
recordTurnMaskDiagnostics({ active: false });
|
|
1013
|
+
}
|
|
1014
|
+
|
|
765
1015
|
// src/background.js
|
|
766
1016
|
function ensureBackgroundLayer(name) {
|
|
767
1017
|
if (document.body === null || name === null) return null;
|
|
768
|
-
let
|
|
769
|
-
if (
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
document.body.appendChild(
|
|
1018
|
+
let layer3 = document.querySelector("[" + BACKGROUND_LAYER_ATTRIBUTE + "]");
|
|
1019
|
+
if (layer3 === null || layer3.tagName !== "IMG") {
|
|
1020
|
+
layer3?.remove();
|
|
1021
|
+
layer3 = document.createElement("img");
|
|
1022
|
+
layer3.className = "dshcb-background-layer";
|
|
1023
|
+
layer3.setAttribute(BACKGROUND_LAYER_ATTRIBUTE, "");
|
|
1024
|
+
layer3.setAttribute("aria-hidden", "true");
|
|
1025
|
+
layer3.setAttribute("alt", "");
|
|
1026
|
+
layer3.setAttribute("decoding", "async");
|
|
1027
|
+
layer3.setAttribute("draggable", "false");
|
|
1028
|
+
document.body.appendChild(layer3);
|
|
779
1029
|
}
|
|
780
1030
|
const src = FILE_URL + encodeURIComponent(name);
|
|
781
|
-
if (
|
|
782
|
-
return
|
|
1031
|
+
if (layer3.getAttribute("src") !== src) layer3.setAttribute("src", src);
|
|
1032
|
+
return layer3;
|
|
783
1033
|
}
|
|
784
1034
|
function removeBackgroundLayer() {
|
|
785
1035
|
for (const element of document.querySelectorAll("[" + BACKGROUND_LAYER_ATTRIBUTE + "]")) element.remove();
|
|
@@ -815,7 +1065,7 @@
|
|
|
815
1065
|
function readRightScrim() {
|
|
816
1066
|
return readRegionScrim(SCRIM_RIGHT_KEY, VEIL_KEY, VEIL_DEFAULT);
|
|
817
1067
|
}
|
|
818
|
-
function
|
|
1068
|
+
function layer2(alpha) {
|
|
819
1069
|
const rgb = isDark() ? "9,11,15" : "250,250,250";
|
|
820
1070
|
const value = (alpha / 100).toFixed(3);
|
|
821
1071
|
return `linear-gradient(rgba(${rgb},${value}), rgba(${rgb},${value}))`;
|
|
@@ -860,16 +1110,16 @@
|
|
|
860
1110
|
}
|
|
861
1111
|
function markColumn(attribute, slotName) {
|
|
862
1112
|
const outlet = document.querySelector(`[data-slot="${slotName}"]`);
|
|
863
|
-
const
|
|
864
|
-
if (
|
|
1113
|
+
const column2 = outlet === null ? null : outlet.parentElement;
|
|
1114
|
+
if (column2 !== null && !column2.hasAttribute(attribute)) column2.setAttribute(attribute, "");
|
|
865
1115
|
}
|
|
866
1116
|
function clearColumn(attribute) {
|
|
867
1117
|
for (const element of document.querySelectorAll(`[${attribute}]`)) element.removeAttribute(attribute);
|
|
868
1118
|
}
|
|
869
1119
|
function markSidebarColumn() {
|
|
870
1120
|
const outlet = document.querySelector('[data-slot="sidebar"]');
|
|
871
|
-
const
|
|
872
|
-
if (
|
|
1121
|
+
const column2 = outlet === null ? null : outlet.parentElement;
|
|
1122
|
+
if (column2 !== null) column2.setAttribute(SIDEBAR_COLUMN_ATTRIBUTE, "");
|
|
873
1123
|
}
|
|
874
1124
|
function markMainColumn() {
|
|
875
1125
|
markColumn(MAIN_COLUMN_ATTRIBUTE, "main");
|
|
@@ -936,7 +1186,7 @@
|
|
|
936
1186
|
observedPanel = null;
|
|
937
1187
|
}
|
|
938
1188
|
const outlet = document.querySelector('[data-slot="rightbar"]');
|
|
939
|
-
const
|
|
1189
|
+
const column2 = outlet === null ? null : outlet.parentElement;
|
|
940
1190
|
const panel = outlet === null ? null : outlet.querySelector('[class*="panel"]');
|
|
941
1191
|
const root = document.documentElement;
|
|
942
1192
|
const labelFull = hasExitFullscreenControl();
|
|
@@ -966,12 +1216,12 @@
|
|
|
966
1216
|
element.removeAttribute(RIGHT_PANEL_FULL_ATTRIBUTE);
|
|
967
1217
|
};
|
|
968
1218
|
setFull(panel);
|
|
969
|
-
if (
|
|
1219
|
+
if (column2 !== null) setFull(column2);
|
|
970
1220
|
if (full) {
|
|
971
1221
|
const leftOutlet = document.querySelector('[data-slot="sidebar"]');
|
|
972
1222
|
const leftColumn = leftOutlet === null ? null : leftOutlet.parentElement;
|
|
973
1223
|
const leftWidth = leftColumn === null ? 0 : leftColumn.getBoundingClientRect().width;
|
|
974
|
-
const columnLeft =
|
|
1224
|
+
const columnLeft = column2 === null ? 0 : column2.getBoundingClientRect().left;
|
|
975
1225
|
const offset = Math.round(leftWidth - columnLeft);
|
|
976
1226
|
const width = Math.round(window.innerWidth - leftWidth);
|
|
977
1227
|
root.style.setProperty("--dsh-chat-background-full-left", offset + "px");
|
|
@@ -984,15 +1234,15 @@
|
|
|
984
1234
|
}
|
|
985
1235
|
function animateLayoutFrame() {
|
|
986
1236
|
const outlet = document.querySelector('[data-slot="main"]');
|
|
987
|
-
const
|
|
988
|
-
const
|
|
989
|
-
if (
|
|
1237
|
+
const column2 = outlet === null ? null : outlet.parentElement;
|
|
1238
|
+
const frame2 = column2 === null ? null : column2.parentElement;
|
|
1239
|
+
if (frame2 === null) return;
|
|
990
1240
|
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
|
|
991
|
-
if (!reduced)
|
|
1241
|
+
if (!reduced) frame2.setAttribute(FRAME_ANIMATING_ATTRIBUTE, "");
|
|
992
1242
|
if (frameAnimTimer !== 0) window.clearTimeout(frameAnimTimer);
|
|
993
1243
|
frameAnimTimer = window.setTimeout(() => {
|
|
994
1244
|
frameAnimTimer = 0;
|
|
995
|
-
|
|
1245
|
+
frame2.removeAttribute(FRAME_ANIMATING_ATTRIBUTE);
|
|
996
1246
|
}, 340);
|
|
997
1247
|
}
|
|
998
1248
|
function installRightbarGestures() {
|
|
@@ -1052,9 +1302,9 @@
|
|
|
1052
1302
|
}
|
|
1053
1303
|
function markLayoutFrame() {
|
|
1054
1304
|
const outlet = document.querySelector('[data-slot="main"]');
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1057
|
-
if (
|
|
1305
|
+
const column2 = outlet === null ? null : outlet.parentElement;
|
|
1306
|
+
const frame2 = column2 === null ? null : column2.parentElement;
|
|
1307
|
+
if (frame2 !== null && !frame2.hasAttribute(LAYOUT_FRAME_ATTRIBUTE)) frame2.setAttribute(LAYOUT_FRAME_ATTRIBUTE, "");
|
|
1058
1308
|
}
|
|
1059
1309
|
function markRegionColumns() {
|
|
1060
1310
|
markSidebarColumn();
|
|
@@ -1112,7 +1362,7 @@
|
|
|
1112
1362
|
if (candidates.length > 0) syncDynamicRules([candidates[0]]);
|
|
1113
1363
|
return candidates;
|
|
1114
1364
|
}
|
|
1115
|
-
var
|
|
1365
|
+
var applied2 = { selection: void 0, dark: null, scrimLeft: null, scrimMain: null, scrimRight: null };
|
|
1116
1366
|
var markSignature = -1;
|
|
1117
1367
|
var markContainer = null;
|
|
1118
1368
|
var flushHandle = 0;
|
|
@@ -1128,7 +1378,7 @@
|
|
|
1128
1378
|
const container = conversationContainer();
|
|
1129
1379
|
const painters = markedPainters();
|
|
1130
1380
|
const needsPaint = signature !== markSignature || container !== markContainer || painters.length === 0;
|
|
1131
|
-
const sameVars =
|
|
1381
|
+
const sameVars = applied2.selection === next && applied2.dark === dark && applied2.scrimLeft === scrimLeft && applied2.scrimMain === scrimMain && applied2.scrimRight === scrimRight;
|
|
1132
1382
|
if (next === null) {
|
|
1133
1383
|
if (sameVars && !root.hasAttribute(ACTIVE_ATTRIBUTE)) return;
|
|
1134
1384
|
for (const property of INLINE_PROPERTIES) root.style.removeProperty(property);
|
|
@@ -1140,8 +1390,9 @@
|
|
|
1140
1390
|
clearPainters();
|
|
1141
1391
|
clearRegionColumns();
|
|
1142
1392
|
syncDynamicRules([]);
|
|
1393
|
+
teardownTurnMasks();
|
|
1143
1394
|
nudgeNativeTitlebar(false);
|
|
1144
|
-
|
|
1395
|
+
applied2 = { selection: null, dark, scrimLeft, scrimMain, scrimRight };
|
|
1145
1396
|
markSignature = -1;
|
|
1146
1397
|
markContainer = null;
|
|
1147
1398
|
recordDiagnostics({ background: { active: false } });
|
|
@@ -1171,13 +1422,14 @@
|
|
|
1171
1422
|
});
|
|
1172
1423
|
if (!sameVars) {
|
|
1173
1424
|
root.style.setProperty("--dsh-chat-background-image", 'url("' + FILE_URL + encodeURIComponent(next) + '")');
|
|
1174
|
-
root.style.setProperty("--dsh-chat-background-scrim-left",
|
|
1175
|
-
root.style.setProperty("--dsh-chat-background-scrim-main",
|
|
1176
|
-
root.style.setProperty("--dsh-chat-background-scrim-right",
|
|
1425
|
+
root.style.setProperty("--dsh-chat-background-scrim-left", layer2(scrimLeft));
|
|
1426
|
+
root.style.setProperty("--dsh-chat-background-scrim-main", layer2(scrimMain));
|
|
1427
|
+
root.style.setProperty("--dsh-chat-background-scrim-right", layer2(scrimRight));
|
|
1177
1428
|
root.setAttribute(ACTIVE_ATTRIBUTE, next);
|
|
1178
|
-
|
|
1429
|
+
applied2 = { selection: next, dark, scrimLeft, scrimMain, scrimRight };
|
|
1179
1430
|
nudgeNativeTitlebar(true);
|
|
1180
1431
|
}
|
|
1432
|
+
updateTurnMasks();
|
|
1181
1433
|
}
|
|
1182
1434
|
function flushBackground() {
|
|
1183
1435
|
if (flushHandle !== 0) return;
|
|
@@ -1186,6 +1438,7 @@
|
|
|
1186
1438
|
applyBackground(readSelection());
|
|
1187
1439
|
renderComposerStrip();
|
|
1188
1440
|
renderTokenTotal();
|
|
1441
|
+
updateTurnMasks();
|
|
1189
1442
|
};
|
|
1190
1443
|
if (typeof window.requestAnimationFrame === "function") {
|
|
1191
1444
|
flushIsTimeout = false;
|
|
@@ -1195,6 +1448,11 @@
|
|
|
1195
1448
|
flushHandle = window.setTimeout(run, 16);
|
|
1196
1449
|
}
|
|
1197
1450
|
}
|
|
1451
|
+
function invalidatePainters() {
|
|
1452
|
+
markSignature = -1;
|
|
1453
|
+
markContainer = null;
|
|
1454
|
+
flushBackground();
|
|
1455
|
+
}
|
|
1198
1456
|
function teardownVisuals() {
|
|
1199
1457
|
if (flushHandle !== 0) {
|
|
1200
1458
|
if (flushIsTimeout) window.clearTimeout(flushHandle);
|
|
@@ -1224,6 +1482,7 @@
|
|
|
1224
1482
|
clearPainters();
|
|
1225
1483
|
clearRegionColumns();
|
|
1226
1484
|
syncDynamicRules([]);
|
|
1485
|
+
teardownTurnMasks();
|
|
1227
1486
|
document.querySelector(`style[data-plugin-css=${cssString(DYNAMIC_TAG_ID)}]`)?.remove();
|
|
1228
1487
|
document.querySelector(`[${STRIP_ATTRIBUTE}]`)?.remove();
|
|
1229
1488
|
document.querySelector(`[${TOKEN_NODE_ATTRIBUTE}]`)?.remove();
|
|
@@ -1231,7 +1490,7 @@
|
|
|
1231
1490
|
cachedContainer = null;
|
|
1232
1491
|
markSignature = -1;
|
|
1233
1492
|
markContainer = null;
|
|
1234
|
-
|
|
1493
|
+
applied2 = { selection: void 0, dark: null, scrimLeft: null, scrimMain: null, scrimRight: null };
|
|
1235
1494
|
}
|
|
1236
1495
|
|
|
1237
1496
|
// src/lib/zoom.js
|
|
@@ -1463,7 +1722,7 @@
|
|
|
1463
1722
|
}
|
|
1464
1723
|
|
|
1465
1724
|
// src/rotation.js
|
|
1466
|
-
var
|
|
1725
|
+
var LAYER_CLASS2 = "dshcb-rotate-layer";
|
|
1467
1726
|
var timer = 0;
|
|
1468
1727
|
var layerTimers = [];
|
|
1469
1728
|
var request = null;
|
|
@@ -1522,45 +1781,45 @@
|
|
|
1522
1781
|
return document.body?.hasAttribute("data-ds-dark-theme") === true ? "rgb(9, 11, 15)" : "rgb(250, 250, 250)";
|
|
1523
1782
|
}
|
|
1524
1783
|
function createImageLayer(name) {
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
document.body.appendChild(
|
|
1533
|
-
return
|
|
1784
|
+
const layer3 = document.createElement("img");
|
|
1785
|
+
layer3.className = LAYER_CLASS2;
|
|
1786
|
+
layer3.setAttribute(ROTATION_LAYER_ATTRIBUTE, "");
|
|
1787
|
+
layer3.setAttribute("aria-hidden", "true");
|
|
1788
|
+
layer3.setAttribute("alt", "");
|
|
1789
|
+
layer3.setAttribute("draggable", "false");
|
|
1790
|
+
layer3.src = FILE_URL + encodeURIComponent(name);
|
|
1791
|
+
document.body.appendChild(layer3);
|
|
1792
|
+
return layer3;
|
|
1534
1793
|
}
|
|
1535
1794
|
function createColorLayer(color) {
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
document.body.appendChild(
|
|
1542
|
-
return
|
|
1795
|
+
const layer3 = document.createElement("div");
|
|
1796
|
+
layer3.className = LAYER_CLASS2;
|
|
1797
|
+
layer3.setAttribute(ROTATION_LAYER_ATTRIBUTE, "");
|
|
1798
|
+
layer3.setAttribute("aria-hidden", "true");
|
|
1799
|
+
layer3.style.background = color;
|
|
1800
|
+
document.body.appendChild(layer3);
|
|
1801
|
+
return layer3;
|
|
1543
1802
|
}
|
|
1544
1803
|
function fadeTransition(previous, next, duration) {
|
|
1545
|
-
const
|
|
1546
|
-
|
|
1804
|
+
const layer3 = createImageLayer(previous);
|
|
1805
|
+
layer3.style.opacity = "1";
|
|
1547
1806
|
commit(next);
|
|
1548
|
-
void
|
|
1549
|
-
|
|
1550
|
-
|
|
1807
|
+
void layer3.offsetWidth;
|
|
1808
|
+
layer3.style.transition = `opacity ${String(duration)}s ease`;
|
|
1809
|
+
layer3.style.opacity = "0";
|
|
1551
1810
|
scheduleLayer(removeLayer, duration * 1e3 + 80);
|
|
1552
1811
|
}
|
|
1553
1812
|
function fadeThroughTransition(previous, next, duration) {
|
|
1554
1813
|
const half = Math.max(40, duration * 1e3 / 2);
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
void
|
|
1558
|
-
|
|
1559
|
-
|
|
1814
|
+
const layer3 = createColorLayer(themeBaseColour());
|
|
1815
|
+
layer3.style.opacity = "0";
|
|
1816
|
+
void layer3.offsetWidth;
|
|
1817
|
+
layer3.style.transition = `opacity ${String(half / 1e3)}s ease`;
|
|
1818
|
+
layer3.style.opacity = "1";
|
|
1560
1819
|
scheduleLayer(() => {
|
|
1561
1820
|
commit(next);
|
|
1562
|
-
|
|
1563
|
-
|
|
1821
|
+
layer3.style.transition = `opacity ${String(half / 1e3)}s ease`;
|
|
1822
|
+
layer3.style.opacity = "0";
|
|
1564
1823
|
scheduleLayer(removeLayer, half + 80);
|
|
1565
1824
|
}, half);
|
|
1566
1825
|
void previous;
|
|
@@ -1814,24 +2073,15 @@
|
|
|
1814
2073
|
Row,
|
|
1815
2074
|
{ title: copy.modeTitle },
|
|
1816
2075
|
h(
|
|
1817
|
-
"
|
|
1818
|
-
{
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
className: "dshcb-select",
|
|
1827
|
-
value: mode,
|
|
1828
|
-
"aria-label": copy.modeTitle,
|
|
1829
|
-
onChange: (event) => change(event.target.value)
|
|
1830
|
-
},
|
|
1831
|
-
h("option", { value: "fixed" }, copy.modeFixed),
|
|
1832
|
-
h("option", { value: "rotation" }, copy.modeRotation)
|
|
1833
|
-
)
|
|
1834
|
-
)
|
|
2076
|
+
"select",
|
|
2077
|
+
{
|
|
2078
|
+
className: "dshcb-select",
|
|
2079
|
+
value: mode,
|
|
2080
|
+
"aria-label": copy.modeTitle,
|
|
2081
|
+
onChange: (event) => change(event.target.value)
|
|
2082
|
+
},
|
|
2083
|
+
h("option", { value: "fixed" }, copy.modeFixed),
|
|
2084
|
+
h("option", { value: "rotation" }, copy.modeRotation)
|
|
1835
2085
|
)
|
|
1836
2086
|
);
|
|
1837
2087
|
}
|
|
@@ -2359,36 +2609,42 @@ ${active} {
|
|
|
2359
2609
|
object-position: center center;
|
|
2360
2610
|
}
|
|
2361
2611
|
|
|
2362
|
-
/* Rotation settings block.
|
|
2363
|
-
*
|
|
2612
|
+
/* Rotation settings block. Each field is its own two-column module so the
|
|
2613
|
+
* captions, inputs and value slots keep the same width on every display,
|
|
2614
|
+
* font scale and window size; the block only decides the module width. */
|
|
2364
2615
|
.dshcb-rotation-controls {
|
|
2365
|
-
display:
|
|
2366
|
-
flex-direction: column;
|
|
2616
|
+
display: grid;
|
|
2367
2617
|
gap: 8px;
|
|
2368
|
-
|
|
2369
|
-
|
|
2618
|
+
width: min(340px, 100%);
|
|
2619
|
+
min-width: 0;
|
|
2370
2620
|
}
|
|
2371
2621
|
.dshcb-control-field {
|
|
2372
|
-
display:
|
|
2622
|
+
display: grid;
|
|
2623
|
+
grid-template-columns: 84px minmax(0, 1fr) auto;
|
|
2373
2624
|
align-items: center;
|
|
2374
|
-
gap: 8px;
|
|
2625
|
+
column-gap: 8px;
|
|
2626
|
+
min-width: 0;
|
|
2375
2627
|
font-size: 12px;
|
|
2376
2628
|
}
|
|
2377
2629
|
.dshcb-control-field > .dshcb-control-caption {
|
|
2378
|
-
|
|
2630
|
+
min-width: 0;
|
|
2379
2631
|
color: var(--dsw-alias-label-secondary);
|
|
2632
|
+
overflow: hidden;
|
|
2633
|
+
text-overflow: ellipsis;
|
|
2634
|
+
white-space: nowrap;
|
|
2380
2635
|
}
|
|
2381
2636
|
.dshcb-control-field > .dshcb-select {
|
|
2382
|
-
|
|
2637
|
+
width: 100%;
|
|
2638
|
+
max-width: none;
|
|
2383
2639
|
min-width: 0;
|
|
2384
2640
|
}
|
|
2385
2641
|
.dshcb-control-field > .dshcb-range {
|
|
2386
|
-
|
|
2387
|
-
min-width:
|
|
2642
|
+
width: 100%;
|
|
2643
|
+
min-width: 0;
|
|
2388
2644
|
}
|
|
2389
2645
|
.dshcb-control-field > .dshcb-value {
|
|
2390
|
-
|
|
2391
|
-
|
|
2646
|
+
min-width: 40px;
|
|
2647
|
+
justify-self: end;
|
|
2392
2648
|
text-align: end;
|
|
2393
2649
|
}
|
|
2394
2650
|
.dshcb-group-list {
|
|
@@ -2481,6 +2737,21 @@ ${active} [class*="sidebarCol"] {
|
|
|
2481
2737
|
isolation: isolate !important;
|
|
2482
2738
|
}
|
|
2483
2739
|
|
|
2740
|
+
/* The app paints the base surface on the frame and on the center/right
|
|
2741
|
+
* columns: the Windows titlebar frame gives the center column an opaque fill
|
|
2742
|
+
* plus a rounded corner, and macOS fills the right column. The measured
|
|
2743
|
+
* painter marks only describe the DOM that was measured, so a rebuilt or
|
|
2744
|
+
* restyled column can come back white while the slots stay the same (the
|
|
2745
|
+
* marker is refreshed on every apply, the measured mark is not). Clearing
|
|
2746
|
+
* through the stable slot-derived markers keeps the image visible across
|
|
2747
|
+
* rebuilds, window restores and titlebar re-themes. */
|
|
2748
|
+
${active} [${MAIN_COLUMN_ATTRIBUTE}],
|
|
2749
|
+
${active} [${RIGHT_COLUMN_ATTRIBUTE}],
|
|
2750
|
+
${active} [${LAYOUT_FRAME_ATTRIBUTE}] {
|
|
2751
|
+
background-color: transparent !important;
|
|
2752
|
+
background-image: none !important;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2484
2755
|
/* Left sidebar scrim. */
|
|
2485
2756
|
${active} [${SIDEBAR_COLUMN_ATTRIBUTE}]::before,
|
|
2486
2757
|
${active} [class*="sidebarCol"]::before {
|
|
@@ -2492,13 +2763,33 @@ ${active} [class*="sidebarCol"]::before {
|
|
|
2492
2763
|
background: var(--dsh-chat-background-scrim-left);
|
|
2493
2764
|
}
|
|
2494
2765
|
|
|
2495
|
-
/*
|
|
2496
|
-
|
|
2497
|
-
|
|
2766
|
+
/* The reading-area veil is painted per conversation turn by the turn-mask
|
|
2767
|
+
* layer (src/turn-masks.js), not as one rectangle over the whole centre
|
|
2768
|
+
* column: the wallpaper stays visible between two turns while every turn —
|
|
2769
|
+
* prompt and reply — still reads as one card. The layer lives inside the
|
|
2770
|
+
* reading column so its width follows that column's drag handle. */
|
|
2771
|
+
${active} [${READ_COLUMN_ATTRIBUTE}] {
|
|
2772
|
+
position: relative !important;
|
|
2773
|
+
isolation: isolate !important;
|
|
2774
|
+
}
|
|
2775
|
+
.dshcb-turn-scrims {
|
|
2498
2776
|
position: absolute;
|
|
2499
|
-
|
|
2500
|
-
|
|
2777
|
+
top: 0;
|
|
2778
|
+
height: 0;
|
|
2779
|
+
overflow: visible;
|
|
2501
2780
|
pointer-events: none;
|
|
2781
|
+
z-index: -1;
|
|
2782
|
+
/* The host's flow-gap rule targets every direct child of the flow, including
|
|
2783
|
+
* this layer; without the reset the whole layer (and every card in it) sits
|
|
2784
|
+
* one flow gap below the reading column top, leaving prompt bubbles outside
|
|
2785
|
+
* the veil. */
|
|
2786
|
+
margin: 0 !important;
|
|
2787
|
+
}
|
|
2788
|
+
.dshcb-turn-scrim {
|
|
2789
|
+
position: absolute;
|
|
2790
|
+
left: 0;
|
|
2791
|
+
width: 100%;
|
|
2792
|
+
border-radius: var(--dsw-radius-lg, 12px);
|
|
2502
2793
|
background: var(--dsh-chat-background-scrim-main);
|
|
2503
2794
|
}
|
|
2504
2795
|
|
|
@@ -2594,8 +2885,8 @@ ${active} body > span[style*="dsw-specific-sidebar-fill"] {
|
|
|
2594
2885
|
background-image: none !important;
|
|
2595
2886
|
}
|
|
2596
2887
|
${active} [${SIDEBAR_COLUMN_ATTRIBUTE}]::before,
|
|
2597
|
-
${active} [${
|
|
2598
|
-
${active}
|
|
2888
|
+
${active} [${RIGHT_COLUMN_ATTRIBUTE}]::after,
|
|
2889
|
+
${active} .dshcb-turn-scrims {
|
|
2599
2890
|
display: none;
|
|
2600
2891
|
}
|
|
2601
2892
|
}
|
|
@@ -2609,8 +2900,8 @@ ${active} body > span[style*="dsw-specific-sidebar-fill"] {
|
|
|
2609
2900
|
background-image: none !important;
|
|
2610
2901
|
}
|
|
2611
2902
|
${active} [${SIDEBAR_COLUMN_ATTRIBUTE}]::before,
|
|
2612
|
-
${active} [${
|
|
2613
|
-
${active}
|
|
2903
|
+
${active} [${RIGHT_COLUMN_ATTRIBUTE}]::after,
|
|
2904
|
+
${active} .dshcb-turn-scrims {
|
|
2614
2905
|
display: none;
|
|
2615
2906
|
}
|
|
2616
2907
|
}
|
|
@@ -2766,31 +3057,41 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
2766
3057
|
}
|
|
2767
3058
|
.dshcb-bg-file {
|
|
2768
3059
|
display: flex;
|
|
3060
|
+
flex-wrap: wrap;
|
|
2769
3061
|
align-items: center;
|
|
2770
3062
|
gap: 8px;
|
|
2771
3063
|
}
|
|
2772
3064
|
.dshcb-bg-select {
|
|
2773
|
-
flex: 1;
|
|
3065
|
+
flex: 1 1 200px;
|
|
2774
3066
|
max-width: none;
|
|
2775
3067
|
min-width: 0;
|
|
2776
3068
|
}
|
|
2777
3069
|
.dshcb-bg-file .dshcb-select {
|
|
2778
|
-
flex: 1;
|
|
3070
|
+
flex: 1 1 200px;
|
|
2779
3071
|
max-width: none;
|
|
3072
|
+
min-width: 0;
|
|
3073
|
+
}
|
|
3074
|
+
.dshcb-bg-file .dshcb-button {
|
|
3075
|
+
flex: none;
|
|
2780
3076
|
}
|
|
2781
3077
|
|
|
3078
|
+
/* Every setting row is a two-column module: the copy takes the free space,
|
|
3079
|
+
* the control block keeps its own intrinsic width and is pinned to the right.
|
|
3080
|
+
* Grid (not flex + space-between) is what stops a wide control from squeezing
|
|
3081
|
+
* the label or shifting it between window sizes, zoom levels and displays. */
|
|
2782
3082
|
.dshcb-row {
|
|
2783
|
-
display:
|
|
3083
|
+
display: grid;
|
|
3084
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
2784
3085
|
align-items: center;
|
|
2785
|
-
|
|
2786
|
-
gap:
|
|
3086
|
+
column-gap: 24px;
|
|
3087
|
+
row-gap: 8px;
|
|
2787
3088
|
padding: 16px 0;
|
|
2788
3089
|
border-bottom: 0.5px solid var(--dsw-alias-border-l2);
|
|
2789
3090
|
}
|
|
2790
3091
|
.dshcb-text {
|
|
2791
3092
|
display: flex;
|
|
2792
3093
|
flex-direction: column;
|
|
2793
|
-
|
|
3094
|
+
align-items: stretch;
|
|
2794
3095
|
gap: 4px;
|
|
2795
3096
|
min-width: 0;
|
|
2796
3097
|
}
|
|
@@ -2816,14 +3117,20 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
2816
3117
|
font-size: 12px;
|
|
2817
3118
|
line-height: 18px;
|
|
2818
3119
|
}
|
|
3120
|
+
/* One self-contained module per control group: it keeps its size and stays
|
|
3121
|
+
* right-aligned whatever the label wraps to. */
|
|
2819
3122
|
.dshcb-control {
|
|
2820
|
-
display:
|
|
3123
|
+
display: flex;
|
|
3124
|
+
flex: none;
|
|
2821
3125
|
align-items: center;
|
|
3126
|
+
justify-content: flex-end;
|
|
2822
3127
|
gap: 8px;
|
|
3128
|
+
min-width: 0;
|
|
2823
3129
|
}
|
|
2824
3130
|
/* ---- controls ------------------------------------------------------------ */
|
|
2825
3131
|
|
|
2826
3132
|
.dshcb-button {
|
|
3133
|
+
flex: none;
|
|
2827
3134
|
height: 32px;
|
|
2828
3135
|
padding: 0 12px;
|
|
2829
3136
|
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
@@ -2833,6 +3140,7 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
2833
3140
|
font: inherit;
|
|
2834
3141
|
font-size: 13px;
|
|
2835
3142
|
line-height: 20px;
|
|
3143
|
+
white-space: nowrap;
|
|
2836
3144
|
cursor: pointer;
|
|
2837
3145
|
}
|
|
2838
3146
|
.dshcb-button:hover:not(:disabled) {
|
|
@@ -2843,8 +3151,10 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
2843
3151
|
cursor: default;
|
|
2844
3152
|
}
|
|
2845
3153
|
.dshcb-select {
|
|
2846
|
-
|
|
3154
|
+
flex: none;
|
|
3155
|
+
width: 180px;
|
|
2847
3156
|
max-width: 180px;
|
|
3157
|
+
height: 32px;
|
|
2848
3158
|
padding: 0 6px;
|
|
2849
3159
|
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
2850
3160
|
border-radius: var(--dsw-radius-sm);
|
|
@@ -2921,8 +3231,13 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
2921
3231
|
outline-offset: 2px;
|
|
2922
3232
|
}
|
|
2923
3233
|
|
|
2924
|
-
/* Range slider.
|
|
3234
|
+
/* Range slider. The track and thumb sizes are variables and the thumb offset
|
|
3235
|
+
* is derived from them; a hand-tuned pixel offset drifts once the app's font
|
|
3236
|
+
* metrics, page zoom or device pixel ratio change (the track's vertical
|
|
3237
|
+
* position depends on the native control's line box, not on this stylesheet). */
|
|
2925
3238
|
.dshcb-range {
|
|
3239
|
+
--dshcb-range-track-height: 4px;
|
|
3240
|
+
--dshcb-range-thumb-size: 14px;
|
|
2926
3241
|
appearance: none;
|
|
2927
3242
|
-webkit-appearance: none;
|
|
2928
3243
|
flex: none;
|
|
@@ -2931,12 +3246,16 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
2931
3246
|
margin: 0;
|
|
2932
3247
|
background: transparent;
|
|
2933
3248
|
cursor: pointer;
|
|
3249
|
+
/* Collapse the native line box so the track cannot be pushed off-centre by
|
|
3250
|
+
* the inherited font size. */
|
|
3251
|
+
line-height: 0;
|
|
2934
3252
|
}
|
|
2935
3253
|
.dshcb-range:disabled {
|
|
2936
3254
|
cursor: default;
|
|
2937
3255
|
}
|
|
2938
3256
|
.dshcb-range::-webkit-slider-runnable-track {
|
|
2939
|
-
|
|
3257
|
+
box-sizing: border-box;
|
|
3258
|
+
height: var(--dshcb-range-track-height);
|
|
2940
3259
|
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
2941
3260
|
border-radius: 999px;
|
|
2942
3261
|
background: var(--dsw-alias-bg-module-platform);
|
|
@@ -2944,9 +3263,9 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
2944
3263
|
.dshcb-range::-webkit-slider-thumb {
|
|
2945
3264
|
appearance: none;
|
|
2946
3265
|
-webkit-appearance: none;
|
|
2947
|
-
width:
|
|
2948
|
-
height:
|
|
2949
|
-
margin-top: -
|
|
3266
|
+
width: var(--dshcb-range-thumb-size);
|
|
3267
|
+
height: var(--dshcb-range-thumb-size);
|
|
3268
|
+
margin-top: calc((var(--dshcb-range-track-height) - var(--dshcb-range-thumb-size)) / 2);
|
|
2950
3269
|
border: 0;
|
|
2951
3270
|
border-radius: 50%;
|
|
2952
3271
|
background: var(--dsw-alias-brand-primary);
|
|
@@ -2955,14 +3274,15 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
2955
3274
|
background: var(--dsw-alias-state-idle-primary, #a2a4a6);
|
|
2956
3275
|
}
|
|
2957
3276
|
.dshcb-range::-moz-range-track {
|
|
2958
|
-
|
|
3277
|
+
box-sizing: border-box;
|
|
3278
|
+
height: var(--dshcb-range-track-height);
|
|
2959
3279
|
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
2960
3280
|
border-radius: 999px;
|
|
2961
3281
|
background: var(--dsw-alias-bg-module-platform);
|
|
2962
3282
|
}
|
|
2963
3283
|
.dshcb-range::-moz-range-thumb {
|
|
2964
|
-
width:
|
|
2965
|
-
height:
|
|
3284
|
+
width: var(--dshcb-range-thumb-size);
|
|
3285
|
+
height: var(--dshcb-range-thumb-size);
|
|
2966
3286
|
border: 0;
|
|
2967
3287
|
border-radius: 50%;
|
|
2968
3288
|
background: var(--dsw-alias-brand-primary);
|
|
@@ -2975,6 +3295,21 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
2975
3295
|
text-align: right;
|
|
2976
3296
|
}
|
|
2977
3297
|
|
|
3298
|
+
/* Narrow settings panels: stack the control module under the copy instead of
|
|
3299
|
+
* letting the two columns fight for room. The module keeps its own layout, so
|
|
3300
|
+
* nothing moves between devices — only its position changes. */
|
|
3301
|
+
@media (max-width: 640px) {
|
|
3302
|
+
.dshcb-row {
|
|
3303
|
+
grid-template-columns: minmax(0, 1fr);
|
|
3304
|
+
}
|
|
3305
|
+
.dshcb-control {
|
|
3306
|
+
justify-content: flex-start;
|
|
3307
|
+
}
|
|
3308
|
+
.dshcb-rotation-controls {
|
|
3309
|
+
width: 100%;
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
|
|
2978
3313
|
/* ---- task progress bar --------------------------------------------------- */
|
|
2979
3314
|
|
|
2980
3315
|
/* Mounted into the composer's input dock (a full-width entry above the composer
|
|
@@ -3170,8 +3505,26 @@ ${active} :is([data-slot="settings.section"], [data-slot="settings.header"], [da
|
|
|
3170
3505
|
};
|
|
3171
3506
|
if (document.body === null) document.addEventListener("DOMContentLoaded", observe, { once: true });
|
|
3172
3507
|
else observe();
|
|
3508
|
+
const rootObserver = new MutationObserver(() => {
|
|
3509
|
+
invalidatePainters();
|
|
3510
|
+
});
|
|
3511
|
+
rootObserver.observe(document.documentElement, {
|
|
3512
|
+
attributes: true,
|
|
3513
|
+
attributeFilter: ["data-windows-titlebar", "data-platform"]
|
|
3514
|
+
});
|
|
3515
|
+
const onFocus = () => {
|
|
3516
|
+
invalidatePainters();
|
|
3517
|
+
};
|
|
3518
|
+
const onVisibility = () => {
|
|
3519
|
+
if (!document.hidden) invalidatePainters();
|
|
3520
|
+
};
|
|
3521
|
+
window.addEventListener("focus", onFocus);
|
|
3522
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
3173
3523
|
return () => {
|
|
3174
3524
|
observer.disconnect();
|
|
3525
|
+
rootObserver.disconnect();
|
|
3526
|
+
window.removeEventListener("focus", onFocus);
|
|
3527
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
3175
3528
|
};
|
|
3176
3529
|
}, "chat-background: DOM, scheme and switch follow-up");
|
|
3177
3530
|
ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), "chat-background: settings dictionaries");
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -117,14 +117,22 @@ composer 包装层 ResizeObserver(纯样式尺寸) ──┘ ├─ a
|
|
|
117
117
|
### 5.0 背景区块、三区遮罩与原生标题栏
|
|
118
118
|
|
|
119
119
|
- 设置页内部有「背景 / UI」两个 pill 标签页;切换是组件内 `useState`,不产生额外路由。
|
|
120
|
+
- 所有设置行都经 `Row` 组件渲染:两列 grid(说明 / 控件模块)。控件组保持自身宽度并右对齐,所以语言切换、字体缩放、不同 DPI 或窗口变窄时只有位置变化,不会互相挤压或错位;滑块尺寸由变量推导,不写死像素偏移。
|
|
120
121
|
- **背景**标签:大图预览、当前文件名下拉框 + 「打开文件夹」「重新扫描」按钮,下方三个遮罩层滑杆;区块与行之间用默认分隔线。
|
|
121
122
|
- **UI**标签:背景条、进度条、Token 三组设置各用一张 `1px` 细边框 + 大圆角卡片包住,组内两行之间用默认分隔线。
|
|
122
123
|
- 右栏 panel 通过 `markRightPanel()` 打标:普通状态由右栏列的 `::after` 画遮罩(随列宽收缩);全屏时**左侧栏保持可见**,只隐藏主窗口列,panel 用 offset/width 变量从左侧栏右缘开始覆盖「主窗口 + 原右栏」并绘制统一遮罩;布局 frame 通过 `markLayoutFrame()` 打标,仅在展开/收起点击的 340ms 窗口内启用 300ms `grid-template-columns` 过渡(与 app 内容动画同步),拖动宽度时不启用过渡。
|
|
123
124
|
- 三个遮罩层没有开关,只有 0–100 的不透明度:0 全透明、100 完全不透明;侧栏/右栏的表面始终让位于图片,由遮罩决定遮挡程度。
|
|
125
|
+
- **主窗口遮罩按对话轮次绘制**:不再给整个主窗口列铺一层,而是由 `src/turn-masks.js` 读取 `[data-chat-turn]`,把同一轮的用户消息与回复合成一张绝对定位的卡片,
|
|
126
|
+
宽度 = 阅读列宽 + 两侧各 24px 留白(正好到阅读列宽度手柄的净空,跟随手柄自动变化),轮次之间保留约 20px 壁纸缝;同时把每轮第一个条目的宿主变量 `--dsh-chat-flow-gap` 写成 28px(原生 12px),把轮次之间的**真实布局间距**调成舒适档,关闭背景图或卸载时还原。卡片本身只写 `top/height`,不影响会话布局;卡片层会把宿主的 flow-gap margin 清零,否则整个层会随 flow 间距下移、用户气泡顶边露在遮罩外。几何计算在
|
|
127
|
+
`src/lib/turn-masks.js`,有单元测试覆盖;DOM 线程只在布局变化时重算。
|
|
124
128
|
|
|
125
129
|
- 左侧栏/主窗口/右侧栏由稳定槽位(`[data-slot="sidebar"]`、`[data-slot="main"]`、`[data-slot="rightbar"]`)的父节点打标,
|
|
126
130
|
各自建立 `position: relative` + `isolation: isolate` 的层叠上下文,再用 `::before`/`::after` 在 `z-index: -1` 画独立遮罩;
|
|
127
131
|
因此调暗侧栏不会影响正文,反之亦然。
|
|
132
|
+
- app 会把 base surface 画在布局 frame、主窗口列和右侧栏上:Windows 标题栏模式还会给主窗口列铺不透明底色和 16px 圆角。
|
|
133
|
+
这些表面通过**槽位标记**(`data-dsh-chat-background-main/right/frame`)清成透明,而不是只依赖一次性测量出的 `lift` 标记,
|
|
134
|
+
所以列被重建、标题栏重新主题化或窗口从遮挡恢复后都不会重新变白。测量标记丢失、`data-windows-titlebar`/`data-platform`
|
|
135
|
+
变化、窗口 `focus` 或 `visibilitychange` 变可见时会主动重新测量,见 `src/background.js` 的 `invalidatePainters()`。
|
|
128
136
|
- 窗口控制条:桌面预加载用隐藏 probe 的 `background-color: var(--dsw-specific-sidebar-fill)` 采样原生 titleBarOverlay 颜色;
|
|
129
137
|
插件只在该 probe 上覆盖为透明(不改 token),并通过改写 body 自定义属性触发预加载的 observer 重新发送,见 `src/background.js`。
|
|
130
138
|
|
package/docs/CONVENTIONS.md
CHANGED
|
@@ -109,6 +109,8 @@ export function createSwitch({ key, onChange }) { ... }
|
|
|
109
109
|
- 选择器按「背景 → 侧栏 → composer → 设置 → 控件 → 进度条 → token 行」分组,组间用 `/* ---- 组名 ---- */` 分隔。
|
|
110
110
|
- 不给宿主元素打标时优先用稳定的 `data-slot` / `data-*` 钩子;确实没有稳定钩子时,才允许用 CSS 模块名后缀选择器(如 `[class*="sidebarCol"]`),并在注释里写明这是升级敏感点。
|
|
111
111
|
- 规则若依赖某个层叠/包含块不变量,必须写注释解释原因。
|
|
112
|
+
- 设置行统一是两列 grid 模块:左侧说明、右侧控件整体成块(`flex: none`、固定槽位、右对齐),控件尺寸不随语言、字体缩放、DPI 或窗口宽度漂移;只有窄屏(≤640px)才把控件堆到说明下方。
|
|
113
|
+
- 原生控件的尺寸不要用手调像素偏移去对齐(如滑块的 `margin-top: -50.5px`),用尺寸变量推导(`calc((track - thumb) / 2)`),否则在别的缩放/DPI 下会漂移。
|
|
112
114
|
|
|
113
115
|
## 5. JSON / YAML
|
|
114
116
|
|
package/docs/PUBLISHING.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm run verify # 格式 + 构建一致性 + 静态检查 + 单元测试
|
|
9
|
-
npm run test:e2e #
|
|
9
|
+
npm run test:e2e # 11 个端到端套件(需要本机 DSH)
|
|
10
10
|
npm pack --dry-run # 确认 tarball 内容
|
|
11
11
|
```
|
|
12
12
|
|
|
@@ -25,12 +25,12 @@ npm pack --dry-run # 确认 tarball 内容
|
|
|
25
25
|
- 删除旧 tag(如 `git tag -d v1.2.0`),只保留要发布的版本;
|
|
26
26
|
- 复查 `git log --oneline` 与 `git ls-files`,确认没有个人文件后再推送。
|
|
27
27
|
|
|
28
|
-
当前仓库已按此方式重建为单提交的 1.0.0。
|
|
28
|
+
当前仓库已按此方式重建为单提交的 1.0.0 起点,当前发布版本 1.1.0。
|
|
29
29
|
|
|
30
30
|
### 2.2 首次创建仓库
|
|
31
31
|
|
|
32
32
|
1. 在 GitHub 新建公开仓库 `dsh-chat-background`(默认分支 `main`);
|
|
33
|
-
2. 推送代码与 `v1.
|
|
33
|
+
2. 推送代码与 `v1.1.0` 标签;
|
|
34
34
|
3. 仓库 **About** 填写简介:
|
|
35
35
|
- 描述:`给 DeepSeek Harness 桌面端换背景:背景图/轮换分组/三栏遮罩/输入栏背景条/任务进度条/Token 用量`;
|
|
36
36
|
- 网站:留空或填仓库地址;
|
|
@@ -49,26 +49,30 @@ npm pack --dry-run # 确认 tarball 内容
|
|
|
49
49
|
生成 npm tarball 并创建 GitHub Release。发布后在 Release 中附上主要更新内容即可。
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
git tag v1.
|
|
52
|
+
git tag v1.1.0
|
|
53
53
|
git push origin main --tags
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
## 3. npm
|
|
56
|
+
## 3. npm 发布
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
`1.1.0` 已发布到 npm,`dsh plugin --profile desktop add dsh-chat-background` 可直接安装。
|
|
59
|
+
发新版本时:
|
|
59
60
|
|
|
60
61
|
```bash
|
|
61
|
-
npm
|
|
62
|
-
npm publish --access public
|
|
62
|
+
npm whoami # 确认是维护者账号 lemonjam
|
|
63
|
+
npm publish --access public # 需要带 bypass 2FA 的 granular token
|
|
63
64
|
```
|
|
64
65
|
|
|
65
66
|
验证:
|
|
66
67
|
|
|
67
68
|
```bash
|
|
68
|
-
npm view dsh-chat-background version
|
|
69
|
-
dsh plugin --profile desktop add dsh-chat-background@1.
|
|
69
|
+
npm view dsh-chat-background version # 应等于当前版本号
|
|
70
|
+
dsh plugin --profile desktop add dsh-chat-background@1.1.0
|
|
70
71
|
```
|
|
71
72
|
|
|
73
|
+
> npm 页面上的 README 是发布时 tarball 的快照,发布后再改 README 不会回写 npm 页面,
|
|
74
|
+
> 下次发版时才会同步;以 GitHub 仓库的 README 为准。
|
|
75
|
+
|
|
72
76
|
## 4. 提交 DSH 插件市场
|
|
73
77
|
|
|
74
78
|
DSH 0.2.x 的插件市场会扫描 GitHub 上的公开 DSH 插件;收录要求:
|
package/docs/TESTING.md
CHANGED
|
@@ -59,17 +59,19 @@ npm run test:e2e -- --keep # 保留临时 DSH_HOME 与产物,便于
|
|
|
59
59
|
|
|
60
60
|
### 2.4 套件一览
|
|
61
61
|
|
|
62
|
-
| 套件 | 覆盖
|
|
63
|
-
| ------------------------- |
|
|
64
|
-
| `01-smoke` | 背景应用、样式表/测量规则安装、背景条覆盖用量行且与输入卡片等宽、进度条空闲隐藏、Token 行位置、设置页标签页、背景区块、UI 分组卡片与控件类型、行 `aria-labelledby/describedby`
|
|
65
|
-
| `02-controls` | 3 个开关、3 个滑杆、2 个调色盘、深色主题默认色、语言切换(经设置页语言控件)
|
|
66
|
-
| `03-robustness` | 稳定钩子优先(英文 pill + `display: contents` 包装层)、零布局影响与 fixed 探针、纯样式尺寸变化跟随、移除/重建用量行、连续开关不重复、窗口 resize
|
|
67
|
-
| `04-host-routes` | `/list`、图片 200/ETag/304、路径穿越 404、`/folder` 的 405/403、token 汇总自洽
|
|
68
|
-
| `05-rightbar` | 右栏收起/展开的临时网格过渡(中间帧宽度)、拖动宽度无过渡、全屏 panel 覆盖窗口/统一遮罩/主窗口隐藏、退出全屏恢复
|
|
69
|
-
| `08-security` | `/health`、目录脱敏、响应头、SVG CSP、真实导航到 SVG 后脚本不执行
|
|
70
|
-
| `06-image-zoom` | 固定层图片滚轮缩放/双击还原;设置弹窗内图片不缩放;右栏图片预览的普通滚轮转换为 Ctrl+滚轮
|
|
71
|
-
| `07-rotation` | 轮换分组内切换、过渡层出现、三行设置顺序/控件值/无小字、分组 5 行滚动、仅勾选框选择、缩略图、选择不重排、拖动排序动画、切换固定后停止轮换
|
|
72
|
-
| `07b-animated-background` | 动图文件被列为背景并由已加载的固定 IMG 层绘制,根元素不再绘制背景图
|
|
62
|
+
| 套件 | 覆盖 |
|
|
63
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `01-smoke` | 背景应用、样式表/测量规则安装、背景条覆盖用量行且与输入卡片等宽、进度条空闲隐藏、Token 行位置、设置页标签页、背景区块、UI 分组卡片与控件类型、行 `aria-labelledby/describedby` |
|
|
65
|
+
| `02-controls` | 3 个开关、3 个滑杆、2 个调色盘、深色主题默认色、语言切换(经设置页语言控件) |
|
|
66
|
+
| `03-robustness` | 稳定钩子优先(英文 pill + `display: contents` 包装层)、零布局影响与 fixed 探针、纯样式尺寸变化跟随、移除/重建用量行、连续开关不重复、窗口 resize |
|
|
67
|
+
| `04-host-routes` | `/list`、图片 200/ETag/304、路径穿越 404、`/folder` 的 405/403、token 汇总自洽 |
|
|
68
|
+
| `05-rightbar` | 右栏收起/展开的临时网格过渡(中间帧宽度)、拖动宽度无过渡、全屏 panel 覆盖窗口/统一遮罩/主窗口隐藏、退出全屏恢复 |
|
|
69
|
+
| `08-security` | `/health`、目录脱敏、响应头、SVG CSP、真实导航到 SVG 后脚本不执行 |
|
|
70
|
+
| `06-image-zoom` | 固定层图片滚轮缩放/双击还原;设置弹窗内图片不缩放;右栏图片预览的普通滚轮转换为 Ctrl+滚轮 |
|
|
71
|
+
| `07-rotation` | 轮换分组内切换、过渡层出现、三行设置顺序/控件值/无小字、分组 5 行滚动、仅勾选框选择、缩略图、选择不重排、拖动排序动画、切换固定后停止轮换 |
|
|
72
|
+
| `07b-animated-background` | 动图文件被列为背景并由已加载的固定 IMG 层绘制,根元素不再绘制背景图 |
|
|
73
|
+
| `10-turn-masks` | 每个对话一轮一张遮罩卡(宽度=阅读列宽+两侧留白)、舒适档 28px 轮次布局间距(写回宿主 `--dsh-chat-flow-gap`)、卡片间约 14px 壁纸缝(上边额外 6px 出血,保证用户气泡顶边被盖住;卡片层 margin 清零不受宿主 flow gap 影响)、用户消息与回复同卡、主窗口不再整列铺遮罩、移除会话后卡片/列标记/间距一起还原 |
|
|
74
|
+
| `09-windows-titlebar` | Windows 标题栏标记给主窗口列铺不透明底色后,靠槽位标记而不是一次性测量结果保持透明;丢失测量标记后仍透明;focus/visibility 重新测量;移除标记后图片仍在 |
|
|
73
75
|
|
|
74
76
|
### 2.5 新增断言
|
|
75
77
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-chat-background",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Chat background image, sidebar veil, composer strip, task progress bar and all-session token total for the DeepSeek Harness (DSH) Web GUI.",
|
|
5
5
|
"meta": {
|
|
6
6
|
"title": "聊天背景",
|