PhonoLex 4.0.0 Release — Implementation Plan¶
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Ship PhonoLex 4.0.0 — out of beta, modern stack, discoverable, documented, tested.
Architecture: Five sub-branches merge into release/4.0.0, which merges to main with a v4.0.0 tag. Branch 1 (cleanup) is already done and committed.
Tech Stack: Hono/D1 (Cloudflare Workers), React 19, MUI 7, Vite 7, ESLint 9, Vitest 4, TypeScript
Design doc: docs/plans/2026-03-10-release-4.0.0-design.md
Branch 0: Setup¶
Task 0.1: Create release branch¶
Step 1: Create and push release/4.0.0 from develop
git checkout develop
git checkout -b release/4.0.0
git merge cleanup/claude-md-roleplay
git push -u origin release/4.0.0
Step 2: Verify merge is clean
npm run type-check --prefix webapp/frontend
cd workers && npx tsc --noEmit
Expected: Both pass with no errors.
Branch 1: cleanup/claude-md-roleplay — DONE¶
Already committed. See design doc for details.
Branch 2: deps/react-mui-vite¶
Task 2.1: Create branch and upgrade React 18 → 19¶
Files:
- Modify: webapp/frontend/package.json
Step 1: Create branch from release/4.0.0
git checkout release/4.0.0
git checkout -b deps/react-mui-vite
Step 2: Upgrade React
cd webapp/frontend
npm install react@^19 react-dom@^19
npm install -D @types/react@^19 @types/react-dom@^19
Step 3: Verify build
npm run type-check
npm run build
Expected: Both pass. React 19 is mostly backwards-compatible.
Step 4: Commit
git add package.json package-lock.json
git commit -m "deps: upgrade React 18 → 19"
Task 2.2: Upgrade MUI 5 → 6 (deprecation step)¶
MUI migration must go 5 → 6 → 7. v6 introduces deprecations; v7 removes them.
Files:
- Modify: webapp/frontend/package.json
- Modify: All files using Grid, TextField InputProps, Accordion TransitionProps
Step 1: Install MUI 6
cd webapp/frontend
npm install @mui/material@^6 @mui/icons-material@^6 @emotion/react@latest @emotion/styled@latest
Step 2: Run MUI codemods for deprecations
npx @mui/codemod@latest deprecations/all webapp/frontend/src/
This handles: TextField InputProps → slotProps.input, Accordion TransitionProps → slotProps.transition, etc.
Step 3: Run Grid v2 codemod
npx @mui/codemod@latest v6.0.0/grid-v2-props webapp/frontend/src/
This converts: <Grid item xs={6}> → <Grid size={6}>, removes item prop.
Step 4: Verify build
npm run type-check
npm run build
Fix any remaining type errors from the codemods. Common issues:
- Grid import may need to change from @mui/material/Grid to @mui/material/Grid2 in v6
- slotProps types may need manual fixes if codemods miss edge cases
Step 5: Commit
git add -A
git commit -m "deps: upgrade MUI 5 → 6, run deprecation codemods"
Task 2.3: Upgrade MUI 6 → 7¶
Step 1: Upgrade to MUI 7
cd webapp/frontend
npm install @mui/material@^7 @mui/icons-material@^7
Step 2: Verify build
npm run type-check
npm run build
Known issues to check:
- Grid import: in v7, Grid2 is promoted to Grid. If codemods changed imports to Grid2, change back to Grid.
- AccordionSummary now wraps content in <h3> by default — check CSS if accordion styling looks off.
- react-is version: MUI 7 depends on react-is@19. Since we're on React 19 now, this should be fine. If there are runtime errors, add an override in package.json.
Step 3: Commit
git add -A
git commit -m "deps: upgrade MUI 6 → 7"
Task 2.4: Upgrade Vite 5 → 7¶
Files:
- Modify: webapp/frontend/package.json
- Possibly modify: webapp/frontend/vite.config.ts
Step 1: Upgrade Vite and plugin
cd webapp/frontend
npm install -D vite@^7 @vitejs/plugin-react@^5
Step 2: Check for breaking config changes
Read webapp/frontend/vite.config.ts. Vite 7 may require adjustments to:
- server config options
- Plugin API changes
Step 3: Verify build
npm run build
npm run dev # quick smoke test, ctrl+c
Step 4: Commit
git add -A
git commit -m "deps: upgrade Vite 5 → 7"
Task 2.5: Visual verification¶
Step 1: Start dev server and check all 5 tools
cd webapp/frontend && npm run dev
Open http://localhost:3000 and verify each tool renders correctly:
1. Custom Word Lists (Builder) — filters, pattern matching, table
2. Text Analysis — text input, highlighting, stats
3. Contrastive Sets — phoneme picker, accordion, results table
4. Sound Similarity — weight sliders, results
5. Lookup — word search, phoneme features, associations
Step 2: Fix any visual regressions (Grid spacing, Accordion headings, TextField styling)
Step 3: Final commit
git add -A
git commit -m "fix: resolve MUI 7 visual regressions"
Task 2.6: Merge to release branch¶
git checkout release/4.0.0
git merge deps/react-mui-vite
Verify: npm run type-check && npm run build in webapp/frontend/.
Branch 3: deps/eslint-vitest¶
Task 3.1: Create branch and upgrade ESLint to flat config¶
Files:
- Delete: webapp/frontend/.eslintrc.cjs
- Create: webapp/frontend/eslint.config.js
- Modify: webapp/frontend/package.json (deps + lint scripts)
Step 1: Create branch
git checkout release/4.0.0
git checkout -b deps/eslint-vitest
Step 2: Swap ESLint packages
cd webapp/frontend
npm uninstall @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh
npm install -D eslint@^9 typescript-eslint@^8 @eslint/js@^9 globals eslint-plugin-react@latest eslint-plugin-react-hooks@latest eslint-plugin-react-refresh@^0.5
Step 3: Create eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import globals from 'globals';
export default tseslint.config(
{ ignores: ['dist', 'vite.config.ts', 'vitest.config.ts'] },
eslint.configs.recommended,
...tseslint.configs.recommended,
react.configs.flat.recommended,
react.configs.flat['jsx-runtime'],
reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite,
{
languageOptions: {
globals: { ...globals.browser },
},
settings: {
react: { version: 'detect' },
},
rules: {
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
}],
'@typescript-eslint/no-explicit-any': 'warn',
'react/prop-types': 'off',
'react/no-unescaped-entities': 'off',
},
},
);
Step 4: Delete .eslintrc.cjs
rm webapp/frontend/.eslintrc.cjs
Step 5: Update lint scripts in package.json
Remove --ext ts,tsx flag (no longer needed in flat config):
"lint": "eslint . --report-unused-disable-directives --max-warnings 50",
"lint:fix": "eslint . --fix"
Step 6: Run lint
npm run lint
Fix any new errors. Common issues:
- exhaustive-deps may be stricter in react-hooks v5+
- Some rules may have new defaults
Step 7: Commit
git add -A
git commit -m "deps: migrate ESLint 8 → 9 with flat config"
Task 3.2: Upgrade Vitest 3 → 4 + cloudflare pool¶
Files:
- Modify: workers/package.json
- Modify: webapp/frontend/package.json
- Possibly modify: workers/vitest.config.ts, webapp/frontend/vitest.config.ts
Step 1: Upgrade workers vitest + pool
cd workers
npm install -D vitest@^4 @cloudflare/vitest-pool-workers@latest
Step 2: Upgrade frontend vitest
cd webapp/frontend
npm install -D vitest@^4 @vitest/ui@^4
Step 3: Verify configs still work
cd workers && npx vitest run 2>&1 # will still say "no tests found" — that's expected
cd ../webapp/frontend && npx vitest run 2>&1
Step 4: Commit
git add -A
git commit -m "deps: upgrade Vitest 3 → 4, cloudflare pool 0.8 → latest"
Task 3.3: Write baseline API tests¶
Files:
- Create: workers/src/__tests__/api.test.ts
Step 1: Write tests for core endpoints
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';
import app from '../index';
describe('PhonoLex API', () => {
const request = async (path: string, options?: RequestInit) => {
const req = new Request(`http://localhost${path}`, options);
const ctx = createExecutionContext();
const res = await app.fetch(req, env, ctx);
await waitOnExecutionContext(ctx);
return res;
};
describe('GET /api/health', () => {
it('returns ok', async () => {
const res = await request('/api/health');
expect(res.status).toBe(200);
const body = await res.json();
expect(body.status).toBe('ok');
});
});
describe('GET /api/words/:word', () => {
it('returns word data for a known word', async () => {
const res = await request('/api/words/cat');
expect(res.status).toBe(200);
const body = await res.json();
expect(body.word).toBe('cat');
expect(body.ipa).toBeDefined();
expect(body.phonemes).toBeDefined();
});
it('returns 404 for unknown word', async () => {
const res = await request('/api/words/xyznotaword');
expect(res.status).toBe(404);
});
});
describe('POST /api/words/search', () => {
it('returns results with filters', async () => {
const res = await request('/api/words/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filters: { min_syllable_count: 1, max_syllable_count: 1 },
limit: 5,
}),
});
expect(res.status).toBe(200);
const body = await res.json();
expect(body.words.length).toBeLessThanOrEqual(5);
expect(body.total).toBeGreaterThan(0);
});
});
describe('GET /api/property-metadata', () => {
it('returns property definitions', async () => {
const res = await request('/api/property-metadata');
expect(res.status).toBe(200);
const body = await res.json();
expect(Array.isArray(body)).toBe(true);
expect(body.length).toBeGreaterThan(30);
});
});
describe('GET /api/phonemes', () => {
it('returns phoneme list', async () => {
const res = await request('/api/phonemes');
expect(res.status).toBe(200);
const body = await res.json();
expect(Array.isArray(body)).toBe(true);
expect(body.length).toBeGreaterThan(30);
});
});
});
Note: These tests use @cloudflare/vitest-pool-workers which runs against a local D1 instance. The test structure above may need adjustment based on how the pool exposes the app. Check workers/vitest.config.ts for the pool configuration and adjust imports accordingly.
Step 2: Run tests
cd workers && npm test
Expected: All pass (requires local D1 to be seeded). If D1 isn't seeded locally, tests that query data will fail — that's fine, note it and move on. The test infra being wired up is the goal.
Step 3: Commit
git add -A
git commit -m "test: add baseline API tests for workers"
Task 3.4: Merge to release branch¶
git checkout release/4.0.0
git merge deps/eslint-vitest
Verify: npm run lint && npm run type-check && npm run build in webapp/frontend/.
Branch 4: docs/4.0¶
Task 4.1: Create branch and delete stale docs¶
Step 1: Create branch
git checkout release/4.0.0
git checkout -b docs/4.0
Step 2: Delete stale technical docs
rm docs/VOCABULARY_FILTERING.md
rm docs/MAXIMAL_OPPOSITION_TOOL.md
rm docs/CONTRASTIVE_INTERVENTION_UNIFIED_ARCHITECTURE.md
Step 3: Update mkdocs.yml nav — remove the three deleted files from the Technical section
Step 4: Commit
git add -A
git commit -m "docs: remove stale v3 technical docs"
Task 4.2: Clean architecture.md and index.md¶
Files:
- Modify: docs/technical/architecture.md
- Modify: docs/index.md
Step 1: In architecture.md:
- Replace "cognitive graph" with neutral descriptions ("consolidated dataset", "data platform")
- Update version references from v3.0 to v4.0
- Remove references to webapp/backend/
- Update data flow to reflect workers/scripts/config.py location
Step 2: In index.md:
- Remove any "cognitive graph" references
- Update version badge/references if present
Step 3: Verify docs build
pip install -r docs/requirements.txt # if not already installed
mkdocs build
Step 4: Commit
git add -A
git commit -m "docs: clean architecture.md and index.md for v4.0"
Task 4.3: Write Data & Methods page¶
Files:
- Create: docs/reference/data-and-methods.md
- Modify: mkdocs.yml (add to nav)
This is the key new page — explains PhonoLex to researchers and SLPs without exposing implementation details.
Structure: 1. Overview — What PhonoLex measures and why 2. Word Properties — The 9 categories explained in plain language, what each measures, why an SLP would care. Full citations per dataset. 3. Phonological Similarity — How the similarity algorithm works at a conceptual level (component weights, what onset/nucleus/coda mean for rhyming vs. alliteration). Not DP tables — intuition. 4. Cognitive Association Data — The 7 datasets (SWOW, USF, MEN, ECCC, SPP, SimLex, WordSim) explained: what each measures, how many entries, what makes them different. 5. Contrastive Intervention Research — Gierut's maximal opposition, Storkel's multiple opposition. Brief clinical context for why these tools exist. 6. Phonological Features — PHOIBLE, 38 distinctive features, what they represent. Link to the full feature reference page. 7. Full Citations — Complete academic citations for all 18 datasets.
Content guidance: - Write for a researcher or curious SLP, not a developer - Every dataset gets: what it measures, who collected it, how many entries, and a citation - The similarity section should explain what the weights do in terms an SLP understands ("higher onset weight = prioritize words that start similarly") - No mention of D1, Hono, pickle, NetworkX, or any implementation detail
Step 1: Write the page (consult docs/about/citations.md, workers/src/config/properties.ts, and docs/reference/psycholinguistic-norms.md for accurate details)
Step 2: Add to mkdocs.yml nav under Reference:
- Reference:
- Data & Methods: reference/data-and-methods.md
- API: reference/api.md
- Psycholinguistic Norms: reference/psycholinguistic-norms.md
- PHOIBLE Features: reference/phoible-features.md
Step 3: Build and verify
mkdocs build
mkdocs serve # check at localhost:8000, ctrl+c when done
Step 4: Commit
git add -A
git commit -m "docs: add Data & Methods page for researchers and clinicians"
Task 4.4: Merge to release branch¶
git checkout release/4.0.0
git merge docs/4.0
Branch 5: seo/landing-pages¶
Task 5.1: Create branch and remove Ko-fi¶
Files:
- Modify: webapp/frontend/index.html
Step 1: Create branch
git checkout release/4.0.0
git checkout -b seo/landing-pages
Step 2: Remove Ko-fi widget from index.html
Delete everything from the <style> block (line ~100) through the closing </div> of kofi-overlay (line ~168). This removes:
- #kofi-btn styles
- #kofi-overlay styles
- #kofi-panel styles
- The Ko-fi button element
- The Ko-fi overlay/iframe element
Step 3: Update JSON-LD version
Change "softwareVersion": "3.0.0-beta" to "softwareVersion": "4.0.0".
Step 4: Commit
git add -A
git commit -m "chore: remove Ko-fi widget, update JSON-LD version"
Task 5.2: Create static landing pages¶
Files:
- Create: webapp/frontend/public/landing/minimal-pairs-generator.html
- Create: webapp/frontend/public/landing/slp-word-list-generator.html
- Create: webapp/frontend/public/landing/phonological-therapy-materials.html
Each landing page should be:
- Standalone HTML — no React, no JS dependency. Loads fast, indexable.
- Proper <head> — unique <title>, <meta description>, canonical URL, OG tags.
- Content — 300-500 words explaining the tool in SLP terms. What it does, who it's for, key features. Natural keyword usage.
- CTA — Clear link into the SPA tool (e.g., "Try the Minimal Pairs Generator" → https://phonolex.com/)
- Shared styling — Minimal inline CSS that matches PhonoLex brand (colors from theme: #1E5A5A primary). No external CSS dependency.
- Navigation — Links to other landing pages and back to the main app.
- Footer — "PhonoLex by Neumann's Workshop, LLC" with link to docs, privacy, terms.
Target keywords per page:
- minimal-pairs-generator.html: "minimal pairs generator", "minimal pairs for speech therapy", "SLP minimal pairs tool", "phonological contrast therapy"
- slp-word-list-generator.html: "SLP word list generator", "speech therapy word lists", "phonological word lists", "custom word lists for SLPs"
- phonological-therapy-materials.html: "phonological therapy materials", "speech sound disorder resources", "phonological intervention tools", "maximal opposition therapy"
Step 1: Write the three pages. Keep them lean — no frameworks, no build step.
Step 2: Commit
git add -A
git commit -m "seo: add static landing pages for SLP search terms"
Task 5.3: Update sitemap and add Cloudflare redirect rules¶
Files:
- Modify: webapp/frontend/public/sitemap.xml
- Modify: webapp/frontend/public/_redirects (Cloudflare Pages redirects)
Step 1: Update sitemap — add landing page URLs, update all <lastmod> dates
<url>
<loc>https://phonolex.com/landing/minimal-pairs-generator.html</loc>
<lastmod>2026-03-10</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://phonolex.com/landing/slp-word-list-generator.html</loc>
<lastmod>2026-03-10</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://phonolex.com/landing/phonological-therapy-materials.html</loc>
<lastmod>2026-03-10</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
Step 2: Verify _redirects doesn't interfere with landing page routing. Cloudflare Pages serves static files first, then falls back to SPA routing. Since landing pages are in public/, they'll be served directly — no redirect changes needed unless there's a catch-all.
Step 3: Commit
git add -A
git commit -m "seo: update sitemap with landing pages"
Task 5.4: Add Dependabot and engine fields¶
Files:
- Create: .github/dependabot.yml
- Modify: package.json, workers/package.json, webapp/frontend/package.json
Step 1: Create .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /workers
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: npm
directory: /webapp/frontend
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 3
Step 2: Add engines field to all three package.json files:
"engines": {
"node": ">=20"
}
Step 3: Commit
git add -A
git commit -m "chore: add Dependabot config and Node engine constraints"
Task 5.5: Merge to release branch¶
git checkout release/4.0.0
git merge seo/landing-pages
Branch 6: Final release¶
Task 6.1: Final verification¶
Step 1: Full test suite
cd workers && npm test
cd ../webapp/frontend && npm run type-check && npm run lint && npm run build
Step 2: Start dev server, verify all 5 tools + landing pages
Step 3: Check landing pages render as standalone HTML (no JS required)
Task 6.2: Merge to main and tag¶
git checkout main
git merge release/4.0.0
git tag -a v4.0.0 -m "PhonoLex 4.0.0 — out of beta"
git push origin main --tags
Task 6.3: Verify deployment¶
GitHub Actions will deploy on push to main. Verify: - https://phonolex.com — app loads - https://phonolex.com/landing/minimal-pairs-generator.html — landing page renders - https://phonolex.com/docs/ — docs site renders - https://phonolex.com/api/health — API responds
Execution Order¶
0.1 Setup release branch
2.1 React 19
2.2 MUI 5 → 6 (codemods)
2.3 MUI 6 → 7
2.4 Vite 7
2.5 Visual verification
2.6 Merge deps/react-mui-vite → release
3.1 ESLint 9 flat config
3.2 Vitest 4
3.3 Baseline API tests
3.4 Merge deps/eslint-vitest → release
4.1 Delete stale docs
4.2 Clean architecture + index
4.3 Write Data & Methods page
4.4 Merge docs/4.0 → release
5.1 Remove Ko-fi, fix JSON-LD
5.2 Landing pages
5.3 Sitemap
5.4 Dependabot + engines
5.5 Merge seo/landing-pages → release
6.1 Final verification
6.2 Merge to main + tag
6.3 Verify deployment
Branches 2, 3, 4, and 5 can be worked in parallel since they touch different files. The natural serial path is 2 → 3 (both are deps), then 4 and 5 in parallel.