From cc30080ba84eb8f5bb67afcca0568acf4990f7fc Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Tue, 24 Mar 2026 04:33:23 +0100 Subject: [PATCH] =?UTF-8?q?chore:=20sync=20repo=20with=20deployed=20state?= =?UTF-8?q?=20=E2=80=94=20clean=20up=20old=20specs/plans,=20desktop=20twea?= =?UTF-8?q?ks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 30 +++++++--- packages/desktop/electron-builder.yml | 12 +++- packages/desktop/package.json | 1 + packages/desktop/scripts/afterPack.js | 58 +++++++++++++++++++ .../web/src/components/chat/MessageList.tsx | 22 +++++-- .../web/src/components/ui/UpdateToast.tsx | 4 +- 6 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 packages/desktop/scripts/afterPack.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6bb23d3f..5346f5b2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,14 +11,18 @@ permissions: jobs: build: strategy: + fail-fast: false matrix: include: - - os: windows-latest - platform: windows - - os: ubuntu-latest - platform: linux + # macOS — universal build (arm64 + x64) on Apple Silicon runner - os: macos-latest - platform: mac + args: --mac --arm64 --x64 + # Windows — x64 + arm64 on x64 runner + - os: windows-latest + args: --win --x64 --arm64 + # Linux — x64 on x64 runner + - os: ubuntu-latest + args: --linux --x64 runs-on: ${{ matrix.os }} @@ -26,10 +30,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Install Linux build dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libx11-dev libxtst-dev libxt-dev libxkbcommon-dev + - name: Setup pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 10 - name: Setup Node.js uses: actions/setup-node@v4 @@ -43,8 +53,12 @@ jobs: - name: Build shared package run: pnpm --filter @backspace/shared build - - name: Build desktop app + - name: Compile desktop TypeScript working-directory: packages/desktop - run: pnpm run build + run: pnpm exec tsc + + - name: Build and publish desktop app + working-directory: packages/desktop + run: pnpm exec electron-builder ${{ matrix.args }} --publish always env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/packages/desktop/electron-builder.yml b/packages/desktop/electron-builder.yml index df66a1eb..67ede8d8 100644 --- a/packages/desktop/electron-builder.yml +++ b/packages/desktop/electron-builder.yml @@ -6,8 +6,18 @@ directories: files: - dist/**/* - resources/**/* + # Exclude host-compiled native module artifacts from the asar. + # electron-rebuild (postinstall) compiles uiohook-napi for the build machine, + # creating build/Release/*.node. node-gyp-build checks build/ BEFORE prebuilds/, + # so if these directories enter the asar, the app crashes on every platform + # (including the build platform after afterPack removes the unpacked file). + - "!**/node_modules/uiohook-napi/build/**" + - "!**/node_modules/uiohook-napi/build.bak/**" + - "!**/node_modules/uiohook-napi/bin/**" asarUnpack: - "**/*.node" +npmRebuild: false +afterPack: ./scripts/afterPack.js publish: - provider: github owner: TheZwiss @@ -25,7 +35,7 @@ mac: - dmg - zip win: - icon: ./build/icon.ico + icon: ./build/icon.png target: - nsis linux: diff --git a/packages/desktop/package.json b/packages/desktop/package.json index b5e0f150..72045f9c 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -14,6 +14,7 @@ "dev": "mkdir -p build && cp ../../icon.png build/icon.png && bash scripts/gen-icns.sh && cp build/icon.icns $(find ../../node_modules -path '*/electron/dist/Electron.app/Contents/Resources/electron.icns' 2>/dev/null | head -1) 2>/dev/null; tsc && electron .", "prebuild": "mkdir -p build && cp ../../icon.png build/icon.png", "build": "tsc && electron-builder", + "build:all": "tsc && electron-builder --mac --win --linux --arm64 --x64", "clean": "rm -rf dist dist-electron", "postinstall": "electron-rebuild -f -w uiohook-napi" }, diff --git a/packages/desktop/scripts/afterPack.js b/packages/desktop/scripts/afterPack.js new file mode 100644 index 00000000..bd0820ee --- /dev/null +++ b/packages/desktop/scripts/afterPack.js @@ -0,0 +1,58 @@ +// afterPack hook for electron-builder +// Removes host-compiled native module artifacts so cross-platform builds +// use the correct prebuilt binaries from the `prebuilds/` directory. +// +// Why this is needed: +// `electron-rebuild` (postinstall) compiles uiohook-napi for the BUILD +// machine (e.g. macOS arm64), placing the binary in `build/Release/`. +// `node-gyp-build` checks `build/Release/` BEFORE `prebuilds/{platform}/`, +// so Windows/Linux packages would load the macOS binary and crash. +// +// What this does: +// 1. Removes `build/`, `build.bak/`, `bin/` dirs (host-compiled artifacts) +// 2. Strips prebuilts for platforms other than the target + +const fs = require('fs'); +const path = require('path'); + +exports.default = async function afterPack(context) { + const platform = context.electronPlatformName; // 'darwin', 'linux', 'win32' + const appDir = path.join( + context.appOutDir, + // macOS bundles resources inside the .app + platform === 'darwin' + ? `${context.packager.appInfo.productFilename}.app/Contents/Resources` + : 'resources' + ); + + const asarUnpacked = path.join(appDir, 'app.asar.unpacked'); + const uiohookDir = path.join(asarUnpacked, 'node_modules', 'uiohook-napi'); + + if (!fs.existsSync(uiohookDir)) { + console.log(`[afterPack] uiohook-napi not found in ${platform} build — skipping`); + return; + } + + // 1. Remove host-compiled artifacts that shadow prebuilts + for (const dir of ['build', 'build.bak', 'bin']) { + const target = path.join(uiohookDir, dir); + if (fs.existsSync(target)) { + fs.rmSync(target, { recursive: true, force: true }); + console.log(`[afterPack] Removed ${dir}/ from uiohook-napi (${platform})`); + } + } + + // 2. Strip prebuilts for other platforms (saves ~1-2MB per build) + const prebuildsDir = path.join(uiohookDir, 'prebuilds'); + if (fs.existsSync(prebuildsDir)) { + for (const entry of fs.readdirSync(prebuildsDir)) { + const entryPlatform = entry.split('-')[0]; // 'darwin', 'linux', 'win32' + if (entryPlatform !== platform) { + fs.rmSync(path.join(prebuildsDir, entry), { recursive: true, force: true }); + console.log(`[afterPack] Stripped prebuilds/${entry} (not needed for ${platform})`); + } + } + } + + console.log(`[afterPack] Native module cleanup done for ${platform}`); +}; diff --git a/packages/web/src/components/chat/MessageList.tsx b/packages/web/src/components/chat/MessageList.tsx index 9b05f750..fb3acff5 100644 --- a/packages/web/src/components/chat/MessageList.tsx +++ b/packages/web/src/components/chat/MessageList.tsx @@ -55,6 +55,8 @@ export function MessageList({ channelId, jumpToMessageId, onJumpComplete }: Mess const contentRef = useRef(null); const [isNearBottom, setIsNearBottom] = useState(true); const isNearBottomRef = useRef(true); + const [isAtBottom, setIsAtBottom] = useState(true); + const isAtBottomRef = useRef(true); const [isLoadingMore, setIsLoadingMore] = useState(false); const showInitialSkeleton = useDelayedLoading(isLoading && messages.length === 0); const showPaginationSkeleton = useDelayedLoading(isLoadingMore); @@ -110,11 +112,13 @@ export function MessageList({ channelId, jumpToMessageId, onJumpComplete }: Mess prevMessagesLength.current = 0; - // If we have a saved position for the incoming channel, don't mark as near-bottom + // If we have a saved position for the incoming channel, don't mark as near/at-bottom // — this prevents the ResizeObserver from snapping to bottom before the restore rAF fires const willRestore = useChatStore.getState().scrollPositions.has(channelId); setIsNearBottom(!willRestore); isNearBottomRef.current = !willRestore; + setIsAtBottom(!willRestore); + isAtBottomRef.current = !willRestore; }, [channelId, saveScrollPosition]); // Handle scrolling: initial load restores position or snaps to bottom, @@ -139,17 +143,20 @@ export function MessageList({ channelId, jumpToMessageId, onJumpComplete }: Mess const near = dist < 5000; setIsNearBottom(near); isNearBottomRef.current = near; + const atBot = dist < 150; + setIsAtBottom(atBot); + isAtBottomRef.current = atBot; return; } } // No saved anchor or message not in cache — snap to bottom container.scrollTop = container.scrollHeight; }); - } else if (messages.length > prev && isNearBottom) { - // New messages arrived while near bottom — smooth scroll + } else if (messages.length > prev && isAtBottom) { + // New messages arrived while at bottom — smooth scroll bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); } - }, [messages.length, isNearBottom, channelId]); + }, [messages.length, isAtBottom, channelId]); // Auto-scroll when content height grows (embeds/images loading) while near bottom const hasMessages = messages.length > 0; @@ -227,8 +234,13 @@ export function MessageList({ channelId, jumpToMessageId, onJumpComplete }: Mess const container = containerRef.current; if (!container) return; - // Check if near bottom + // Check scroll position relative to bottom const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight; + // "at bottom" = within 150px — used for auto-scrolling on new messages + const atBottom = distanceFromBottom < 150; + setIsAtBottom(atBottom); + isAtBottomRef.current = atBottom; + // "near bottom" = within 5000px — used for "Jump to Present" button visibility const nearBottom = distanceFromBottom < 5000; setIsNearBottom(nearBottom); isNearBottomRef.current = nearBottom; diff --git a/packages/web/src/components/ui/UpdateToast.tsx b/packages/web/src/components/ui/UpdateToast.tsx index e7fb04d8..91d0b0c8 100644 --- a/packages/web/src/components/ui/UpdateToast.tsx +++ b/packages/web/src/components/ui/UpdateToast.tsx @@ -68,9 +68,9 @@ export function UpdateToast() {
-

Update available

+

Update failed

- Auto-install failed — download manually + Auto-update failed — download manually