Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ jobs:
ls -la build/electron/preload.cjs || exit 1
ls -la build/index.html || exit 1

- name: Rebuild and verify Electron native modules
run: yarn electron:prepare-package

- name: Package Electron app
timeout-minutes: 30
run: |
Expand Down Expand Up @@ -274,6 +277,9 @@ jobs:
ls -la build/electron/preload.cjs || exit 1
ls -la build/index.html || exit 1

- name: Rebuild and verify Electron native modules
run: yarn electron:prepare-package

- name: Package Electron app
timeout-minutes: 30
run: |
Expand Down Expand Up @@ -336,6 +342,9 @@ jobs:
ls -la build/electron/preload.cjs || exit 1
ls -la build/index.html || exit 1

- name: Rebuild and verify Electron native modules
run: yarn electron:prepare-package

- name: Package Electron app
timeout-minutes: 30
run: |
Expand Down Expand Up @@ -399,6 +408,10 @@ jobs:
ls -la build/electron/preload.cjs || exit 1
ls -la build/index.html || exit 1

- name: Rebuild and verify Electron native modules
shell: bash
run: yarn electron:prepare-package

- name: Package Electron app
shell: bash
timeout-minutes: 30
Expand Down
10 changes: 10 additions & 0 deletions docs/agent-playbooks/known-surprises.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,13 @@ If uncertain, ask the developer before adding an entry.
- **Impact:** Agents may “fix” the unlisted import by adding `@plebbit/plebbit-js` to `package.json`, which violates project policy.
- **Mitigation:** Do not add `@plebbit/plebbit-js` to `package.json` for this repo. If `knip` flags `electron/start-plebbit-rpc.js`, handle it with a targeted `ignoreIssues` entry instead.
- **Status:** confirmed

### Electron packaging can ship a broken `better-sqlite3` binary

- **Date:** 2026-03-17
- **Observed by:** Codex
- **Context:** Investigating the `v0.7.1` macOS arm64 DMG after the app showed a live IPFS node but never loaded boards or comments.
- **What was surprising:** The packaged app can start IPFS successfully while `electron/start-plebbit-rpc.js` loops forever because `/Applications/5chan.app/.../better_sqlite3.node` was built for plain Node 22 (`NODE_MODULE_VERSION 127`) instead of Electron 36 (`NODE_MODULE_VERSION 135`).
- **Impact:** The local RPC server on `ws://localhost:9138` never starts, so the desktop app cannot load boards, posts, or comments even though node stats look healthy.
- **Mitigation:** Before any Electron package/build job, run `yarn electron:prepare-package` so `better-sqlite3` is rebuilt for Electron and immediately verified via `ELECTRON_RUN_AS_NODE=1 electron`.
- **Status:** confirmed
21 changes: 12 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,18 @@
"electron:no-delete-data": "yarn build:preload && electron .",
"electron:start": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://5chan.localhost:1355 && yarn electron\"",
"electron:start:no-delete-data": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://5chan.localhost:1355 && yarn electron:no-delete-data\"",
"electron:package": "yarn build && yarn build:preload && electron-forge package",
"electron:build": "yarn build && yarn build:preload && electron-forge make",
"electron:build:linux": "yarn build && yarn build:preload && electron-forge make --platform=linux",
"electron:build:mac": "yarn build && yarn build:preload && electron-forge make --platform=darwin",
"electron:build:windows": "yarn build && yarn build:preload && electron-forge make --platform=win32",
"electron:build:linux:x64": "yarn build && yarn build:preload && electron-forge make --platform=linux --arch=x64",
"electron:build:linux:arm64": "yarn build && yarn build:preload && electron-forge make --platform=linux --arch=arm64",
"electron:build:mac:x64": "yarn build && yarn build:preload && electron-forge make --platform=darwin --arch=x64",
"electron:build:mac:arm64": "yarn build && yarn build:preload && electron-forge make --platform=darwin --arch=arm64",
"electron:rebuild-native": "electron-rebuild -f -w better-sqlite3",
"electron:verify-native": "node scripts/verify-electron-native-modules.js",
"electron:prepare-package": "yarn electron:rebuild-native && yarn electron:verify-native",
"electron:package": "yarn build && yarn build:preload && yarn electron:prepare-package && electron-forge package",
"electron:build": "yarn build && yarn build:preload && yarn electron:prepare-package && electron-forge make",
"electron:build:linux": "yarn build && yarn build:preload && yarn electron:prepare-package && electron-forge make --platform=linux",
"electron:build:mac": "yarn build && yarn build:preload && yarn electron:prepare-package && electron-forge make --platform=darwin",
"electron:build:windows": "yarn build && yarn build:preload && yarn electron:prepare-package && electron-forge make --platform=win32",
"electron:build:linux:x64": "yarn build && yarn build:preload && yarn electron:prepare-package && electron-forge make --platform=linux --arch=x64",
"electron:build:linux:arm64": "yarn build && yarn build:preload && yarn electron:prepare-package && electron-forge make --platform=linux --arch=arm64",
"electron:build:mac:x64": "yarn build && yarn build:preload && yarn electron:prepare-package && electron-forge make --platform=darwin --arch=x64",
"electron:build:mac:arm64": "yarn build && yarn build:preload && yarn electron:prepare-package && electron-forge make --platform=darwin --arch=arm64",
"electron:before": "yarn electron:before:delete-data",
"electron:before:delete-data": "node -e \"fs.rmSync('.plebbit', { recursive: true, force: true })\"",
"android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/plebbit-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'",
Expand Down
44 changes: 44 additions & 0 deletions scripts/verify-electron-native-modules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { spawnSync } from 'node:child_process';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const electronBinary = require('electron');

if (typeof electronBinary !== 'string' || electronBinary.length === 0) {
throw new Error('Failed to resolve the Electron executable for native-module verification');
}

const nativeModules = ['better-sqlite3'];
const verificationScript = `
console.log('electron modules', process.versions.modules);
for (const moduleName of ${JSON.stringify(nativeModules)}) {
try {
require(moduleName);
console.log('native-ok', moduleName);
} catch (error) {
console.error('native-fail', moduleName);
console.error(error && error.stack ? error.stack : String(error));
process.exit(1);
}
}
`;

const result = spawnSync(electronBinary, ['-e', verificationScript], {
encoding: 'utf8',
env: {
...process.env,
ELECTRON_RUN_AS_NODE: '1',
},
});

if (result.stdout) {
process.stdout.write(result.stdout);
}

if (result.stderr) {
process.stderr.write(result.stderr);
}

if (result.status !== 0) {
throw new Error(`Electron native-module verification failed with exit code ${result.status ?? 'unknown'}`);
}
13 changes: 11 additions & 2 deletions src/lib/utils/__tests__/misc-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { copyToClipboard } from '../clipboard-utils';
import { hashStringToColor, getTextColorForBackground, removeMarkdown } from '../post-utils';
import { preloadReplyModal, preloadThemeAssets } from '../preload-utils';
import { preloadReplyModal, preloadThemeAssets, resolveAssetUrl } from '../preload-utils';
import { computeOmittedCount, filterRepliesForDisplay, getPreviewDisplayReplies, getTotalReplyCount } from '../replies-preview-utils';
import { getQuotedCidsFromContent, mergeQuotedCids } from '../reply-quote-utils';
import { formatUserIDForDisplay, truncateWithEllipsisInMiddle } from '../string-utils';
Expand Down Expand Up @@ -116,7 +116,16 @@ describe('misc utils', () => {

preloadThemeAssets();

expect(loadedSources).toEqual(['/buttons/default.png', '/buttons/hover.png', '/backgrounds/wallpaper.png']);
expect(loadedSources).toEqual([
`${import.meta.env.BASE_URL}buttons/default.png`,
`${import.meta.env.BASE_URL}buttons/hover.png`,
`${import.meta.env.BASE_URL}backgrounds/wallpaper.png`,
]);
});

it('resolves theme asset URLs against a non-root base URL', () => {
expect(resolveAssetUrl('buttons/default.png', 'file:///app/')).toBe('file:///app/buttons/default.png');
expect(resolveAssetUrl('backgrounds/wallpaper.png', 'file:///app/')).toBe('file:///app/backgrounds/wallpaper.png');
});

it('schedules reply modal preload with requestIdleCallback when available', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/lib/utils/preload-utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { THEME_BUTTON_IMAGES, THEME_BACKGROUND_IMAGES } from '../../generated/asset-manifest';

export const resolveAssetUrl = (path: string, baseUrl = import.meta.env.BASE_URL): string => `${baseUrl}${path}`;

/**
* Preloads an array of image URLs by creating Image objects.
* Images are loaded in the background and cached by the browser.
*/
const preloadImages = (imagePaths: readonly string[]): void => {
imagePaths.forEach((path) => {
const img = new Image();
img.src = `/${path}`;
img.src = resolveAssetUrl(path);
});
};

Expand Down
Loading