From ebbfcfcb74c50775290541d45912664743ae65c1 Mon Sep 17 00:00:00 2001 From: Joe Pitts Date: Thu, 7 May 2026 12:29:51 +0100 Subject: [PATCH] fix: embed build-time secrets in packaged app for Genius API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tsc doesn't substitute env vars, so GENIUS_ACCESS_TOKEN was undefined at runtime in packaged builds — the handler silently returned null. build:main now writes dist/build-env.json with both GENIUS_ACCESS_TOKEN and APPINSIGHTS_CONNECTION_STRING baked in from the CI environment. main.ts loads this file at startup and back-fills any vars that dotenv didn't cover (dev .env still takes priority). Also adds --remote-debugging-port=9333 to the dev script and documents the agent-browser visual verification workflow in CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 42 +++++++++++++++++++++++++++++++++--------- package.json | 4 ++-- src/main.ts | 9 +++++++++ 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 58181aa..abec108 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,30 @@ The renderer is **never type-checked by Vite** (esbuild strips types). `npm run --- +## Visual verification (Claude Code) + +The app supports visual feedback via Chrome DevTools Protocol during development. + +### Verify and connect + +```bash +curl -s http://localhost:9333/json/version # should return Electron/Chrome JSON +agent-browser connect 9333 +agent-browser screenshot /tmp/screenshot.png +agent-browser snapshot -i # DOM inspection +``` + +### Workflow + +After making UI changes: + +1. `npm run dev` (CDP already enabled) +2. `agent-browser connect 9333` +3. `agent-browser screenshot /tmp/tt2-screenshot.png` +4. Inspect the screenshot and fix any rendering issues before committing + +--- + ## Architecture ### IPC bridge @@ -122,7 +146,7 @@ Main process (`src/`), IPC handlers, WebSocket bootstrap, auth flow, and most ho - **`globals.d.ts` must stay ambient** — no `export {}`, no `import`. Adding either makes it a module and breaks global type resolution silently in the renderer. - **Renderer typecheck is separate** — `tsc` at the root only checks `src/`. Always run `npm run typecheck` (which checks both) not just `tsc`. -- **`applyReorderLocally` index math** — `insertAt` is the count of non-selected items whose *original* index is less than `toIndex`, not `toIndex` itself. See `queueHelpers.ts` for comments. +- **`applyReorderLocally` index math** — `insertAt` is the count of non-selected items whose _original_ index is less than `toIndex`, not `toIndex` itself. See `queueHelpers.ts` for comments. - **Image cache is a singleton module** — tests that import `imageCache` must use `vi.resetModules()` and dynamic `import()` in `beforeEach` to avoid state leaking between tests. - **Sandbox mode** — both `uiWin` and `miniWin` have `sandbox: true`. The preload script runs in the sandboxed context; do not use Node.js APIs directly in `preload.ts`. - **Debug windows are intentionally insecure** — `nodeIntegration: true` on the WS/HTTP debug windows is by design and guarded by `app.isPackaged` check. Do not "fix" this. @@ -133,13 +157,13 @@ Main process (`src/`), IPC handlers, WebSocket bootstrap, auth flow, and most ho Resource group: `truetunes-rg` (uksouth) -| Resource | Name | -|---|---| -| Function App | `truetunes-fn` | -| Web PubSub | `truetunes-wps` | -| Cosmos DB | `truetunes-cosmos` | -| Storage | `truetunesfoywo62izs34i` | -| App Insights | `truetunes-ai` | -| Log Analytics | `truetunes-law` | +| Resource | Name | +| ------------- | ------------------------ | +| Function App | `truetunes-fn` | +| Web PubSub | `truetunes-wps` | +| Cosmos DB | `truetunes-cosmos` | +| Storage | `truetunesfoywo62izs34i` | +| App Insights | `truetunes-ai` | +| Log Analytics | `truetunes-law` | Deploy: `cd server && npm run deploy` — wraps `az deployment group create` and reads `VESTABOARD_API_KEY` from the root `.env`, passing it as the `vestaboardApiKey` ARM parameter so the secret is never committed. diff --git a/package.json b/package.json index 3e909c9..a6bc986 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ }, "main": "dist/main.js", "scripts": { - "build:main": "tsc && node -e \"const fs=require('fs');fs.copyFileSync('src/debug-ws.html','dist/debug-ws.html');fs.copyFileSync('src/debug-http.html','dist/debug-http.html')\"", + "build:main": "tsc && node -e \"const fs=require('fs');fs.copyFileSync('src/debug-ws.html','dist/debug-ws.html');fs.copyFileSync('src/debug-http.html','dist/debug-http.html');fs.writeFileSync('dist/build-env.json',JSON.stringify({GENIUS_ACCESS_TOKEN:process.env.GENIUS_ACCESS_TOKEN||'',APPINSIGHTS_CONNECTION_STRING:process.env.APPINSIGHTS_CONNECTION_STRING||''}))\"", "build:renderer": "vite build --config renderer/vite.config.ts", "build": "npm run build:main && npm run build:renderer", "dev:renderer": "vite --config renderer/vite.config.ts", - "dev": "npm run build:main && concurrently -k \"npm run dev:renderer\" \"electron .\"", + "dev": "npm run build:main && concurrently -k \"npm run dev:renderer\" \"electron . --remote-debugging-port=9333\"", "start": "npm run build && electron .", "electron:build": "npm run build && electron-builder", "electron:build:win": "npm run build && electron-builder --win", diff --git a/src/main.ts b/src/main.ts index 7c43dd1..bae8c2c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,15 @@ import { autoUpdater } from 'electron-updater'; import { officePubSub } from './pubsub'; import * as path from 'path'; import * as fs from 'fs/promises'; +import { readFileSync } from 'fs'; + +// Load secrets baked in at build time — fills gaps that dotenv didn't cover (packaged app has no .env) +try { + const baked = JSON.parse(readFileSync(path.join(__dirname, 'build-env.json'), 'utf8')) as Record; + for (const [k, v] of Object.entries(baked)) { + if (v && !process.env[k]) process.env[k] = v; + } +} catch { /* dev: dotenv already handled it */ } import { randomUUID } from 'crypto'; import WebSocket from 'ws'; import type { FetchRequest, FetchResponse } from './types';