build(app): migrate to electron-vite (main/preload/renderer), keep electron-builder (Phase 5d-3a)

Restructure app into src/{main,preload,renderer}; electron.vite.config.ts builds
all three (each main module its own input so CJS requires + the indexer worker
resolve; better-sqlite3 externalized; Vue plugin for renderer). main/index.js
paths updated for the out/ layout + ELECTRON_RENDERER_URL. Still JS/CJS — TS+ESM
and core consumption are the next stages. Verified: npm run dev launches clean;
electron-vite build succeeds; root suite 119/119.
This commit is contained in:
tommy0103
2026-07-09 10:20:18 +08:00
parent e04c07de05
commit 80c125a572
69 changed files with 572 additions and 78 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ import { tmpdir } from 'node:os';
import { join } from 'node:path';
const require = createRequire(import.meta.url);
const { createIndexerService } = require('../app/indexer-service.js');
const { createIndexerService } = require('../app/src/main/indexer-service.js');
function manualTimers() {
const timers = new Set();
+1 -1
View File
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const { createWorkerBuildIndex } = require('../app/indexer-worker-client.js');
const { createWorkerBuildIndex } = require('../app/src/main/indexer-worker-client.js');
test('worker build client resolves build results from a worker thread', async () => {
const instances = [];
+1 -1
View File
@@ -6,7 +6,7 @@ import { tmpdir } from 'node:os';
import { join } from 'node:path';
const require = createRequire(import.meta.url);
const { buildIndex } = require('../app/indexer.js');
const { buildIndex } = require('../app/src/main/indexer.js');
const { DatabaseSync } = require('node:sqlite');
class TestDatabase {
+16 -16
View File
@@ -12,7 +12,7 @@ async function loadMainForWindowFlags(flags) {
const originalArgv = process.argv;
const originalHome = process.env.HOME;
const originalLoad = Module._load;
const mainPath = require.resolve('../app/main.js');
const mainPath = require.resolve('../app/src/main/index.js');
const home = join(tmpdir(), `obelisk-window-flags-${Date.now()}-${Math.random()}`);
mkdirSync(join(home, '.obelisk'), { recursive: true });
writeFileSync(join(home, '.obelisk', 'obelisk.sqlite'), '');
@@ -93,7 +93,7 @@ async function loadMainForWindowFlags(flags) {
};
try {
require('../app/main.js');
require('../app/src/main/index.js');
await new Promise(resolve => setImmediate(resolve));
return windows;
} finally {
@@ -137,7 +137,7 @@ test('main process watches Codex sessions directory instead of Codex root', asyn
process.env.HOME = home;
const serviceOptions = [];
const mainPath = require.resolve('../app/main.js');
const mainPath = require.resolve('../app/src/main/index.js');
delete require.cache[mainPath];
class FakeDatabase {
@@ -202,7 +202,7 @@ test('main process watches Codex sessions directory instead of Codex root', asyn
};
try {
require('../app/main.js');
require('../app/src/main/index.js');
await new Promise(resolve => setImmediate(resolve));
assert.equal(serviceOptions.length, 1);
@@ -229,7 +229,7 @@ test('session IPC hides Codex rows by default and supports explicit source opt-i
const ipcHandlers = new Map();
const queries = [];
const mainPath = require.resolve('../app/main.js');
const mainPath = require.resolve('../app/src/main/index.js');
delete require.cache[mainPath];
class FakeDatabase {
@@ -305,7 +305,7 @@ test('session IPC hides Codex rows by default and supports explicit source opt-i
};
try {
require('../app/main.js');
require('../app/src/main/index.js');
await new Promise(resolve => setImmediate(resolve));
ipcHandlers.get('db:getSessions')(null, {});
@@ -373,7 +373,7 @@ test('main process migrates an existing app database before source-filtered IPC
legacy.close();
const ipcHandlers = new Map();
const mainPath = require.resolve('../app/main.js');
const mainPath = require.resolve('../app/src/main/index.js');
delete require.cache[mainPath];
class FakeBrowserWindow {
@@ -454,7 +454,7 @@ test('main process migrates an existing app database before source-filtered IPC
};
try {
require('../app/main.js');
require('../app/src/main/index.js');
await new Promise(resolve => setImmediate(resolve));
const sessions = ipcHandlers.get('db:getSessions')(null, {});
@@ -480,7 +480,7 @@ test('closing the last macOS window releases background resources until activati
const home = join(tmpdir(), `obelisk-main-window-${Date.now()}`);
mkdirSync(join(home, '.obelisk'), { recursive: true });
writeFileSync(join(home, '.obelisk', 'obelisk.sqlite'), '');
const mainPath = require.resolve('../app/main.js');
const mainPath = require.resolve('../app/src/main/index.js');
delete require.cache[mainPath];
process.env.HOME = home;
Object.defineProperty(process, 'platform', { value: 'darwin' });
@@ -561,7 +561,7 @@ test('closing the last macOS window releases background resources until activati
};
try {
require('../app/main.js');
require('../app/src/main/index.js');
await new Promise(resolve => setImmediate(resolve));
assert.equal(windows.length, 1);
@@ -618,7 +618,7 @@ test('settings rebuild reopens the database from the configured Claude path', as
const buildCalls = [];
const serviceEvents = [];
const originalLoad = Module._load;
const mainPath = require.resolve('../app/main.js');
const mainPath = require.resolve('../app/src/main/index.js');
delete require.cache[mainPath];
class FakeDatabase {
@@ -703,7 +703,7 @@ test('settings rebuild reopens the database from the configured Claude path', as
};
try {
require('../app/main.js');
require('../app/src/main/index.js');
await new Promise(resolve => setImmediate(resolve));
const rebuild = ipcHandlers.get('settings:rebuildIndex');
@@ -750,7 +750,7 @@ test('settings rebuild keeps the existing database after a worker failure', asyn
const closedDbPaths = [];
const serviceEvents = [];
const originalLoad = Module._load;
const mainPath = require.resolve('../app/main.js');
const mainPath = require.resolve('../app/src/main/index.js');
delete require.cache[mainPath];
class FakeDatabase {
@@ -834,7 +834,7 @@ test('settings rebuild keeps the existing database after a worker failure', asyn
};
try {
require('../app/main.js');
require('../app/src/main/index.js');
await new Promise(resolve => setImmediate(resolve));
const rebuild = ipcHandlers.get('settings:rebuildIndex');
@@ -878,7 +878,7 @@ test('settings rebuild cancels an in-flight background build instead of waiting
const ipcHandlers = new Map();
const serviceEvents = [];
const originalLoad = Module._load;
const mainPath = require.resolve('../app/main.js');
const mainPath = require.resolve('../app/src/main/index.js');
delete require.cache[mainPath];
class FakeDatabase {
@@ -963,7 +963,7 @@ test('settings rebuild cancels an in-flight background build instead of waiting
};
try {
require('../app/main.js');
require('../app/src/main/index.js');
await new Promise(resolve => setImmediate(resolve));
const rebuild = ipcHandlers.get('settings:rebuildIndex');
+1 -1
View File
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const { buildRecapExportQuery, cleanRecapFilename } = require('../app/recap-capture-query.js');
const { buildRecapExportQuery, cleanRecapFilename } = require('../app/src/main/recap-capture-query.js');
test('recap export query includes the selected recap filename', () => {
const query = buildRecapExportQuery({
+17 -17
View File
@@ -155,10 +155,10 @@ test('cover card retrieval and writing choose one dominant human claim', async (
test('cover card schema uses claim instead of subtitle', async () => {
const pattern = await read('references/recap/pattern1-cover.md');
const writing = await read('references/recap/writing1-cover.md');
const component = await read('app/renderer/src/components/recap/CoverCard.vue');
const detail = await read('app/renderer/src/views/RecapDetail.vue');
const list = await read('app/renderer/src/views/RecapList.vue');
const mock = await read('app/renderer/src/mock/recap-2026-W24.json');
const component = await read('app/src/renderer/src/components/recap/CoverCard.vue');
const detail = await read('app/src/renderer/src/views/RecapDetail.vue');
const list = await read('app/src/renderer/src/views/RecapList.vue');
const mock = await read('app/src/renderer/src/mock/recap-2026-W24.json');
assert.match(pattern, /claim that lists three topics/i);
assert.doesNotMatch(pattern, /subtitle/);
@@ -197,8 +197,8 @@ test('thinking card retrieval searches for turns instead of implementation timel
test('thinking path schema uses turn instead of outcome', async () => {
const pattern = await read('references/recap/pattern2-thinking.md');
const writing = await read('references/recap/writing2-thinking.md');
const component = await read('app/renderer/src/components/recap/PathCard.vue');
const mock = await read('app/renderer/src/mock/recap-2026-W24.json');
const component = await read('app/src/renderer/src/components/recap/PathCard.vue');
const mock = await read('app/src/renderer/src/mock/recap-2026-W24.json');
assert.match(pattern, /prompt, turn, and an `evidence` anchor/i);
assert.doesNotMatch(pattern, /outcome/);
@@ -230,9 +230,9 @@ test('vibe card retrieval finds small user voice, not a correction audit', async
test('vibe card schema uses voice_lines instead of observations', async () => {
const writing = await read('references/recap/writing3-vibe.md');
const component = await read('app/renderer/src/components/recap/VibeCard.vue');
const detail = await read('app/renderer/src/views/RecapDetail.vue');
const mock = await read('app/renderer/src/mock/recap-2026-W24.json');
const component = await read('app/src/renderer/src/components/recap/VibeCard.vue');
const detail = await read('app/src/renderer/src/views/RecapDetail.vue');
const mock = await read('app/src/renderer/src/mock/recap-2026-W24.json');
assert.match(writing, /voice_lines: Array/);
assert.match(writing, /"voice_lines": \[/);
@@ -273,8 +273,8 @@ test('workflow card retrieval scopes real workflow runs and user reactions', asy
test('workflow card uses reaction instead of outcome for row copy', async () => {
const pattern = await read('references/recap/pattern4-workflow.md');
const writing = await read('references/recap/writing4-workflow.md');
const component = await read('app/renderer/src/components/recap/WorkflowCard.vue');
const mock = await read('app/renderer/src/mock/recap-2026-W24.json');
const component = await read('app/src/renderer/src/components/recap/WorkflowCard.vue');
const mock = await read('app/src/renderer/src/mock/recap-2026-W24.json');
assert.match(pattern, /items\[\]\.reaction/i);
assert.match(writing, /reaction: string/);
@@ -288,9 +288,9 @@ test('workflow card uses reaction instead of outcome for row copy', async () =>
test('workflow card schema uses deck instead of summary for the visible line', async () => {
const writing = await read('references/recap/writing4-workflow.md');
const component = await read('app/renderer/src/components/recap/WorkflowCard.vue');
const detail = await read('app/renderer/src/views/RecapDetail.vue');
const mock = await read('app/renderer/src/mock/recap-2026-W24.json');
const component = await read('app/src/renderer/src/components/recap/WorkflowCard.vue');
const detail = await read('app/src/renderer/src/views/RecapDetail.vue');
const mock = await read('app/src/renderer/src/mock/recap-2026-W24.json');
assert.match(writing, /deck\?: string/);
assert.match(writing, /"deck": "你召唤了机器军团。结果各有不同。"/);
@@ -323,9 +323,9 @@ test('closing card retrieval and writing keep a small personal receipt', async (
test('closing card schema uses receipts instead of stats', async () => {
const pattern = await read('references/recap/pattern5-closing.md');
const writing = await read('references/recap/writing5-closing.md');
const component = await read('app/renderer/src/components/recap/ClosingCard.vue');
const detail = await read('app/renderer/src/views/RecapDetail.vue');
const mock = await read('app/renderer/src/mock/recap-2026-W24.json');
const component = await read('app/src/renderer/src/components/recap/ClosingCard.vue');
const detail = await read('app/src/renderer/src/views/RecapDetail.vue');
const mock = await read('app/src/renderer/src/mock/recap-2026-W24.json');
assert.match(pattern, /one or two compact receipts/i);
assert.doesNotMatch(pattern, /receipt stats/);
+1 -1
View File
@@ -5,7 +5,7 @@ import {
createSessionLiveState,
consumeSessionDirty,
noteSessionUpdated,
} from '../app/renderer/src/session-live.mjs';
} from '../app/src/renderer/src/session-live.mjs';
test('session live state marks non-visible updated sessions as dirty', () => {
const live = createSessionLiveState();
+1 -1
View File
@@ -1,6 +1,6 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { buildSidebarProjects } from '../app/renderer/src/sidebar-projects.mjs';
import { buildSidebarProjects } from '../app/src/renderer/src/sidebar-projects.mjs';
test('session sidebar projects follow database recency order instead of label order', () => {
const sessions = [