···11+# BrainWaves Migration Session Summary
22+33+## Architecture Change: Webpack/Babel/Yarn → Electron-Vite/npm
44+55+The project was migrated from a legacy Electron + Webpack + Babel + Yarn stack to a modern **electron-vite** setup. This is a significant architectural shift:
66+77+| Concern | Before | After |
88+|---|---|---|
99+| Build system | Webpack + Babel | **electron-vite** (esbuild + Rollup) |
1010+| Package manager | Yarn | **npm** |
1111+| Module format | CommonJS (`require`) | **ESM** (`import`) |
1212+| Env variables (renderer) | `process.env.*` | **`import.meta.env.VITE_*`** |
1313+| Process split | Single config | **Three explicit targets**: `main`, `preload`, `renderer` |
1414+| Path utilities | `path-browserify` (2019, no ESM) | **`pathe`** (modern, pure ESM) |
1515+| Dev server | Webpack HMR | **Vite HMR** |
1616+1717+The electron-vite architecture enforces a clean Electron process split:
1818+- **Main** (`src/main/index.ts`) — Node.js process, IPC handlers, file system
1919+- **Preload** (`src/preload/index.ts`) — sandboxed bridge with `contextBridge`
2020+- **Renderer** (`src/renderer/`) — pure browser context, React app
2121+2222+---
2323+2424+## Major Work Done
2525+2626+### 1. Build System Migration
2727+- Replaced all Webpack config with `vite.config.ts` using `defineConfig` from `electron-vite`
2828+- Converted `require()` calls to ESM `import` statements across the codebase
2929+- Used `git mv` for all file renames to preserve git history
3030+- Set `package.json` `"main"` field to `"./out/main/index.js"` (electron-vite's output convention)
3131+3232+### 2. Electron API Modernization
3333+- **Replaced deprecated devtools APIs**: `session.getAllExtensions()` / `session.loadExtension()` → `session.extensions.*` (new namespaced API)
3434+- **Fixed preload `process` conflict**: Removed redundant `import process from 'process'` — Electron injects it natively
3535+- **Added dev HTTP cache clearing**: `session.defaultSession.clearCache()` in `app.whenReady()` (dev only) to prevent Electron's persistent HTTP cache from serving stale Vite pre-bundled assets
3636+3737+### 3. Dependency Upgrades
3838+3939+| Package | From | To | Reason |
4040+|---|---|---|---|
4141+| `@neurosity/pipes` | v3 | **v5** | Eliminated `dsp.js` which used `this[name]` globals incompatible with strict ESM |
4242+| `rxjs` | v6 | **v7** | Required by pipes v5 |
4343+| `redux-observable` | v1 | **v2-rc** | Required by RxJS v7 |
4444+| `plotly.js` | v1.54 (bundles **d3 v3**) | **v2.35** (uses d3 v6) | Eliminated all `this.document` / `this.navigator` / `this.Element` errors |
4545+| `react-plotly.js` | v2.4 | **v2.6** | Compatibility with plotly.js v2 |
4646+| `d3` (direct) | v5.16 | **v7.9** | Modern pure-ESM version |
4747+| `path-browserify` | v1 (2019, CJS) | **`pathe`** (modern ESM) | Drop-in replacement with active maintenance |
4848+4949+### 4. Environment Variable Migration
5050+Renderer code cannot access `process.env` in Vite (no Node.js context). All renderer references were migrated:
5151+- `process.env.CLIENT_ID` → `import.meta.env.VITE_CLIENT_ID`
5252+- `process.env.NODE_ENV` → `import.meta.env.MODE`
5353+- Emotiv SDK credentials are loaded from `keys.js` at config time and injected as `process.env.VITE_*` so Vite picks them up natively
5454+5555+### 5. Content Security Policy (CSP)
5656+Built up the CSP in `src/renderer/index.html` incrementally to allow legitimate sources while remaining secure:
5757+- Added `https://fonts.googleapis.com` to `style-src` (Semantic UI's Google Fonts)
5858+- Added `https://fonts.gstatic.com` to `font-src` (actual font files)
5959+- Added `webpack:` to `connect-src` (source map protocol)
6060+- Added `'self'` to `worker-src` (Vite serves workers as HTTP URLs in dev, not `blob:`)
6161+6262+### 6. Pyodide / Web Worker Fix
6363+- **Problem**: Vite transforms every `.js` file it serves by injecting `import { createHotContext } from '/@vite/client'`, turning files into ES modules. `importScripts()` in a classic worker cannot execute ES modules — causing a `NetworkError`.
6464+- **Fix**: Configured `publicDir` in the renderer Vite config to point at the pyodide install directory (`src/renderer/utils/pyodide/src/`). Vite serves `publicDir` files verbatim with zero transformation. Updated `webworker.js` to use absolute paths (`/pyodide/pyodide.js`) instead of fragile relative ones.
6565+6666+### 7. redux-observable v2 API Fix
6767+`action$.ofType()` was removed in redux-observable v2. Updated three call sites in `experimentEpics.ts` to use the pipeable `ofType` operator:
6868+6969+```ts
7070+// Before (v1):
7171+action$.ofType('@@router/LOCATION_CHANGE').pipe(...)
7272+7373+// After (v2):
7474+action$.pipe(ofType('@@router/LOCATION_CHANGE'), ...)
7575+```
7676+7777+### 8. Browser Compatibility Fixes
7878+- **`cortex.js`**: `global.process` → `typeof process !== 'undefined' && process.env` (no `global` in browser)
7979+- **`muse.ts`**: Removed `import 'hazardous'` — a Node.js-only asar path library that was incorrectly imported in the renderer
8080+8181+---
8282+8383+## Key Roadblocks
8484+8585+### Electron HTTP Cache vs. Vite Pre-bundle Cache
8686+The trickiest issue of the session. Vite sets `Cache-Control: max-age=31536000, immutable` on
8787+pre-bundled deps. Electron's renderer stores these permanently in
8888+`~/Library/Application Support/BrainWaves/Cache/`. Even after patching files on disk, Electron
8989+kept serving the old cached version because the URL's `v=` hash hadn't changed (Vite keys its
9090+cache hash on the package version, not file content). The solution required both patching the
9191+Vite pre-bundle cache file on disk *and* clearing the Electron session HTTP cache at startup
9292+in dev mode (`session.defaultSession.clearCache()`).
9393+9494+### plotly.js / d3 v3 `this.xxx` Chain
9595+Three separate globals (`this.document`, `this.Element`, `this.CSSStyleDeclaration`,
9696+`this.navigator`) needed patching before the root cause was identified as d3 v3 being bundled
9797+inside plotly.js v1. In Vite's strict-mode ESM context, bare `this` at the module level is
9898+`undefined`. Upgrading to plotly.js v2 (which uses d3 v6, pure ESM) eliminated all of them at
9999+once.
100100+101101+### `patchDeps.mjs` Strategy Evolution
102102+The plotly fix went through several iterations before the root cause was found:
103103+1. Vite server middleware to intercept HTTP requests — failed due to middleware ordering
104104+2. esbuild plugin in `optimizeDeps.esbuildOptions` — didn't apply to already-cached bundles
105105+3. Patching the npm source only — Vite doesn't re-bundle when the package version hasn't changed
106106+4. Patching both source and Vite's cached pre-bundle file — worked, but made entirely moot by upgrading plotly.js to v2
107107+108108+### Pyodide Worker Loading
109109+The worker's `importScripts()` call appeared to reference a valid URL, but the load silently
110110+failed. The cause was subtle: Vite injects HMR boilerplate (an `import` statement) into every
111111+`.js` file it serves, converting them to ES modules. `importScripts()` in a classic worker
112112+can only execute classic scripts — not ES modules. Moving pyodide to `publicDir` bypassed
113113+Vite's transform pipeline entirely.
···11import React, { useEffect } from 'react';
22import path from 'pathe';
33-import clonedeep from 'lodash.clonedeep';
33+import { cloneDeep as clonedeep } from 'lodash';
44import * as lab from 'lab.js/dist/lab.dev';
55import {
66 ExperimentObject,
+5-2
src/renderer/components/HomeComponent/index.tsx
···22import { isNil } from 'lodash';
33import { Grid, Button, Header, Image, Table } from 'semantic-ui-react';
44import { toast } from 'react-toastify';
55-import * as moment from 'moment';
55+import dayjs from 'dayjs';
66+import relativeTime from 'dayjs/plugin/relativeTime';
67import { History } from 'history';
88+99+dayjs.extend(relativeTime);
710import { Observable } from 'rxjs';
811import styles from '../styles/common.module.css';
912import {
···235238 </Table.Cell>
236239 <Table.Cell className={styles.experimentRowName}>
237240 {dateModified &&
238238- moment.default(dateModified).fromNow()}
241241+ dayjs(dateModified).fromNow()}
239242 </Table.Cell>
240243 <Table.Cell className={styles.experimentRowName}>
241244 <Button