The foundation for a shareable team build. Replaces the .env.local-only dev config
with a real, per-teammate connection flow.
main:
- config-store.ts: token encrypted at rest via Electron safeStorage (OS keychain),
config JSON in userData. Token lives only in main; renderer gets everything but.
- resolveConfig: saved config > .env.local (dev) > null; ignored under COMMITEA_E2E.
pm-state repo defaults to `${repo}-pm-state`. resetClients() re-reads on change so
saving config takes effect without a restart. gitea:status gains `demo` (e2e).
- IPC: config:get (no token), config:test (authed read validates token+repo),
config:set (encrypt+save+reset), config:clear. Model bridge reads config.modelUrl
and probes reachability — chat is "configured" only if a model actually answers;
localhost default is dev-only (app.isPackaged gate).
renderer:
- ConnectScreen: real onboarding form (URL/owner/repo/PAT/optional model) → test →
save. AppShell gates on it: demo → shell (fixtures/e2e); configured → shell (real);
else → connect. Settings Connection card is real (repo/url/model/sidecar) with
Reconfigure + Disconnect. Chat cleanly disables with a "no model" state instead of
the scripted canned reply.
Verified: main + desktop typecheck clean, 14 fixture e2e green (demo mode unchanged),
live onboarding e2e: fresh app → connect form → validated PAT → real board (24 done /
10 open). COMMITEA_NO_ENV_LOCAL + COMMITEA_USERDATA are test hooks for the onboarding path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
53 lines
1.3 KiB
TypeScript
53 lines
1.3 KiB
TypeScript
import { join } from 'node:path'
|
|
|
|
import { BrowserWindow, app, shell } from 'electron'
|
|
|
|
// Test hook: isolate config/cache to a throwaway dir (must run before any getPath).
|
|
if (process.env.COMMITEA_USERDATA) app.setPath('userData', process.env.COMMITEA_USERDATA)
|
|
|
|
import { registerGiteaIpc } from './gitea.js'
|
|
import { registerModelIpc } from './model.js'
|
|
|
|
function createWindow(): void {
|
|
const win = new BrowserWindow({
|
|
width: 1440,
|
|
height: 900,
|
|
minWidth: 1080,
|
|
minHeight: 700,
|
|
show: false,
|
|
autoHideMenuBar: true,
|
|
backgroundColor: '#F0F4F0',
|
|
webPreferences: {
|
|
preload: join(import.meta.dirname, '../preload/index.mjs'),
|
|
sandbox: false,
|
|
},
|
|
})
|
|
|
|
win.on('ready-to-show', () => win.show())
|
|
|
|
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
void shell.openExternal(url)
|
|
return { action: 'deny' }
|
|
})
|
|
|
|
if (process.env.ELECTRON_RENDERER_URL) {
|
|
void win.loadURL(process.env.ELECTRON_RENDERER_URL)
|
|
} else {
|
|
void win.loadFile(join(import.meta.dirname, '../renderer/index.html'))
|
|
}
|
|
}
|
|
|
|
void app.whenReady().then(() => {
|
|
registerGiteaIpc()
|
|
registerModelIpc()
|
|
createWindow()
|
|
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
|
})
|
|
})
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (process.platform !== 'darwin') app.quit()
|
|
})
|