scrabble-solver 2.15.25 → 2.16.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build.yml +3 -5
- package/.github/workflows/{npx.yml → bunx.yml} +7 -11
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/{cypress.yml → e2e-tests.yml} +7 -7
- package/.github/workflows/oxfmt.yml +3 -5
- package/.github/workflows/oxlint.yml +4 -6
- package/.github/workflows/{jest.yml → unit-tests.yml} +6 -8
- package/.nx/workspace-data/d/server-process.json +5 -0
- package/.nx/workspace-data/file-map.json +1321 -1309
- package/.nx/workspace-data/lockfile-dependencies.hash +1 -1
- package/.nx/workspace-data/lockfile-nodes.hash +1 -1
- package/.nx/workspace-data/nx_files.nxt +0 -0
- package/.nx/workspace-data/parsed-lock-file.dependencies.json +5301 -11011
- package/.nx/workspace-data/parsed-lock-file.nodes.json +16259 -7681
- package/.nx/workspace-data/project-graph.json +25619 -19944
- package/.nx/workspace-data/source-maps.json +96 -0
- package/.oxlintrc.json +0 -2
- package/CLAUDE.md +120 -0
- package/README.md +21 -21
- package/bin/scrabble-solver.js +1 -1
- package/bump-version.js +3 -4
- package/bun-test-globals.d.ts +1 -0
- package/bun.lock +3984 -0
- package/bun.test.preload.ts +21 -0
- package/bunfig.toml +2 -0
- package/lerna.json +1 -1
- package/package.json +18 -20
- package/packages/configs/package.json +3 -3
- package/packages/constants/package.json +1 -1
- package/packages/dictionaries/package.json +4 -4
- package/packages/logger/package.json +1 -1
- package/packages/scrabble-solver/.next/BUILD_ID +1 -1
- package/packages/scrabble-solver/.next/build-manifest.json +5 -5
- package/packages/scrabble-solver/.next/cache/.previewinfo +1 -1
- package/packages/scrabble-solver/.next/cache/.rscinfo +1 -1
- package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
- package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/prerender-manifest.json +4 -4
- package/packages/scrabble-solver/.next/required-server-files.js +2 -1
- package/packages/scrabble-solver/.next/required-server-files.json +2 -1
- package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
- package/packages/scrabble-solver/.next/server/chunks/712.js +1 -1
- package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/404.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/500.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/not-found.html +1 -1
- package/packages/scrabble-solver/.next/server/webpack-api-runtime.js +1 -1
- package/packages/scrabble-solver/.next/static/{FDUqldlsD7FXe6iWhIJmu → HFiEUpdZIGYd_XKb5mCLZ}/_buildManifest.js +1 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/{_app-c0932b8ca24945cb.js → _app-9c9aaea7b6881765.js} +4 -4
- package/packages/scrabble-solver/.next/static/chunks/pages/{index-08b3b0a754095919.js → index-e9ea63e97865edcf.js} +1 -1
- package/packages/scrabble-solver/.next/static/css/{500cdc9a24075d91.css → 78726ae7cf7a5497.css} +1 -1
- package/packages/scrabble-solver/.next/trace +21 -21
- package/packages/scrabble-solver/.next/trace-build +1 -1
- package/packages/scrabble-solver/README.md +3 -3
- package/packages/scrabble-solver/next.config.js +1 -0
- package/packages/scrabble-solver/package.json +9 -8
- package/packages/scrabble-solver/public/service-worker.js +1 -1
- package/packages/scrabble-solver/src/@types/scss.d.ts +5 -0
- package/packages/scrabble-solver/src/components/Board/Board.module.scss +2 -2
- package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +26 -6
- package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Button/Button.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +1 -1
- package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.module.scss +1 -1
- package/packages/scrabble-solver/src/components/IconButton/IconButton.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Loading/Loading.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Modal/Modal.module.scss +1 -1
- package/packages/scrabble-solver/src/components/NotFound/NotFound.module.scss +1 -1
- package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +11 -6
- package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Radio/Radio.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Results/Results.module.scss +1 -1
- package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Spinner/Spinner.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +1 -1
- package/packages/scrabble-solver/src/hooks/useLocalStorage.ts +4 -52
- package/packages/scrabble-solver/src/lib/isMac.ts +1 -1
- package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss +1 -1
- package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -1
- package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss +1 -1
- package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +1 -1
- package/packages/scrabble-solver/src/parameters/index.ts +45 -44
- package/packages/scrabble-solver/src/state/localStorage.ts +42 -52
- package/packages/scrabble-solver/src/state/settings/initialState.ts +7 -7
- package/packages/scrabble-solver/src/styles/_tokens.scss +59 -0
- package/packages/scrabble-solver/src/styles/global.scss +3 -3
- package/packages/scrabble-solver/src/styles/mixins.scss +13 -12
- package/packages/scrabble-solver/src/styles/variables.module.scss +53 -0
- package/packages/scrabble-solver/src/styles/variables.scss +44 -41
- package/packages/scrabble-solver/tsconfig.tsbuildinfo +1 -1
- package/packages/solver/package.json +7 -6
- package/packages/types/package.json +2 -2
- package/packages/word-definitions/package.json +4 -3
- package/packages/word-definitions/src/parse.test.ts +7 -2
- package/packages/word-lists/package.json +2 -2
- package/tsconfig.json +1 -1
- package/jest.config.js +0 -15
- package/jest.setup.js +0 -6
- package/tsconfig.jest.json +0 -10
- /package/packages/scrabble-solver/.next/static/{FDUqldlsD7FXe6iWhIJmu → HFiEUpdZIGYd_XKb5mCLZ}/_ssgManifest.js +0 -0
|
@@ -680,6 +680,10 @@
|
|
|
680
680
|
"packages/scrabble-solver/package.json",
|
|
681
681
|
"nx/core/package-json"
|
|
682
682
|
],
|
|
683
|
+
"metadata.targetGroups.NPM Scripts.6": [
|
|
684
|
+
"packages/scrabble-solver/package.json",
|
|
685
|
+
"nx/core/package-json"
|
|
686
|
+
],
|
|
683
687
|
"metadata.description": [
|
|
684
688
|
"packages/scrabble-solver/package.json",
|
|
685
689
|
"nx/core/package-json"
|
|
@@ -852,6 +856,34 @@
|
|
|
852
856
|
"packages/scrabble-solver/package.json",
|
|
853
857
|
"nx/core/package-json"
|
|
854
858
|
],
|
|
859
|
+
"targets.test": [
|
|
860
|
+
"packages/scrabble-solver/package.json",
|
|
861
|
+
"nx/core/package-json"
|
|
862
|
+
],
|
|
863
|
+
"targets.test.executor": [
|
|
864
|
+
"packages/scrabble-solver/package.json",
|
|
865
|
+
"nx/core/package-json"
|
|
866
|
+
],
|
|
867
|
+
"targets.test.options": [
|
|
868
|
+
"packages/scrabble-solver/package.json",
|
|
869
|
+
"nx/core/package-json"
|
|
870
|
+
],
|
|
871
|
+
"targets.test.metadata": [
|
|
872
|
+
"packages/scrabble-solver/package.json",
|
|
873
|
+
"nx/core/package-json"
|
|
874
|
+
],
|
|
875
|
+
"targets.test.options.script": [
|
|
876
|
+
"packages/scrabble-solver/package.json",
|
|
877
|
+
"nx/core/package-json"
|
|
878
|
+
],
|
|
879
|
+
"targets.test.metadata.scriptContent": [
|
|
880
|
+
"packages/scrabble-solver/package.json",
|
|
881
|
+
"nx/core/package-json"
|
|
882
|
+
],
|
|
883
|
+
"targets.test.metadata.runCommand": [
|
|
884
|
+
"packages/scrabble-solver/package.json",
|
|
885
|
+
"nx/core/package-json"
|
|
886
|
+
],
|
|
855
887
|
"targets.type-check": [
|
|
856
888
|
"packages/scrabble-solver/package.json",
|
|
857
889
|
"nx/core/package-json"
|
|
@@ -926,6 +958,10 @@
|
|
|
926
958
|
"packages/solver/package.json",
|
|
927
959
|
"nx/core/package-json"
|
|
928
960
|
],
|
|
961
|
+
"metadata.targetGroups.NPM Scripts.2": [
|
|
962
|
+
"packages/solver/package.json",
|
|
963
|
+
"nx/core/package-json"
|
|
964
|
+
],
|
|
929
965
|
"metadata.description": [
|
|
930
966
|
"packages/solver/package.json",
|
|
931
967
|
"nx/core/package-json"
|
|
@@ -1014,6 +1050,34 @@
|
|
|
1014
1050
|
"packages/solver/package.json",
|
|
1015
1051
|
"nx/core/package-json"
|
|
1016
1052
|
],
|
|
1053
|
+
"targets.test": [
|
|
1054
|
+
"packages/solver/package.json",
|
|
1055
|
+
"nx/core/package-json"
|
|
1056
|
+
],
|
|
1057
|
+
"targets.test.executor": [
|
|
1058
|
+
"packages/solver/package.json",
|
|
1059
|
+
"nx/core/package-json"
|
|
1060
|
+
],
|
|
1061
|
+
"targets.test.options": [
|
|
1062
|
+
"packages/solver/package.json",
|
|
1063
|
+
"nx/core/package-json"
|
|
1064
|
+
],
|
|
1065
|
+
"targets.test.metadata": [
|
|
1066
|
+
"packages/solver/package.json",
|
|
1067
|
+
"nx/core/package-json"
|
|
1068
|
+
],
|
|
1069
|
+
"targets.test.options.script": [
|
|
1070
|
+
"packages/solver/package.json",
|
|
1071
|
+
"nx/core/package-json"
|
|
1072
|
+
],
|
|
1073
|
+
"targets.test.metadata.scriptContent": [
|
|
1074
|
+
"packages/solver/package.json",
|
|
1075
|
+
"nx/core/package-json"
|
|
1076
|
+
],
|
|
1077
|
+
"targets.test.metadata.runCommand": [
|
|
1078
|
+
"packages/solver/package.json",
|
|
1079
|
+
"nx/core/package-json"
|
|
1080
|
+
],
|
|
1017
1081
|
"targets.build.dependsOn": [
|
|
1018
1082
|
"nx.json",
|
|
1019
1083
|
"nx/target-defaults"
|
|
@@ -1194,6 +1258,10 @@
|
|
|
1194
1258
|
"packages/word-definitions/package.json",
|
|
1195
1259
|
"nx/core/package-json"
|
|
1196
1260
|
],
|
|
1261
|
+
"metadata.targetGroups.NPM Scripts.2": [
|
|
1262
|
+
"packages/word-definitions/package.json",
|
|
1263
|
+
"nx/core/package-json"
|
|
1264
|
+
],
|
|
1197
1265
|
"metadata.description": [
|
|
1198
1266
|
"packages/word-definitions/package.json",
|
|
1199
1267
|
"nx/core/package-json"
|
|
@@ -1282,6 +1350,34 @@
|
|
|
1282
1350
|
"packages/word-definitions/package.json",
|
|
1283
1351
|
"nx/core/package-json"
|
|
1284
1352
|
],
|
|
1353
|
+
"targets.test": [
|
|
1354
|
+
"packages/word-definitions/package.json",
|
|
1355
|
+
"nx/core/package-json"
|
|
1356
|
+
],
|
|
1357
|
+
"targets.test.executor": [
|
|
1358
|
+
"packages/word-definitions/package.json",
|
|
1359
|
+
"nx/core/package-json"
|
|
1360
|
+
],
|
|
1361
|
+
"targets.test.options": [
|
|
1362
|
+
"packages/word-definitions/package.json",
|
|
1363
|
+
"nx/core/package-json"
|
|
1364
|
+
],
|
|
1365
|
+
"targets.test.metadata": [
|
|
1366
|
+
"packages/word-definitions/package.json",
|
|
1367
|
+
"nx/core/package-json"
|
|
1368
|
+
],
|
|
1369
|
+
"targets.test.options.script": [
|
|
1370
|
+
"packages/word-definitions/package.json",
|
|
1371
|
+
"nx/core/package-json"
|
|
1372
|
+
],
|
|
1373
|
+
"targets.test.metadata.scriptContent": [
|
|
1374
|
+
"packages/word-definitions/package.json",
|
|
1375
|
+
"nx/core/package-json"
|
|
1376
|
+
],
|
|
1377
|
+
"targets.test.metadata.runCommand": [
|
|
1378
|
+
"packages/word-definitions/package.json",
|
|
1379
|
+
"nx/core/package-json"
|
|
1380
|
+
],
|
|
1285
1381
|
"targets.build.dependsOn": [
|
|
1286
1382
|
"nx.json",
|
|
1287
1383
|
"nx/target-defaults"
|
package/.oxlintrc.json
CHANGED
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Repository layout
|
|
6
|
+
|
|
7
|
+
This is a Bun-workspaces monorepo (managed with Lerna for versioning/publishing and Nx purely for build caching/ordering). `engines` requires **Bun >=1.3** and **Node.js >=24**. The lockfile is `bun.lock` (text JSON, lockfileVersion 1) — `package-lock.json` was removed when the project migrated off npm/Jest in `#421` (Apr 2026). Workspace scripts use `bun run --filter <pattern> <script>` — the `'./packages/*'` glob runs in dependency order (built-in Nx cache), and `'*'` skips workspaces without that script (relevant for `test`).
|
|
8
|
+
|
|
9
|
+
The nine packages live under `packages/` and have a strict dependency order. When something breaks "downstream" of where you edited, rebuild the upstream package first:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
constants → types → configs ──┐
|
|
13
|
+
├──→ dictionaries → solver → scrabble-solver (Next.js app)
|
|
14
|
+
word-lists ───────────────────┤
|
|
15
|
+
word-definitions ─────────────┘
|
|
16
|
+
logger (independent, used by app + dictionaries)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `solver` — pure word-finding engine. Given a `Trie`, `Config`, `Board`, and `Tile[]`, returns scored `Result`s. Has no I/O. Pipeline is `generatePatterns → fillPattern (per pattern) → areDigraphsValid (only when `config.twoCharacterTiles` is non-empty) → getUniquePatterns → getPatternScore`. New solving rules belong in this pipeline; `solve.ts` itself is just orchestration.
|
|
20
|
+
- `dictionaries` — downloads/caches per-locale word lists to `$HOME/.scrabble-solver/dictionaries` and exposes them as `Trie`s. The `Dictionaries` class layers a `MemoryCache` over a `DiskCache` (`LayeredCache`) and uses a per-locale `createAsyncProxy` to coalesce concurrent downloads. Cache entries older than `CACHE_STALE_THRESHOLD` (1 day) are refreshed on access. Only this package and `logger` perform filesystem I/O — keep other packages pure so they can run in Edge / browser contexts.
|
|
21
|
+
- `word-lists` — pulls raw word lists from upstream sources (one fetcher per locale in `src/languages/`). Used by `dictionaries` during downloads.
|
|
22
|
+
- `word-definitions` — per-locale `crawl(word) → string` and a parser for each source (Merriam-Webster, CNRTL, DWDS, SJP, dexonline, vajehyab, etc.). Add a new locale by adding a `crawl` and a `parse` function in `src/languages/` and wiring them into `crawl.ts` / `parse.ts`. `parse.test.ts` is where you add fixture-based parser tests.
|
|
23
|
+
- `types` — domain model classes (`Board`, `Cell`, `Tile`, `Pattern`, `Result`, `Config`, `Locale`, …) plus `*Json` shapes. Most of these have a `fromJson` / `toJson` round-trip — use them at the wire boundary instead of hand-rolled serialization.
|
|
24
|
+
- `configs` — split into **games** (`scrabble`, `superScrabble`, `scrabbleDuel`, `letterLeague`, `crossplay`, `literaki`, `kelimelik` — each defines board size, bonuses, rack size, blanks count, bingo bonus) and **languages** (`english`, `french`, …, each spreads a base game config and overrides `locale` + `tiles`). A locale config is one game × one language; e.g. `polishScrabble` = `scrabble` ⊕ Polish tiles + digraphs. Adding a language means a new config here **and** the 15-step checklist under "Add a new language" in `README.md`.
|
|
25
|
+
- `logger` — Winston logger writing JSON to `$HOME/.scrabble-solver/logs/{all,error}.log`. Only `error` goes to console. Used server-side by the app + dictionaries; do **not** import from browser code.
|
|
26
|
+
- `constants` — shared primitives (`BLANK`, `BONUS_CHARACTER`, `BONUS_WORD`, …). No runtime dependencies.
|
|
27
|
+
|
|
28
|
+
### App package (`@scrabble-solver/scrabble-solver`)
|
|
29
|
+
|
|
30
|
+
- **Routing**: Next.js Pages Router (`src/pages/`). API routes: `solve`, `verify`, `visit`, `dictionary/[locale]/[word]`. The path alias `@/*` resolves to `src/*` (set in `tsconfig.json`).
|
|
31
|
+
- **State**: Redux Toolkit + Redux-Saga. Slices in `src/state/{board,cellFilters,dictionary,rack,results,settings,solve,verify}`, each exporting `<name>Slice` (reducer + actions) and selectors. The root saga in `state/sagas.ts` reacts to slice actions: `submit` → call SDK → write results back. `solve`, `verify`, and `dictionary` use `takeLatest` (only the latest in-flight request resolves); cell/rack edits use `takeEvery`. State is intentionally **not** serializable-checked (`serializableCheck: false`) because slices hold class instances (`Board`, `Tile`).
|
|
32
|
+
- **SDK layer (`src/sdk/`)**: thin browser/server clients for the four API routes. `findWordDefinitions` is memoized at the saga level via `lib/memoize`. Always go through SDK — never `fetch` directly from a saga or component.
|
|
33
|
+
- **Persistence**: settings, board, and rack auto-persist to `localStorage` via `store2` under the `scrabble-solver` namespace. The `useLocalStorage` hook (mounted in `pages/index.tsx`) subscribes to the three slices and writes them out on every change — **adding a new field to `SettingsState` is enough; you don't need to touch any save code** (PR #321, Apr 2026). On boot, `settingsInitialState` spreads `localStorage.getSettings()` last so persisted values win over the defaults computed at module-load time. When changing settings shape, add a migration block (see `migrateLegacySettings` for the pattern — dated comment with introduction date and life expectancy).
|
|
34
|
+
- **Service worker**: built by `WorkboxPlugin.InjectManifest` from `src/service-worker/index.ts` to `public/service-worker.js`. It precaches the app shell and intercepts `/api/solve` and `/api/verify` requests so the app can still solve offline once a dictionary is cached. Only generated in production builds (`!isServer && !dev`).
|
|
35
|
+
- **i18n**: `src/i18n/languages/<lang>.json` (8 languages, mapped to `Locale` in `i18n.ts`). The `LOCALE_FEATURES` registry in `src/i18n/constants.ts` carries per-locale UI metadata: `direction` ('ltr' | 'rtl'), `comma`/`separator` glyphs (Latin vs Arabic), flag `Icon`, language `label`/`name`, and `consonants`/`vowels` flags that drive the auto-group-tiles UI. The `useDirection` hook applies `direction` to `<html dir>`. Add a new locale by extending this map plus the i18n JSON dictionary plus a `Flag<XX>.svg` icon. `useTranslate()` is the lookup hook.
|
|
36
|
+
- **Layout**: `AppLayoutProvider` (`src/app-layout/`) is a React context exposing `useAppLayoutValue`. It was extracted from a custom hook for memoization reasons — consume layout via `useAppLayout()`, not by re-deriving it. The active modal in `pages/index.tsx` is tracked as a `Record<Modal, boolean>` patched through a single `patchModals` callback.
|
|
37
|
+
- **Styling**: SCSS modules with a shared design-token system. SCSS tokens live in `src/styles/_tokens.scss`; the same values are re-exported to TS via `:export` in `variables.module.scss` (imported in JS as a module, typed by `src/@types/scss.d.ts`). Concrete JS constants live in `src/parameters/index.ts` (e.g. `BREAKPOINTS`, `COLOR_BLUE`, `TRANSITION_DURATION`) — that file is the only place that should read from `variables.module.scss`. Update `_tokens.scss` first → expose via `variables.module.scss` `:export` block → consume via `parameters/`. This was added in PR #228 (Apr 2026); before it, JS-side colors and breakpoints were hard-coded duplicates. Responsive helpers come from `include-media`.
|
|
38
|
+
- **SVGs**: imported as React components via `@svgr/webpack` (configured in `next.config.js`), typed by `src/@types/svg.d.ts`.
|
|
39
|
+
- **Service worker registration**: production-only. Registered by `serviceWorkerManager.ts` from the index page; Cypress tests must call `unregisterServiceWorkers()` (in `cypress/support/lib`) in `beforeEach` and `cy.clearLocalStorage()` in `afterEach` to avoid bleed-through between tests.
|
|
40
|
+
|
|
41
|
+
## Common commands
|
|
42
|
+
|
|
43
|
+
All commands run from the repo root unless noted.
|
|
44
|
+
|
|
45
|
+
| Task | Command |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| Install + build everything | `bun install && bun run build` |
|
|
48
|
+
| Build all packages | `bun run build` (Nx-cached, respects dep order) |
|
|
49
|
+
| Build one package | `bun run --filter @scrabble-solver/<name> build` |
|
|
50
|
+
| Dev server (port 3000) | `bun run dev` |
|
|
51
|
+
| Production server (port 3333) | `bun start` |
|
|
52
|
+
| Lint | `bun run lint` (oxlint) / `bun run lint:fix` |
|
|
53
|
+
| Format check / fix | `bun run format` / `bun run format:fix` (oxfmt) |
|
|
54
|
+
| Type-check the app | `bun run --filter @scrabble-solver/scrabble-solver type-check` (uses `tsgo`, the TypeScript native preview) |
|
|
55
|
+
| Unit tests (all workspaces) | `bun run test-unit` |
|
|
56
|
+
| Unit tests (one package) | `bun run --filter @scrabble-solver/solver test` |
|
|
57
|
+
| One unit test file | `cd packages/solver && bun test src/solve.test.ts` |
|
|
58
|
+
| One unit test by name | `cd packages/solver && bun test -t "pattern"` |
|
|
59
|
+
| Cypress (interactive) | `bun run test-cypress` (expects dev server on :3000) |
|
|
60
|
+
| Cypress (headless) | `bun run test-cypress:run` (expects server on :3333) |
|
|
61
|
+
| Full test pipeline | `bun run test` (build → unit → `start-server-and-test` boots the app on :3333 → cypress run) — note: `bun test` invokes Bun's built-in test runner, not this script |
|
|
62
|
+
|
|
63
|
+
Hot reload only works for the `scrabble-solver` package. Edits to any other package require rebuilding that package before the app picks them up.
|
|
64
|
+
|
|
65
|
+
## Testing notes
|
|
66
|
+
|
|
67
|
+
- Unit tests run on **Bun's test runner**, not Jest. The API is Jest-compatible (`describe`/`it`/`expect`), which is why the oxlint config still loads the `jest` plugin for rules like `no-focused-tests`.
|
|
68
|
+
- Only `solver`, `word-definitions`, and `scrabble-solver` have a `test` script. Tests are auto-discovered under `src/` matching `*.test.ts(x)`. The 180s timeout is needed because some solver tests build a real `Trie` from a downloaded dictionary.
|
|
69
|
+
- `bunfig.toml` + `bun.test.preload.ts` register a SCSS loader stub (returns a `Proxy` whose keys are their own names) so component tests can import `*.scss` modules without a real compiler. If you add other non-JS imports to test-touched code (images, etc.), extend the preload.
|
|
70
|
+
- Each package's `tsconfig.json` excludes `**/*.test.ts` from the build output. Tests are not part of published packages.
|
|
71
|
+
- Cypress: tests in `cypress/e2e/`. Custom command setup in `cypress/support/commands.ts` (registers `@testing-library/cypress` and `cypress-real-events`). Two base URLs are in play: `cypress.config.ts` defaults to `http://localhost:3000` (matches `bun run dev`); `test-cypress:run` and CI override to `:3333` (matches `bun start`). Pick the script that matches the server you're actually running.
|
|
72
|
+
|
|
73
|
+
## Tooling specifics
|
|
74
|
+
|
|
75
|
+
- **Linting**: `oxlint` (Rust-based ESLint replacement) configured in `.oxlintrc.json`. Type-aware rules require `oxlint-tsgolint`. Adding a new top-level JS config file usually means adding it to `ignorePatterns`. The oxlint config still loads the `jest` plugin and `jest` global because Bun's test runner mirrors the Jest API; do not remove them.
|
|
76
|
+
- **Formatting**: `oxfmt` covers `*.{js,ts,tsx,scss}`.
|
|
77
|
+
- **TypeScript**: the project upgraded to TypeScript 7 via the native-preview compiler (PR #422). The runtime devDep is `typescript@^6.0.3` (kept for tooling that expects classic `tsc`), but the actual compiler used by the app's `type-check` and by `next build` is `tsgo` from `@typescript/native-preview` (a 7.x dev build). Plain `tsc` is not in the build path. Root `tsconfig.json` sets `types: ["bun"]` for global test-runner types and excludes `cypress` and `cypress.config.ts`; library packages extend it and additionally exclude `**/*.test.ts` from emitted output.
|
|
78
|
+
- **Next.js**: built with `--webpack` flag explicitly (the default Turbopack is intentionally not used). `next.config.js` registers `@svgr/webpack` for SVG-as-component imports and the Workbox `InjectManifest` plugin for the service worker. SCSS load paths are extended to `./src` and `node_modules/include-media/dist`.
|
|
79
|
+
- **Nx**: `nx.json` only defines a `build` target with `dependsOn: ["^build"]` and `cache: true`. It is used purely for dependency-aware build ordering and caching — there are no Nx generators or executors.
|
|
80
|
+
|
|
81
|
+
## CI workflows
|
|
82
|
+
|
|
83
|
+
`.github/workflows/`:
|
|
84
|
+
|
|
85
|
+
- `build.yml` — `bun install --frozen-lockfile && bun run build`.
|
|
86
|
+
- `unit-tests.yml` — `bun run build && bun run test-unit`.
|
|
87
|
+
- `e2e-tests.yml` — Cypress against `bun start` on :3333. Uploads screenshots on failure.
|
|
88
|
+
- `oxlint.yml` / `oxfmt.yml` — lint and format-check.
|
|
89
|
+
- `bunx.yml` — runs daily and on PR. Installs the latest published `scrabble-solver` via `bun add --global scrabble-solver@latest`, runs Cypress against it. Catches packaging regressions in the `bin/scrabble-solver.js` launcher.
|
|
90
|
+
- `deploy.yml` — `workflow_dispatch` only. SSHs into prod, pulls, builds, restarts `scrabble-solver.service`.
|
|
91
|
+
|
|
92
|
+
When adding a workflow, match the existing pattern: trigger on `push`/`pull_request` to `master`, use `actions/checkout@v6` and `oven-sh/setup-bun@v2`, install with `bun install --frozen-lockfile`.
|
|
93
|
+
|
|
94
|
+
## Versioning & publishing
|
|
95
|
+
|
|
96
|
+
`bun run release` chains `reinstall → version:bump → np → lerna publish from-package`. `version:bump` runs `lerna version --force-publish` (bumps every package in lockstep) followed by `bump-version.js` to sync any other version references, then commits. Don't hand-edit `version` fields across packages — use the script.
|
|
97
|
+
|
|
98
|
+
## Deploys
|
|
99
|
+
|
|
100
|
+
The `Deploy` GitHub workflow (`workflow_dispatch` only) SSHs into the production host, pulls the chosen branch, runs `bun install && bun run build`, and restarts `scrabble-solver.service` via `systemctl`. There's no separate staging environment.
|
|
101
|
+
|
|
102
|
+
## Runtime data
|
|
103
|
+
|
|
104
|
+
The app reads/writes user data outside the project directory:
|
|
105
|
+
|
|
106
|
+
- `$HOME/.scrabble-solver/dictionaries/` — cached `Trie`s, one per locale, refreshed when older than 1 day.
|
|
107
|
+
- `$HOME/.scrabble-solver/logs/{all,error}.log` — Winston JSON logs.
|
|
108
|
+
|
|
109
|
+
The `bunx scrabble-solver@latest` entry point (`bin/scrabble-solver.js`) just `cd`s to the package root and runs `bun start`. The app then serves on http://localhost:3333.
|
|
110
|
+
|
|
111
|
+
## Recent migrations to keep in mind
|
|
112
|
+
|
|
113
|
+
Look here when something seems set up oddly — the reason is usually one of these recent changes. Reference issue numbers, not dates, when grepping git log.
|
|
114
|
+
|
|
115
|
+
- **#421 — Bun migration** (Apr 2026). npm/Jest → Bun. Top-level scripts now use `bun run --filter`, lockfile is `bun.lock`, unit tests run on `bun test`, the published binary's launcher (`bin/scrabble-solver.js`) shells out to `bun start`, and the old `npx.yml` workflow was renamed to `bunx.yml` (now installs the published package via `bun add --global`). All workflows use `oven-sh/setup-bun@v2`; `e2e-tests.yml` is the only one that also uses `setup-node` (Cypress action).
|
|
116
|
+
- **#422 — TypeScript 7** (Apr 2026). Build/type-check use `tsgo` (native preview). Don't reintroduce `tsc` calls.
|
|
117
|
+
- **#420 — ESLint → Oxlint**. The `eslint-plugin-*` packages still appear in devDeps because oxlint loads them as JS plugins (`jsPlugins` in `.oxlintrc.json`). Don't strip them.
|
|
118
|
+
- **#321 — Auto-persisted settings** (Apr 2026). Settings, board, and rack are written through a single `useLocalStorage` effect. Don't dispatch save actions manually.
|
|
119
|
+
- **#228 — CSS variables in JS** (Apr 2026). JS constants for colors/sizes flow from `_tokens.scss` → `variables.module.scss` `:export` → `parameters/index.ts`. Don't hard-code the same values in TS.
|
|
120
|
+
- **#360 — Dart Sass deprecations**. SCSS files migrated to modern syntax (`@use`, `math.div`, etc.). New SCSS should follow that style; `next.config.js` sets `sassOptions.quietDeps: true` to keep upstream warnings out of build output.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
20
|
<p>
|
|
21
|
-
You can <a href="#run">run</a> it on your machine: <code>
|
|
21
|
+
You can <a href="#run">run</a> it on your machine: <code>bunx scrabble-solver@latest</code>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p>
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
|
|
42
42
|
<p>
|
|
43
43
|
<img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/build.yml/badge.svg" alt="Build" />
|
|
44
|
-
<img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/jest.yml/badge.svg" alt="Jest Tests" />
|
|
45
|
-
<img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/cypress.yml/badge.svg" alt="Cypress Tests" />
|
|
46
44
|
<img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/oxlint.yml/badge.svg" alt="Oxlint" />
|
|
47
45
|
<img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/oxfmt.yml/badge.svg" alt="Oxfmt" />
|
|
48
|
-
<img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/
|
|
46
|
+
<img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/e2e-tests.yml/badge.svg" alt="E2E tests" />
|
|
47
|
+
<img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/unit-tests.yml/badge.svg" alt="Unit tests" />
|
|
48
|
+
<img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/bunx.yml/badge.svg" alt="bunx" />
|
|
49
49
|
</p>
|
|
50
50
|
|
|
51
51
|
<img alt="Screencast GIF showing user interface when solving for oxyphenbutazone, which is a top-scoring word in English version of Scrabble" src="https://raw.githubusercontent.com/kamilmielnik/scrabble-solver/master/screencast.gif" />
|
|
@@ -86,10 +86,10 @@ Some of the word lists below are sourced from the companion repository [kamilmie
|
|
|
86
86
|
|
|
87
87
|
## Run
|
|
88
88
|
|
|
89
|
-
You can run Scrabble Solver on your machine - all you need is [
|
|
89
|
+
You can run Scrabble Solver on your machine - all you need is [Bun](https://bun.sh/) 1.3 or later.
|
|
90
90
|
|
|
91
91
|
```Shell
|
|
92
|
-
|
|
92
|
+
bunx scrabble-solver@latest
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
Give it a while to install and download dictionaries. When it's done it will automatically open http://localhost:3333/ in your browser.
|
|
@@ -116,8 +116,8 @@ One-time project setup.
|
|
|
116
116
|
```Shell
|
|
117
117
|
git clone https://github.com/kamilmielnik/scrabble-solver.git
|
|
118
118
|
cd scrabble-solver
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
bun install
|
|
120
|
+
bun run build
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
### Run app dev server
|
|
@@ -125,7 +125,7 @@ npm run build
|
|
|
125
125
|
The following command will serve the app at http://localhost:3000/.
|
|
126
126
|
|
|
127
127
|
```Shell
|
|
128
|
-
|
|
128
|
+
bun run dev
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
Note: hot code reload works only for the [`scrabble-solver`](https://github.com/kamilmielnik/scrabble-solver/tree/master/packages/scrabble-solver) package. If you make changes to any other package, you will need to rebuild it ([see below](#rebuild-a-single-package)).
|
|
@@ -133,7 +133,7 @@ Note: hot code reload works only for the [`scrabble-solver`](https://github.com/
|
|
|
133
133
|
### Rebuild the entire project
|
|
134
134
|
|
|
135
135
|
```Shell
|
|
136
|
-
|
|
136
|
+
bun run build
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
### Rebuild a single package
|
|
@@ -141,15 +141,15 @@ npm run build
|
|
|
141
141
|
For convenience, here's a list of commands to rebuild every package individually.
|
|
142
142
|
|
|
143
143
|
```Shell
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
bun run --filter @scrabble-solver/configs build
|
|
145
|
+
bun run --filter @scrabble-solver/constants build
|
|
146
|
+
bun run --filter @scrabble-solver/dictionaries build
|
|
147
|
+
bun run --filter @scrabble-solver/logger build
|
|
148
|
+
bun run --filter @scrabble-solver/scrabble-solver build
|
|
149
|
+
bun run --filter @scrabble-solver/solver build
|
|
150
|
+
bun run --filter @scrabble-solver/types build
|
|
151
|
+
bun run --filter @scrabble-solver/word-definitions build
|
|
152
|
+
bun run --filter @scrabble-solver/word-lists build
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
### Add a new language
|
|
@@ -161,7 +161,7 @@ npm run build -w @scrabble-solver/word-lists
|
|
|
161
161
|
4. Add IETF language tag for the new locale in [packages/types/src/Locale.ts](https://github.com/kamilmielnik/scrabble-solver/blob/master/packages/types/src/Locale.ts)
|
|
162
162
|
5. Rebuild the types package
|
|
163
163
|
```Shell
|
|
164
|
-
|
|
164
|
+
bun run --filter @scrabble-solver/types build
|
|
165
165
|
```
|
|
166
166
|
6. Add locale configuration in [packages/scrabble-solver/src/i18n/constants.ts](https://github.com/kamilmielnik/scrabble-solver/blob/master/packages/scrabble-solver/src/i18n/constants.ts)
|
|
167
167
|
7. Update locale-detecting code in [packages/scrabble-solver/src/state/settings/lib.ts](https://github.com/kamilmielnik/scrabble-solver/blob/master/packages/scrabble-solver/src/state/settings/lib.ts)
|
|
@@ -179,6 +179,7 @@ npm run build -w @scrabble-solver/word-lists
|
|
|
179
179
|
## Tech stack
|
|
180
180
|
|
|
181
181
|
- [TypeScript](https://www.typescriptlang.org/)
|
|
182
|
+
- [Bun](https://bun.sh/docs)
|
|
182
183
|
- [Node.js](https://nodejs.org/)
|
|
183
184
|
- [Next.js](https://nextjs.org/)
|
|
184
185
|
- [Express](https://expressjs.com/)
|
|
@@ -192,7 +193,6 @@ npm run build -w @scrabble-solver/word-lists
|
|
|
192
193
|
- [include-media](https://eduardoboucas.github.io/include-media/)
|
|
193
194
|
- [Lerna](https://lerna.js.org/)
|
|
194
195
|
- [Cypress](https://www.cypress.io/)
|
|
195
|
-
- [Jest](https://jestjs.io/)
|
|
196
196
|
- [Oxlint](https://oxc.rs/docs/guide/usage/linter)
|
|
197
197
|
- [Oxfmt](https://oxc.rs/docs/guide/usage/formatter)
|
|
198
198
|
|
package/bin/scrabble-solver.js
CHANGED
package/bump-version.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Due to the use of
|
|
2
|
+
* Due to the use of Bun workspaces we need to explicitly specify "@scrabble-solver/scrabble-solver"
|
|
3
3
|
* package in the dependencies of this very "scrabble-solver" package. Otherwise none of
|
|
4
|
-
* the "dependencies" from underlying packages/ would have been installed and any
|
|
5
|
-
* that work on subpackages would not work - specifically:
|
|
4
|
+
* the "dependencies" from underlying packages/ would have been installed and any package scripts
|
|
5
|
+
* that work on subpackages would not work - specifically: bun start.
|
|
6
6
|
*
|
|
7
7
|
* This script exists to ensure that the dependency version is bumped during the release.
|
|
8
8
|
*/
|
|
@@ -24,4 +24,3 @@ const updateDependencyVersion = (filename, dependency, version) => {
|
|
|
24
24
|
|
|
25
25
|
const currentAppVersion = getCurrentAppVersion();
|
|
26
26
|
updateDependencyVersion('package.json', '@scrabble-solver/scrabble-solver', currentAppVersion);
|
|
27
|
-
updateDependencyVersion('package-lock.json', '@scrabble-solver/scrabble-solver', currentAppVersion);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="bun-types/test-globals" />
|