scrabble-solver 2.15.25 → 2.16.0-alpha.1

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.
Files changed (125) hide show
  1. package/.github/workflows/build.yml +3 -5
  2. package/.github/workflows/{npx.yml → bunx.yml} +7 -11
  3. package/.github/workflows/deploy.yml +2 -2
  4. package/.github/workflows/{cypress.yml → e2e-tests.yml} +7 -7
  5. package/.github/workflows/oxfmt.yml +3 -5
  6. package/.github/workflows/oxlint.yml +4 -6
  7. package/.github/workflows/{jest.yml → unit-tests.yml} +6 -8
  8. package/.nx/workspace-data/d/server-process.json +5 -0
  9. package/.nx/workspace-data/file-map.json +467 -455
  10. package/.nx/workspace-data/lockfile-dependencies.hash +1 -1
  11. package/.nx/workspace-data/lockfile-nodes.hash +1 -1
  12. package/.nx/workspace-data/nx_files.nxt +0 -0
  13. package/.nx/workspace-data/parsed-lock-file.dependencies.json +5301 -11011
  14. package/.nx/workspace-data/parsed-lock-file.nodes.json +16259 -7681
  15. package/.nx/workspace-data/project-graph.json +25619 -19944
  16. package/.nx/workspace-data/source-maps.json +96 -0
  17. package/.oxlintrc.json +0 -2
  18. package/CLAUDE.md +120 -0
  19. package/README.md +26 -20
  20. package/bin/scrabble-solver.js +1 -1
  21. package/bump-version.js +4 -4
  22. package/bun-test-globals.d.ts +1 -0
  23. package/bun.lock +3984 -0
  24. package/bun.test.preload.ts +21 -0
  25. package/bunfig.toml +2 -0
  26. package/lerna.json +1 -1
  27. package/package.json +19 -21
  28. package/packages/configs/package.json +3 -3
  29. package/packages/constants/package.json +1 -1
  30. package/packages/dictionaries/package.json +4 -4
  31. package/packages/logger/package.json +1 -1
  32. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  33. package/packages/scrabble-solver/.next/build-manifest.json +5 -5
  34. package/packages/scrabble-solver/.next/cache/.previewinfo +1 -1
  35. package/packages/scrabble-solver/.next/cache/.rscinfo +1 -1
  36. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  37. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  38. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  39. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
  40. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  41. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  42. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  43. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  44. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
  45. package/packages/scrabble-solver/.next/prerender-manifest.json +4 -4
  46. package/packages/scrabble-solver/.next/required-server-files.js +2 -1
  47. package/packages/scrabble-solver/.next/required-server-files.json +2 -1
  48. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  49. package/packages/scrabble-solver/.next/server/chunks/712.js +1 -1
  50. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  51. package/packages/scrabble-solver/.next/server/pages/404.html +1 -1
  52. package/packages/scrabble-solver/.next/server/pages/500.html +1 -1
  53. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  54. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  55. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  56. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  57. package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
  58. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  59. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
  60. package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
  61. package/packages/scrabble-solver/.next/server/pages/index.html +1 -1
  62. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  63. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  64. package/packages/scrabble-solver/.next/server/pages/not-found.html +1 -1
  65. package/packages/scrabble-solver/.next/server/webpack-api-runtime.js +1 -1
  66. package/packages/scrabble-solver/.next/static/{FDUqldlsD7FXe6iWhIJmu → YS42EFHDGPCCe3JyeXaNi}/_buildManifest.js +1 -1
  67. package/packages/scrabble-solver/.next/static/chunks/pages/{_app-c0932b8ca24945cb.js → _app-9c9aaea7b6881765.js} +4 -4
  68. package/packages/scrabble-solver/.next/static/chunks/pages/{index-08b3b0a754095919.js → index-e9ea63e97865edcf.js} +1 -1
  69. package/packages/scrabble-solver/.next/static/css/{500cdc9a24075d91.css → 78726ae7cf7a5497.css} +1 -1
  70. package/packages/scrabble-solver/.next/trace +21 -21
  71. package/packages/scrabble-solver/.next/trace-build +1 -1
  72. package/packages/scrabble-solver/README.md +3 -3
  73. package/packages/scrabble-solver/next.config.js +1 -0
  74. package/packages/scrabble-solver/package.json +9 -8
  75. package/packages/scrabble-solver/public/service-worker.js +1 -1
  76. package/packages/scrabble-solver/src/@types/scss.d.ts +5 -0
  77. package/packages/scrabble-solver/src/components/Board/Board.module.scss +2 -2
  78. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +1 -1
  79. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +26 -6
  80. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.module.scss +1 -1
  81. package/packages/scrabble-solver/src/components/Button/Button.module.scss +1 -1
  82. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +1 -1
  83. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.module.scss +1 -1
  84. package/packages/scrabble-solver/src/components/IconButton/IconButton.module.scss +1 -1
  85. package/packages/scrabble-solver/src/components/Loading/Loading.module.scss +1 -1
  86. package/packages/scrabble-solver/src/components/Modal/Modal.module.scss +1 -1
  87. package/packages/scrabble-solver/src/components/NotFound/NotFound.module.scss +1 -1
  88. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.module.scss +1 -1
  89. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +11 -6
  90. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +1 -1
  91. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.module.scss +1 -1
  92. package/packages/scrabble-solver/src/components/Radio/Radio.module.scss +1 -1
  93. package/packages/scrabble-solver/src/components/Results/Results.module.scss +1 -1
  94. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.module.scss +1 -1
  95. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +1 -1
  96. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.module.scss +1 -1
  97. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +1 -1
  98. package/packages/scrabble-solver/src/components/Spinner/Spinner.module.scss +1 -1
  99. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +1 -1
  100. package/packages/scrabble-solver/src/hooks/useLocalStorage.ts +4 -52
  101. package/packages/scrabble-solver/src/lib/isMac.ts +1 -1
  102. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss +1 -1
  103. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -1
  104. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss +1 -1
  105. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +1 -1
  106. package/packages/scrabble-solver/src/parameters/index.ts +45 -44
  107. package/packages/scrabble-solver/src/state/localStorage.ts +42 -52
  108. package/packages/scrabble-solver/src/state/settings/initialState.ts +7 -7
  109. package/packages/scrabble-solver/src/styles/_tokens.scss +59 -0
  110. package/packages/scrabble-solver/src/styles/global.scss +3 -3
  111. package/packages/scrabble-solver/src/styles/mixins.scss +13 -12
  112. package/packages/scrabble-solver/src/styles/variables.module.scss +53 -0
  113. package/packages/scrabble-solver/src/styles/variables.scss +44 -41
  114. package/packages/scrabble-solver/tsconfig.tsbuildinfo +1 -1
  115. package/packages/solver/package.json +7 -6
  116. package/packages/types/package.json +2 -2
  117. package/packages/word-definitions/package.json +4 -3
  118. package/packages/word-definitions/src/parse.test.ts +7 -2
  119. package/packages/word-lists/package.json +2 -2
  120. package/todo.txt +3 -0
  121. package/tsconfig.json +1 -1
  122. package/jest.config.js +0 -15
  123. package/jest.setup.js +0 -6
  124. package/tsconfig.jest.json +0 -10
  125. /package/packages/scrabble-solver/.next/static/{FDUqldlsD7FXe6iWhIJmu → YS42EFHDGPCCe3JyeXaNi}/_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
@@ -58,8 +58,6 @@
58
58
  "**/.eslintrc.js",
59
59
  "**/babel.config.js",
60
60
  "**/bump-version.js",
61
- "**/jest.config.js",
62
- "**/jest.setup.js",
63
61
  "packages/scrabble-solver/next.config.js",
64
62
  "packages/logger/scripts/stats.js",
65
63
  "packages/scrabble-solver/public/service-worker.js",
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>npx scrabble-solver@latest</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>
@@ -36,16 +36,17 @@
36
36
  <p>
37
37
  <img src="https://img.shields.io/github/package-json/v/kamilmielnik/scrabble-solver" alt="Version" />
38
38
  <img src="https://img.shields.io/npm/l/scrabble-solver" alt="License" />
39
+ <img src="https://img.shields.io/badge/bun-%3E=1.3-brightgreen.svg" />
39
40
  <img src="https://img.shields.io/node/v/scrabble-solver" alt="Node version" />
40
41
  </p>
41
42
 
42
43
  <p>
43
44
  <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
45
  <img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/oxlint.yml/badge.svg" alt="Oxlint" />
47
46
  <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/npx.yml/badge.svg" alt="npx" />
47
+ <img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/e2e-tests.yml/badge.svg" alt="E2E tests" />
48
+ <img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/unit-tests.yml/badge.svg" alt="Unit tests" />
49
+ <img src="https://github.com/kamilmielnik/scrabble-solver/actions/workflows/bunx.yml/badge.svg" alt="bunx" />
49
50
  </p>
50
51
 
51
52
  <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,8 +87,13 @@ Some of the word lists below are sourced from the companion repository [kamilmie
86
87
 
87
88
  ## Run
88
89
 
89
- You can run Scrabble Solver on your machine - all you need is [Node.js](https://nodejs.org/) 24 or later.
90
+ You can run Scrabble Solver on your machine - all you need is [Bun](https://bun.sh/) 1.3 or later.
90
91
 
92
+ ```Shell
93
+ bunx scrabble-solver@latest
94
+ ```
95
+
96
+ Alternatively you can also use [Node.js](https://nodejs.org/) 24 or later.
91
97
  ```Shell
92
98
  npx scrabble-solver@latest
93
99
  ```
@@ -116,8 +122,8 @@ One-time project setup.
116
122
  ```Shell
117
123
  git clone https://github.com/kamilmielnik/scrabble-solver.git
118
124
  cd scrabble-solver
119
- npm install
120
- npm run build
125
+ bun install
126
+ bun run build
121
127
  ```
122
128
 
123
129
  ### Run app dev server
@@ -125,7 +131,7 @@ npm run build
125
131
  The following command will serve the app at http://localhost:3000/.
126
132
 
127
133
  ```Shell
128
- npm run dev
134
+ bun run dev
129
135
  ```
130
136
 
131
137
  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 +139,7 @@ Note: hot code reload works only for the [`scrabble-solver`](https://github.com/
133
139
  ### Rebuild the entire project
134
140
 
135
141
  ```Shell
136
- npm run build
142
+ bun run build
137
143
  ```
138
144
 
139
145
  ### Rebuild a single package
@@ -141,15 +147,15 @@ npm run build
141
147
  For convenience, here's a list of commands to rebuild every package individually.
142
148
 
143
149
  ```Shell
144
- npm run build -w @scrabble-solver/configs
145
- npm run build -w @scrabble-solver/constants
146
- npm run build -w @scrabble-solver/dictionaries
147
- npm run build -w @scrabble-solver/logger
148
- npm run build -w @scrabble-solver/scrabble-solver
149
- npm run build -w @scrabble-solver/solver
150
- npm run build -w @scrabble-solver/types
151
- npm run build -w @scrabble-solver/word-definitions
152
- npm run build -w @scrabble-solver/word-lists
150
+ bun run --filter @scrabble-solver/configs build
151
+ bun run --filter @scrabble-solver/constants build
152
+ bun run --filter @scrabble-solver/dictionaries build
153
+ bun run --filter @scrabble-solver/logger build
154
+ bun run --filter @scrabble-solver/scrabble-solver build
155
+ bun run --filter @scrabble-solver/solver build
156
+ bun run --filter @scrabble-solver/types build
157
+ bun run --filter @scrabble-solver/word-definitions build
158
+ bun run --filter @scrabble-solver/word-lists build
153
159
  ```
154
160
 
155
161
  ### Add a new language
@@ -161,7 +167,7 @@ npm run build -w @scrabble-solver/word-lists
161
167
  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
168
  5. Rebuild the types package
163
169
  ```Shell
164
- npm run build -w @scrabble-solver/types
170
+ bun run --filter @scrabble-solver/types build
165
171
  ```
166
172
  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
173
  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 +185,7 @@ npm run build -w @scrabble-solver/word-lists
179
185
  ## Tech stack
180
186
 
181
187
  - [TypeScript](https://www.typescriptlang.org/)
188
+ - [Bun](https://bun.sh/docs)
182
189
  - [Node.js](https://nodejs.org/)
183
190
  - [Next.js](https://nextjs.org/)
184
191
  - [Express](https://expressjs.com/)
@@ -192,7 +199,6 @@ npm run build -w @scrabble-solver/word-lists
192
199
  - [include-media](https://eduardoboucas.github.io/include-media/)
193
200
  - [Lerna](https://lerna.js.org/)
194
201
  - [Cypress](https://www.cypress.io/)
195
- - [Jest](https://jestjs.io/)
196
202
  - [Oxlint](https://oxc.rs/docs/guide/usage/linter)
197
203
  - [Oxfmt](https://oxc.rs/docs/guide/usage/formatter)
198
204
 
@@ -5,4 +5,4 @@ const path = require('path');
5
5
 
6
6
  const rootDirectory = path.join(__dirname, '..');
7
7
  process.chdir(rootDirectory);
8
- execSync('npm start');
8
+ execSync('bun start');
package/bump-version.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Due to the use of npm workspaces we need to explicitly specify "@scrabble-solver/scrabble-solver"
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 npm scripts
5
- * that work on subpackages would not work - specifically: npm start.
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,4 @@ 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);
27
+ updateDependencyVersion('bun.lock', '@scrabble-solver/scrabble-solver', currentAppVersion);
@@ -0,0 +1 @@
1
+ /// <reference types="bun-types/test-globals" />