BrainWaves Modernization — Implementation Progress#
Tracking file for executing the Technical Implementation Plan across sessions.
Last updated: 2026-03-07 Overall status: PHASES 1–4 COMPLETE (Phase 5 pending: npm install + build verification)
Codebase Reconnaissance (completed)#
Key files read and understood before starting implementation:
package.json— current deps, jest config, scriptssrc/renderer/store.ts— usesconnected-react-router(routerMiddleware,createHashHistory)src/renderer/reducers/index.ts— usesconnectRouterfromconnected-react-routersrc/renderer/containers/Root.tsx— usesConnectedRouter, receiveshistorypropsrc/renderer/index.tsx— imports and passeshistoryfrom store to Rootsrc/renderer/routes.tsx— React Router v5<Switch>/<Route component={}>/ customPropsRoutesrc/renderer/epics/experimentEpics.ts—autoSaveEpicandnavigationCleanupEpiclisten to@@router/LOCATION_CHANGEsrc/renderer/containers/TopNavBarContainer.ts— mapsstate.router.locationto propssrc/renderer/components/TopNavComponent/index.tsx— class component, usesthis.props.location.pathnamesrc/renderer/components/HomeComponent/index.tsx— class component, callsthis.props.history.push()src/renderer/components/CollectComponent/index.tsx— passeshistoryprop toConnectModal(unused there)src/renderer/components/CollectComponent/ConnectModal.tsx— hashistoryin Props but does NOT use itsrc/renderer/components/EEGExplorationComponent.tsx— passeshistorytoConnectModal(unused)src/renderer/actions/experimentActions.ts— RTKcreateAction,typesafe-actionssrc/renderer/utils/pyodide/utils.py— usessns.tsplot(removed in seaborn v0.10), seaborn import commented outsrc/renderer/utils/pyodide/webworker.js—importScripts('/pyodide/pyodide.js'), loads matplotlib/mne/pandasinternals/scripts/InstallPyodide.js— downloads pyodide v0.21.0 tarballvite.config.ts—publicDirservessrc/renderer/utils/pyodide/src/as static assets.github/workflows/test.yml— runsnpm run package-ci, lint, tsc (no unit tests)- 26 files import
semantic-ui-react(see list below)
Phase 1: Test Harness & Vitest#
Status: COMPLETE
Step 1.1 — Install and configure Vitest#
What to do:
- Edit
package.json:- Remove from
devDependencies:jest,@types/jest,identity-obj-proxy,react-test-renderer - Add to
devDependencies:vitest,@testing-library/react,@testing-library/jest-dom,jsdom - Change
"test": "cross-env jest --passWithNoTests"→"test": "vitest run" - Add
"test:watch": "vitest" - Remove the
"jest": { ... }config block from package.json
- Remove from
- Create
vitest.config.ts(project root) - Create
src/test-setup.ts(imports@testing-library/jest-dom) - Create
src/renderer/App.test.tsx(basic sanity render test) - Run
npm installto update node_modules
Notes:
- Vitest needs
jsdomenvironment for React component tests - The vite.config.ts babel plugins (decorators, class-properties) must also be present in vitest.config.ts
- CSS modules: vitest handles them natively with
css.modulesconfig; noidentity-obj-proxyneeded
Step 1.2 — Update CI workflow#
What to do:
- Edit
.github/workflows/test.yml: addnpm teststep before or after existing steps - Create
tests/build.test.ts: verifiesout/main/index.jsandout/renderer/index.htmlexist after build
Phase 2: Routing Modernization#
Status: COMPLETE
Step 2.1 — Remove connected-react-router#
Package changes:
- Remove from
dependencies:connected-react-router,history - Remove from
devDependencies:@types/history,@types/react-router,@types/react-router-dom - Remove
overrides["connected-react-router"]block
File changes:
| File | Change |
|---|---|
src/renderer/store.ts |
Remove createHashHistory, history export, routerMiddleware, remove router from middleware |
src/renderer/reducers/index.ts |
Remove connectRouter, History import, remove router from combineReducers, remove router: any from RootState |
src/renderer/containers/Root.tsx |
Remove ConnectedRouter; use HashRouter from react-router-dom; remove history prop |
src/renderer/index.tsx |
Remove history import; pass only store to <Root> |
Step 2.2 — Upgrade to React Router v6#
Package changes:
- Change
react-routerandreact-router-dom:"^5.2.0"→"^6.x"(v6 merges the two packages; keep both entries or consolidate)
New file:
src/renderer/actions/routerActions.ts— definesRouterActions.RouteChanged(pathname: string)action
File changes:
| File | Change |
|---|---|
src/renderer/routes.tsx |
Replace <Switch>/<Route component={}> with <Routes>/<Route element={<C />}>. Remove PropsRoute. Pass activeStep directly as JSX prop on <HomeContainer>. |
src/renderer/containers/App.tsx |
Add NavigationTracker functional component (uses useLocation + useDispatch to dispatch RouterActions.RouteChanged on location change) |
src/renderer/epics/experimentEpics.ts |
Replace both ofType('@@router/LOCATION_CHANGE') epics to use filter(isActionOf(RouterActions.RouteChanged)). Access action.payload as pathname string directly. Remove pluck('payload', 'pathname') etc. |
src/renderer/components/TopNavComponent/index.tsx |
Convert class → functional component. Replace this.props.location.pathname with useLocation().pathname. State becomes useState. Methods become callbacks. |
src/renderer/containers/TopNavBarContainer.ts |
Remove location: state.router.location from mapStateToProps |
src/renderer/components/HomeComponent/index.tsx |
Change history: History prop to navigate: (path: string) => void. Replace all this.props.history.push(X) with this.props.navigate(X). Remove import { History } from 'history'. |
src/renderer/containers/HomeContainer.ts |
Wrap exported component with withNavigate HOC that injects useNavigate() as navigate prop |
src/renderer/components/CollectComponent/index.tsx |
Remove history: History from Props; remove passing history to ConnectModal |
src/renderer/components/EEGExplorationComponent.tsx |
Remove history: History from Props; remove passing history to ConnectModal |
src/renderer/components/CollectComponent/ConnectModal.tsx |
Remove history: History from Props interface |
withNavigate HOC pattern (for HomeContainer.ts):
import React from 'react';
import { useNavigate } from 'react-router-dom';
function withNavigate<P extends { navigate: (path: string) => void }>(
Component: React.ComponentType<P>
) {
return function WithNavigate(props: Omit<P, 'navigate'>) {
const navigate = useNavigate();
return <Component {...(props as P)} navigate={navigate} />;
};
}
NavigationTracker pattern (for App.tsx):
function NavigationTracker() {
const location = useLocation();
const dispatch = useDispatch();
useEffect(() => {
dispatch(RouterActions.RouteChanged(location.pathname));
}, [location.pathname, dispatch]);
return null;
}
Tests to add:
tests/store.test.ts— verifies store initializes without router reducertests/routing.test.tsx— verifies navigation between Home/Design/Collect renders correct components
Phase 3: Pyodide Modernization#
Status: COMPLETE
Step 3.1 — Upgrade Pyodide version#
File: internals/scripts/InstallPyodide.js
Changes:
PYODIDE_VERSION:'0.21.0'→'0.27.0'TAR_NAME:pyodide-build-${PYODIDE_VERSION}.tar.bz2→pyodide-${PYODIDE_VERSION}.tar.bz2TAR_URL: update to match new naming
Note: The tarball for 0.27.0 extracts to a pyodide/ subdirectory, which is correct for the webworker's importScripts('/pyodide/pyodide.js') call. The old 0.21.0 tar was also pyodide-build-* but extracted to a pyodide/ dir.
Caution: mne package availability in pyodide 0.27.0 needs verification. If not in the default package list, webworker.js loadPackage(['matplotlib', 'mne', 'pandas']) will fail. May need to load mne via micropip.install('mne') instead.
Test to add: tests/pyodide.test.ts
Step 3.2 — Fix Python plotting (utils.py)#
File: src/renderer/utils/pyodide/utils.py
The plot_conditions function currently:
- Calls
sns.color_palette(...)— butimport seaborn as snsis commented out - Calls
sns.tsplot(...)— removed from seaborn in v0.10.0 - Calls
sns.despine()— still exists but seaborn not imported
Fix — replace plot_conditions body:
- Replace
sns.color_palette(...)with a hardcoded palette (consistent withplot_topo) - Replace
sns.tsplot(...)with manual bootstrap CI using numpy +ax.plot()+ax.fill_between() - Replace
sns.despine()withax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
Bootstrap CI replacement for sns.tsplot(X[...], time=times, color=color, n_boot=n_boot, ci=ci):
cond_data = X[y.isin(cond), ch_ind]
mean = np.nanmean(cond_data, axis=0)
n_samples = cond_data.shape[0]
boot_means = np.array([
np.nanmean(cond_data[np.random.randint(0, n_samples, n_samples)], axis=0)
for _ in range(n_boot)
])
alpha = (100 - ci) / 2
low = np.percentile(boot_means, alpha, axis=0)
high = np.percentile(boot_means, 100 - alpha, axis=0)
ax.plot(times, mean, color=color)
ax.fill_between(times, low, high, color=color, alpha=0.3)
Test to add: tests/python_utils.test.ts
Phase 4: UI Library Replacement (Semantic UI → Tailwind + Shadcn/ui)#
Status: COMPLETE
Step 4.1 — Install Tailwind CSS and Shadcn/ui#
Package additions (devDependencies):
tailwindcss,postcss,autoprefixer
Package additions (dependencies):
@radix-ui/react-dialog@radix-ui/react-dropdown-menu@radix-ui/react-slotclass-variance-authorityclsxtailwind-merge
Package removals (dependencies):
semantic-ui-reactsemantic-ui-css
New config files:
tailwind.config.jspostcss.config.js
New UI component files (in src/renderer/components/ui/):
utils.ts—cn()helper (clsx+tailwind-merge)button.tsx— Shadcn Button (CVA variants)card.tsx— Shadcn Card (replacesSegment)dialog.tsx— Shadcn Dialog (replacesModal)dropdown-menu.tsx— Shadcn DropdownMenu (replacesDropdown)table.tsx— Shadcn Table (replacesTable)
Update src/renderer/app.global.css: add Tailwind directives (@tailwind base/components/utilities)
Remove from src/renderer/index.tsx: import 'semantic-ui-css/semantic.min.css'
Step 4.2 — Component-by-component replacement#
26 files to update (grep confirmed):
| File | Semantic UI components used | Status |
|---|---|---|
components/AnalyzeComponent.tsx |
Grid, Icon, Segment, Header, Dropdown, Divider, Button, Checkbox, Sidebar, DropdownProps | NOT STARTED |
components/CleanComponent/CleanSidebar.tsx |
(need to read) | NOT STARTED |
components/CleanComponent/index.tsx |
Grid, Button, Icon, Segment, Header, Dropdown, Sidebar, SidebarPusher, Divider, DropdownProps | NOT STARTED |
components/CollectComponent/ConnectModal.tsx |
Modal, Button, Segment, List, Grid, Divider | NOT STARTED |
components/CollectComponent/HelpSidebar.tsx |
(need to read) | NOT STARTED |
components/CollectComponent/PreTestComponent.tsx |
(need to read) | NOT STARTED |
components/CollectComponent/RunComponent.tsx |
(need to read) | NOT STARTED |
components/CollectComponent/index.tsx |
(passes through, may be minimal) | NOT STARTED |
components/DesignComponent/CustomDesignComponent.tsx |
(need to read) | NOT STARTED |
components/DesignComponent/ParamSlider.tsx |
(need to read) | NOT STARTED |
components/DesignComponent/StimuliDesignColumn.tsx |
(need to read) | NOT STARTED |
components/DesignComponent/StimuliRow.tsx |
(need to read) | NOT STARTED |
components/DesignComponent/index.tsx |
(need to read) | NOT STARTED |
components/EEGExplorationComponent.tsx |
Grid, Button, Header, Segment, Image, Divider | NOT STARTED |
components/HomeComponent/ExperimentCard.tsx |
(need to read) | NOT STARTED |
components/HomeComponent/OverviewComponent.tsx |
(need to read) | NOT STARTED |
components/HomeComponent/index.tsx |
Grid, Button, Header, Image, Table | NOT STARTED |
components/InputCollect.tsx |
(need to read) | NOT STARTED |
components/InputModal.tsx |
(need to read) | NOT STARTED |
components/PreviewButtonComponent.tsx |
(need to read) | NOT STARTED |
components/PreviewExperimentComponent.tsx |
(need to read) | NOT STARTED |
components/PyodidePlotWidget.tsx |
(need to read) | NOT STARTED |
components/SecondaryNavComponent/SecondaryNavSegment.tsx |
(need to read) | NOT STARTED |
components/SecondaryNavComponent/index.tsx |
(need to read) | NOT STARTED |
components/SignalQualityIndicatorComponent.tsx |
(need to read) | NOT STARTED |
components/TopNavComponent/PrimaryNavSegment.tsx |
(need to read) | NOT STARTED |
components/TopNavComponent/index.tsx |
Grid, Segment, Image, Dropdown | NOT STARTED (also being changed in Phase 2) |
Replacement mapping:
| Semantic UI | Replacement |
|---|---|
<Grid> |
<div className="grid ..."> (Tailwind) |
<Grid.Row> |
<div className="flex ..."> or grid row |
<Grid.Column width={N}> |
Tailwind col-span-N |
<Segment> |
<div className="p-4 ..."> or <Card> |
<Button> |
<Button> from ./ui/button |
<Button.Group> |
<div className="flex gap-1"> |
<Header as="h1"> |
<h1 className="..."> |
<Image src={x}> |
<img src={x} /> |
<Dropdown> |
<DropdownMenu> from ./ui/dropdown-menu |
<Modal> |
<Dialog> from ./ui/dialog |
<List>/<List.Item> |
<ul>/<li> |
<Divider> |
<hr /> or <div className="my-4"> |
<Checkbox> |
<input type="checkbox" /> |
<Icon name="x"> |
inline SVG or FontAwesome (already installed) |
<Sidebar>/<Sidebar.Pushable> |
<div> with conditional className |
<Table> etc. |
<Table> from ./ui/table or HTML table |
DropdownProps, SemanticICONS |
Remove type imports; use plain React event types |
Phase 5: Final Build Verification#
Status: PENDING — run npm install then npm run build
- Run
npm run build— should complete without errors - Run
npm test— all Vitest suites pass - Run
npm run package— Electron app packages successfully
Dependency Change Summary#
To remove#
| Package | Location |
|---|---|
connected-react-router |
dependencies |
history |
dependencies |
react-router |
dependencies (merged into react-router-dom v6) |
semantic-ui-react |
dependencies |
semantic-ui-css |
dependencies |
jest |
devDependencies |
@types/jest |
devDependencies |
@types/history |
devDependencies |
@types/react-router |
devDependencies |
@types/react-router-dom |
devDependencies |
identity-obj-proxy |
devDependencies |
react-test-renderer |
devDependencies |
To add#
| Package | Location | Purpose |
|---|---|---|
vitest |
devDependencies | test runner |
@testing-library/react |
devDependencies | React test utilities |
@testing-library/jest-dom |
devDependencies | DOM matchers |
jsdom |
devDependencies | DOM env for tests |
react-router-dom (upgrade to v6) |
dependencies | routing |
tailwindcss |
devDependencies | CSS framework |
postcss |
devDependencies | CSS processing |
autoprefixer |
devDependencies | CSS vendor prefixes |
@radix-ui/react-dialog |
dependencies | Dialog primitive |
@radix-ui/react-dropdown-menu |
dependencies | Dropdown primitive |
@radix-ui/react-slot |
dependencies | Slot primitive (shadcn) |
class-variance-authority |
dependencies | CVA for Button variants |
clsx |
dependencies | className utility |
tailwind-merge |
dependencies | Tailwind class merging |
Session Notes#
Session 1 (2026-03-07)#
- Read all relevant source files
- Created this progress tracker
- No code changes made yet
- Ready to begin Phase 1 (Vitest) in next session
Session 2 (2026-03-07)#
- Completed Phases 1–4 in full
- Phase 1: Vitest config, test-setup.ts, App.test.tsx, CI workflow update
- Phase 2: Removed connected-react-router, upgraded to React Router v6, NavigationTracker pattern, withNavigate HOC for HomeContainer + ExperimentDesignContainer, routerActions.ts
- Phase 3: Pyodide version 0.21→0.27, fixed sns.tsplot with manual bootstrap CI in utils.py
- Phase 4: Created Tailwind config, postcss config, shadcn/ui components (button, card, dialog, dropdown-menu, table, utils). Replaced all 26 semantic-ui-react component files. Removed semantic-ui import from app.global.css, added Tailwind directives.
- Next step: Run
npm installthennpm run buildto verify (Phase 5)