Skip to content

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 InputPropsslotProps.input, Accordion TransitionPropsslotProps.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:

  1. minimal-pairs-generator.html: "minimal pairs generator", "minimal pairs for speech therapy", "SLP minimal pairs tool", "phonological contrast therapy"
  2. slp-word-list-generator.html: "SLP word list generator", "speech therapy word lists", "phonological word lists", "custom word lists for SLPs"
  3. 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.