scrabble-solver 2.16.7 → 2.18.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.
Files changed (589) hide show
  1. package/.github/workflows/bunx.yml +13 -10
  2. package/.github/workflows/e2e.yml +7 -9
  3. package/.github/workflows/npx.yml +13 -10
  4. package/.nx/workspace-data/d/server-process.json +2 -2
  5. package/.nx/workspace-data/file-map.json +796 -816
  6. package/.nx/workspace-data/lockfile-dependencies.hash +1 -1
  7. package/.nx/workspace-data/lockfile-nodes.hash +1 -1
  8. package/.nx/workspace-data/nx_files.nxt +0 -0
  9. package/.nx/workspace-data/parsed-lock-file.dependencies.json +218 -1298
  10. package/.nx/workspace-data/parsed-lock-file.nodes.json +770 -2669
  11. package/.nx/workspace-data/project-graph.json +1083 -4104
  12. package/.nx/workspace-data/source-maps.json +128 -0
  13. package/.oxlintrc.json +13 -10
  14. package/CLAUDE.md +36 -28
  15. package/README.md +17 -1
  16. package/bun.lock +233 -528
  17. package/e2e/app.spec.ts +124 -0
  18. package/e2e/bugs/112-character-bonus.spec.ts +25 -0
  19. package/e2e/bugs/129-esc-sidebar.spec.ts +17 -0
  20. package/e2e/bugs/2-q-tile-in-english.spec.ts +19 -0
  21. package/e2e/bugs/3-x-tile-in-polish.spec.ts +19 -0
  22. package/e2e/bugs/30-click-inside-radio-icon.spec.ts +25 -0
  23. package/e2e/bugs/333-disabled-radio-cursor.spec.ts +18 -0
  24. package/e2e/bugs/342-results-sorting.spec.ts +30 -0
  25. package/e2e/bugs/352-dictionary-race-condition.spec.ts +27 -0
  26. package/e2e/bugs/363-dictionary-input-unique-words.spec.ts +27 -0
  27. package/e2e/bugs/380-transliteration-when-pasting.spec.ts +55 -0
  28. package/e2e/bugs/398-digraphs-in-created-words.spec.ts +23 -0
  29. package/e2e/bugs/401-board-state-on-game-change.spec.ts +24 -0
  30. package/e2e/features/198-highlight-unreachable-cells.spec.ts +85 -0
  31. package/e2e/features/91-hover-remaining-tile-highlights.spec.ts +76 -0
  32. package/e2e/features/hidden-coordinates-migration.spec.ts +27 -0
  33. package/e2e/features/solver-paths.spec.ts +45 -0
  34. package/e2e/lib/actions.ts +119 -0
  35. package/e2e/lib/selectors.ts +71 -0
  36. package/e2e/tsconfig.json +10 -0
  37. package/lerna.json +1 -1
  38. package/package.json +16 -18
  39. package/packages/configs/package.json +3 -3
  40. package/packages/constants/package.json +1 -1
  41. package/packages/dictionaries/README.md +1 -1
  42. package/packages/dictionaries/build/Dictionaries.d.ts +2 -2
  43. package/packages/dictionaries/build/Dictionaries.js +6 -6
  44. package/packages/dictionaries/build/lib/DiskCache.d.ts +11 -4
  45. package/packages/dictionaries/build/lib/DiskCache.js +27 -10
  46. package/packages/dictionaries/build/lib/LayeredCache.d.ts +4 -4
  47. package/packages/dictionaries/build/lib/LayeredCache.js +3 -3
  48. package/packages/dictionaries/build/lib/MemoryCache.d.ts +4 -4
  49. package/packages/dictionaries/build/lib/MemoryCache.js +2 -2
  50. package/packages/dictionaries/build/lib/createCacheTimestampComparator.d.ts +2 -2
  51. package/packages/dictionaries/build/lib/downloadDictionary.d.ts +2 -2
  52. package/packages/dictionaries/build/lib/downloadDictionary.js +4 -3
  53. package/packages/dictionaries/build/lib/getDictionaryFilepath.d.ts +3 -1
  54. package/packages/dictionaries/build/lib/getDictionaryFilepath.js +8 -3
  55. package/packages/dictionaries/package.json +7 -6
  56. package/packages/dictionaries/src/Dictionaries.ts +12 -12
  57. package/packages/dictionaries/src/lib/DiskCache.test.ts +85 -0
  58. package/packages/dictionaries/src/lib/DiskCache.ts +34 -14
  59. package/packages/dictionaries/src/lib/LayeredCache.ts +7 -7
  60. package/packages/dictionaries/src/lib/MemoryCache.ts +6 -6
  61. package/packages/dictionaries/src/lib/createCacheTimestampComparator.ts +2 -2
  62. package/packages/dictionaries/src/lib/downloadDictionary.ts +5 -4
  63. package/packages/dictionaries/src/lib/getDictionaryFilepath.ts +7 -2
  64. package/packages/gaddag/build/Gaddag.d.ts +39 -0
  65. package/packages/gaddag/build/Gaddag.js +128 -0
  66. package/packages/gaddag/build/buildGaddag.d.ts +10 -0
  67. package/packages/gaddag/build/buildGaddag.js +360 -0
  68. package/packages/gaddag/build/constants.d.ts +12 -0
  69. package/packages/gaddag/build/constants.js +15 -0
  70. package/packages/gaddag/build/index.d.ts +3 -0
  71. package/packages/gaddag/build/index.js +13 -0
  72. package/packages/logger/package.json +1 -1
  73. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  74. package/packages/scrabble-solver/.next/build-manifest.json +22 -20
  75. package/packages/scrabble-solver/.next/cache/.previewinfo +1 -1
  76. package/packages/scrabble-solver/.next/cache/.rscinfo +1 -1
  77. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  78. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  79. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  80. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
  81. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  82. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  83. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  84. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  85. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
  86. package/packages/scrabble-solver/.next/diagnostics/framework.json +1 -1
  87. package/packages/scrabble-solver/.next/dynamic-css-manifest.json +8 -1
  88. package/packages/scrabble-solver/.next/next-minimal-server.js.nft.json +1 -1
  89. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  90. package/packages/scrabble-solver/.next/prerender-manifest.json +4 -4
  91. package/packages/scrabble-solver/.next/react-loadable-manifest.json +176 -1
  92. package/packages/scrabble-solver/.next/required-server-files.js +3 -2
  93. package/packages/scrabble-solver/.next/required-server-files.json +3 -2
  94. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  95. package/packages/scrabble-solver/.next/server/chunks/30.js +1 -1
  96. package/packages/scrabble-solver/.next/server/chunks/309.js +1 -0
  97. package/packages/scrabble-solver/.next/server/chunks/316.js +1 -0
  98. package/packages/scrabble-solver/.next/server/chunks/344.js +1 -0
  99. package/packages/scrabble-solver/.next/server/chunks/371.js +1 -0
  100. package/packages/scrabble-solver/.next/server/chunks/392.js +1 -1
  101. package/packages/scrabble-solver/.next/server/chunks/465.js +1 -0
  102. package/packages/scrabble-solver/.next/server/chunks/546.js +1 -0
  103. package/packages/scrabble-solver/.next/server/chunks/623.js +1 -0
  104. package/packages/scrabble-solver/.next/server/chunks/624.js +1 -0
  105. package/packages/scrabble-solver/.next/server/chunks/641.js +1 -0
  106. package/packages/scrabble-solver/.next/server/chunks/671.js +1 -1
  107. package/packages/scrabble-solver/.next/server/chunks/692.js +1 -0
  108. package/packages/scrabble-solver/.next/server/chunks/705.js +1 -0
  109. package/packages/scrabble-solver/.next/server/chunks/712.js +1 -1
  110. package/packages/scrabble-solver/.next/server/chunks/846.js +1 -0
  111. package/packages/scrabble-solver/.next/server/chunks/853.js +15 -0
  112. package/packages/scrabble-solver/.next/server/chunks/886.js +1 -0
  113. package/packages/scrabble-solver/.next/server/chunks/916.js +1 -0
  114. package/packages/scrabble-solver/.next/server/chunks/953.js +1 -0
  115. package/packages/scrabble-solver/.next/server/chunks/961.js +1 -0
  116. package/packages/scrabble-solver/.next/server/chunks/968.js +1 -0
  117. package/packages/scrabble-solver/.next/server/dynamic-css-manifest.js +1 -1
  118. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  119. package/packages/scrabble-solver/.next/server/middleware-react-loadable-manifest.js +1 -1
  120. package/packages/scrabble-solver/.next/server/pages/404.html +28 -1
  121. package/packages/scrabble-solver/.next/server/pages/500.html +28 -1
  122. package/packages/scrabble-solver/.next/server/pages/_app.js +1 -1
  123. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  124. package/packages/scrabble-solver/.next/server/pages/_document.js +28 -1
  125. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  126. package/packages/scrabble-solver/.next/server/pages/_error.js +28 -1
  127. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  128. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +1 -1
  129. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  130. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js +1 -1
  131. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  132. package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
  133. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  134. package/packages/scrabble-solver/.next/server/pages/api/verify.js +1 -1
  135. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
  136. package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
  137. package/packages/scrabble-solver/.next/server/pages/index.html +28 -1
  138. package/packages/scrabble-solver/.next/server/pages/index.js +30 -1
  139. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  140. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  141. package/packages/scrabble-solver/.next/server/pages/not-found.html +28 -1
  142. package/packages/scrabble-solver/.next/server/pages/not-found.js.nft.json +1 -1
  143. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
  144. package/packages/scrabble-solver/.next/server/webpack-api-runtime.js +1 -1
  145. package/packages/scrabble-solver/.next/server/webpack-runtime.js +1 -1
  146. package/packages/scrabble-solver/.next/static/FL82obwzFHRzvV6z76I-U/_buildManifest.js +1 -0
  147. package/packages/scrabble-solver/.next/static/chunks/101-829ab8a8d310e7a0.js +3 -0
  148. package/packages/scrabble-solver/.next/static/chunks/106.e414e88694add27f.js +1 -0
  149. package/packages/scrabble-solver/.next/static/chunks/127.125d896a19061d71.js +1 -0
  150. package/packages/scrabble-solver/.next/static/chunks/18.8b91e137d239e15c.js +1 -0
  151. package/packages/scrabble-solver/.next/static/chunks/277.6785dfaf22fdb0b7.js +1 -0
  152. package/packages/scrabble-solver/.next/static/chunks/307.67f52f13e3d60d4e.js +1 -0
  153. package/packages/scrabble-solver/.next/static/chunks/341.79535ba85bf1986f.js +1 -0
  154. package/packages/scrabble-solver/.next/static/chunks/363.01eed3233f92b6a4.js +1 -0
  155. package/packages/scrabble-solver/.next/static/chunks/422.1559ae6756912ca5.js +1 -0
  156. package/packages/scrabble-solver/.next/static/chunks/541.dcd9e8b804f88da5.js +1 -0
  157. package/packages/scrabble-solver/.next/static/chunks/583.7a46807be14c377e.js +1 -0
  158. package/packages/scrabble-solver/.next/static/chunks/631.98d42abad0a60af6.js +1 -0
  159. package/packages/scrabble-solver/.next/static/chunks/663.d2c9ca36b26695b8.js +1 -0
  160. package/packages/scrabble-solver/.next/static/chunks/711.ba7b8f900dfd40d0.js +1 -0
  161. package/packages/scrabble-solver/.next/static/chunks/744.b06c9a3de4a1d877.js +1 -0
  162. package/packages/scrabble-solver/.next/static/chunks/820.97b82f751ca49579.js +1 -0
  163. package/packages/scrabble-solver/.next/static/chunks/865.a9b563a45848a83c.js +1 -0
  164. package/packages/scrabble-solver/.next/static/chunks/912.ff8037ba9d4db5c8.js +1 -0
  165. package/packages/scrabble-solver/.next/static/chunks/939.19b33a3d3c351d73.js +1 -0
  166. package/packages/scrabble-solver/.next/static/chunks/990.61296e5870b91f11.js +1 -0
  167. package/packages/scrabble-solver/.next/static/chunks/framework-626cbd48ac5aca57.js +1 -0
  168. package/packages/scrabble-solver/.next/static/chunks/main-b92e08f3fc96dd8d.js +5 -0
  169. package/packages/scrabble-solver/.next/static/chunks/pages/_app-ba5d628e81a15461.js +1 -0
  170. package/packages/scrabble-solver/.next/static/chunks/pages/{_error-afeda7c5231b6c3c.js → _error-f0290a98eb3ed0ec.js} +1 -1
  171. package/packages/scrabble-solver/.next/static/chunks/pages/index-5f9fa6ac3cd3d97b.js +1 -0
  172. package/packages/scrabble-solver/.next/static/chunks/pages/not-found-23d61fd3ed791cad.js +1 -0
  173. package/packages/scrabble-solver/.next/static/chunks/webpack-e34c3ae12275e978.js +1 -0
  174. package/packages/scrabble-solver/.next/static/css/2d03f0da2c678dfc.css +1 -0
  175. package/packages/scrabble-solver/.next/static/css/7b6724d2df70f48f.css +1 -0
  176. package/packages/scrabble-solver/.next/static/css/abc2425d3ba607bc.css +1 -0
  177. package/packages/scrabble-solver/.next/static/css/b736be3bd02b5d99.css +1 -0
  178. package/packages/scrabble-solver/.next/static/css/b74cd7de82dabcdd.css +1 -0
  179. package/packages/scrabble-solver/.next/static/css/c403ce739a9c0d83.css +1 -0
  180. package/packages/scrabble-solver/.next/static/css/ca311e6c030663d4.css +1 -0
  181. package/packages/scrabble-solver/.next/static/css/dee878eb64099d92.css +1 -0
  182. package/packages/scrabble-solver/.next/static/css/fbfdce70731d0d2b.css +1 -0
  183. package/packages/scrabble-solver/.next/trace +21 -21
  184. package/packages/scrabble-solver/.next/trace-build +1 -1
  185. package/packages/scrabble-solver/empty-module.js +2 -0
  186. package/packages/scrabble-solver/next.config.js +13 -8
  187. package/packages/scrabble-solver/package.json +34 -15
  188. package/packages/scrabble-solver/public/llms.txt +11 -0
  189. package/packages/scrabble-solver/public/robots.txt +4 -0
  190. package/packages/scrabble-solver/public/service-worker.js +2 -2
  191. package/packages/scrabble-solver/scripts/generate-scss-types.ts +112 -0
  192. package/packages/scrabble-solver/src/@types/scss.d.ts +6 -9
  193. package/packages/scrabble-solver/src/@types/svg.d.ts +1 -1
  194. package/packages/scrabble-solver/src/components/Alert/Alert.module.scss.d.ts +13 -0
  195. package/packages/scrabble-solver/src/components/Alert/Alert.tsx +4 -1
  196. package/packages/scrabble-solver/src/components/Badge/Badge.module.scss.d.ts +6 -0
  197. package/packages/scrabble-solver/src/components/Board/Board.module.scss +10 -0
  198. package/packages/scrabble-solver/src/components/Board/Board.module.scss.d.ts +17 -0
  199. package/packages/scrabble-solver/src/components/Board/Board.tsx +14 -26
  200. package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +54 -49
  201. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +18 -18
  202. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss.d.ts +7 -0
  203. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.tsx +6 -3
  204. package/packages/scrabble-solver/src/components/Board/components/Actions/lib.ts +3 -1
  205. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss.d.ts +13 -0
  206. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +32 -34
  207. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.module.scss.d.ts +10 -0
  208. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.tsx +1 -1
  209. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss.d.ts +8 -0
  210. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -2
  211. package/packages/scrabble-solver/src/components/Board/getBoardBackground.ts +175 -0
  212. package/packages/scrabble-solver/src/components/Board/hooks/index.ts +0 -1
  213. package/packages/scrabble-solver/src/components/Board/hooks/useBoardStyle.ts +10 -20
  214. package/packages/scrabble-solver/src/components/Board/hooks/useFloatingActions.ts +3 -6
  215. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +23 -9
  216. package/packages/scrabble-solver/src/components/Board/selectors.ts +1 -1
  217. package/packages/scrabble-solver/src/components/Button/Button.module.scss.d.ts +12 -0
  218. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +8 -22
  219. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss.d.ts +14 -0
  220. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +1 -10
  221. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.module.scss.d.ts +7 -0
  222. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.tsx +1 -1
  223. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.module.scss.d.ts +7 -0
  224. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.tsx +1 -1
  225. package/packages/scrabble-solver/src/components/IconButton/IconButton.module.scss.d.ts +8 -0
  226. package/packages/scrabble-solver/src/components/Key/Key.module.scss.d.ts +6 -0
  227. package/packages/scrabble-solver/src/components/Keys/Arrows/Arrows.module.scss.d.ts +11 -0
  228. package/packages/scrabble-solver/src/components/Keys/Arrows/Arrows.tsx +4 -1
  229. package/packages/scrabble-solver/src/components/Keys/index.tsx +1 -1
  230. package/packages/scrabble-solver/src/components/Loading/Loading.module.scss.d.ts +9 -0
  231. package/packages/scrabble-solver/src/components/Loading/Loading.tsx +1 -1
  232. package/packages/scrabble-solver/src/components/Logo/Logo.tsx +4 -9
  233. package/packages/scrabble-solver/src/components/Logo/LogoBlueprint.tsx +1 -0
  234. package/packages/scrabble-solver/src/components/Modal/Modal.module.scss.d.ts +15 -0
  235. package/packages/scrabble-solver/src/components/Modal/Modal.tsx +16 -3
  236. package/packages/scrabble-solver/src/components/Modal/components/Section/Section.module.scss.d.ts +7 -0
  237. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.module.scss +20 -0
  238. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.module.scss.d.ts +13 -0
  239. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +29 -45
  240. package/packages/scrabble-solver/src/components/NotFound/NotFound.module.scss.d.ts +9 -0
  241. package/packages/scrabble-solver/src/components/NotFound/NotFound.tsx +1 -1
  242. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.module.scss.d.ts +11 -0
  243. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.tsx +0 -1
  244. package/packages/scrabble-solver/src/components/PlainTiles/Tile.tsx +3 -4
  245. package/packages/scrabble-solver/src/components/PlainTiles/lib.ts +4 -19
  246. package/packages/scrabble-solver/src/components/PlainTiles/types.ts +0 -1
  247. package/packages/scrabble-solver/src/components/Progress/Progress.module.scss +7 -0
  248. package/packages/scrabble-solver/src/components/Progress/Progress.module.scss.d.ts +6 -0
  249. package/packages/scrabble-solver/src/components/Progress/Progress.tsx +2 -11
  250. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +3 -0
  251. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss.d.ts +10 -0
  252. package/packages/scrabble-solver/src/components/Rack/Rack.tsx +8 -11
  253. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +2 -0
  254. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss.d.ts +7 -0
  255. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.tsx +1 -13
  256. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/lib.ts +1 -1
  257. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.module.scss.d.ts +6 -0
  258. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.tsx +5 -4
  259. package/packages/scrabble-solver/src/components/Rack/selectors.ts +1 -1
  260. package/packages/scrabble-solver/src/components/Radio/Radio.module.scss.d.ts +12 -0
  261. package/packages/scrabble-solver/src/components/Results/Cell.tsx +3 -12
  262. package/packages/scrabble-solver/src/components/Results/Header.tsx +9 -18
  263. package/packages/scrabble-solver/src/components/Results/HeaderButton.tsx +4 -5
  264. package/packages/scrabble-solver/src/components/Results/Result.tsx +4 -15
  265. package/packages/scrabble-solver/src/components/Results/Results.module.scss +4 -7
  266. package/packages/scrabble-solver/src/components/Results/Results.module.scss.d.ts +27 -0
  267. package/packages/scrabble-solver/src/components/Results/Results.tsx +14 -5
  268. package/packages/scrabble-solver/src/components/Results/SolveButton.tsx +1 -1
  269. package/packages/scrabble-solver/src/components/Results/types.ts +3 -3
  270. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.module.scss.d.ts +7 -0
  271. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.tsx +1 -1
  272. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +6 -5
  273. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss.d.ts +20 -0
  274. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +23 -24
  275. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.module.scss.d.ts +7 -0
  276. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.tsx +1 -1
  277. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss.d.ts +18 -0
  278. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +3 -1
  279. package/packages/scrabble-solver/src/components/Spinner/Spinner.module.scss.d.ts +10 -0
  280. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +3 -0
  281. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss.d.ts +19 -0
  282. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +5 -14
  283. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +2 -12
  284. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.module.scss.d.ts +7 -0
  285. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.tsx +14 -3
  286. package/packages/scrabble-solver/src/components/Tooltip/useTooltip.ts +2 -1
  287. package/packages/scrabble-solver/src/hooks/useColumns.ts +4 -7
  288. package/packages/scrabble-solver/src/hooks/useDeferredRender.ts +20 -0
  289. package/packages/scrabble-solver/src/hooks/useDirection.ts +4 -3
  290. package/packages/scrabble-solver/src/hooks/useLanguage.ts +2 -2
  291. package/packages/scrabble-solver/src/hooks/useLocalStorage.ts +32 -8
  292. package/packages/scrabble-solver/src/hooks/useMedia.ts +26 -26
  293. package/packages/scrabble-solver/src/i18n/constants.ts +2 -15
  294. package/packages/scrabble-solver/src/i18n/i18n.ts +18 -17
  295. package/packages/scrabble-solver/src/i18n/languages/english.json +0 -1
  296. package/packages/scrabble-solver/src/i18n/languages/french.json +0 -1
  297. package/packages/scrabble-solver/src/i18n/languages/german.json +0 -1
  298. package/packages/scrabble-solver/src/i18n/languages/persian.json +0 -1
  299. package/packages/scrabble-solver/src/i18n/languages/polish.json +0 -1
  300. package/packages/scrabble-solver/src/i18n/languages/romanian.json +0 -1
  301. package/packages/scrabble-solver/src/i18n/languages/spanish.json +0 -1
  302. package/packages/scrabble-solver/src/i18n/languages/turkish.json +0 -1
  303. package/packages/scrabble-solver/src/i18n/localeIcons.ts +24 -0
  304. package/packages/scrabble-solver/src/lib/localeTransliterate.test.ts +5 -0
  305. package/packages/scrabble-solver/src/lib/localeTransliterate.ts +26 -6
  306. package/packages/scrabble-solver/src/lib/waitForIdleOrFirstIntent.ts +33 -0
  307. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.module.scss.d.ts +8 -0
  308. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.tsx +3 -1
  309. package/packages/scrabble-solver/src/modals/KeyMapModal/KeyMapModal.tsx +3 -1
  310. package/packages/scrabble-solver/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss.d.ts +11 -0
  311. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss +5 -0
  312. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss.d.ts +10 -0
  313. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.tsx +16 -5
  314. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss +4 -1
  315. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss.d.ts +9 -0
  316. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -6
  317. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
  318. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss.d.ts +12 -0
  319. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.tsx +5 -12
  320. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss.d.ts +8 -0
  321. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.tsx +8 -4
  322. package/packages/scrabble-solver/src/modals/SettingsModal/SettingsModal.tsx +2 -2
  323. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.module.scss.d.ts +7 -0
  324. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -1
  325. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss.d.ts +7 -0
  326. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -1
  327. package/packages/scrabble-solver/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.module.scss.d.ts +7 -0
  328. package/packages/scrabble-solver/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.tsx +1 -1
  329. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss.d.ts +7 -0
  330. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +1 -1
  331. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss.d.ts +8 -0
  332. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +24 -21
  333. package/packages/scrabble-solver/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.module.scss.d.ts +7 -0
  334. package/packages/scrabble-solver/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.tsx +1 -1
  335. package/packages/scrabble-solver/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.module.scss.d.ts +7 -0
  336. package/packages/scrabble-solver/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.tsx +1 -5
  337. package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.module.scss.d.ts +11 -0
  338. package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.tsx +4 -2
  339. package/packages/scrabble-solver/src/modals/preload.ts +24 -0
  340. package/packages/scrabble-solver/src/pages/_app.tsx +5 -8
  341. package/packages/scrabble-solver/src/pages/_document.tsx +91 -5
  342. package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/[word].ts +2 -2
  343. package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/index.ts +80 -3
  344. package/packages/scrabble-solver/src/pages/api/solve.ts +3 -3
  345. package/packages/scrabble-solver/src/pages/api/verify.ts +12 -3
  346. package/packages/scrabble-solver/src/pages/index.module.scss +1 -0
  347. package/packages/scrabble-solver/src/pages/index.module.scss.d.ts +11 -0
  348. package/packages/scrabble-solver/src/pages/index.tsx +98 -38
  349. package/packages/scrabble-solver/src/pages/not-found.tsx +1 -1
  350. package/packages/scrabble-solver/src/parameters/index.ts +24 -93
  351. package/packages/scrabble-solver/src/sdk/solve.ts +13 -7
  352. package/packages/scrabble-solver/src/sdk/verify.ts +12 -6
  353. package/packages/scrabble-solver/src/service-worker/appShell.test.ts +86 -0
  354. package/packages/scrabble-solver/src/service-worker/appShell.ts +39 -0
  355. package/packages/scrabble-solver/src/service-worker/index.ts +4 -5
  356. package/packages/scrabble-solver/src/serviceWorkerManager.ts +5 -19
  357. package/packages/scrabble-solver/src/solver-worker/client.test.ts +151 -0
  358. package/packages/scrabble-solver/src/solver-worker/client.ts +106 -0
  359. package/packages/scrabble-solver/src/solver-worker/dictionaries/constants.ts +9 -0
  360. package/packages/scrabble-solver/src/solver-worker/dictionaries/getDictionary.ts +21 -0
  361. package/packages/scrabble-solver/src/solver-worker/dictionaries/index.ts +2 -0
  362. package/packages/scrabble-solver/src/solver-worker/dictionaries/revalidateDictionary.test.ts +199 -0
  363. package/packages/scrabble-solver/src/solver-worker/dictionaries/revalidateDictionary.ts +86 -0
  364. package/packages/scrabble-solver/src/solver-worker/dictionaries/revalidatedAt.ts +43 -0
  365. package/packages/scrabble-solver/src/solver-worker/getGaddag.test.ts +109 -0
  366. package/packages/scrabble-solver/src/solver-worker/getGaddag.ts +62 -0
  367. package/packages/scrabble-solver/src/solver-worker/index.ts +1 -0
  368. package/packages/scrabble-solver/src/solver-worker/index.worker.ts +124 -0
  369. package/packages/scrabble-solver/src/solver-worker/messages.ts +20 -0
  370. package/packages/scrabble-solver/src/state/actions.ts +1 -1
  371. package/packages/scrabble-solver/src/state/app/index.ts +4 -0
  372. package/packages/scrabble-solver/src/state/app/initialState.ts +6 -0
  373. package/packages/scrabble-solver/src/state/app/selectors.ts +5 -0
  374. package/packages/scrabble-solver/src/state/app/slice.ts +20 -0
  375. package/packages/scrabble-solver/src/state/app/types.ts +4 -0
  376. package/packages/scrabble-solver/src/state/board/initialState.ts +1 -2
  377. package/packages/scrabble-solver/src/state/i18n/index.ts +4 -0
  378. package/packages/scrabble-solver/src/state/i18n/initialState.ts +5 -0
  379. package/packages/scrabble-solver/src/state/i18n/selectors.ts +3 -0
  380. package/packages/scrabble-solver/src/state/i18n/slice.ts +16 -0
  381. package/packages/scrabble-solver/src/state/i18n/types.ts +7 -0
  382. package/packages/scrabble-solver/src/state/index.ts +3 -0
  383. package/packages/scrabble-solver/src/state/lib.ts +1 -1
  384. package/packages/scrabble-solver/src/state/localStorage.ts +86 -29
  385. package/packages/scrabble-solver/src/state/rack/initialState.ts +1 -2
  386. package/packages/scrabble-solver/src/state/rack/slice.ts +4 -1
  387. package/packages/scrabble-solver/src/state/results/lib.ts +5 -5
  388. package/packages/scrabble-solver/src/state/results/selectors.ts +1 -1
  389. package/packages/scrabble-solver/src/state/sagas.ts +139 -13
  390. package/packages/scrabble-solver/src/state/selectors.ts +1 -1
  391. package/packages/scrabble-solver/src/state/settings/initialState.ts +7 -10
  392. package/packages/scrabble-solver/src/state/settings/selectors.ts +7 -2
  393. package/packages/scrabble-solver/src/state/settings/slice.ts +5 -2
  394. package/packages/scrabble-solver/src/state/store.ts +4 -0
  395. package/packages/scrabble-solver/src/state/types.ts +4 -0
  396. package/packages/scrabble-solver/src/state/useTypedStore.ts +6 -0
  397. package/packages/scrabble-solver/src/styles/_tokens.scss +1 -0
  398. package/packages/scrabble-solver/src/styles/global.scss +9 -0
  399. package/packages/scrabble-solver/src/styles/variables.module.scss +0 -21
  400. package/packages/scrabble-solver/src/styles/variables.module.scss.d.ts +27 -0
  401. package/packages/scrabble-solver/src/styles/variables.scss +46 -2
  402. package/packages/scrabble-solver/src/types/api.ts +1 -2
  403. package/packages/scrabble-solver/src/types/index.ts +1 -2
  404. package/packages/scrabble-solver/tsconfig.json +4 -0
  405. package/packages/scrabble-solver/tsconfig.tsbuildinfo +1 -1
  406. package/packages/solver/README.md +24 -0
  407. package/packages/solver/benchmarks/index.ts +15 -0
  408. package/packages/solver/benchmarks/lib.ts +31 -0
  409. package/packages/solver/benchmarks/renderChart.ts +165 -0
  410. package/packages/solver/benchmarks/results/chart.svg +65 -0
  411. package/packages/solver/benchmarks/runBenchmarks.ts +66 -0
  412. package/packages/solver/benchmarks/scenarios.ts +64 -0
  413. package/packages/solver/benchmarks/types.ts +16 -0
  414. package/packages/solver/benchmarks/updateReadme.ts +53 -0
  415. package/packages/solver/build/MoveGenerator.d.ts +116 -0
  416. package/packages/solver/build/MoveGenerator.js +686 -0
  417. package/packages/solver/build/index.d.ts +0 -13
  418. package/packages/solver/build/index.js +1 -27
  419. package/packages/solver/build/solve.d.ts +2 -2
  420. package/packages/solver/build/solve.js +3 -19
  421. package/packages/solver/package.json +7 -6
  422. package/packages/solver/src/MoveGenerator.test.ts +1009 -0
  423. package/packages/solver/src/MoveGenerator.ts +926 -0
  424. package/packages/solver/src/ReferenceSolver.ts +401 -0
  425. package/packages/solver/src/index.ts +0 -13
  426. package/packages/solver/src/solve.test.ts +21 -21
  427. package/packages/solver/src/solve.ts +4 -22
  428. package/packages/solver/tsconfig.json +1 -1
  429. package/packages/types/build/Cell.d.ts +1 -0
  430. package/packages/types/build/Cell.js +3 -0
  431. package/packages/types/build/Direction.d.ts +1 -0
  432. package/packages/types/build/Result.d.ts +3 -2
  433. package/packages/types/build/Result.js +11 -6
  434. package/packages/types/build/ResultJson.d.ts +5 -4
  435. package/packages/types/build/ShowCoordinates.d.ts +1 -2
  436. package/packages/types/build/TextDirection.d.ts +1 -0
  437. package/packages/types/build/index.d.ts +2 -6
  438. package/packages/types/build/index.js +1 -9
  439. package/packages/types/build/readResultCells.d.ts +5 -0
  440. package/packages/types/build/readResultCells.js +75 -0
  441. package/packages/types/package.json +4 -3
  442. package/packages/types/src/Cell.ts +4 -0
  443. package/packages/types/src/Direction.ts +1 -0
  444. package/packages/types/src/Result.ts +15 -6
  445. package/packages/types/src/ResultJson.ts +5 -5
  446. package/packages/types/src/ShowCoordinates.ts +1 -2
  447. package/packages/types/src/TextDirection.ts +1 -0
  448. package/packages/types/src/index.ts +2 -6
  449. package/packages/types/src/readResultCells.test.ts +140 -0
  450. package/packages/types/src/readResultCells.ts +90 -0
  451. package/packages/word-definitions/package.json +2 -2
  452. package/packages/word-lists/package.json +2 -2
  453. package/playwright-report/index.html +49 -0
  454. package/playwright.config.ts +30 -0
  455. package/screencast.gif +0 -0
  456. package/scripts/record-screencast.ts +347 -0
  457. package/test-results/.last-run.json +4 -0
  458. package/tsconfig.json +1 -1
  459. package/babel.config.js +0 -4
  460. package/cypress/e2e/app.spec.cy.ts +0 -123
  461. package/cypress/e2e/bugs/112-character-bonus.ts +0 -40
  462. package/cypress/e2e/bugs/129-esc-sidebar.ts +0 -23
  463. package/cypress/e2e/bugs/2-q-tile-in-english.ts +0 -33
  464. package/cypress/e2e/bugs/3-x-tile-in-polish.ts +0 -33
  465. package/cypress/e2e/bugs/30-click-inside-radio-icon.ts +0 -26
  466. package/cypress/e2e/bugs/333-disabled-radio-cursor.ts +0 -24
  467. package/cypress/e2e/bugs/342-results-sorting.ts +0 -48
  468. package/cypress/e2e/bugs/348-results-word-padding.ts +0 -74
  469. package/cypress/e2e/bugs/352-dictionary-race-condition.ts +0 -29
  470. package/cypress/e2e/bugs/363-dictionary-input-unique-words.ts +0 -41
  471. package/cypress/e2e/bugs/380-transliteration-when-pasting.ts +0 -75
  472. package/cypress/e2e/bugs/398-digraphs-in-created-words.ts +0 -38
  473. package/cypress/e2e/bugs/401-board-state-on-game-change.ts +0 -36
  474. package/cypress/e2e/bugs.spec.cy.ts +0 -13
  475. package/cypress/e2e/features/198-highlight-unreachable-cells.ts +0 -101
  476. package/cypress/e2e/features/91-hover-remaining-tile-highlights.ts +0 -90
  477. package/cypress/e2e/features.spec.cy.ts +0 -2
  478. package/cypress/support/commands.ts +0 -4
  479. package/cypress/support/e2e.ts +0 -1
  480. package/cypress/support/index.ts +0 -1
  481. package/cypress/support/lib/actions.ts +0 -85
  482. package/cypress/support/lib/selectors.ts +0 -70
  483. package/cypress/tsconfig.json +0 -10
  484. package/cypress.config.ts +0 -17
  485. package/packages/scrabble-solver/.next/server/chunks/318.js +0 -3
  486. package/packages/scrabble-solver/.next/server/chunks/791.js +0 -15
  487. package/packages/scrabble-solver/.next/static/chunks/framework-3cf15b8041c59dd3.js +0 -1
  488. package/packages/scrabble-solver/.next/static/chunks/main-852475ca502b4e49.js +0 -5
  489. package/packages/scrabble-solver/.next/static/chunks/pages/_app-a5bbe02710f13d05.js +0 -7
  490. package/packages/scrabble-solver/.next/static/chunks/pages/index-ec0f6aeeaf7be227.js +0 -1
  491. package/packages/scrabble-solver/.next/static/chunks/pages/not-found-9e076427bad69d90.js +0 -1
  492. package/packages/scrabble-solver/.next/static/chunks/webpack-1a7a166b34ed8d24.js +0 -1
  493. package/packages/scrabble-solver/.next/static/css/0af35adb4bab56f0.css +0 -2
  494. package/packages/scrabble-solver/.next/static/css/a78e04fe95452380.css +0 -1
  495. package/packages/scrabble-solver/.next/static/vYpMmuC_yEurZGcyGVnLf/_buildManifest.js +0 -1
  496. package/packages/scrabble-solver/src/app-layout/AppLayoutContext.tsx +0 -25
  497. package/packages/scrabble-solver/src/app-layout/index.ts +0 -1
  498. package/packages/scrabble-solver/src/app-layout/useAppLayoutValue.ts +0 -112
  499. package/packages/scrabble-solver/src/components/Board/hooks/useBackgroundImage.tsx +0 -279
  500. package/packages/scrabble-solver/src/components/index.ts +0 -26
  501. package/packages/scrabble-solver/src/hooks/index.ts +0 -12
  502. package/packages/scrabble-solver/src/hooks/useOnWindowResize.ts +0 -11
  503. package/packages/scrabble-solver/src/hooks/useViewportSize.ts +0 -17
  504. package/packages/scrabble-solver/src/i18n/index.ts +0 -2
  505. package/packages/scrabble-solver/src/icons/Flag.svg +0 -4
  506. package/packages/scrabble-solver/src/icons/Star.svg +0 -4
  507. package/packages/scrabble-solver/src/icons/index.ts +0 -49
  508. package/packages/scrabble-solver/src/lib/getTileSizes.ts +0 -13
  509. package/packages/scrabble-solver/src/lib/index.ts +0 -24
  510. package/packages/scrabble-solver/src/modals/index.ts +0 -7
  511. package/packages/scrabble-solver/src/sdk/getDictionary.ts +0 -9
  512. package/packages/scrabble-solver/src/service-worker/average.ts +0 -7
  513. package/packages/scrabble-solver/src/service-worker/dictionaries/constants.ts +0 -5
  514. package/packages/scrabble-solver/src/service-worker/dictionaries/getDictionary.ts +0 -20
  515. package/packages/scrabble-solver/src/service-worker/dictionaries/index.ts +0 -2
  516. package/packages/scrabble-solver/src/service-worker/dictionaries/revalidateDictionary.ts +0 -33
  517. package/packages/scrabble-solver/src/service-worker/getTrie.ts +0 -24
  518. package/packages/scrabble-solver/src/service-worker/routeSolveRequests.ts +0 -91
  519. package/packages/scrabble-solver/src/service-worker/routeVerifyRequests.ts +0 -37
  520. package/packages/solver/build/areDigraphsValid.d.ts +0 -2
  521. package/packages/solver/build/areDigraphsValid.js +0 -17
  522. package/packages/solver/build/fillPattern.d.ts +0 -6
  523. package/packages/solver/build/fillPattern.js +0 -52
  524. package/packages/solver/build/generateEndIndices.d.ts +0 -2
  525. package/packages/solver/build/generateEndIndices.js +0 -17
  526. package/packages/solver/build/generateHorizontalPatterns.d.ts +0 -2
  527. package/packages/solver/build/generateHorizontalPatterns.js +0 -16
  528. package/packages/solver/build/generatePattern.d.ts +0 -7
  529. package/packages/solver/build/generatePattern.js +0 -20
  530. package/packages/solver/build/generatePatterns.d.ts +0 -2
  531. package/packages/solver/build/generatePatterns.js +0 -11
  532. package/packages/solver/build/generateStartIndices.d.ts +0 -2
  533. package/packages/solver/build/generateStartIndices.js +0 -16
  534. package/packages/solver/build/generateVectors.d.ts +0 -7
  535. package/packages/solver/build/generateVectors.js +0 -7
  536. package/packages/solver/build/generateVerticalPatterns.d.ts +0 -2
  537. package/packages/solver/build/generateVerticalPatterns.js +0 -16
  538. package/packages/solver/build/getCellsScore.d.ts +0 -2
  539. package/packages/solver/build/getCellsScore.js +0 -18
  540. package/packages/solver/build/getPatternHash.d.ts +0 -2
  541. package/packages/solver/build/getPatternHash.js +0 -14
  542. package/packages/solver/build/getPatternScore.d.ts +0 -2
  543. package/packages/solver/build/getPatternScore.js +0 -22
  544. package/packages/solver/build/getUniquePatterns.d.ts +0 -2
  545. package/packages/solver/build/getUniquePatterns.js +0 -17
  546. package/packages/solver/src/areDigraphsValid.ts +0 -18
  547. package/packages/solver/src/fillPattern.test.ts +0 -107
  548. package/packages/solver/src/fillPattern.ts +0 -68
  549. package/packages/solver/src/generateEndIndices.test.ts +0 -23
  550. package/packages/solver/src/generateEndIndices.ts +0 -19
  551. package/packages/solver/src/generateHorizontalPatterns.test.ts +0 -31
  552. package/packages/solver/src/generateHorizontalPatterns.ts +0 -15
  553. package/packages/solver/src/generatePattern.ts +0 -33
  554. package/packages/solver/src/generatePatterns.test.ts +0 -13
  555. package/packages/solver/src/generatePatterns.ts +0 -11
  556. package/packages/solver/src/generateStartIndices.test.ts +0 -19
  557. package/packages/solver/src/generateStartIndices.ts +0 -17
  558. package/packages/solver/src/generateVectors.test.ts +0 -12
  559. package/packages/solver/src/generateVectors.ts +0 -10
  560. package/packages/solver/src/generateVerticalPatterns.test.ts +0 -32
  561. package/packages/solver/src/generateVerticalPatterns.ts +0 -15
  562. package/packages/solver/src/getCellsScore.ts +0 -21
  563. package/packages/solver/src/getPatternHash.test.ts +0 -17
  564. package/packages/solver/src/getPatternHash.ts +0 -12
  565. package/packages/solver/src/getPatternScore.test.ts +0 -204
  566. package/packages/solver/src/getPatternScore.ts +0 -23
  567. package/packages/solver/src/getUniquePatterns.ts +0 -19
  568. package/packages/types/build/CollisionJson.d.ts +0 -2
  569. package/packages/types/build/FinalPattern.d.ts +0 -6
  570. package/packages/types/build/FinalPattern.js +0 -14
  571. package/packages/types/build/HorizontalPattern.d.ts +0 -5
  572. package/packages/types/build/HorizontalPattern.js +0 -34
  573. package/packages/types/build/Pattern.d.ts +0 -20
  574. package/packages/types/build/Pattern.js +0 -50
  575. package/packages/types/build/PatternJson.d.ts +0 -6
  576. package/packages/types/build/VerticalPattern.d.ts +0 -5
  577. package/packages/types/build/VerticalPattern.js +0 -34
  578. package/packages/types/src/CollisionJson.ts +0 -3
  579. package/packages/types/src/FinalPattern.ts +0 -14
  580. package/packages/types/src/HorizontalPattern.ts +0 -37
  581. package/packages/types/src/Pattern.ts +0 -71
  582. package/packages/types/src/PatternJson.ts +0 -7
  583. package/packages/types/src/VerticalPattern.ts +0 -37
  584. /package/{cypress/support → e2e}/lib/index.ts +0 -0
  585. /package/packages/scrabble-solver/.next/static/{vYpMmuC_yEurZGcyGVnLf → FL82obwzFHRzvV6z76I-U}/_ssgManifest.js +0 -0
  586. /package/packages/scrabble-solver/src/{service-worker → solver-worker}/dictionaries/expirationManager.ts +0 -0
  587. /package/packages/scrabble-solver/src/{service-worker → solver-worker}/dictionaries/getDictionaryUrl.ts +0 -0
  588. /package/packages/types/build/{CollisionJson.js → Direction.js} +0 -0
  589. /package/packages/types/build/{PatternJson.js → TextDirection.js} +0 -0
@@ -332,6 +332,10 @@
332
332
  "packages/dictionaries/package.json",
333
333
  "nx/core/package-json"
334
334
  ],
335
+ "metadata.targetGroups.NPM Scripts.4": [
336
+ "packages/dictionaries/package.json",
337
+ "nx/core/package-json"
338
+ ],
335
339
  "metadata.description": [
336
340
  "packages/dictionaries/package.json",
337
341
  "nx/core/package-json"
@@ -463,6 +467,34 @@
463
467
  "targets.postbuild.metadata.runCommand": [
464
468
  "packages/dictionaries/package.json",
465
469
  "nx/core/package-json"
470
+ ],
471
+ "targets.test": [
472
+ "packages/dictionaries/package.json",
473
+ "nx/core/package-json"
474
+ ],
475
+ "targets.test.executor": [
476
+ "packages/dictionaries/package.json",
477
+ "nx/core/package-json"
478
+ ],
479
+ "targets.test.metadata": [
480
+ "packages/dictionaries/package.json",
481
+ "nx/core/package-json"
482
+ ],
483
+ "targets.test.options.script": [
484
+ "packages/dictionaries/package.json",
485
+ "nx/core/package-json"
486
+ ],
487
+ "targets.test.options": [
488
+ "packages/dictionaries/package.json",
489
+ "nx/core/package-json"
490
+ ],
491
+ "targets.test.metadata.scriptContent": [
492
+ "packages/dictionaries/package.json",
493
+ "nx/core/package-json"
494
+ ],
495
+ "targets.test.metadata.runCommand": [
496
+ "packages/dictionaries/package.json",
497
+ "nx/core/package-json"
466
498
  ]
467
499
  },
468
500
  "packages/logger": {
@@ -708,6 +740,10 @@
708
740
  "packages/scrabble-solver/package.json",
709
741
  "nx/core/package-json"
710
742
  ],
743
+ "metadata.targetGroups.NPM Scripts.7": [
744
+ "packages/scrabble-solver/package.json",
745
+ "nx/core/package-json"
746
+ ],
711
747
  "metadata.description": [
712
748
  "packages/scrabble-solver/package.json",
713
749
  "nx/core/package-json"
@@ -840,6 +876,34 @@
840
876
  "packages/scrabble-solver/package.json",
841
877
  "nx/core/package-json"
842
878
  ],
879
+ "targets.generate-scss-types": [
880
+ "packages/scrabble-solver/package.json",
881
+ "nx/core/package-json"
882
+ ],
883
+ "targets.generate-scss-types.executor": [
884
+ "packages/scrabble-solver/package.json",
885
+ "nx/core/package-json"
886
+ ],
887
+ "targets.generate-scss-types.metadata": [
888
+ "packages/scrabble-solver/package.json",
889
+ "nx/core/package-json"
890
+ ],
891
+ "targets.generate-scss-types.options.script": [
892
+ "packages/scrabble-solver/package.json",
893
+ "nx/core/package-json"
894
+ ],
895
+ "targets.generate-scss-types.options": [
896
+ "packages/scrabble-solver/package.json",
897
+ "nx/core/package-json"
898
+ ],
899
+ "targets.generate-scss-types.metadata.scriptContent": [
900
+ "packages/scrabble-solver/package.json",
901
+ "nx/core/package-json"
902
+ ],
903
+ "targets.generate-scss-types.metadata.runCommand": [
904
+ "packages/scrabble-solver/package.json",
905
+ "nx/core/package-json"
906
+ ],
843
907
  "targets.start": [
844
908
  "packages/scrabble-solver/package.json",
845
909
  "nx/core/package-json"
@@ -986,6 +1050,10 @@
986
1050
  "packages/solver/package.json",
987
1051
  "nx/core/package-json"
988
1052
  ],
1053
+ "metadata.targetGroups.NPM Scripts.3": [
1054
+ "packages/solver/package.json",
1055
+ "nx/core/package-json"
1056
+ ],
989
1057
  "metadata.description": [
990
1058
  "packages/solver/package.json",
991
1059
  "nx/core/package-json"
@@ -1014,6 +1082,34 @@
1014
1082
  "packages/solver/package.json",
1015
1083
  "nx/core/package-json"
1016
1084
  ],
1085
+ "targets.benchmark": [
1086
+ "packages/solver/package.json",
1087
+ "nx/core/package-json"
1088
+ ],
1089
+ "targets.benchmark.executor": [
1090
+ "packages/solver/package.json",
1091
+ "nx/core/package-json"
1092
+ ],
1093
+ "targets.benchmark.metadata": [
1094
+ "packages/solver/package.json",
1095
+ "nx/core/package-json"
1096
+ ],
1097
+ "targets.benchmark.options.script": [
1098
+ "packages/solver/package.json",
1099
+ "nx/core/package-json"
1100
+ ],
1101
+ "targets.benchmark.options": [
1102
+ "packages/solver/package.json",
1103
+ "nx/core/package-json"
1104
+ ],
1105
+ "targets.benchmark.metadata.scriptContent": [
1106
+ "packages/solver/package.json",
1107
+ "nx/core/package-json"
1108
+ ],
1109
+ "targets.benchmark.metadata.runCommand": [
1110
+ "packages/solver/package.json",
1111
+ "nx/core/package-json"
1112
+ ],
1017
1113
  "targets.build.metadata": [
1018
1114
  "packages/solver/package.json",
1019
1115
  "nx/core/package-json"
@@ -1148,6 +1244,10 @@
1148
1244
  "packages/types/package.json",
1149
1245
  "nx/core/package-json"
1150
1246
  ],
1247
+ "metadata.targetGroups.NPM Scripts.2": [
1248
+ "packages/types/package.json",
1249
+ "nx/core/package-json"
1250
+ ],
1151
1251
  "metadata.description": [
1152
1252
  "packages/types/package.json",
1153
1253
  "nx/core/package-json"
@@ -1223,6 +1323,34 @@
1223
1323
  "targets.clean.metadata.runCommand": [
1224
1324
  "packages/types/package.json",
1225
1325
  "nx/core/package-json"
1326
+ ],
1327
+ "targets.test": [
1328
+ "packages/types/package.json",
1329
+ "nx/core/package-json"
1330
+ ],
1331
+ "targets.test.executor": [
1332
+ "packages/types/package.json",
1333
+ "nx/core/package-json"
1334
+ ],
1335
+ "targets.test.metadata": [
1336
+ "packages/types/package.json",
1337
+ "nx/core/package-json"
1338
+ ],
1339
+ "targets.test.options.script": [
1340
+ "packages/types/package.json",
1341
+ "nx/core/package-json"
1342
+ ],
1343
+ "targets.test.options": [
1344
+ "packages/types/package.json",
1345
+ "nx/core/package-json"
1346
+ ],
1347
+ "targets.test.metadata.scriptContent": [
1348
+ "packages/types/package.json",
1349
+ "nx/core/package-json"
1350
+ ],
1351
+ "targets.test.metadata.runCommand": [
1352
+ "packages/types/package.json",
1353
+ "nx/core/package-json"
1226
1354
  ]
1227
1355
  },
1228
1356
  "packages/word-definitions": {
package/.oxlintrc.json CHANGED
@@ -3,15 +3,13 @@
3
3
  "plugins": ["eslint", "typescript", "react", "import", "unicorn", "oxc", "jest"],
4
4
  "jsPlugins": [
5
5
  { "name": "import-js", "specifier": "eslint-plugin-import" },
6
- { "name": "mocha", "specifier": "eslint-plugin-mocha" },
6
+ { "name": "playwright", "specifier": "eslint-plugin-playwright" },
7
7
  { "name": "react-js", "specifier": "eslint-plugin-react" }
8
8
  ],
9
9
  "env": {
10
10
  "browser": true,
11
11
  "node": true,
12
12
  "jest": true,
13
- "mocha": true,
14
- "jquery": true,
15
13
  "serviceworker": true
16
14
  },
17
15
  "globals": {
@@ -19,8 +17,6 @@
19
17
  "RequestInit": "readonly",
20
18
  "ServiceWorkerGlobalScope": "readonly",
21
19
  "beforeAll": "readonly",
22
- "cy": "readonly",
23
- "Cypress": "readonly",
24
20
  "define": "readonly",
25
21
  "describe": "readonly",
26
22
  "expect": "readonly",
@@ -43,7 +39,7 @@
43
39
  "import-js/resolver": {
44
40
  "typescript": {
45
41
  "alwaysTryTypes": true,
46
- "project": ["tsconfig.json", "packages/*/tsconfig.json", "cypress/tsconfig.json"]
42
+ "project": ["tsconfig.json", "packages/*/tsconfig.json", "e2e/tsconfig.json"]
47
43
  },
48
44
  "node": true
49
45
  }
@@ -56,9 +52,9 @@
56
52
  "**/node_modules/**/*",
57
53
  "**/*.d.ts",
58
54
  "**/.eslintrc.js",
59
- "**/babel.config.js",
60
55
  "**/bump-version.js",
61
56
  "packages/scrabble-solver/next.config.js",
57
+ "scripts/record-screencast.ts",
62
58
  "packages/logger/scripts/stats.js",
63
59
  "packages/scrabble-solver/public/service-worker.js",
64
60
  "**/.next/**/*"
@@ -81,7 +77,6 @@
81
77
  }
82
78
  ],
83
79
 
84
- "mocha/no-exclusive-tests": "error",
85
80
  "jest/no-focused-tests": "error",
86
81
 
87
82
  "react-js/no-deprecated": "error",
@@ -261,6 +256,12 @@
261
256
  "react/exhaustive-deps": "error"
262
257
  },
263
258
  "overrides": [
259
+ {
260
+ "files": ["packages/*/benchmarks/**", "packages/*/scripts/**"],
261
+ "rules": {
262
+ "no-console": "off"
263
+ }
264
+ },
264
265
  {
265
266
  "files": ["**/*.ts", "**/*.tsx"],
266
267
  "rules": {
@@ -297,11 +298,13 @@
297
298
  }
298
299
  },
299
300
  {
300
- "files": ["**/*.spec.cy.ts", "cypress/**"],
301
+ "files": ["e2e/**"],
301
302
  "rules": {
302
303
  "max-statements": "off",
304
+ "max-lines": "off",
303
305
  "jest/valid-expect": "off",
304
- "jest/no-focused-tests": "off"
306
+ "jest/no-focused-tests": "off",
307
+ "playwright/no-focused-test": "error"
305
308
  }
306
309
  },
307
310
  {
package/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Repository layout
6
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`).
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`). The root `workspaces` field is an explicit nine-entry list in dependency order, not a glob.
8
8
 
9
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
10
 
@@ -16,27 +16,31 @@ word-definitions ─────────────┘
16
16
  logger (independent, used by app + dictionaries)
17
17
  ```
18
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.
19
+ The GADDAG itself lives outside this repo, in [`@kamilmielnik/gaddag`](https://github.com/kamilmielnik/gaddag) — a flat typed-array automaton with `has`/`hasPrefix`/`getArc`, binary `serialize`/`deserialize`, and `Gaddag.fromArray(words)`. It replaced `@kamilmielnik/trie` (#164) and is consumed by `dictionaries`, `solver`, and the app as a regular npm dependency (`^2.0.0`). A local `packages/gaddag/` directory may exist — it holds git-ignored compiled output left over from before the package was extracted and is **not** a workspace.
20
+
21
+ - `solver` — pure word-finding engine. Given a `Gaddag`, `Config`, `Board`, and `Tile[]`, returns scored `ResultJson`s. Has no I/O. `solve.ts` delegates to `MoveGenerator` (a single file holding all the logic) — anchor-based GADDAG move generation with per-cell cross-check masks, run once per direction; results are scored inline and sorted deterministically so UI ties resolve identically. `MoveGenerator` throws on alphabets over 64 tiles or boards over 32×32 because move sort keys pack direction/line/start/end into one integer. `benchmarks/` measures median `solve()` times on fixed mid-game boards (en-US, en-GB, pl-PL; 0–2 blanks): `bun run benchmark` from the root reruns them and rewrites the chart SVG + results table in the package `README.md`; it needs real (downloaded) dictionaries.
22
+ - `dictionaries` — downloads/caches per-locale word lists to `$HOME/.scrabble-solver/dictionaries` and exposes them as `Gaddag`s (binary `<locale>.gaddag` files on disk; building the Polish GADDAG from its 3.2M-word list takes ~25 s, so it happens in `postbuild`/background updates, not per request; the `update-dictionaries` / `remove-dictionaries` bin scripts wrap this, and `update()` refreshes only stale locales unless forced). 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 considered stale, but only `update()` (run from `postbuild` and the packaging workflows) refreshes them — `get()` serves stale entries as-is. `DiskCache.get` treats files it cannot deserialize (corrupted, or written by an app version with an incompatible format — the binary magic doubles as the format version) as cache misses: it deletes them and the dictionary is re-downloaded and re-serialized automatically; `set` also removes the pre-#164 `<locale>.txt` trie cache. The service worker does the same client-side: `getGaddag` returns undefined for undeserializable cached dictionaries, falling back to the server and revalidating. Only this package and `logger` perform filesystem I/O — keep other packages pure so they can run in Edge / browser contexts.
21
23
  - `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 (Wiktionary, 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
+ - `word-definitions` — per-locale `crawl(word) → string` and a parser for each source (Wiktionary, CNRTL, DWDS, SJP, dexonline, vajehyab, etc.). English uses the Wiktionary REST API (#431): `parse` JSON-parses the payload and uses cheerio only to strip HTML from definition strings, so the English fixtures are `.json`. 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.
25
+ - `types` — domain model classes (`Board`, `Cell`, `Tile`, `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
26
  - `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
27
  - `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
28
  - `constants` — shared primitives (`BLANK`, `BONUS_CHARACTER`, `BONUS_WORD`, …). No runtime dependencies.
27
29
 
28
30
  ### App package (`@scrabble-solver/scrabble-solver`)
29
31
 
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
+ - **Routing**: Next.js Pages Router (`src/pages/`). API routes: `solve`, `verify`, `visit`, `dictionary/[locale]` (binary GADDAG download), and `dictionary/[locale]/[word]` (definitions). The path alias `@/*` resolves to `src/*` (set in `tsconfig.json`).
33
+ - **State**: Redux Toolkit + Redux-Saga. Slices in `src/state/{app,board,cellFilters,dictionary,hoveredTile,i18n,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`). `initialize({ version })` carries the app version (from `getStaticProps`) into the `app` slice; the translations cache is keyed on it.
34
+ - **Board render budget**: `Cell`/`Tile` render 225+ times at once and must not subscribe to the store — every value they need flows down from `BoardPure` as memo-friendly props, and event handlers read state at event time via `useTypedStore().getState()` (see `Cell.tsx`). Anything added to a cell that subscribes via `useSelector` multiplies by 225 and shows up directly in TBT.
32
35
  - **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`.
36
+ - **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 (skipping each effect's first post-hydration run, which would only echo what was just read) — **adding a new field to `SettingsState` is enough; you don't need to touch any save code** (PR #321, Apr 2026). Boot state is deterministic (SSR/hydration must match — issue #412): persisted settings/board/rack are applied post-mount by `hydratePersistedState` in the `initialize()` saga, guarded so a throw leaves the deterministic defaults and `app.hydrated` still fires (`finally`) — otherwise persistence would silently die for the session. The `state/localStorage.ts` getters treat corrupt/mismatched entries as absent and remove them; the saga skips no-op `init` dispatches so an empty-storage boot changes no state identities and re-renders nothing. The active locale's translations are also cached (`translations` key, keyed by app version + locale) and hydrated synchronously so a returning user's first post-hydration paint is already translated. When changing settings shape, add a migration block (see `migrateLegacySettings` for the pattern — dated comment with introduction date and life expectancy).
37
+ - **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`). The precache excludes `.css` — every page inlines its stylesheets (see `_document.tsx`), so the emitted CSS files are never requested.
38
+ - **i18n**: `src/i18n/languages/<lang>.json` (8 languages, mapped to `Locale` in `i18n.ts`). English is statically bundled; every other locale is a dynamic-import chunk (`loadTranslations`), loaded into the `i18n` slice — `selectTranslations` falls back to the complete English object, so missing keys are impossible mid-load. All locales preload on idle/first intent (`preloadTranslationsWhenIdle`), which is what makes switching languages apply instantly; the active locale is additionally cached in localStorage (see Persistence). The `LOCALE_FEATURES` registry in `src/i18n/constants.ts` carries per-locale UI metadata: `direction` ('ltr' | 'rtl'), `comma`/`separator` glyphs (Latin vs Arabic), language `label`/`name`, and `consonants`/`vowels` flags that drive the auto-group-tiles UI; flag icons live separately in `src/i18n/localeIcons.ts` (`LOCALE_ICONS`) so they stay out of the main bundle. The `useDirection` hook applies `direction` to `<html dir>`. Add a new locale by extending both maps plus the i18n JSON dictionary plus a `Flag<XX>.svg` icon. `useTranslate()` is the lookup hook.
39
+ - **Layout**: all layout sizing lives in CSS custom properties (`src/styles/variables.scss`: `--cell-size`, `--rack-tile-size`, `--max-board-width`, …), with the config-dependent inputs (`--board-cols`, `--board-rows`, `--rack-size`) set on `:root` twice: before first paint by an inline script in `_document.tsx` (which reads persisted settings, also sets `dir`/`lang`, and adds the `config-pending` class that hides `main` until hydration applies a persisted non-default game — see `CONFIG_PENDING_CLASS`), and post-hydration by an effect in `pages/index.tsx`. There is no JS layout hook — components size themselves via these variables (the board grid templates use `repeat(var(--board-cols), …)` so the held board has its final geometry pre-hydration); JS reads viewport state only through `useMediaQuery`. **Boot-path DOM rules**: never read computed style in mount effects (forced reflow — compare against inline style or known defaults instead), and guard same-value writes to `<html>` attributes/variables (they invalidate style/paint document-wide and destabilize LCP). The active modal in `pages/index.tsx` is tracked as a `Record<Modal, boolean>` patched through a single `patchModals` callback; each modal mounts on first open and stays mounted (`mountedModals` latch), because a `next/dynamic({ssr: false})` component starts its chunk request during render.
40
+ - **Styling**: SCSS modules with a shared design-token system. When one module's class overrides another component's styles (e.g. `Actions` restyling `Button`), it must win on **specificity**, not stylesheet order — webpack's CSS emission order shifts with the import graph (this flipped once and widened a square button). Class names are type-checked (#16): `scripts/generate-scss-types.ts` writes a git-ignored sibling `.d.ts` for every `*.module.scss` — the `build` and `type-check` scripts run it up front, `dev` runs it in watch mode, and its sass options mirror `sassOptions` in `next.config.js`. `src/@types/scss.d.ts` only covers side-effect imports of non-module stylesheets, deliberately exporting nothing so an import of an ungenerated module fails the type-check. SCSS tokens live in `src/styles/_tokens.scss`; the same values are re-exported to TS via `:export` in `variables.module.scss`. 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
41
  - **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.
42
+ - **Service worker registration**: production-only. Registered by `serviceWorkerManager.ts` from the index page, deferred until load + idle (`waitForIdle`) so the install's precache downloads never compete with page resources (an eager install once caused Lighthouse's robots.txt fetch to time out). Playwright blocks registration entirely via `serviceWorkers: 'block'` in `playwright.config.ts`, and each test gets a fresh browser context, so e2e tests need no service-worker or localStorage cleanup.
43
+ - **Deferral pattern**: anything that shouldn't compete with boot goes through `lib/waitForIdleOrFirstIntent.ts` (load + `requestIdleCallback`, or first pointerdown/keydown) — dictionary prefetch, translations preload, modal chunk warming, `visit()`, and (idle-only via `waitForIdle`) service worker registration. Tooltips defer their floating-ui setup one task past hydration via `useDeferredRender`.
40
44
 
41
45
  ## Common commands
42
46
 
@@ -48,7 +52,7 @@ All commands run from the repo root unless noted.
48
52
  | Build all packages | `bun run build` (Nx-cached, respects dep order) |
49
53
  | Build one package | `bun run --filter @scrabble-solver/<name> build` |
50
54
  | Dev server (port 3000) | `bun run dev` |
51
- | Production server (port 3333) | `bun start` |
55
+ | Production server (port 3333) | `bun start` (also waits for :3333 and opens the browser; `bun run start:app` is server-only) |
52
56
  | Lint | `bun run lint` (oxlint) / `bun run lint:fix` |
53
57
  | Format check / fix | `bun run format` / `bun run format:fix` (oxfmt) |
54
58
  | Type-check the app | `bun run --filter @scrabble-solver/scrabble-solver type-check` (uses `tsc` from stable TypeScript 7, the native Go compiler) |
@@ -56,26 +60,28 @@ All commands run from the repo root unless noted.
56
60
  | Unit tests (one package) | `bun run --filter @scrabble-solver/solver test` |
57
61
  | One unit test file | `cd packages/solver && bun test src/solve.test.ts` |
58
62
  | 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 |
63
+ | Solver benchmarks | `bun run benchmark` (rewrites the results table + chart in `packages/solver/README.md`) |
64
+ | Playwright (UI mode) | `bun run test-playwright` (expects dev server on :3000) |
65
+ | Playwright (headless) | `bun run test-playwright:run` (expects server on :3333) |
66
+ | Full test pipeline | `bun run test` (build → unit → `start-server-and-test` boots the app on :3333 → playwright test) — note: `bun test` invokes Bun's built-in test runner, not this script |
62
67
 
63
68
  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
69
 
65
70
  ## Testing notes
66
71
 
67
72
  - 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.
73
+ - Only `dictionaries`, `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 `Gaddag` from a downloaded dictionary.
69
74
  - `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
75
  - 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. `cypress.config.ts` registers `@cypress/webpack-batteries-included-preprocessor` from npm as the `file:preprocessor` — the copy bundled inside Cypress 15.19.0 crashes on TypeScript 7 projects (its `@babel/preset-typescript` ships without a `package.json`); drop the override once a fixed Cypress release lands.
76
+ - Playwright: specs in `e2e/` (`app.spec.ts`, `bugs/`, `features/`), shared page helpers in `e2e/lib/` (selectors return `Locator`s, actions take `page` first). Two base URLs are in play: `playwright.config.ts` defaults to `http://localhost:3000` (matches `bun run dev`); `test-playwright:run` and CI set `PLAYWRIGHT_BASE_URL=http://localhost:3333` (matches `bun start`). Pick the script that matches the server you're actually running. The config blocks service workers and runs Chromium only; browser binaries come from `bunx playwright install chromium`.
72
77
 
73
78
  ## Tooling specifics
74
79
 
75
80
  - **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
81
  - **Formatting**: `oxfmt` covers `*.{js,ts,tsx,scss}`.
77
- - **TypeScript**: stable TypeScript 7 (`typescript@^7.0.2`), whose `tsc` is the native Go compiler. It runs every package `build`, the app's `type-check`, and `next build` — the latter via `experimental.useTypeScriptCli` in `next.config.js`, required because TS7 has no JS compiler API (which also forces Next onto the 16.3 preview line until 16.3 is stable). The interim `@typescript/native-preview` (`tsgo`) setup from PR #422 was removed (Aug 2026). 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`.
82
+ - **Install layout**: `bunfig.toml` pins `[install] linker = "hoisted"`. With Bun's isolated layout, `next build` fails — webpack bundles `unzipper` from `word-lists` and errors on its uninstalled optional `@aws-sdk/client-s3` dependency. Don't remove it.
83
+ - **TypeScript**: stable TypeScript 7 (`typescript@^7.0.2`), whose `tsc` is the native Go compiler. It runs every package `build`, the app's `type-check`, and `next build` — the latter because Next.js runs the project-local `tsc` CLI (TS7 has no JS compiler API). Next 16.3.0 turned that on by default, so the `experimental.useTypeScriptCli` flag that pinned the app to the 16.3 preview line is gone from `next.config.js`; set it to `false` only to opt back into the JS compiler API, which TS7 cannot serve. The interim `@typescript/native-preview` (`tsgo`) setup from PR #422 was removed (Aug 2026). Root `tsconfig.json` sets `types: ["bun"]` for global test-runner types and excludes `e2e` and `playwright.config.ts` (covered by `e2e/tsconfig.json` instead); library packages extend it and additionally exclude `**/*.test.ts` from emitted output.
84
+ - **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, and stubs out Next's unconditionally-bundled ES2019-2022 polyfills (`polyfill-module`) with `empty-module.js` via `NormalModuleReplacementPlugin` — every browserslist target (`package.json` `browserslist`: Chrome/Edge/Firefox ≥ 100, Safari ≥ 15.4, …) supports those natively, and browserslist alone does not control that chunk. SCSS load paths are extended to `./src` and `node_modules/include-media/dist`.
79
85
  - **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
86
 
81
87
  ## CI workflows
@@ -83,11 +89,11 @@ Hot reload only works for the `scrabble-solver` package. Edits to any other pack
83
89
  `.github/workflows/`:
84
90
 
85
91
  - `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` / `npx.yml` — run daily and on push to master. Download the latest published `scrabble-solver` tarball with `npm pack`, install the global binary (`bun add --global` / `npm install --global`), then run Cypress from the extracted tarball against that binary. The Cypress specs come from the published package, **not** from `master` — if they came from master, any spec added for a feature that's merged but not yet released would run against the older published binary that lacks the feature, and fail (#428). 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`.
92
+ - `unit.yml` — `bun run build && bun run test-unit`.
93
+ - `e2e.yml` — Playwright against the app on :3333 (via `start-server-and-test`). Uploads the HTML report and traces on failure.
94
+ - `oxlint.yml` / `oxfmt.yml` — lint and format-check. `oxlint.yml` runs `bun run build` first because type-aware rules need built packages.
95
+ - `bunx.yml` / `npx.yml` — run daily, on push to master, and via `workflow_dispatch`. They poll npm (up to 15 min) until the current version of every workspace package is published, download the published `scrabble-solver` tarball with `npm pack`, install the global binary (`bun add --global` / `npm install --global`), pre-warm the dictionaries cache, then run Playwright from the extracted tarball against that binary. The Playwright specs come from the published package, **not** from `master` — if they came from master, any spec added for a feature that's merged but not yet released would run against the older published binary that lacks the feature, and fail (#428). Catches packaging regressions in the `bin/scrabble-solver.js` launcher.
96
+ - `deploy.yml` — `workflow_dispatch` only (branch input, default `master`). SSHs into prod, pulls, builds, restarts `scrabble-solver.service`.
91
97
 
92
98
  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
99
 
@@ -103,7 +109,7 @@ The `Deploy` GitHub workflow (`workflow_dispatch` only) SSHs into the production
103
109
 
104
110
  The app reads/writes user data outside the project directory:
105
111
 
106
- - `$HOME/.scrabble-solver/dictionaries/` — cached `Trie`s, one per locale, refreshed when older than 1 day.
112
+ - `$HOME/.scrabble-solver/dictionaries/` — cached serialized `Gaddag`s (binary), one per locale, refreshed when older than 1 day.
107
113
  - `$HOME/.scrabble-solver/logs/{all,error}.log` — Winston JSON logs.
108
114
 
109
115
  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.
@@ -112,7 +118,9 @@ The `bunx scrabble-solver@latest` entry point (`bin/scrabble-solver.js`) just `c
112
118
 
113
119
  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
120
 
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).
121
+ - **#447 — Lighthouse 100** (Aug 2026, issue #412). The index page is fully server-rendered (the old `isClient` client-only gate is gone), so everything at boot must be SSR/hydration-safe and deterministic. The JS layout layer (`useAppLayoutValue`, `AppLayoutContext`) was deleted in favor of CSS variables; stylesheets are inlined into the HTML by `InlineCssHead` in `_document.tsx` (the emitted `.css` files exist but nothing requests them); a pre-paint inline script in `_document.tsx` applies persisted board dimensions/direction and holds `main` hidden until hydration when the persisted game differs from the default. Board cells are store-subscription-free (see "Board render budget"), modals mount on first open, tooltips/service-worker/dictionary/translations work is deferred past load or idle, and per-locale i18n chunks replaced the bundled all-locales map (with an idle preload and a version-keyed localStorage cache for instant/flash-free language behavior). `transliteration` was replaced with Unicode normalization, all barrel files were removed for tree-shaking, and Next's bundled polyfills are stubbed out. Perf invariants to preserve: no store subscriptions in `Cell`/`Tile`, no computed-style reads or same-value `<html>` writes in boot effects, nothing heavy before load+idle, and hydration must not change state identities on an empty-storage boot.
122
+ - **#164 — GADDAG solver** (Aug 2026). `@kamilmielnik/trie` was dropped everywhere in favor of the external `@kamilmielnik/gaddag` package; `solve()` was rewritten as anchor-based GADDAG move generation (~40-60× faster). Disk-cached dictionaries changed from serialized-trie `.txt` to binary `.gaddag` files, and `/api/dictionary/[locale]` now serves `application/octet-stream`, gzipped when the client accepts it (compressed once per in-memory dictionary via a `WeakMap` cache — the route parses `Accept-Encoding` q-values so `gzip;q=0` gets identity and `*` counts as accepting, evicts failed compression promises from the cache, and sets `Vary: Accept-Encoding`). Responses are `Cache-Control: no-cache`, so clients revalidate with `If-None-Match` and unchanged re-downloads become 304s — the `ETag` comes from Next's built-in per-payload generation (it overrides any hand-set `ETag` on `send()`, so don't set one). The service worker deserializes with `Gaddag.deserialize` (the browser undoes `Content-Encoding` before the Cache API stores the body) and memoizes the deserialized `Gaddag` per locale, keyed on the cached response's `ETag`/`Date`/`Content-Length`, until `revalidateDictionary` replaces the entry.
123
+ - **#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 packaging workflow now runs in two variants (`bunx.yml` installs via `bun add --global`, `npx.yml` via `npm install --global`). All workflows use `oven-sh/setup-bun@v2`; `npx.yml` is the only one that also uses `setup-node`.
116
124
  - **#422 — TypeScript 7** (Apr 2026). Moved build/type-check to `tsgo` (native preview). Superseded in Aug 2026 by stable `typescript@7`, where the native compiler ships as plain `tsc` — scripts call `tsc` again and `@typescript/native-preview` is gone.
117
125
  - **#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
126
  - **#321 — Auto-persisted settings** (Apr 2026). Settings, board, and rack are written through a single `useLocalStorage` effect. Don't dispatch save actions manually.
package/README.md CHANGED
@@ -188,6 +188,7 @@ bun run --filter @scrabble-solver/word-lists build
188
188
  - [TypeScript](https://www.typescriptlang.org/)
189
189
  - [Bun](https://bun.sh/docs)
190
190
  - [Node.js](https://nodejs.org/)
191
+ - [@kamilmielnik/gaddag](https://github.com/kamilmielnik/gaddag)
191
192
  - [Next.js](https://nextjs.org/)
192
193
  - [Workbox](https://developer.chrome.com/docs/workbox/)
193
194
  - [React](https://reactjs.org/)
@@ -198,12 +199,20 @@ bun run --filter @scrabble-solver/word-lists build
198
199
  - [CSS Modules](https://github.com/css-modules/css-modules)
199
200
  - [include-media](https://eduardoboucas.github.io/include-media/)
200
201
  - [Lerna](https://lerna.js.org/)
201
- - [Cypress](https://www.cypress.io/)
202
+ - [Nx](https://nx.dev/)
203
+ - [Playwright](https://playwright.dev/)
202
204
  - [Oxlint](https://oxc.rs/docs/guide/usage/linter)
203
205
  - [Oxfmt](https://oxc.rs/docs/guide/usage/formatter)
204
206
 
205
207
  ## Related projects
206
208
 
209
+ ### @kamilmielnik/gaddag
210
+
211
+ The [GADDAG](https://en.wikipedia.org/wiki/GADDAG) data structure powering the solver. Dictionaries are stored as a GADDAG, which is what makes it possible to generate moves anchored on tiles already placed on the board.
212
+
213
+ - GitHub: https://github.com/kamilmielnik/gaddag
214
+ - npm: https://www.npmjs.com/package/@kamilmielnik/gaddag
215
+
207
216
  ### Lexatious
208
217
 
209
218
  The user interface and source code for the web front end of Lexatious is based on Scrabble Solver.
@@ -211,6 +220,13 @@ The user interface and source code for the web front end of Lexatious is based o
211
220
  - Web: https://lexatious.com
212
221
  - GitHub: https://github.com/ericgjackson/lexatious_web
213
222
 
223
+ ### ScrabbleBench
224
+
225
+ A benchmark by Sunny Bala measuring how well large language models play Scrabble. Models are asked only for the word they want to play - Scrabble Solver then finds the optimal placement of that word on the board.
226
+
227
+ - Web: https://sunnybala.github.io/scrabblebench/
228
+ - GitHub: https://github.com/sunnybala/scrabblebench
229
+
214
230
  ## Media
215
231
 
216
232
  - [5 Free Scrabble Solver Websites To Quickly Find Top Scoring Words](https://www.ilovefreesoftware.com/05/featured/free-scrabble-solver-websites-to-quickly-find-top-scoring-words.html)