From 57baa328d94da9d7f99e7cfbaa43615f9f953f2e Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 18:52:54 -1000 Subject: [PATCH 01/10] build: allow npm>=12 install scripts for native runtime deps npm >= 12 blocks lifecycle scripts by default. Approve the native and build-critical deps (node-pty, koffi, esbuild, bundled node) and record explicit denials for Windows-only/no-op scripts. The vendored dsh-subprocess-local postinstall (chmod of a macOS-only spawn helper) cannot take a portable allowScripts key because npm keys file: deps by their resolved absolute URL; skipping it on Linux is harmless. --- package.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/package.json b/package.json index e1e39b4a..a00c30d1 100644 --- a/package.json +++ b/package.json @@ -410,5 +410,14 @@ "createDesktopShortcut": true, "createStartMenuShortcut": true } + }, + "allowScripts": { + "node-pty@1.2.0-beta.15": true, + "koffi@3.1.5": true, + "esbuild": true, + "node": true, + "electron-winstaller": false, + "protobufjs": false, + "@google/genai": false } } From 40f087bab4fbf53f755cb61b239c68fba23f5f7f Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 19:16:39 -1000 Subject: [PATCH 02/10] feat(linux): package Linux x64 AppImage, scripts, icon, and docs Electron-builder gains a linux.target (AppImage, x64) with a committed 512px 8-bit RGBA icon (the existing 16-bit 1024px source PNG is not accepted reliably by AppImage tooling), package:linux:x64 and package:dev:linux:x64 scripts mirroring the mac/win flow (verify-target + build + --publish never), desktopName/syncDesktopName so Wayland compositors associate windows with the launcher entry, and docs/README updates replacing 'Linux not supported' with the from-source community build story (in-app updates stay macOS/Windows-only). --- README.es.md | 4 ++-- README.ja.md | 4 ++-- README.md | 4 ++-- README.pt.md | 4 ++-- README.ru.md | 4 ++-- README.zh.md | 4 ++-- build/app-icon-512.png | Bin 0 -> 44770 bytes docs/development.md | 21 +++++++++++++++++++++ package.json | 18 +++++++++++++++++- 9 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 build/app-icon-512.png diff --git a/README.es.md b/README.es.md index b0f1e84c..1e4d4fd8 100644 --- a/README.es.md +++ b/README.es.md @@ -91,9 +91,9 @@ open -a "DSH Desktop" --args --safe-mode | macOS Intel | DMG/ZIP firmados y notarizados | Compatible | | Windows x64 | Instalador NSIS firmado | Compatible | | Windows ARM64 | — | No compatible actualmente | -| Linux | — | No compatible actualmente | +| Linux x64 | AppImage comunitaria (compilada desde el código) | Compatible | -Harness incluye dependencias nativas, por lo que cada artefacto se compila en el sistema operativo y la arquitectura correspondientes. +Harness incluye dependencias nativas, por lo que cada artefacto se compila en el sistema operativo y la arquitectura correspondientes. En Linux, compila la AppImage comunitaria sin firmar con `npm run package:linux:x64`; el actualizador integrado sigue siendo exclusivo de macOS/Windows y en Linux se recompila desde un árbol sincronizado aguas arriba. ## Desarrollo y arquitectura diff --git a/README.ja.md b/README.ja.md index d6e87e85..c353231b 100644 --- a/README.ja.md +++ b/README.ja.md @@ -90,9 +90,9 @@ open -a "DSH Desktop" --args --safe-mode | macOS Intel | 署名・公証済み DMG/ZIP | 対応 | | Windows x64 | コード署名済み NSIS インストーラー | 対応 | | Windows ARM64 | — | 未対応 | -| Linux | — | 未対応 | +| Linux x64 | ソースからビルドするコミュニティ版 AppImage | 対応 | -Harness にはターゲット固有のネイティブ依存関係が含まれるため、各リリースは対応する OS とアーキテクチャ上でビルドされます。 +Harness にはターゲット固有のネイティブ依存関係が含まれるため、各リリースは対応する OS とアーキテクチャ上でビルドされます。Linux では `npm run package:linux:x64` で未署名のコミュニティ版 AppImage をソースからビルドできます。アプリ内アップデートは引き続き macOS/Windows のみで、Linux の場合は同期した上流ツリーから再ビルドします。 ## 開発とアーキテクチャ diff --git a/README.md b/README.md index ea88f99a..359f6ff5 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,9 @@ open -a "DSH Desktop" --args --safe-mode | macOS Intel | Signed and notarized DMG/ZIP | Supported | | Windows x64 | Code-signed NSIS installer | Supported | | Windows ARM64 | — | Not currently supported | -| Linux | — | Not currently supported | +| Linux x64 | Community AppImage build (built from source) | Supported | -Harness includes target-native dependencies, so every release artifact is built on the matching operating system and architecture. +Harness includes target-native dependencies, so every release artifact is built on the matching operating system and architecture. On Linux, build the unsigned community AppImage from source with `npm run package:linux:x64`; the in-app updater stays macOS/Windows-only and Linux users rebuild from a synced upstream checkout instead. ## Development and architecture diff --git a/README.pt.md b/README.pt.md index 0e63d38d..49019cab 100644 --- a/README.pt.md +++ b/README.pt.md @@ -91,9 +91,9 @@ open -a "DSH Desktop" --args --safe-mode | macOS Intel | DMG/ZIP assinados e notarizados | Compatível | | Windows x64 | Instalador NSIS assinado | Compatível | | Windows ARM64 | — | Não compatível atualmente | -| Linux | — | Não compatível atualmente | +| Linux x64 | AppImage da comunidade (compilada do código) | Compatível | -O Harness inclui dependências nativas, portanto cada artefato é compilado no sistema operacional e na arquitetura correspondentes. +O Harness inclui dependências nativas, portanto cada artefato é compilado no sistema operacional e na arquitetura correspondentes. No Linux, compile a AppImage da comunidade não assinada com `npm run package:linux:x64`; o atualizador integrado continua exclusivo do macOS/Windows e no Linux rebuilda-se a partir de uma árvore sincronizada de upstream. ## Desenvolvimento e arquitetura diff --git a/README.ru.md b/README.ru.md index c2c520bb..c0f28322 100644 --- a/README.ru.md +++ b/README.ru.md @@ -91,9 +91,9 @@ open -a "DSH Desktop" --args --safe-mode | macOS Intel | Подписанные и нотариально заверенные DMG/ZIP | Поддерживается | | Windows x64 | Подписанный установщик NSIS | Поддерживается | | Windows ARM64 | — | Не поддерживается | -| Linux | — | Не поддерживается | +| Linux x64 | AppImage от сообщества (сборка из исходников) | Поддерживается | -Harness содержит нативные зависимости, поэтому каждый выпуск собирается на соответствующей операционной системе и архитектуре. +Harness содержит нативные зависимости, поэтому каждый выпуск собирается на соответствующей операционной системе и архитектуре. В Linux соберите неподписанное AppImage сообщества из исходников командой `npm run package:linux:x64`; встроенный обновлятор остаётся доступным только для macOS/Windows — в Linux дерево сборки переводится из синхронизированного апстрима. ## Разработка и архитектура diff --git a/README.zh.md b/README.zh.md index a8984976..03250021 100644 --- a/README.zh.md +++ b/README.zh.md @@ -91,9 +91,9 @@ open -a "DSH Desktop" --args --safe-mode | macOS Intel | 已签名并通过公证的 DMG/ZIP | 支持 | | Windows x64 | 已完成代码签名的 NSIS 安装包 | 支持 | | Windows ARM64 | — | 当前不支持 | -| Linux | — | 当前不支持 | +| Linux x64 | 由源码构建的社区 AppImage | 支持 | -Harness 包含目标平台原生依赖,因此每一种正式安装包都在对应操作系统与架构上构建。 +Harness 包含目标平台原生依赖,因此每一种正式安装包都在对应操作系统与架构上构建。在 Linux 上,可以使用 `npm run package:linux:x64` 从源码构建未签名的社区版 AppImage;应用内更新仍仅限 macOS/Windows,Linux 用户改为从同步的上游代码重新构建。 ## 开发与架构 diff --git a/build/app-icon-512.png b/build/app-icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..777224e7c3d6b80a912238668808ff31ca2fdbba GIT binary patch literal 44770 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 aiNV3HiTLPFYT$vu&t(&f3uVS`cmE$d7j-`X literal 0 HcmV?d00001 diff --git a/docs/development.md b/docs/development.md index 82581af6..2cfa489f 100644 --- a/docs/development.md +++ b/docs/development.md @@ -87,10 +87,31 @@ npm run package:mac:x64 # Windows x64 NSIS installer, on a Windows x64 machine or runner npm run package:win + +# Linux x64 AppImage, on a Linux x64 machine or runner +npm run package:linux:x64 ``` Do not invoke `electron-builder --win` from macOS or Linux for a distributable Windows package. The target verification scripts intentionally reject host/target mismatches. +On Linux, the practical port surface is small: the Harness host already runs from +the bundled Node.js on every non-macOS platform (see `build/harness-node-entry.mjs`), +the POSIX pnpm shim and hostname-profile paths have live Linux branches, and the +mobile tunnel asset map covers linux-x64/linux-arm64. Packaging needs a 512×512 +8-bit RGBA PNG (`build/app-icon-512.png`, committed); regenerating icns/ico with +`scripts/generate-app-icons.mjs` stays macOS-only (`sips`/`iconutil`). AppImage +window association under Wayland uses the packaged `desktopName`. + +Under npm ≥ 12 (Node ≥ 24 toolchains), dependency lifecycle scripts are blocked +until reviewed. This repository carries the reviewed `allowScripts` approvals in +`package.json`; the vendored `@deepseek-ai/dsh-subprocess-local` helper script +(only chmods a macOS-only spawn helper) cannot take a portable `allowScripts` +key because npm keys `file:` dependencies by their resolved absolute URL, so its +script is skipped — harmless on Linux/Windows alike. Lockfile `resolved` URLs +may point at `registry.npmmirror.com`; run installs with +`npm_config_allow_remote=all` (or a project `.npmrc`) instead of rewriting the +lockfile, and prefer reusing the registry that upstream pinned. + For local unsigned development packages, use the corresponding `package:dev:*` command. Before handing off a Windows installer, verify that `resources/app/node_modules/node/bin/node.exe` exists in `win-unpacked` and require the packaged Windows Harness smoke test to pass. Formal release artifacts are built, signed, and published by the tag workflow. A local build or pull-request check is not formal release evidence. diff --git a/package.json b/package.json index a00c30d1..8f091c64 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,9 @@ "package:mac": "npm run build && electron-builder --mac --publish never", "package:mac:arm64": "node scripts/verify-target.mjs darwin arm64 && npm run build && electron-builder --mac --arm64 --publish never", "package:mac:x64": "node scripts/verify-target.mjs darwin x64 && npm run build && electron-builder --mac --x64 --publish never", - "package:win": "node scripts/verify-target.mjs win32 x64 && npm run build && electron-builder --win --x64 --publish never" + "package:win": "node scripts/verify-target.mjs win32 x64 && npm run build && electron-builder --win --x64 --publish never", + "package:dev:linux:x64": "node scripts/verify-target.mjs linux x64 && npm run build && electron-builder --linux --x64 --publish never --config electron-builder.dev.cjs", + "package:linux:x64": "node scripts/verify-target.mjs linux x64 && npm run build && electron-builder --linux --x64 --publish never" }, "dependencies": { "@deepseek-ai/cordis": "file:packages/harness-0.1.2-rc.1/npm-vendor/deepseek-ai-cordis-4.0.2.tgz", @@ -409,6 +411,20 @@ "allowToChangeInstallationDirectory": true, "createDesktopShortcut": true, "createStartMenuShortcut": true + }, + "linux": { + "icon": "build/app-icon-512.png", + "category": "Development", + "desktopName": "dsh-desktop.desktop", + "syncDesktopName": true, + "target": [ + { + "target": "AppImage", + "arch": [ + "x64" + ] + } + ] } }, "allowScripts": { From 69514718e87864d50259f5be4eb4f3351e291f41 Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 19:16:39 -1000 Subject: [PATCH 03/10] fix(test): type upgrade sockets as Duplex for @types/node 24 The test only calls destroy(); @types/node 24 no longer has stream Duplex extend net.Socket, so a Duplex (instead of net.Socket) is the correct type. --- test/lan-mobile-bridge.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lan-mobile-bridge.test.ts b/test/lan-mobile-bridge.test.ts index 0ff951a5..c902875c 100644 --- a/test/lan-mobile-bridge.test.ts +++ b/test/lan-mobile-bridge.test.ts @@ -667,7 +667,7 @@ describe('LAN mobile bridge pairing surface', () => { // cleanup() has removed the real listener. Without a permanent sink that // self-inflicted error becomes an unhandled 'error' event and takes the // whole process down (consumeMux documents the identical crash path). - const upgraded: import('node:net').Socket[] = [] + const upgraded: import('node:stream').Duplex[] = [] const harness = createServer((_request, response) => { response.statusCode = 404 response.end() From 8a43bd69478b2789afbe5d123048f396ce1605c5 Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 19:19:19 -1000 Subject: [PATCH 04/10] ci(linux): AppImage build workflow and weekly upstream sync check linux-build.yml runs the port's validation + packaging on every push to the linux branch and uploads the AppImage artifact. upstream-sync.yml rebases the port commits onto dataelement/dsh-desktop:main weekly (or manually), reruns the full suite and packaging after the rebase, then pushes and refreshes a PR in this fork. A failing rebase or run is the signal that new upstream platform work needs porting attention. --- .github/workflows/linux-build.yml | 49 +++++++++++++++++++ .github/workflows/upstream-sync.yml | 74 +++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 .github/workflows/linux-build.yml create mode 100644 .github/workflows/upstream-sync.yml diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml new file mode 100644 index 00000000..ed83a60b --- /dev/null +++ b/.github/workflows/linux-build.yml @@ -0,0 +1,49 @@ +name: Linux x64 build + +on: + push: + branches: + - linux + pull_request: + branches: + - linux + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: linux-build-${{ github.ref }} + cancel-in-progress: true + +jobs: + linux-x64: + name: Linux x64 AppImage + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - name: Install dependencies + env: + # Upstream lockfile resolved URLs may point at registry.npmmirror.com, + # which newer npm treats as a remote fetch. + npm_config_allow_remote: all + run: npm ci + - run: npm test + - run: npm run typecheck + - name: Package Linux x64 AppImage + run: npm run package:linux:x64 + - name: Verify packaged Harness resources + run: | + test -x dist/linux-unpacked/resources/app/node_modules/node/bin/node + node dist/linux-unpacked/resources/app/node_modules/node/bin/node -p 'process.versions.node' + - uses: actions/upload-artifact@v4 + with: + name: dsh-desktop-linux-x64 + path: dist/dsh-desktop-linux-x86_64.AppImage + if-no-files-found: error diff --git a/.github/workflows/upstream-sync.yml b/.github/workflows/upstream-sync.yml new file mode 100644 index 00000000..e4a9d049 --- /dev/null +++ b/.github/workflows/upstream-sync.yml @@ -0,0 +1,74 @@ +name: Upstream sync check + +# Keeps the `linux` branch tracking dataelement/dsh-desktop:main. When upstream +# moves, the port commits are rebased and re-validated; a clean result is pushed +# and surfaced as a PR in this fork. A failing rebase or test run is the signal +# that new upstream platform work needs porting attention. + +on: + schedule: + - cron: "23 4 * * 2" # Tue 04:23 UTC, weekly + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: upstream-sync + cancel-in-progress: false + +jobs: + sync: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: linux + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve upstream state + id: upstream + run: | + git remote add upstream https://github.com/dataelement/dsh-desktop.git + git fetch upstream main + echo "sha=$(git rev-parse upstream/main)" >> "$GITHUB_OUTPUT" + echo "behind=$(git rev-list --count HEAD..upstream/main)" >> "$GITHUB_OUTPUT" + + - name: Rebase port commits onto upstream/main + if: steps.upstream.outputs.behind != '0' + run: | + git config user.name "upstream-sync[bot]" + git config user.email "upstream-sync[bot]@users.noreply.github.com" + git rebase upstream/main + + - uses: actions/setup-node@v4 + if: steps.upstream.outputs.behind != '0' + with: + node-version: 22 + cache: npm + + - name: Validate after rebase + if: steps.upstream.outputs.behind != '0' + env: + npm_config_allow_remote: all + run: | + npm ci + npm test + npm run typecheck + npm run package:linux:x64 + + - name: Push rebased branch + if: steps.upstream.outputs.behind != '0' + run: git push --force-with-lease origin linux + + - name: Open or refresh the sync PR + if: steps.upstream.outputs.behind != '0' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr list --head linux --base main --state open --json number | grep -q . \ + || gh pr create --head linux --base main \ + --title "upstream sync: rebase linux onto dataelement/dsh-desktop" \ + --body "Automated rebase pass (upstream/main at ${{ steps.upstream.outputs.sha }}). The linux-x64 build workflow carries the artifact." From 890f1d5296970b59f7417ecbe6a2dfc3b46904a4 Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 19:22:52 -1000 Subject: [PATCH 05/10] fix(linux): desktopName belongs to metadata, not the linux config electron-builder 26 validates desktopName as a top-level metadata field; linux.syncDesktopName derives the .desktop filename from it so the StartupWMClass matches on Wayland compositors. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f091c64..981c441a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "dsh-desktop", "version": "0.1.1", + "desktopName": "dsh-desktop.desktop", "description": "A cross-platform desktop shell for DeepSeek Harness.", "private": true, "type": "module", @@ -415,7 +416,6 @@ "linux": { "icon": "build/app-icon-512.png", "category": "Development", - "desktopName": "dsh-desktop.desktop", "syncDesktopName": true, "target": [ { From bd92a710161b679018cbd3786f4b6710f7c24b73 Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 19:57:30 -1000 Subject: [PATCH 06/10] perf(runtime): give Linux the Windows-grade 120s Harness startup window Live first-launch evidence on a packaged Linux AppImage: cold-start readiness blew the 45s non-Windows default and the desktop killed the healthy-but-slow Harness process. Windows got 120s for the same class of slow cold start; Linux now matches (macOS keeps 45s). --- src/main/runtime/harness-runtime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/runtime/harness-runtime.ts b/src/main/runtime/harness-runtime.ts index 0ea3ca15..60c54d3b 100644 --- a/src/main/runtime/harness-runtime.ts +++ b/src/main/runtime/harness-runtime.ts @@ -383,7 +383,7 @@ export class HarnessRuntime { profile ) const startupTimeoutMs = - this.options.startupTimeoutMs ?? (process.platform === 'win32' ? 120_000 : 45_000) + this.options.startupTimeoutMs ?? (process.platform === 'win32' || process.platform === 'linux' ? 120_000 : 45_000) this.writeLog(`\n[desktop] starting ${new Date().toISOString()}`) this.writeLog(`[desktop] launch directory ${launchDirectory}`) From 0ae652add293c658ea8598d2565a4561580d8053 Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 19:57:30 -1000 Subject: [PATCH 07/10] feat(linux): hide the in-window application menubar like the Windows build Windows hides the menu (setMenuBarVisibility(false), autoHideMenuBar); macOS uses the system menubar. Linux shipped a visible in-window menubar because the hide was gated on isWindows. Auto-hide + hide now apply to every non-darwin platform, so the app keeps its product chrome on Linux while accelerators (and the Alt-revealed menu) stay available. Pinned strings in test/branding-patch.test.ts and test/windows-titlebar.test.ts stay verbatim. --- src/main/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/index.ts b/src/main/index.ts index d4ea8115..299df381 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -931,7 +931,8 @@ function createWindow(): BrowserWindow { if (process.platform === 'darwin') { window.setWindowButtonVisibility(true) window.setWindowButtonPosition({ x: 12, y: 9 }) - } else if (isWindows) { + } else { + window.setAutoHideMenuBar(true) window.setMenuBarVisibility(false) } window.on('close', (event) => { From 93dc5df21baee27b6d55721564153009b4319455 Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 20:51:22 -1000 Subject: [PATCH 08/10] ci(linux): keep a rolling pre-release with a shareable AppImage asset Every push to the linux branch refreshes the linux-rolling pre-release with dsh-desktop-linux-x64.AppImage, so the fork always offers a fresh download without manual release work after upstream syncs. --- .github/workflows/linux-build.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index ed83a60b..caec88d6 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: permissions: - contents: read + contents: write concurrency: group: linux-build-${{ github.ref }} @@ -47,3 +47,15 @@ jobs: name: dsh-desktop-linux-x64 path: dist/dsh-desktop-linux-x86_64.AppImage if-no-files-found: error + - name: Name the release asset for sharing + if: github.event_name == 'push' + run: cp dist/dsh-desktop-linux-x86_64.AppImage dist/dsh-desktop-linux-x64.AppImage + - name: Refresh the rolling pre-release asset + if: github.event_name == 'push' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create linux-rolling --repo "$GITHUB_REPOSITORY" --prerelease \ + --target linux --title "DSH Desktop Linux x64 (rolling)" \ + --notes "Auto-refreshed on every push to the linux branch. Unsigned community build; needs FUSE (or run with --appimage-extract-and-run). Updates arrive whenever the linux branch is synced upstream." 2>/dev/null || true + gh release upload linux-rolling --repo "$GITHUB_REPOSITORY" --clobber dist/dsh-desktop-linux-x64.AppImage From ce00db1d64b624d7b1cb274259ec48131fa038a3 Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 22:06:56 -1000 Subject: [PATCH 09/10] feat(linux): scroll containment for the sidebar region and settings panel When the window (or viewport) is short, the sidebar's region area clipped its content and pushed the settings cluster out of view, and the settings dialog could extend past the visual viewport on Wayland. The region column now carries the tracked data-dsh-sidebar-region attribute and scrolls (overscroll contained), the settings panel height gains the 100dvh fallback alongside 100vh for dynamic-viewport correctness on Wayland, and the settings nav column scrolls instead of clipping. Patches regenerated from pristine vendored tarballs; verified by git-apply round-trip. --- ...lient-ui-settings-general+0.1.2-rc.1.patch | 13 +++++++++++++ ...-ai+dsh-client-ui-sidebar+0.1.2-rc.1.patch | 19 ++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 patches/@deepseek-ai+dsh-client-ui-settings-general+0.1.2-rc.1.patch diff --git a/patches/@deepseek-ai+dsh-client-ui-settings-general+0.1.2-rc.1.patch b/patches/@deepseek-ai+dsh-client-ui-settings-general+0.1.2-rc.1.patch new file mode 100644 index 00000000..b8bff359 --- /dev/null +++ b/patches/@deepseek-ai+dsh-client-ui-settings-general+0.1.2-rc.1.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js b/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js +index 0457ae0..dee6238 100644 +--- a/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js ++++ b/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js +@@ -25,7 +25,7 @@ + } + //#endregion + //#region \0dsh-css:/private/tmp/dsh-harness-v0.1.2-rc.1/packages/client/ui-settings-general/src/client/SettingsRoot.module.css.mjs +- const css$3 = ".tqoa8q_triggerRow{flex:none;align-items:center;gap:8px;width:calc(100% + 4px);margin:4px -2px;display:flex}.tqoa8q_triggerRow.tqoa8q_railRow{width:36px;margin:8px 0 10px}.tqoa8q_trigger{box-sizing:border-box;cursor:pointer;width:auto;min-width:0;height:42px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:12px;flex:1;align-items:center;gap:8px;margin:0;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.tqoa8q_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.tqoa8q_trigger.tqoa8q_rail{corner-shape:round;border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.tqoa8q_triggerLabel{white-space:nowrap;overflow:hidden}.tqoa8q_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.tqoa8q_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.tqoa8q_panel{z-index:1;background:var(--dsw-alias-bg-layer-2);width:800px;max-width:calc(100vw - 48px);height:min(800px,100vh - 48px);box-shadow:var(--dsw-elevation-prominent);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:32px;display:flex;position:relative;overflow:hidden}.tqoa8q_nav{box-sizing:border-box;flex-direction:column;flex:none;gap:18px;width:188px;padding:22px 12px 0;display:flex}.tqoa8q_navTitle{color:var(--dsw-alias-label-primary);padding:0 12px;font-size:16px;font-weight:500;line-height:24px}.tqoa8q_navList{flex-direction:column;gap:4px;display:flex}.tqoa8q_navCell{box-sizing:border-box;cursor:pointer;height:40px;color:var(--dsw-alias-label-primary);text-align:left;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;padding:9px 16px 9px 12px;font-family:inherit;font-size:14px;font-weight:400;line-height:22px;display:flex}.tqoa8q_navCell:hover{background:var(--dsw-specific-sidebar-nav-item-hover)}.tqoa8q_navCell.tqoa8q_active{background:var(--dsw-specific-sidebar-nav-item-active)}.tqoa8q_navIcon{flex:none}.tqoa8q_navLabel{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.tqoa8q_content{flex-direction:column;flex:1;min-width:0;display:flex}.tqoa8q_header{box-sizing:border-box;flex:none;justify-content:space-between;align-items:flex-start;gap:8px;height:54px;padding:20px 14px 8px 10px;display:flex}.tqoa8q_actions{justify-content:flex-end;align-items:center;gap:8px;min-width:0;margin-left:auto;display:flex}.tqoa8q_close{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.tqoa8q_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.tqoa8q_options{flex:1;min-height:0;padding:0 24px 24px;overflow-y:auto}.tqoa8q_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}"; ++ const css$3 = ".tqoa8q_triggerRow{flex:none;align-items:center;gap:8px;width:calc(100% + 4px);margin:4px -2px;display:flex}.tqoa8q_triggerRow.tqoa8q_railRow{width:36px;margin:8px 0 10px}.tqoa8q_trigger{box-sizing:border-box;cursor:pointer;width:auto;min-width:0;height:42px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:12px;flex:1;align-items:center;gap:8px;margin:0;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.tqoa8q_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.tqoa8q_trigger.tqoa8q_rail{corner-shape:round;border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.tqoa8q_triggerLabel{white-space:nowrap;overflow:hidden}.tqoa8q_overlay{z-index:1000;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.tqoa8q_mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}.tqoa8q_panel{z-index:1;background:var(--dsw-alias-bg-layer-2);width:800px;max-width:calc(100vw - 48px);height:min(800px,100vh - 48px);height:min(800px,100dvh - 48px);box-shadow:var(--dsw-elevation-prominent);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:32px;display:flex;position:relative;overflow:hidden}.tqoa8q_nav{box-sizing:border-box;flex-direction:column;flex:none;gap:18px;width:188px;min-height:0;padding:22px 12px 0;display:flex;overflow-y:auto;overscroll-behavior:contain}.tqoa8q_navTitle{color:var(--dsw-alias-label-primary);padding:0 12px;font-size:16px;font-weight:500;line-height:24px}.tqoa8q_navList{flex-direction:column;gap:4px;display:flex}.tqoa8q_navCell{box-sizing:border-box;cursor:pointer;height:40px;color:var(--dsw-alias-label-primary);text-align:left;background:0 0;border:none;border-radius:12px;align-items:center;gap:8px;padding:9px 16px 9px 12px;font-family:inherit;font-size:14px;font-weight:400;line-height:22px;display:flex}.tqoa8q_navCell:hover{background:var(--dsw-specific-sidebar-nav-item-hover)}.tqoa8q_navCell.tqoa8q_active{background:var(--dsw-specific-sidebar-nav-item-active)}.tqoa8q_navIcon{flex:none}.tqoa8q_navLabel{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.tqoa8q_content{flex-direction:column;flex:1;min-width:0;display:flex}.tqoa8q_header{box-sizing:border-box;flex:none;justify-content:space-between;align-items:flex-start;gap:8px;height:54px;padding:20px 14px 8px 10px;display:flex}.tqoa8q_actions{justify-content:flex-end;align-items:center;gap:8px;min-width:0;margin-left:auto;display:flex}.tqoa8q_close{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:0;display:inline-flex}.tqoa8q_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.tqoa8q_options{flex:1;min-height:0;padding:0 24px 24px;overflow-y:auto}.tqoa8q_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}"; + const tagId$3 = "@deepseek-ai/dsh-client-ui-settings-general/SettingsRoot.module.css"; + if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) { + const tag = document.createElement("style"); diff --git a/patches/@deepseek-ai+dsh-client-ui-sidebar+0.1.2-rc.1.patch b/patches/@deepseek-ai+dsh-client-ui-sidebar+0.1.2-rc.1.patch index 019b747c..11bb41d0 100644 --- a/patches/@deepseek-ai+dsh-client-ui-sidebar+0.1.2-rc.1.patch +++ b/patches/@deepseek-ai+dsh-client-ui-sidebar+0.1.2-rc.1.patch @@ -1,17 +1,17 @@ diff --git a/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js b/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js -index 696591c..90c1520 100644 +index 557185e..3206076 100644 --- a/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js +++ b/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js -@@ -29,7 +29,7 @@ window.__ModuleLoader__.load({ +@@ -29,7 +29,7 @@ const tag = document.createElement("style"); tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-sidebar"; tag.dataset.pluginCss = tagId; - tag.textContent = css; -+ tag.textContent = css + "[data-dsh-sidebar-root][data-dsh-sidebar-wide=\"true\"]{padding-top:32px}" + (navigator.userAgent.includes("Macintosh") ? "[data-dsh-sidebar-root][data-dsh-sidebar-wide=\"false\"]{padding:46px 22px 6px}" : ""); ++ tag.textContent = css + "[data-dsh-sidebar-root][data-dsh-sidebar-wide=\"true\"]{padding-top:32px}" + "[data-dsh-sidebar-region]{overflow-y:auto;overscroll-behavior:contain}" + (navigator.userAgent.includes("Macintosh") ? "[data-dsh-sidebar-root][data-dsh-sidebar-wide=\"false\"]{padding:46px 22px 6px}" : ""); document.head.appendChild(tag); } var SidebarRoot_module_css_default = { -@@ -150,6 +150,8 @@ window.__ModuleLoader__.load({ +@@ -150,6 +150,8 @@ const buildVersion = localBuildVersion(); return (0, react_jsx_runtime.jsxs)("div", { ref: column, @@ -20,7 +20,16 @@ index 696591c..90c1520 100644 className: clsx(SidebarRoot_module_css_default.root, !wide && SidebarRoot_module_css_default.collapsed, !wide && everWide.current && SidebarRoot_module_css_default.railIn, collapsed && wide && SidebarRoot_module_css_default.fading, !pointerInside && SidebarRoot_module_css_default.quietBars), style: wide ? { width: collapsed ? lastWideWidth.current : width } : void 0, onPointerEnter: () => { -@@ -246,5 +248,6 @@ window.__ModuleLoader__.load({ +@@ -231,6 +233,7 @@ + }) + }), + (0, react_jsx_runtime.jsx)("div", { ++ "data-dsh-sidebar-region": "", + className: SidebarRoot_module_css_default.regionArea, + children: renderSlot("sidebar.workspaces", { + wide, +@@ -245,6 +248,7 @@ + className: SidebarRoot_module_css_default.footerActions, children: renderSlot("sidebar.footer.action", { wide }) }), (0, react_jsx_runtime.jsx)("div", { + "data-dsh-sidebar-settings": "", From 530fe7f901e7face283d85ef961cc55575f0eb98 Mon Sep 17 00:00:00 2001 From: Jonathan Gutierrez Date: Sat, 5 Sep 2026 22:30:13 -1000 Subject: [PATCH 10/10] ci(linux): probe the bundled node binary directly in the verify step The verify step passed the packaged node executable to the system node interpreter as a script file, which fails parsing the ELF. Invoke the packaged binary itself. --- .github/workflows/linux-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index caec88d6..f6080fdd 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -41,7 +41,7 @@ jobs: - name: Verify packaged Harness resources run: | test -x dist/linux-unpacked/resources/app/node_modules/node/bin/node - node dist/linux-unpacked/resources/app/node_modules/node/bin/node -p 'process.versions.node' + dist/linux-unpacked/resources/app/node_modules/node/bin/node -p 'process.versions.node' - uses: actions/upload-artifact@v4 with: name: dsh-desktop-linux-x64