@sparkelf/dsh-patch-better-sidebar-app-frame 0.1.0-rc.10
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +17 -0
- package/README.zh.md +17 -0
- package/package.json +37 -0
- package/patches/app-frame-space.patch +597 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write patches/npm/better-sidebar-app-frame/README.md
|
|
5
|
+
README.md: cd34bc4e59c59e5e13b74d080470decae52ac7ff
|
|
6
|
+
README.zh.md: 560ab312ab490be83ec238089acbce0a94e7e9fe
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Better Sidebar app-frame patch
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
This data-only npm package carries one temporary compatibility payload for the verified `dsh-better-sidebar 0.17.1` target. `dsh-plus apply` selects and applies one payload while materializing the npm profile; installing the package does not mutate dependencies through an npm lifecycle script.
|
|
6
|
+
|
|
7
|
+
The single variant covers only the verified `0.17.1` target. The local `0.17.0` artifact used by accepted production already contains the same AppFrame and Host-route behavior and requires no payload. The exact patch-package, DSH, target-package, variant, and payload selection belongs to the deployment lock.
|
|
8
|
+
|
|
9
|
+
The patch preserves the accepted app-frame spacing used by the Plus Web composition. Its upstream pull request, owner, and retirement status remain in the Plus curated-plugin manifest. Remove this package from the Plus distribution when the tracked Better Sidebar upstream release contains the same app-frame behavior.
|
|
10
|
+
|
|
11
|
+
## Model Experience
|
|
12
|
+
|
|
13
|
+
This package does not register a Cordis plugin, add model-visible text, alter token usage, or affect KV-cache reuse. Its target plugin owns any runtime and model experience.
|
|
14
|
+
|
|
15
|
+
## Known Limitations and Deferred Work
|
|
16
|
+
|
|
17
|
+
The package supports only the declared target package and payload. It does not provide a fallback implementation when exact patch application fails.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Better Sidebar AppFrame 补丁
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
这个data-only npm package为已验证的`dsh-better-sidebar 0.17.1` target携带一个临时compatibility payload。`dsh-plus apply`在materialize npm profile时选择并应用一个payload;安装package不会通过npm lifecycle script修改依赖。
|
|
6
|
+
|
|
7
|
+
Single variant只覆盖已验证的`0.17.1` target。accepted production使用的local `0.17.0` artifact已包含相同AppFrame与Host-route behavior,不需要payload。精确patch-package、DSH、target-package、variant与payload选择属于deployment lock。
|
|
8
|
+
|
|
9
|
+
该patch保留Plus Web组合已验收的AppFrame间距。其upstream pull request、owner与retirement status仍由Plus curated-plugin manifest拥有。当Better Sidebar发布版本包含同等AppFrame行为时,从Plus distribution删除这个package。
|
|
10
|
+
|
|
11
|
+
## Model Experience
|
|
12
|
+
|
|
13
|
+
本package不注册Cordis plugin、不增加model-visible text、不改变token usage,也不影响KV-cache reuse。目标插件拥有其runtime与model experience。
|
|
14
|
+
|
|
15
|
+
## Known Limitations and Deferred Work
|
|
16
|
+
|
|
17
|
+
Package只支持已声明target package与payload。精确patch应用失败时不提供fallback implementation。
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Compatibility patch for Better Sidebar app-frame spacing in DeepSeek Harness Plus",
|
|
3
|
+
"dshPatch": {
|
|
4
|
+
"formatVersion": 1,
|
|
5
|
+
"variants": [
|
|
6
|
+
{
|
|
7
|
+
"dsh": ">=0.1.0-rc.8",
|
|
8
|
+
"file": "./patches/app-frame-space.patch",
|
|
9
|
+
"id": "better-sidebar-app-frame-space",
|
|
10
|
+
"target": {
|
|
11
|
+
"kind": "npm",
|
|
12
|
+
"name": "dsh-better-sidebar",
|
|
13
|
+
"range": "0.17.1"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
"./package.json": "./package.json",
|
|
20
|
+
"./patches/app-frame-space.patch": "./patches/app-frame-space.patch"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"patches/*.patch"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"name": "@sparkelf/dsh-patch-better-sidebar-app-frame",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"directory": "patches/npm/better-sidebar-app-frame",
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/SparkElf/deepseek-harness-plus.git"
|
|
34
|
+
},
|
|
35
|
+
"type": "module",
|
|
36
|
+
"version": "0.1.0-rc.10"
|
|
37
|
+
}
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
diff --git a/lib/client-editor.js b/lib/client-editor.js
|
|
2
|
+
index 87b3e8785626ab8bf400b7a6d64d698c43f482d0..f7ba248d449aa049cb34453576d4944e1b335e29 100644
|
|
3
|
+
--- a/lib/client-editor.js
|
|
4
|
+
+++ b/lib/client-editor.js
|
|
5
|
+
@@ -17935,6 +17935,16 @@ globalThis.__dshChunks__["editor"] = (require) => {
|
|
6
|
+
return `${HTML_ROUTE_PREFIX}${encodeURIComponent(sessionId)}/${unc ? "/" : ""}${segments.map(encodeURIComponent).join("/")}`;
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
+ //#region src/client/host-route-url.ts
|
|
10
|
+
+ /** Resolve one Host-owned client route against the injected document base.
|
|
11
|
+
+ * @param path - Root-style or relative Host route path.
|
|
12
|
+
+ * @param baseUrl - Browser document base or an explicit test base.
|
|
13
|
+
+ * @returns The absolute route URL.
|
|
14
|
+
+ */
|
|
15
|
+
+ function hostRouteUrl(path, baseUrl = document.baseURI) {
|
|
16
|
+
+ return new URL(path.replace(/^\/+/, ""), baseUrl);
|
|
17
|
+
+ }
|
|
18
|
+
+ //#endregion
|
|
19
|
+
//#region src/client/api.ts
|
|
20
|
+
/**
|
|
21
|
+
* Typed fetch wrapper over the /sidebar JSON API. Every call posts to
|
|
22
|
+
@@ -17955,7 +17965,7 @@ globalThis.__dshChunks__["editor"] = (require) => {
|
|
23
|
+
async function call$1(method, payload, signal) {
|
|
24
|
+
let response;
|
|
25
|
+
try {
|
|
26
|
+
- response = await fetch(`/sidebar/api/${method}`, {
|
|
27
|
+
+ response = await fetch(hostRouteUrl(`sidebar/api/${method}`), {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "content-type": "application/json" },
|
|
30
|
+
body: JSON.stringify(payload),
|
|
31
|
+
@@ -17984,7 +17994,7 @@ globalThis.__dshChunks__["editor"] = (require) => {
|
|
32
|
+
if (scope.cwd !== void 0 && scope.cwd !== "") params.set("cwd", scope.cwd);
|
|
33
|
+
let response;
|
|
34
|
+
try {
|
|
35
|
+
- response = await fetch(`/sidebar/upload?${params.toString()}`, {
|
|
36
|
+
+ response = await fetch(hostRouteUrl(`sidebar/upload?${params.toString()}`), {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "content-type": "application/octet-stream" },
|
|
39
|
+
body,
|
|
40
|
+
@@ -18126,7 +18136,7 @@ globalThis.__dshChunks__["editor"] = (require) => {
|
|
41
|
+
* client-side platform signal is needed.
|
|
42
|
+
*/
|
|
43
|
+
function htmlUrl(scope, path) {
|
|
44
|
+
- return encodeHtmlUrl(scope.sessionId, path);
|
|
45
|
+
+ return hostRouteUrl(encodeHtmlUrl(scope.sessionId, path)).href;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/client/paths.ts
|
|
49
|
+
@@ -18249,7 +18259,7 @@ globalThis.__dshChunks__["editor"] = (require) => {
|
|
50
|
+
path: normalizeLocalPath(candidate)
|
|
51
|
+
});
|
|
52
|
+
if (scope.cwd !== void 0 && scope.cwd !== "") params.set("cwd", scope.cwd);
|
|
53
|
+
- return `${origin}/sidebar/file?${params.toString()}`;
|
|
54
|
+
+ return hostRouteUrl(`sidebar/file?${params.toString()}`, origin).href;
|
|
55
|
+
}
|
|
56
|
+
function rewriteLocalImageUrls(text, scope, filePath, origin) {
|
|
57
|
+
const resolve = (dest) => resolveLocalMediaDest(dest, scope, filePath, origin);
|
|
58
|
+
@@ -44527,7 +44537,7 @@ globalThis.__dshChunks__["editor"] = (require) => {
|
|
59
|
+
"@deepseek-ai/dsh-client-runtime/client"
|
|
60
|
+
];
|
|
61
|
+
/** Chunk script endpoint served by the plugin host half (src/bundle-route.ts). */
|
|
62
|
+
- const CHUNK_URL = (name) => `/sidebar/bundle/${name}.js`;
|
|
63
|
+
+ const CHUNK_URL = (name) => hostRouteUrl(`sidebar/bundle/${name}.js`).href;
|
|
64
|
+
/** Bound on the revalidation HEAD round-trip. A timeout fails open (drop +
|
|
65
|
+
* re-fetch on the next open) so a stuck bundle route can never wedge lazy
|
|
66
|
+
* chunk loads behind the revalidation barrier. */
|
|
67
|
+
@@ -48049,7 +48059,7 @@ globalThis.__dshChunks__["editor"] = (require) => {
|
|
68
|
+
* absolute media URLs (see {@link rewriteLocalImageUrls}); the raw
|
|
69
|
+
* `mdText` stays untouched for selection/line lookup and for mermaid-block
|
|
70
|
+
* detection, which are unaffected by image syntax. */
|
|
71
|
+
- const previewText = markdown ? rewriteLocalImageUrls(mdText, scope, path, window.location.origin) : mdText;
|
|
72
|
+
+ const previewText = markdown ? rewriteLocalImageUrls(mdText, scope, path, document.baseURI) : mdText;
|
|
73
|
+
/** md/mermaid block split for the preview (mermaid fences lift out). Split
|
|
74
|
+
* only in preview mode: edit-mode keystrokes must not re-scan the source. */
|
|
75
|
+
const mdBlocks = (0, react.useMemo)(() => markdown && mode === "preview" ? splitMermaidBlocks(mdText) : [], [
|
|
76
|
+
diff --git a/lib/client-registry.js b/lib/client-registry.js
|
|
77
|
+
index b1630afa375df6b3ec03ceca5178bce14d7d67a9..76557b9229b610bab2711342073da5f638e3880e 100644
|
|
78
|
+
--- a/lib/client-registry.js
|
|
79
|
+
+++ b/lib/client-registry.js
|
|
80
|
+
@@ -1785,6 +1785,26 @@ window.__ModuleLoader__.load({
|
|
81
|
+
return state.activePane ?? "";
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
+ //#region src/client/host-route-url.ts
|
|
85
|
+
+ /** Resolve one Host-owned client route against the injected document base.
|
|
86
|
+
+ * @param path - Root-style or relative Host route path.
|
|
87
|
+
+ * @param baseUrl - Browser document base or an explicit test base.
|
|
88
|
+
+ * @returns The absolute route URL.
|
|
89
|
+
+ */
|
|
90
|
+
+ function hostRouteUrl(path, baseUrl = document.baseURI) {
|
|
91
|
+
+ return new URL(path.replace(/^\/+/, ""), baseUrl);
|
|
92
|
+
+ }
|
|
93
|
+
+ /** Resolve one Host-owned WebSocket route against the injected document base.
|
|
94
|
+
+ * @param path - Root-style or relative Host WebSocket route path.
|
|
95
|
+
+ * @param baseUrl - Browser document base or an explicit test base.
|
|
96
|
+
+ * @returns The absolute ws: or wss: route URL.
|
|
97
|
+
+ */
|
|
98
|
+
+ function hostWebSocketUrl(path, baseUrl = document.baseURI) {
|
|
99
|
+
+ const url = hostRouteUrl(path, baseUrl);
|
|
100
|
+
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
101
|
+
+ return url;
|
|
102
|
+
+ }
|
|
103
|
+
+ //#endregion
|
|
104
|
+
//#region src/client/chunk-loader.ts
|
|
105
|
+
/**
|
|
106
|
+
* The platform externals a chunk bundle may require (mirror of
|
|
107
|
+
@@ -1815,7 +1835,7 @@ window.__ModuleLoader__.load({
|
|
108
|
+
"@deepseek-ai/dsh-client-runtime/client"
|
|
109
|
+
];
|
|
110
|
+
/** Chunk script endpoint served by the plugin host half (src/bundle-route.ts). */
|
|
111
|
+
- const CHUNK_URL = (name) => `/sidebar/bundle/${name}.js`;
|
|
112
|
+
+ const CHUNK_URL = (name) => hostRouteUrl(`sidebar/bundle/${name}.js`).href;
|
|
113
|
+
/** Bound on the revalidation HEAD round-trip. A timeout fails open (drop +
|
|
114
|
+
* re-fetch on the next open) so a stuck bundle route can never wedge lazy
|
|
115
|
+
* chunk loads behind the revalidation barrier. */
|
|
116
|
+
@@ -10505,7 +10525,7 @@ window.__ModuleLoader__.load({
|
|
117
|
+
async function call(method, payload, signal) {
|
|
118
|
+
let response;
|
|
119
|
+
try {
|
|
120
|
+
- response = await fetch(`/sidebar/api/${method}`, {
|
|
121
|
+
+ response = await fetch(hostRouteUrl(`sidebar/api/${method}`), {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { "content-type": "application/json" },
|
|
124
|
+
body: JSON.stringify(payload),
|
|
125
|
+
@@ -10534,7 +10554,7 @@ window.__ModuleLoader__.load({
|
|
126
|
+
if (scope.cwd !== void 0 && scope.cwd !== "") params.set("cwd", scope.cwd);
|
|
127
|
+
let response;
|
|
128
|
+
try {
|
|
129
|
+
- response = await fetch(`/sidebar/upload?${params.toString()}`, {
|
|
130
|
+
+ response = await fetch(hostRouteUrl(`sidebar/upload?${params.toString()}`), {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers: { "content-type": "application/octet-stream" },
|
|
133
|
+
body,
|
|
134
|
+
@@ -10684,7 +10704,7 @@ window.__ModuleLoader__.load({
|
|
135
|
+
});
|
|
136
|
+
if (scope.cwd !== void 0 && scope.cwd !== "") params.set("cwd", scope.cwd);
|
|
137
|
+
if (download) params.set("download", "1");
|
|
138
|
+
- return `/sidebar/file?${params.toString()}`;
|
|
139
|
+
+ return hostRouteUrl(`sidebar/file?${params.toString()}`).href;
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/client/binary-download.tsx
|
|
143
|
+
@@ -19418,8 +19438,7 @@ Mode: this is a continuable side conversation. Your answers stay in this side th
|
|
144
|
+
let failures = 0;
|
|
145
|
+
const connect = () => {
|
|
146
|
+
if (closed) return;
|
|
147
|
+
- const url = new URL("/sidebar/ws/agent-terminals", location.origin);
|
|
148
|
+
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
149
|
+
+ const url = hostWebSocketUrl("sidebar/ws/agent-terminals");
|
|
150
|
+
url.search = new URLSearchParams({ sessionId }).toString();
|
|
151
|
+
socket = new WebSocket(url.toString());
|
|
152
|
+
socket.onmessage = (event) => {
|
|
153
|
+
@@ -19472,8 +19491,7 @@ Mode: this is a continuable side conversation. Your answers stay in this side th
|
|
154
|
+
let failures = 0;
|
|
155
|
+
const connect = () => {
|
|
156
|
+
if (closed) return;
|
|
157
|
+
- const url = new URL("/sidebar/ws/agent-opens", location.origin);
|
|
158
|
+
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
159
|
+
+ const url = hostWebSocketUrl("sidebar/ws/agent-opens");
|
|
160
|
+
url.search = new URLSearchParams({ sessionId }).toString();
|
|
161
|
+
socket = new WebSocket(url.toString());
|
|
162
|
+
socket.onmessage = (event) => {
|
|
163
|
+
@@ -22117,7 +22135,7 @@ Mode: this is a continuable side conversation. Your answers stay in this side th
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region \0dsh-css:/home/runner/work/DSH-better-sidebar/DSH-better-sidebar/src/client/layout.css.mjs
|
|
167
|
+
- const css = "/**\n * Layout push: when a panel is open it OCCUPIES the layout instead of\n * floating over it — the app shell (#root, the AppFrame three-column grid)\n * gives up space. Only the center column is flexible (1fr), so the right\n * panel's width squeeze (margin-right on #root) lands exactly on the\n * conversation output and the input bar, like a VSCode sidebar.\n *\n * The width rides `calc(100% - var(...))` instead of a bare margin on a\n * full-width box: some desktop shells (DSH Desktop, #208) set #root to\n * width:100%, where a margin would overflow the viewport additively —\n * the calc keeps the box at exactly 100% minus the push in every shell.\n * Width and margin transition in lockstep (same variable, same duration\n * and easing), so expand/collapse animates the content width exactly as\n * the bare-margin version did.\n *\n * The bottom panel squeezes ONLY the center column — it must not cover the\n * app's own left sidebar or the right panel. The anchor is the frame's\n * center grid item via its stable data attribute ([data-pane=\"conversation\"]\n * on the centerCol inside [data-dsh-frame]), not a positional path: the\n * frame's child order also contains an overlay layer and drag handles\n * (verified against a live DSH 0.1.x page), so nth-child is brittle. A\n * stretched grid item shrinks by its margins, so the conversation content\n * (output + input bar) lifts without touching the sidebars.\n *\n * The sizes ride CSS variables updated by the Sidebar shell (0 while\n * collapsed); expand/collapse animates both the margins and the panel\n * slides on the same theme duration. Drags disable the transition so the\n * layout tracks the pointer.\n */\n#root {\n margin-right: var(--dsh-sidebar-width, 0px);\n width: calc(100% - var(--dsh-sidebar-width, 0px));\n transition:\n margin-right var(--ds-transition-duration-slow) var(--ds-ease-in-out),\n width var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n/* The AppFrame's center column, anchored by data attributes (see above).\n Composite selector: DSH 0.1.x versions name the center grid item\n `[data-pane=\"conversation\"]`, while rc.8-era shells put a\n `[data-slot=\"conversation\"]` child inside it — both selectors resolve to\n the SAME element on live pages (verified), and keeping both future-proofs\n the rule against a host rename without touching shell-specific markup. */\n#root [data-dsh-frame] > [data-pane=\"conversation\"],\n#root :has(> [data-slot=\"conversation\"]) {\n margin-bottom: var(--dsh-sidebar-height, 0px);\n /* Grid/flex items default to min-height:auto. A long unbreakable token\n (OAuth URL) then grows this column past the viewport and clips the\n composer + left-rail Settings row. min-height:0 lets the cell shrink;\n overflow-wrap lets the token wrap instead of forcing its intrinsic\n size. The host's own descendants remain responsible for scrolling. */\n min-height: 0;\n overflow-wrap: anywhere;\n transition: margin-bottom var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n/* When the sidebar is collapsed, the toggle cluster reclaims the top-right\n corner. Push the DSH session header's right padding out so its right-aligned\n utilities (the \"Session log\" download capsule) yield the corner instead of\n hiding under the cluster. The header default right-pads 28px; the 2-button\n cluster spans right 10→70px, so 78px clears it with an 8px gap. Anchor on\n the header's slot host wrapper ([data-slot=\"conversation.session.header\"])\n rather than a positional path: DSH 0.1.x nests the header several levels\n under the center column. The Sidebar shell toggles the body attribute with\n the panel open state. */\nbody[data-dsh-sidebar-collapsed] [data-slot=\"conversation.session.header\"] > header {\n padding-right: 78px;\n}\n\nbody[data-dsh-sidebar-dragging] #root,\nbody[data-dsh-sidebar-dragging] #root [data-dsh-frame] > [data-pane=\"conversation\"],\nbody[data-dsh-sidebar-dragging] #root :has(> [data-slot=\"conversation\"]) {\n transition: none;\n}\n\n/* DSH 0.1.x gives external settings sections a generic gear and exposes no\n icon field in the settings.section contract. settings-nav-icon.ts marks\n only this plugin's localized row; render the requested Lucide\n gallery-horizontal-end SVG as a currentColor mask so it follows the native\n nav hover/active colors without changing the shell's 16px icon rhythm. */\n[data-dsh-better-sidebar-settings-nav] > svg:first-child {\n display: none;\n}\n\n[data-dsh-better-sidebar-settings-nav]::before {\n content: '';\n flex: none;\n width: 16px;\n height: 16px;\n background: currentColor;\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E\") center / contain no-repeat;\n}\n\n@media (prefers-reduced-motion: reduce) {\n #root,\n #root [data-dsh-frame] > [data-pane=\"conversation\"],\n #root :has(> [data-slot=\"conversation\"]) {\n transition: none;\n }\n}\n";
|
|
168
|
+
+ const css = "/**\n * Layout push: an open panel occupies layout instead of floating over it.\n * The right panel reserves padding inside the AppFrame three-column grid,\n * so the app sidebar, conversation, and details column all remain to its\n * left while the frame's border-box stays viewport-wide. Harness measures\n * that border-box to choose desktop versus narrow presentation; preserving\n * it prevents a desktop window from entering narrow mode merely because the\n * plugin panel opened.\n *\n * AppFrame positions its details drag handle from the measured border-box.\n * Move that handle left by the same reserved width so it stays on the details\n * column edge. The ordinary sidebar handle remains at its native coordinate.\n *\n * The bottom panel squeezes only the conversation column. The anchor is the\n * frame's center grid item via stable data attributes, not a positional path:\n * the frame also contains an overlay layer and drag handles. A stretched grid\n * item shrinks by its bottom margin, so the conversation output and composer\n * lift without touching either sidebar.\n *\n * Sizes ride CSS variables updated by the Sidebar shell (0 while collapsed).\n * Expand/collapse uses the host theme duration; drags disable transitions so\n * every reserved edge tracks the pointer.\n */\n/* Current shells expose [data-dsh-frame]; rc.8-era shells expose the same\n AppFrame only as the root slot's direct child. */\n#root [data-dsh-frame],\n#root > [data-slot=\"root\"] > div {\n box-sizing: border-box;\n padding-right: var(--dsh-sidebar-width, 0px);\n transition: padding-right var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n#root [data-dsh-frame] > [data-side=\"details\"],\n#root > [data-slot=\"root\"] > div > [data-side=\"details\"] {\n transform: translateX(calc(0px - var(--dsh-sidebar-width, 0px)));\n transition:\n left var(--ds-transition-duration-slow) var(--ds-ease-in-out),\n transform var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n/* The AppFrame's center column, anchored by data attributes (see above).\n Composite selector: DSH 0.1.x versions name the center grid item\n `[data-pane=\"conversation\"]`, while rc.8-era shells put a\n `[data-slot=\"conversation\"]` child inside it — both selectors resolve to\n the SAME element on live pages (verified), and keeping both future-proofs\n the rule against a host rename without touching shell-specific markup. */\n#root [data-dsh-frame] > [data-pane=\"conversation\"],\n#root :has(> [data-slot=\"conversation\"]) {\n margin-bottom: var(--dsh-sidebar-height, 0px);\n /* Grid/flex items default to min-height:auto. A long unbreakable token\n (OAuth URL) then grows this column past the viewport and clips the\n composer + left-rail Settings row. min-height:0 lets the cell shrink;\n overflow-wrap lets the token wrap instead of forcing its intrinsic\n size. The host's own descendants remain responsible for scrolling. */\n min-height: 0;\n overflow-wrap: anywhere;\n transition: margin-bottom var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n/* When the sidebar is collapsed, the toggle cluster reclaims the top-right\n corner. Push the DSH session header's right padding out so its right-aligned\n utilities (the \"Session log\" download capsule) yield the corner instead of\n hiding under the cluster. The header default right-pads 28px; the 2-button\n cluster spans right 10→70px, so 78px clears it with an 8px gap. Anchor on\n the header's slot host wrapper ([data-slot=\"conversation.session.header\"])\n rather than a positional path: DSH 0.1.x nests the header several levels\n under the center column. The Sidebar shell toggles the body attribute with\n the panel open state. */\nbody[data-dsh-sidebar-collapsed] [data-slot=\"conversation.session.header\"] > header {\n padding-right: 78px;\n}\n\nbody[data-dsh-sidebar-dragging] #root [data-dsh-frame],\nbody[data-dsh-sidebar-dragging] #root > [data-slot=\"root\"] > div,\nbody[data-dsh-sidebar-dragging] #root [data-dsh-frame] > [data-side=\"details\"],\nbody[data-dsh-sidebar-dragging] #root > [data-slot=\"root\"] > div > [data-side=\"details\"],\nbody[data-dsh-sidebar-dragging] #root [data-dsh-frame] > [data-pane=\"conversation\"],\nbody[data-dsh-sidebar-dragging] #root :has(> [data-slot=\"conversation\"]),\n#root [data-dsh-frame][data-dragging] > [data-side=\"details\"],\n#root > [data-slot=\"root\"] > div[data-dragging] > [data-side=\"details\"] {\n transition: none;\n}\n\n/* DSH 0.1.x gives external settings sections a generic gear and exposes no\n icon field in the settings.section contract. settings-nav-icon.ts marks\n only this plugin's localized row; render the requested Lucide\n gallery-horizontal-end SVG as a currentColor mask so it follows the native\n nav hover/active colors without changing the shell's 16px icon rhythm. */\n[data-dsh-better-sidebar-settings-nav] > svg:first-child {\n display: none;\n}\n\n[data-dsh-better-sidebar-settings-nav]::before {\n content: '';\n flex: none;\n width: 16px;\n height: 16px;\n background: currentColor;\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E\") center / contain no-repeat;\n}\n\n@media (prefers-reduced-motion: reduce) {\n #root [data-dsh-frame],\n #root > [data-slot=\"root\"] > div,\n #root [data-dsh-frame] > [data-side=\"details\"],\n #root > [data-slot=\"root\"] > div > [data-side=\"details\"],\n #root [data-dsh-frame] > [data-pane=\"conversation\"],\n #root :has(> [data-slot=\"conversation\"]) {\n transition: none;\n }\n}\n";
|
|
169
|
+
const tagId = "dsh-external/dsh-better-sidebar/layout.css";
|
|
170
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
171
|
+
const tag = document.createElement("style");
|
|
172
|
+
diff --git a/lib/client-terminal.js b/lib/client-terminal.js
|
|
173
|
+
index 3ac3cf5a7decf123a3a6d602fd6206da4702ee74..457ccc9f0e3f60a94d3ce03b6a7fcf3c6f2a2622 100644
|
|
174
|
+
--- a/lib/client-terminal.js
|
|
175
|
+
+++ b/lib/client-terminal.js
|
|
176
|
+
@@ -8055,6 +8055,26 @@ globalThis.__dshChunks__["terminal"] = (require) => {
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
+ //#region src/client/host-route-url.ts
|
|
181
|
+
+ /** Resolve one Host-owned client route against the injected document base.
|
|
182
|
+
+ * @param path - Root-style or relative Host route path.
|
|
183
|
+
+ * @param baseUrl - Browser document base or an explicit test base.
|
|
184
|
+
+ * @returns The absolute route URL.
|
|
185
|
+
+ */
|
|
186
|
+
+ function hostRouteUrl(path, baseUrl = document.baseURI) {
|
|
187
|
+
+ return new URL(path.replace(/^\/+/, ""), baseUrl);
|
|
188
|
+
+ }
|
|
189
|
+
+ /** Resolve one Host-owned WebSocket route against the injected document base.
|
|
190
|
+
+ * @param path - Root-style or relative Host WebSocket route path.
|
|
191
|
+
+ * @param baseUrl - Browser document base or an explicit test base.
|
|
192
|
+
+ * @returns The absolute ws: or wss: route URL.
|
|
193
|
+
+ */
|
|
194
|
+
+ function hostWebSocketUrl(path, baseUrl = document.baseURI) {
|
|
195
|
+
+ const url = hostRouteUrl(path, baseUrl);
|
|
196
|
+
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
197
|
+
+ return url;
|
|
198
|
+
+ }
|
|
199
|
+
+ //#endregion
|
|
200
|
+
//#region src/client/api.ts
|
|
201
|
+
/** One wire failure. */
|
|
202
|
+
var SidebarApiError = class extends Error {
|
|
203
|
+
@@ -8067,7 +8087,7 @@ globalThis.__dshChunks__["terminal"] = (require) => {
|
|
204
|
+
async function call(method, payload, signal) {
|
|
205
|
+
let response;
|
|
206
|
+
try {
|
|
207
|
+
- response = await fetch(`/sidebar/api/${method}`, {
|
|
208
|
+
+ response = await fetch(hostRouteUrl(`sidebar/api/${method}`), {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: { "content-type": "application/json" },
|
|
211
|
+
body: JSON.stringify(payload),
|
|
212
|
+
@@ -8096,7 +8116,7 @@ globalThis.__dshChunks__["terminal"] = (require) => {
|
|
213
|
+
if (scope.cwd !== void 0 && scope.cwd !== "") params.set("cwd", scope.cwd);
|
|
214
|
+
let response;
|
|
215
|
+
try {
|
|
216
|
+
- response = await fetch(`/sidebar/upload?${params.toString()}`, {
|
|
217
|
+
+ response = await fetch(hostRouteUrl(`sidebar/upload?${params.toString()}`), {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: { "content-type": "application/octet-stream" },
|
|
220
|
+
body,
|
|
221
|
+
@@ -9146,8 +9166,7 @@ globalThis.__dshChunks__["terminal"] = (require) => {
|
|
222
|
+
let retry;
|
|
223
|
+
let failures = 0;
|
|
224
|
+
const wsUrl = () => {
|
|
225
|
+
- const url = new URL("/sidebar/ws/terminal", location.origin);
|
|
226
|
+
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
227
|
+
+ const url = hostWebSocketUrl("sidebar/ws/terminal");
|
|
228
|
+
if (isAgentTabId(tabId)) url.search = new URLSearchParams({ uuid: agentUuidOf(tabId) }).toString();
|
|
229
|
+
else {
|
|
230
|
+
const params = new URLSearchParams({
|
|
231
|
+
diff --git a/lib/client.js b/lib/client.js
|
|
232
|
+
index 4f339ef28ed7eebd08cf7195b145924ca383b6be..55b791a4fd4f305a0394d61c3f1d298e2cfe7e13 100644
|
|
233
|
+
--- a/lib/client.js
|
|
234
|
+
+++ b/lib/client.js
|
|
235
|
+
@@ -1785,6 +1785,26 @@ window.__ModuleLoader__.load({
|
|
236
|
+
return state.activePane ?? "";
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
+ //#region src/client/host-route-url.ts
|
|
240
|
+
+ /** Resolve one Host-owned client route against the injected document base.
|
|
241
|
+
+ * @param path - Root-style or relative Host route path.
|
|
242
|
+
+ * @param baseUrl - Browser document base or an explicit test base.
|
|
243
|
+
+ * @returns The absolute route URL.
|
|
244
|
+
+ */
|
|
245
|
+
+ function hostRouteUrl(path, baseUrl = document.baseURI) {
|
|
246
|
+
+ return new URL(path.replace(/^\/+/, ""), baseUrl);
|
|
247
|
+
+ }
|
|
248
|
+
+ /** Resolve one Host-owned WebSocket route against the injected document base.
|
|
249
|
+
+ * @param path - Root-style or relative Host WebSocket route path.
|
|
250
|
+
+ * @param baseUrl - Browser document base or an explicit test base.
|
|
251
|
+
+ * @returns The absolute ws: or wss: route URL.
|
|
252
|
+
+ */
|
|
253
|
+
+ function hostWebSocketUrl(path, baseUrl = document.baseURI) {
|
|
254
|
+
+ const url = hostRouteUrl(path, baseUrl);
|
|
255
|
+
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
256
|
+
+ return url;
|
|
257
|
+
+ }
|
|
258
|
+
+ //#endregion
|
|
259
|
+
//#region src/client/chunk-loader.ts
|
|
260
|
+
/**
|
|
261
|
+
* The platform externals a chunk bundle may require (mirror of
|
|
262
|
+
@@ -1815,7 +1835,7 @@ window.__ModuleLoader__.load({
|
|
263
|
+
"@deepseek-ai/dsh-client-runtime/client"
|
|
264
|
+
];
|
|
265
|
+
/** Chunk script endpoint served by the plugin host half (src/bundle-route.ts). */
|
|
266
|
+
- const CHUNK_URL = (name) => `/sidebar/bundle/${name}.js`;
|
|
267
|
+
+ const CHUNK_URL = (name) => hostRouteUrl(`sidebar/bundle/${name}.js`).href;
|
|
268
|
+
/** Bound on the revalidation HEAD round-trip. A timeout fails open (drop +
|
|
269
|
+
* re-fetch on the next open) so a stuck bundle route can never wedge lazy
|
|
270
|
+
* chunk loads behind the revalidation barrier. */
|
|
271
|
+
@@ -10505,7 +10525,7 @@ window.__ModuleLoader__.load({
|
|
272
|
+
async function call(method, payload, signal) {
|
|
273
|
+
let response;
|
|
274
|
+
try {
|
|
275
|
+
- response = await fetch(`/sidebar/api/${method}`, {
|
|
276
|
+
+ response = await fetch(hostRouteUrl(`sidebar/api/${method}`), {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: { "content-type": "application/json" },
|
|
279
|
+
body: JSON.stringify(payload),
|
|
280
|
+
@@ -10534,7 +10554,7 @@ window.__ModuleLoader__.load({
|
|
281
|
+
if (scope.cwd !== void 0 && scope.cwd !== "") params.set("cwd", scope.cwd);
|
|
282
|
+
let response;
|
|
283
|
+
try {
|
|
284
|
+
- response = await fetch(`/sidebar/upload?${params.toString()}`, {
|
|
285
|
+
+ response = await fetch(hostRouteUrl(`sidebar/upload?${params.toString()}`), {
|
|
286
|
+
method: "POST",
|
|
287
|
+
headers: { "content-type": "application/octet-stream" },
|
|
288
|
+
body,
|
|
289
|
+
@@ -10684,7 +10704,7 @@ window.__ModuleLoader__.load({
|
|
290
|
+
});
|
|
291
|
+
if (scope.cwd !== void 0 && scope.cwd !== "") params.set("cwd", scope.cwd);
|
|
292
|
+
if (download) params.set("download", "1");
|
|
293
|
+
- return `/sidebar/file?${params.toString()}`;
|
|
294
|
+
+ return hostRouteUrl(`sidebar/file?${params.toString()}`).href;
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/client/binary-download.tsx
|
|
298
|
+
@@ -19418,8 +19438,7 @@ Mode: this is a continuable side conversation. Your answers stay in this side th
|
|
299
|
+
let failures = 0;
|
|
300
|
+
const connect = () => {
|
|
301
|
+
if (closed) return;
|
|
302
|
+
- const url = new URL("/sidebar/ws/agent-terminals", location.origin);
|
|
303
|
+
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
304
|
+
+ const url = hostWebSocketUrl("sidebar/ws/agent-terminals");
|
|
305
|
+
url.search = new URLSearchParams({ sessionId }).toString();
|
|
306
|
+
socket = new WebSocket(url.toString());
|
|
307
|
+
socket.onmessage = (event) => {
|
|
308
|
+
@@ -19472,8 +19491,7 @@ Mode: this is a continuable side conversation. Your answers stay in this side th
|
|
309
|
+
let failures = 0;
|
|
310
|
+
const connect = () => {
|
|
311
|
+
if (closed) return;
|
|
312
|
+
- const url = new URL("/sidebar/ws/agent-opens", location.origin);
|
|
313
|
+
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
314
|
+
+ const url = hostWebSocketUrl("sidebar/ws/agent-opens");
|
|
315
|
+
url.search = new URLSearchParams({ sessionId }).toString();
|
|
316
|
+
socket = new WebSocket(url.toString());
|
|
317
|
+
socket.onmessage = (event) => {
|
|
318
|
+
@@ -22117,7 +22135,7 @@ Mode: this is a continuable side conversation. Your answers stay in this side th
|
|
319
|
+
}
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region \0dsh-css:/home/runner/work/DSH-better-sidebar/DSH-better-sidebar/src/client/layout.css.mjs
|
|
322
|
+
- const css = "/**\n * Layout push: when a panel is open it OCCUPIES the layout instead of\n * floating over it — the app shell (#root, the AppFrame three-column grid)\n * gives up space. Only the center column is flexible (1fr), so the right\n * panel's width squeeze (margin-right on #root) lands exactly on the\n * conversation output and the input bar, like a VSCode sidebar.\n *\n * The width rides `calc(100% - var(...))` instead of a bare margin on a\n * full-width box: some desktop shells (DSH Desktop, #208) set #root to\n * width:100%, where a margin would overflow the viewport additively —\n * the calc keeps the box at exactly 100% minus the push in every shell.\n * Width and margin transition in lockstep (same variable, same duration\n * and easing), so expand/collapse animates the content width exactly as\n * the bare-margin version did.\n *\n * The bottom panel squeezes ONLY the center column — it must not cover the\n * app's own left sidebar or the right panel. The anchor is the frame's\n * center grid item via its stable data attribute ([data-pane=\"conversation\"]\n * on the centerCol inside [data-dsh-frame]), not a positional path: the\n * frame's child order also contains an overlay layer and drag handles\n * (verified against a live DSH 0.1.x page), so nth-child is brittle. A\n * stretched grid item shrinks by its margins, so the conversation content\n * (output + input bar) lifts without touching the sidebars.\n *\n * The sizes ride CSS variables updated by the Sidebar shell (0 while\n * collapsed); expand/collapse animates both the margins and the panel\n * slides on the same theme duration. Drags disable the transition so the\n * layout tracks the pointer.\n */\n#root {\n margin-right: var(--dsh-sidebar-width, 0px);\n width: calc(100% - var(--dsh-sidebar-width, 0px));\n transition:\n margin-right var(--ds-transition-duration-slow) var(--ds-ease-in-out),\n width var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n/* The AppFrame's center column, anchored by data attributes (see above).\n Composite selector: DSH 0.1.x versions name the center grid item\n `[data-pane=\"conversation\"]`, while rc.8-era shells put a\n `[data-slot=\"conversation\"]` child inside it — both selectors resolve to\n the SAME element on live pages (verified), and keeping both future-proofs\n the rule against a host rename without touching shell-specific markup. */\n#root [data-dsh-frame] > [data-pane=\"conversation\"],\n#root :has(> [data-slot=\"conversation\"]) {\n margin-bottom: var(--dsh-sidebar-height, 0px);\n /* Grid/flex items default to min-height:auto. A long unbreakable token\n (OAuth URL) then grows this column past the viewport and clips the\n composer + left-rail Settings row. min-height:0 lets the cell shrink;\n overflow-wrap lets the token wrap instead of forcing its intrinsic\n size. The host's own descendants remain responsible for scrolling. */\n min-height: 0;\n overflow-wrap: anywhere;\n transition: margin-bottom var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n/* When the sidebar is collapsed, the toggle cluster reclaims the top-right\n corner. Push the DSH session header's right padding out so its right-aligned\n utilities (the \"Session log\" download capsule) yield the corner instead of\n hiding under the cluster. The header default right-pads 28px; the 2-button\n cluster spans right 10→70px, so 78px clears it with an 8px gap. Anchor on\n the header's slot host wrapper ([data-slot=\"conversation.session.header\"])\n rather than a positional path: DSH 0.1.x nests the header several levels\n under the center column. The Sidebar shell toggles the body attribute with\n the panel open state. */\nbody[data-dsh-sidebar-collapsed] [data-slot=\"conversation.session.header\"] > header {\n padding-right: 78px;\n}\n\nbody[data-dsh-sidebar-dragging] #root,\nbody[data-dsh-sidebar-dragging] #root [data-dsh-frame] > [data-pane=\"conversation\"],\nbody[data-dsh-sidebar-dragging] #root :has(> [data-slot=\"conversation\"]) {\n transition: none;\n}\n\n/* DSH 0.1.x gives external settings sections a generic gear and exposes no\n icon field in the settings.section contract. settings-nav-icon.ts marks\n only this plugin's localized row; render the requested Lucide\n gallery-horizontal-end SVG as a currentColor mask so it follows the native\n nav hover/active colors without changing the shell's 16px icon rhythm. */\n[data-dsh-better-sidebar-settings-nav] > svg:first-child {\n display: none;\n}\n\n[data-dsh-better-sidebar-settings-nav]::before {\n content: '';\n flex: none;\n width: 16px;\n height: 16px;\n background: currentColor;\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E\") center / contain no-repeat;\n}\n\n@media (prefers-reduced-motion: reduce) {\n #root,\n #root [data-dsh-frame] > [data-pane=\"conversation\"],\n #root :has(> [data-slot=\"conversation\"]) {\n transition: none;\n }\n}\n";
|
|
323
|
+
+ const css = "/**\n * Layout push: an open panel occupies layout instead of floating over it.\n * The right panel reserves padding inside the AppFrame three-column grid,\n * so the app sidebar, conversation, and details column all remain to its\n * left while the frame's border-box stays viewport-wide. Harness measures\n * that border-box to choose desktop versus narrow presentation; preserving\n * it prevents a desktop window from entering narrow mode merely because the\n * plugin panel opened.\n *\n * AppFrame positions its details drag handle from the measured border-box.\n * Move that handle left by the same reserved width so it stays on the details\n * column edge. The ordinary sidebar handle remains at its native coordinate.\n *\n * The bottom panel squeezes only the conversation column. The anchor is the\n * frame's center grid item via stable data attributes, not a positional path:\n * the frame also contains an overlay layer and drag handles. A stretched grid\n * item shrinks by its bottom margin, so the conversation output and composer\n * lift without touching either sidebar.\n *\n * Sizes ride CSS variables updated by the Sidebar shell (0 while collapsed).\n * Expand/collapse uses the host theme duration; drags disable transitions so\n * every reserved edge tracks the pointer.\n */\n/* Current shells expose [data-dsh-frame]; rc.8-era shells expose the same\n AppFrame only as the root slot's direct child. */\n#root [data-dsh-frame],\n#root > [data-slot=\"root\"] > div {\n box-sizing: border-box;\n padding-right: var(--dsh-sidebar-width, 0px);\n transition: padding-right var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n#root [data-dsh-frame] > [data-side=\"details\"],\n#root > [data-slot=\"root\"] > div > [data-side=\"details\"] {\n transform: translateX(calc(0px - var(--dsh-sidebar-width, 0px)));\n transition:\n left var(--ds-transition-duration-slow) var(--ds-ease-in-out),\n transform var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n/* The AppFrame's center column, anchored by data attributes (see above).\n Composite selector: DSH 0.1.x versions name the center grid item\n `[data-pane=\"conversation\"]`, while rc.8-era shells put a\n `[data-slot=\"conversation\"]` child inside it — both selectors resolve to\n the SAME element on live pages (verified), and keeping both future-proofs\n the rule against a host rename without touching shell-specific markup. */\n#root [data-dsh-frame] > [data-pane=\"conversation\"],\n#root :has(> [data-slot=\"conversation\"]) {\n margin-bottom: var(--dsh-sidebar-height, 0px);\n /* Grid/flex items default to min-height:auto. A long unbreakable token\n (OAuth URL) then grows this column past the viewport and clips the\n composer + left-rail Settings row. min-height:0 lets the cell shrink;\n overflow-wrap lets the token wrap instead of forcing its intrinsic\n size. The host's own descendants remain responsible for scrolling. */\n min-height: 0;\n overflow-wrap: anywhere;\n transition: margin-bottom var(--ds-transition-duration-slow) var(--ds-ease-in-out);\n}\n\n/* When the sidebar is collapsed, the toggle cluster reclaims the top-right\n corner. Push the DSH session header's right padding out so its right-aligned\n utilities (the \"Session log\" download capsule) yield the corner instead of\n hiding under the cluster. The header default right-pads 28px; the 2-button\n cluster spans right 10→70px, so 78px clears it with an 8px gap. Anchor on\n the header's slot host wrapper ([data-slot=\"conversation.session.header\"])\n rather than a positional path: DSH 0.1.x nests the header several levels\n under the center column. The Sidebar shell toggles the body attribute with\n the panel open state. */\nbody[data-dsh-sidebar-collapsed] [data-slot=\"conversation.session.header\"] > header {\n padding-right: 78px;\n}\n\nbody[data-dsh-sidebar-dragging] #root [data-dsh-frame],\nbody[data-dsh-sidebar-dragging] #root > [data-slot=\"root\"] > div,\nbody[data-dsh-sidebar-dragging] #root [data-dsh-frame] > [data-side=\"details\"],\nbody[data-dsh-sidebar-dragging] #root > [data-slot=\"root\"] > div > [data-side=\"details\"],\nbody[data-dsh-sidebar-dragging] #root [data-dsh-frame] > [data-pane=\"conversation\"],\nbody[data-dsh-sidebar-dragging] #root :has(> [data-slot=\"conversation\"]),\n#root [data-dsh-frame][data-dragging] > [data-side=\"details\"],\n#root > [data-slot=\"root\"] > div[data-dragging] > [data-side=\"details\"] {\n transition: none;\n}\n\n/* DSH 0.1.x gives external settings sections a generic gear and exposes no\n icon field in the settings.section contract. settings-nav-icon.ts marks\n only this plugin's localized row; render the requested Lucide\n gallery-horizontal-end SVG as a currentColor mask so it follows the native\n nav hover/active colors without changing the shell's 16px icon rhythm. */\n[data-dsh-better-sidebar-settings-nav] > svg:first-child {\n display: none;\n}\n\n[data-dsh-better-sidebar-settings-nav]::before {\n content: '';\n flex: none;\n width: 16px;\n height: 16px;\n background: currentColor;\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7v10'/%3E%3Cpath d='M6 5v14'/%3E%3Crect width='12' height='18' x='10' y='3' rx='2'/%3E%3C/svg%3E\") center / contain no-repeat;\n}\n\n@media (prefers-reduced-motion: reduce) {\n #root [data-dsh-frame],\n #root > [data-slot=\"root\"] > div,\n #root [data-dsh-frame] > [data-side=\"details\"],\n #root > [data-slot=\"root\"] > div > [data-side=\"details\"],\n #root [data-dsh-frame] > [data-pane=\"conversation\"],\n #root :has(> [data-slot=\"conversation\"]) {\n transition: none;\n }\n}\n";
|
|
324
|
+
const tagId = "dsh-better-sidebar/layout.css";
|
|
325
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
326
|
+
const tag = document.createElement("style");
|
|
327
|
+
diff --git a/src/client/Sidebar.tsx b/src/client/Sidebar.tsx
|
|
328
|
+
index 8897571263a83c8caa1992059ad99e2bce140b28..ffb1aede7b2ebdbd8fcdf3c1e5d575966f0a3230 100644
|
|
329
|
+
--- a/src/client/Sidebar.tsx
|
|
330
|
+
+++ b/src/client/Sidebar.tsx
|
|
331
|
+
@@ -50,6 +50,7 @@ import { layoutPushSize } from './layout-push.ts'
|
|
332
|
+
import { parseDesktopEnv } from './desktop-env.ts'
|
|
333
|
+
import { getWcoSnapshot, subscribeWco } from './wco.ts'
|
|
334
|
+
import { getShellPreset } from './shell-presets.ts'
|
|
335
|
+
+import { hostWebSocketUrl } from './host-route-url.ts'
|
|
336
|
+
import { computeTitleBarStrip } from './titlebar-strip.ts'
|
|
337
|
+
import type { NewTabOption } from './TabBar.tsx'
|
|
338
|
+
import { TAB_DRAG_TYPE, parseDrag, type TabDragPayload } from './TabBar.tsx'
|
|
339
|
+
@@ -428,8 +429,7 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
|
|
340
|
+
let failures = 0
|
|
341
|
+
const connect = (): void => {
|
|
342
|
+
if (closed) return
|
|
343
|
+
- const url = new URL('/sidebar/ws/agent-terminals', location.origin)
|
|
344
|
+
- url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
345
|
+
+ const url = hostWebSocketUrl('sidebar/ws/agent-terminals')
|
|
346
|
+
url.search = new URLSearchParams({ sessionId }).toString()
|
|
347
|
+
socket = new WebSocket(url.toString())
|
|
348
|
+
socket.onmessage = (event) => {
|
|
349
|
+
@@ -485,8 +485,7 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
|
|
350
|
+
let failures = 0
|
|
351
|
+
const connect = (): void => {
|
|
352
|
+
if (closed) return
|
|
353
|
+
- const url = new URL('/sidebar/ws/agent-opens', location.origin)
|
|
354
|
+
- url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
355
|
+
+ const url = hostWebSocketUrl('sidebar/ws/agent-opens')
|
|
356
|
+
url.search = new URLSearchParams({ sessionId }).toString()
|
|
357
|
+
socket = new WebSocket(url.toString())
|
|
358
|
+
socket.onmessage = (event) => {
|
|
359
|
+
diff --git a/src/client/TerminalView.tsx b/src/client/TerminalView.tsx
|
|
360
|
+
index eba3d93c17747a28537a45c2bdd023991d25f086..f9f075a5202873b1896c4fa08cc7bc9bd5f5d35e 100644
|
|
361
|
+
--- a/src/client/TerminalView.tsx
|
|
362
|
+
+++ b/src/client/TerminalView.tsx
|
|
363
|
+
@@ -47,6 +47,7 @@ import {
|
|
364
|
+
shouldActivateTerminalLink,
|
|
365
|
+
openTerminalUrl,
|
|
366
|
+
} from './terminal-links.ts'
|
|
367
|
+
+import { hostWebSocketUrl } from './host-route-url.ts'
|
|
368
|
+
import css from './sidebar.module.css'
|
|
369
|
+
|
|
370
|
+
/** How many consecutive unreasoned failures before showing the error banner. */
|
|
371
|
+
@@ -182,8 +183,7 @@ export function TerminalView(props: { scope: SessionScope; tabId: string; store:
|
|
372
|
+
let failures = 0
|
|
373
|
+
|
|
374
|
+
const wsUrl = (): string => {
|
|
375
|
+
- const url = new URL('/sidebar/ws/terminal', location.origin)
|
|
376
|
+
- url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
377
|
+
+ const url = hostWebSocketUrl('sidebar/ws/terminal')
|
|
378
|
+
// Agent terminals attach by uuid (the host looks them up in the agent
|
|
379
|
+
// pty registry); UI-tab terminals attach by sessionId+tab (the host
|
|
380
|
+
// uses the UI-tab pty manager). Same upgrade endpoint, different query.
|
|
381
|
+
@@ -194,9 +194,7 @@ export function TerminalView(props: { scope: SessionScope; tabId: string; store:
|
|
382
|
+
if (scope.cwd !== undefined && scope.cwd !== '') params.set('cwd', scope.cwd)
|
|
383
|
+
url.search = params.toString()
|
|
384
|
+
}
|
|
385
|
+
- // Same construction the app's own downlink WebSockets use (new URL
|
|
386
|
+
- // over location.origin + protocol swap): whatever the environment
|
|
387
|
+
- // does to the app's websockets applies identically here.
|
|
388
|
+
+ // The injected document base keeps the socket under the same reverse-proxy prefix as the page.
|
|
389
|
+
return url.toString()
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
diff --git a/src/client/TextEditor.tsx b/src/client/TextEditor.tsx
|
|
393
|
+
index 25114c907091233c03cd0b74447c3f39f3479636..3bf06a443d1445d17cbd5e69030a9f3c8de27ad1 100644
|
|
394
|
+
--- a/src/client/TextEditor.tsx
|
|
395
|
+
+++ b/src/client/TextEditor.tsx
|
|
396
|
+
@@ -251,7 +251,7 @@ export function TextEditor(props: FileViewerProps) {
|
|
397
|
+
* `mdText` stays untouched for selection/line lookup and for mermaid-block
|
|
398
|
+
* detection, which are unaffected by image syntax. */
|
|
399
|
+
const previewText = markdown
|
|
400
|
+
- ? rewriteLocalImageUrls(mdText, scope, path, window.location.origin)
|
|
401
|
+
+ ? rewriteLocalImageUrls(mdText, scope, path, document.baseURI)
|
|
402
|
+
: mdText
|
|
403
|
+
/** md/mermaid block split for the preview (mermaid fences lift out). Split
|
|
404
|
+
* only in preview mode: edit-mode keystrokes must not re-scan the source. */
|
|
405
|
+
diff --git a/src/client/api.ts b/src/client/api.ts
|
|
406
|
+
index 8173cd90cb4917dbae044d286236a109c2f3ae40..19c2501427caa467c19ca65f711ee977d383d860 100644
|
|
407
|
+
--- a/src/client/api.ts
|
|
408
|
+
+++ b/src/client/api.ts
|
|
409
|
+
@@ -7,6 +7,7 @@
|
|
410
|
+
* request). Failures surface as {@link SidebarApiError} with the wire code.
|
|
411
|
+
*/
|
|
412
|
+
import { encodeHtmlUrl } from '../html-route.ts'
|
|
413
|
+
+import { hostRouteUrl } from './host-route-url.ts'
|
|
414
|
+
import type { LastActivity } from '../subagent-activity.ts'
|
|
415
|
+
import type { SidechatThreadInfo } from '../sidechat-core.ts'
|
|
416
|
+
import type { BrowserProbeResult } from './browser.ts'
|
|
417
|
+
@@ -115,7 +116,7 @@ export type TerminalDepsStatus =
|
|
418
|
+
async function call<T>(method: string, payload: Record<string, unknown>, signal?: AbortSignal): Promise<T> {
|
|
419
|
+
let response: Response
|
|
420
|
+
try {
|
|
421
|
+
- response = await fetch(`/sidebar/api/${method}`, {
|
|
422
|
+
+ response = await fetch(hostRouteUrl(`sidebar/api/${method}`), {
|
|
423
|
+
method: 'POST',
|
|
424
|
+
headers: { 'content-type': 'application/json' },
|
|
425
|
+
body: JSON.stringify(payload),
|
|
426
|
+
@@ -153,7 +154,7 @@ async function fetchUpload<T>(
|
|
427
|
+
if (scope.cwd !== undefined && scope.cwd !== '') params.set('cwd', scope.cwd)
|
|
428
|
+
let response: Response
|
|
429
|
+
try {
|
|
430
|
+
- response = await fetch(`/sidebar/upload?${params.toString()}`, {
|
|
431
|
+
+ response = await fetch(hostRouteUrl(`sidebar/upload?${params.toString()}`), {
|
|
432
|
+
method: 'POST',
|
|
433
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
434
|
+
body,
|
|
435
|
+
@@ -341,7 +342,7 @@ function fileUrl(scope: SessionScope, path: string, download: boolean): string {
|
|
436
|
+
const params = new URLSearchParams({ sessionId: scope.sessionId, path })
|
|
437
|
+
if (scope.cwd !== undefined && scope.cwd !== '') params.set('cwd', scope.cwd)
|
|
438
|
+
if (download) params.set('download', '1')
|
|
439
|
+
- return `/sidebar/file?${params.toString()}`
|
|
440
|
+
+ return hostRouteUrl(`sidebar/file?${params.toString()}`).href
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
@@ -353,5 +354,5 @@ function fileUrl(scope: SessionScope, path: string, download: boolean): string {
|
|
445
|
+
* client-side platform signal is needed.
|
|
446
|
+
*/
|
|
447
|
+
export function htmlUrl(scope: SessionScope, path: string): string {
|
|
448
|
+
- return encodeHtmlUrl(scope.sessionId, path)
|
|
449
|
+
+ return hostRouteUrl(encodeHtmlUrl(scope.sessionId, path)).href
|
|
450
|
+
}
|
|
451
|
+
diff --git a/src/client/chunk-loader.ts b/src/client/chunk-loader.ts
|
|
452
|
+
index 5f7f64fe3b412054feb1f4a3985a88639550e2b1..441b80c3d54d17e65a047b9062bb13ab24a45558 100644
|
|
453
|
+
--- a/src/client/chunk-loader.ts
|
|
454
|
+
+++ b/src/client/chunk-loader.ts
|
|
455
|
+
@@ -49,6 +49,8 @@
|
|
456
|
+
* client.js); an edit that does land while a core HMR happens is caught by
|
|
457
|
+
* the ETag comparison on the next activation.
|
|
458
|
+
*/
|
|
459
|
+
+import { hostRouteUrl } from './host-route-url.ts'
|
|
460
|
+
+
|
|
461
|
+
export type ChunkName = 'terminal' | 'editor' | 'mermaid'
|
|
462
|
+
|
|
463
|
+
/** The module exports a chunk factory provides (namespace-ish record). */
|
|
464
|
+
@@ -87,7 +89,7 @@ export const CHUNK_EXTERNALS: readonly string[] = [
|
|
465
|
+
]
|
|
466
|
+
|
|
467
|
+
/** Chunk script endpoint served by the plugin host half (src/bundle-route.ts). */
|
|
468
|
+
-const CHUNK_URL = (name: ChunkName): string => `/sidebar/bundle/${name}.js`
|
|
469
|
+
+const CHUNK_URL = (name: ChunkName): string => hostRouteUrl(`sidebar/bundle/${name}.js`).href
|
|
470
|
+
|
|
471
|
+
/** Bound on the revalidation HEAD round-trip. A timeout fails open (drop +
|
|
472
|
+
* re-fetch on the next open) so a stuck bundle route can never wedge lazy
|
|
473
|
+
diff --git a/src/client/layout.css b/src/client/layout.css
|
|
474
|
+
index 17075c6188972f69e141ad798425da7e37a51e24..2d378f076d6fbeca8d86438e6b249f1e6e18b046 100644
|
|
475
|
+
--- a/src/client/layout.css
|
|
476
|
+
+++ b/src/client/layout.css
|
|
477
|
+
@@ -1,38 +1,41 @@
|
|
478
|
+
/**
|
|
479
|
+
- * Layout push: when a panel is open it OCCUPIES the layout instead of
|
|
480
|
+
- * floating over it — the app shell (#root, the AppFrame three-column grid)
|
|
481
|
+
- * gives up space. Only the center column is flexible (1fr), so the right
|
|
482
|
+
- * panel's width squeeze (margin-right on #root) lands exactly on the
|
|
483
|
+
- * conversation output and the input bar, like a VSCode sidebar.
|
|
484
|
+
+ * Layout push: an open panel occupies layout instead of floating over it.
|
|
485
|
+
+ * The right panel reserves padding inside the AppFrame three-column grid,
|
|
486
|
+
+ * so the app sidebar, conversation, and details column all remain to its
|
|
487
|
+
+ * left while the frame's border-box stays viewport-wide. Harness measures
|
|
488
|
+
+ * that border-box to choose desktop versus narrow presentation; preserving
|
|
489
|
+
+ * it prevents a desktop window from entering narrow mode merely because the
|
|
490
|
+
+ * plugin panel opened.
|
|
491
|
+
*
|
|
492
|
+
- * The width rides `calc(100% - var(...))` instead of a bare margin on a
|
|
493
|
+
- * full-width box: some desktop shells (DSH Desktop, #208) set #root to
|
|
494
|
+
- * width:100%, where a margin would overflow the viewport additively —
|
|
495
|
+
- * the calc keeps the box at exactly 100% minus the push in every shell.
|
|
496
|
+
- * Width and margin transition in lockstep (same variable, same duration
|
|
497
|
+
- * and easing), so expand/collapse animates the content width exactly as
|
|
498
|
+
- * the bare-margin version did.
|
|
499
|
+
+ * AppFrame positions its details drag handle from the measured border-box.
|
|
500
|
+
+ * Move that handle left by the same reserved width so it stays on the details
|
|
501
|
+
+ * column edge. The ordinary sidebar handle remains at its native coordinate.
|
|
502
|
+
*
|
|
503
|
+
- * The bottom panel squeezes ONLY the center column — it must not cover the
|
|
504
|
+
- * app's own left sidebar or the right panel. The anchor is the frame's
|
|
505
|
+
- * center grid item via its stable data attribute ([data-pane="conversation"]
|
|
506
|
+
- * on the centerCol inside [data-dsh-frame]), not a positional path: the
|
|
507
|
+
- * frame's child order also contains an overlay layer and drag handles
|
|
508
|
+
- * (verified against a live DSH 0.1.x page), so nth-child is brittle. A
|
|
509
|
+
- * stretched grid item shrinks by its margins, so the conversation content
|
|
510
|
+
- * (output + input bar) lifts without touching the sidebars.
|
|
511
|
+
+ * The bottom panel squeezes only the conversation column. The anchor is the
|
|
512
|
+
+ * frame's center grid item via stable data attributes, not a positional path:
|
|
513
|
+
+ * the frame also contains an overlay layer and drag handles. A stretched grid
|
|
514
|
+
+ * item shrinks by its bottom margin, so the conversation output and composer
|
|
515
|
+
+ * lift without touching either sidebar.
|
|
516
|
+
*
|
|
517
|
+
- * The sizes ride CSS variables updated by the Sidebar shell (0 while
|
|
518
|
+
- * collapsed); expand/collapse animates both the margins and the panel
|
|
519
|
+
- * slides on the same theme duration. Drags disable the transition so the
|
|
520
|
+
- * layout tracks the pointer.
|
|
521
|
+
+ * Sizes ride CSS variables updated by the Sidebar shell (0 while collapsed).
|
|
522
|
+
+ * Expand/collapse uses the host theme duration; drags disable transitions so
|
|
523
|
+
+ * every reserved edge tracks the pointer.
|
|
524
|
+
*/
|
|
525
|
+
-#root {
|
|
526
|
+
- margin-right: var(--dsh-sidebar-width, 0px);
|
|
527
|
+
- width: calc(100% - var(--dsh-sidebar-width, 0px));
|
|
528
|
+
+/* Current shells expose [data-dsh-frame]; rc.8-era shells expose the same
|
|
529
|
+
+ AppFrame only as the root slot's direct child. */
|
|
530
|
+
+#root [data-dsh-frame],
|
|
531
|
+
+#root > [data-slot="root"] > div {
|
|
532
|
+
+ box-sizing: border-box;
|
|
533
|
+
+ padding-right: var(--dsh-sidebar-width, 0px);
|
|
534
|
+
+ transition: padding-right var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
535
|
+
+}
|
|
536
|
+
+
|
|
537
|
+
+#root [data-dsh-frame] > [data-side="details"],
|
|
538
|
+
+#root > [data-slot="root"] > div > [data-side="details"] {
|
|
539
|
+
+ transform: translateX(calc(0px - var(--dsh-sidebar-width, 0px)));
|
|
540
|
+
transition:
|
|
541
|
+
- margin-right var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
542
|
+
- width var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
543
|
+
+ left var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
544
|
+
+ transform var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* The AppFrame's center column, anchored by data attributes (see above).
|
|
548
|
+
@@ -67,9 +70,14 @@ body[data-dsh-sidebar-collapsed] [data-slot="conversation.session.header"] > hea
|
|
549
|
+
padding-right: 78px;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
-body[data-dsh-sidebar-dragging] #root,
|
|
553
|
+
+body[data-dsh-sidebar-dragging] #root [data-dsh-frame],
|
|
554
|
+
+body[data-dsh-sidebar-dragging] #root > [data-slot="root"] > div,
|
|
555
|
+
+body[data-dsh-sidebar-dragging] #root [data-dsh-frame] > [data-side="details"],
|
|
556
|
+
+body[data-dsh-sidebar-dragging] #root > [data-slot="root"] > div > [data-side="details"],
|
|
557
|
+
body[data-dsh-sidebar-dragging] #root [data-dsh-frame] > [data-pane="conversation"],
|
|
558
|
+
-body[data-dsh-sidebar-dragging] #root :has(> [data-slot="conversation"]) {
|
|
559
|
+
+body[data-dsh-sidebar-dragging] #root :has(> [data-slot="conversation"]),
|
|
560
|
+
+#root [data-dsh-frame][data-dragging] > [data-side="details"],
|
|
561
|
+
+#root > [data-slot="root"] > div[data-dragging] > [data-side="details"] {
|
|
562
|
+
transition: none;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
@@ -93,7 +101,10 @@ body[data-dsh-sidebar-dragging] #root :has(> [data-slot="conversation"]) {
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
@media (prefers-reduced-motion: reduce) {
|
|
569
|
+
- #root,
|
|
570
|
+
+ #root [data-dsh-frame],
|
|
571
|
+
+ #root > [data-slot="root"] > div,
|
|
572
|
+
+ #root [data-dsh-frame] > [data-side="details"],
|
|
573
|
+
+ #root > [data-slot="root"] > div > [data-side="details"],
|
|
574
|
+
#root [data-dsh-frame] > [data-pane="conversation"],
|
|
575
|
+
#root :has(> [data-slot="conversation"]) {
|
|
576
|
+
transition: none;
|
|
577
|
+
diff --git a/src/client/markdown-images.ts b/src/client/markdown-images.ts
|
|
578
|
+
index cb223c9fb87a74dcac732131811f67cf226b011d..5aab4a6fc2b09c1cc50f5978fa091d8fb4ca0551 100644
|
|
579
|
+
--- a/src/client/markdown-images.ts
|
|
580
|
+
+++ b/src/client/markdown-images.ts
|
|
581
|
+
@@ -11,6 +11,7 @@
|
|
582
|
+
*/
|
|
583
|
+
|
|
584
|
+
import type { SessionScope } from './api.ts'
|
|
585
|
+
+import { hostRouteUrl } from './host-route-url.ts'
|
|
586
|
+
import { isAbsolutePath } from './paths.ts'
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
@@ -90,7 +91,7 @@ export function resolveLocalMediaDest(
|
|
590
|
+
// absolute so the shared MarkdownText http(s) allowlist accepts it.
|
|
591
|
+
const params = new URLSearchParams({ sessionId: scope.sessionId, path: normalizeLocalPath(candidate) })
|
|
592
|
+
if (scope.cwd !== undefined && scope.cwd !== '') params.set('cwd', scope.cwd)
|
|
593
|
+
- return `${origin}/sidebar/file?${params.toString()}`
|
|
594
|
+
+ return hostRouteUrl(`sidebar/file?${params.toString()}`, origin).href
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export function rewriteLocalImageUrls(
|