scrabble-solver 2.17.0 → 2.18.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 (422) 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 +1406 -1406
  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 +212 -1317
  10. package/.nx/workspace-data/parsed-lock-file.nodes.json +758 -2693
  11. package/.nx/workspace-data/project-graph.json +1076 -4158
  12. package/.nx/workspace-data/source-maps.json +204 -0
  13. package/.oxlintrc.json +8 -11
  14. package/CLAUDE.md +18 -15
  15. package/README.md +8 -1
  16. package/bun.lock +230 -499
  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 +30 -52
  39. package/packages/configs/package.json +13 -4
  40. package/packages/constants/package.json +6 -2
  41. package/packages/dictionaries/package.json +12 -5
  42. package/packages/logger/package.json +7 -2
  43. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  44. package/packages/scrabble-solver/.next/build-manifest.json +22 -20
  45. package/packages/scrabble-solver/.next/cache/.previewinfo +1 -1
  46. package/packages/scrabble-solver/.next/cache/.rscinfo +1 -1
  47. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  48. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  49. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  50. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
  51. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  52. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  53. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  54. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  55. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
  56. package/packages/scrabble-solver/.next/diagnostics/framework.json +1 -1
  57. package/packages/scrabble-solver/.next/dynamic-css-manifest.json +8 -1
  58. package/packages/scrabble-solver/.next/next-minimal-server.js.nft.json +1 -1
  59. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  60. package/packages/scrabble-solver/.next/prerender-manifest.json +4 -4
  61. package/packages/scrabble-solver/.next/react-loadable-manifest.json +176 -1
  62. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  63. package/packages/scrabble-solver/.next/server/chunks/309.js +1 -0
  64. package/packages/scrabble-solver/.next/server/chunks/{785.js → 316.js} +1 -1
  65. package/packages/scrabble-solver/.next/server/chunks/344.js +1 -0
  66. package/packages/scrabble-solver/.next/server/chunks/371.js +1 -0
  67. package/packages/scrabble-solver/.next/server/chunks/465.js +1 -0
  68. package/packages/scrabble-solver/.next/server/chunks/546.js +1 -0
  69. package/packages/scrabble-solver/.next/server/chunks/623.js +1 -0
  70. package/packages/scrabble-solver/.next/server/chunks/624.js +1 -0
  71. package/packages/scrabble-solver/.next/server/chunks/641.js +1 -0
  72. package/packages/scrabble-solver/.next/server/chunks/671.js +1 -1
  73. package/packages/scrabble-solver/.next/server/chunks/692.js +1 -0
  74. package/packages/scrabble-solver/.next/server/chunks/705.js +1 -0
  75. package/packages/scrabble-solver/.next/server/chunks/712.js +1 -1
  76. package/packages/scrabble-solver/.next/server/chunks/846.js +1 -0
  77. package/packages/scrabble-solver/.next/server/chunks/853.js +15 -0
  78. package/packages/scrabble-solver/.next/server/chunks/886.js +1 -0
  79. package/packages/scrabble-solver/.next/server/chunks/916.js +1 -0
  80. package/packages/scrabble-solver/.next/server/chunks/953.js +1 -0
  81. package/packages/scrabble-solver/.next/server/chunks/961.js +1 -0
  82. package/packages/scrabble-solver/.next/server/chunks/968.js +1 -0
  83. package/packages/scrabble-solver/.next/server/dynamic-css-manifest.js +1 -1
  84. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  85. package/packages/scrabble-solver/.next/server/middleware-react-loadable-manifest.js +1 -1
  86. package/packages/scrabble-solver/.next/server/pages/404.html +29 -1
  87. package/packages/scrabble-solver/.next/server/pages/500.html +29 -1
  88. package/packages/scrabble-solver/.next/server/pages/_app.js +1 -1
  89. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  90. package/packages/scrabble-solver/.next/server/pages/_document.js +28 -1
  91. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  92. package/packages/scrabble-solver/.next/server/pages/_error.js +28 -1
  93. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  94. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  95. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  96. package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
  97. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  98. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
  99. package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
  100. package/packages/scrabble-solver/.next/server/pages/index.html +29 -1
  101. package/packages/scrabble-solver/.next/server/pages/index.js +30 -1
  102. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  103. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  104. package/packages/scrabble-solver/.next/server/pages/not-found.html +29 -1
  105. package/packages/scrabble-solver/.next/server/pages/not-found.js.nft.json +1 -1
  106. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
  107. package/packages/scrabble-solver/.next/server/webpack-api-runtime.js +1 -1
  108. package/packages/scrabble-solver/.next/server/webpack-runtime.js +1 -1
  109. package/packages/scrabble-solver/.next/static/5KOWzYqfY1Z2rjrzptr8D/_buildManifest.js +1 -0
  110. package/packages/scrabble-solver/.next/static/chunks/101-829ab8a8d310e7a0.js +3 -0
  111. package/packages/scrabble-solver/.next/static/chunks/106.e414e88694add27f.js +1 -0
  112. package/packages/scrabble-solver/.next/static/chunks/127.125d896a19061d71.js +1 -0
  113. package/packages/scrabble-solver/.next/static/chunks/18.8b91e137d239e15c.js +1 -0
  114. package/packages/scrabble-solver/.next/static/chunks/277.6785dfaf22fdb0b7.js +1 -0
  115. package/packages/scrabble-solver/.next/static/chunks/307.67f52f13e3d60d4e.js +1 -0
  116. package/packages/scrabble-solver/.next/static/chunks/341.79535ba85bf1986f.js +1 -0
  117. package/packages/scrabble-solver/.next/static/chunks/363.01eed3233f92b6a4.js +1 -0
  118. package/packages/scrabble-solver/.next/static/chunks/422.1559ae6756912ca5.js +1 -0
  119. package/packages/scrabble-solver/.next/static/chunks/541.dcd9e8b804f88da5.js +1 -0
  120. package/packages/scrabble-solver/.next/static/chunks/631.98d42abad0a60af6.js +1 -0
  121. package/packages/scrabble-solver/.next/static/chunks/663.d2c9ca36b26695b8.js +1 -0
  122. package/packages/scrabble-solver/.next/static/chunks/711.ba7b8f900dfd40d0.js +1 -0
  123. package/packages/scrabble-solver/.next/static/chunks/744.b06c9a3de4a1d877.js +1 -0
  124. package/packages/scrabble-solver/.next/static/chunks/820.97b82f751ca49579.js +1 -0
  125. package/packages/scrabble-solver/.next/static/chunks/865.a9b563a45848a83c.js +1 -0
  126. package/packages/scrabble-solver/.next/static/chunks/912.ff8037ba9d4db5c8.js +1 -0
  127. package/packages/scrabble-solver/.next/static/chunks/939.19b33a3d3c351d73.js +1 -0
  128. package/packages/scrabble-solver/.next/static/chunks/990.61296e5870b91f11.js +1 -0
  129. package/packages/scrabble-solver/.next/static/chunks/framework-626cbd48ac5aca57.js +1 -0
  130. package/packages/scrabble-solver/.next/static/chunks/main-b92e08f3fc96dd8d.js +5 -0
  131. package/packages/scrabble-solver/.next/static/chunks/pages/_app-2e607dcd4135f340.js +1 -0
  132. package/packages/scrabble-solver/.next/static/chunks/pages/{_error-afeda7c5231b6c3c.js → _error-f0290a98eb3ed0ec.js} +1 -1
  133. package/packages/scrabble-solver/.next/static/chunks/pages/index-e0087247c995328d.js +1 -0
  134. package/packages/scrabble-solver/.next/static/chunks/pages/not-found-a59f9deeeff63d17.js +1 -0
  135. package/packages/scrabble-solver/.next/static/chunks/webpack-cfb34d175e081981.js +1 -0
  136. package/packages/scrabble-solver/.next/static/css/2d03f0da2c678dfc.css +1 -0
  137. package/packages/scrabble-solver/.next/static/css/4dbcacc1122ac6b9.css +2 -0
  138. package/packages/scrabble-solver/.next/static/css/7b6724d2df70f48f.css +1 -0
  139. package/packages/scrabble-solver/.next/static/css/b736be3bd02b5d99.css +1 -0
  140. package/packages/scrabble-solver/.next/static/css/b74cd7de82dabcdd.css +1 -0
  141. package/packages/scrabble-solver/.next/static/css/c403ce739a9c0d83.css +1 -0
  142. package/packages/scrabble-solver/.next/static/css/ca311e6c030663d4.css +1 -0
  143. package/packages/scrabble-solver/.next/static/css/dee878eb64099d92.css +1 -0
  144. package/packages/scrabble-solver/.next/static/css/fbfdce70731d0d2b.css +1 -0
  145. package/packages/scrabble-solver/.next/trace +21 -21
  146. package/packages/scrabble-solver/.next/trace-build +1 -1
  147. package/packages/scrabble-solver/empty-module.js +2 -0
  148. package/packages/scrabble-solver/next.config.js +13 -2
  149. package/packages/scrabble-solver/package.json +42 -16
  150. package/packages/scrabble-solver/public/llms.txt +11 -0
  151. package/packages/scrabble-solver/public/robots.txt +5 -0
  152. package/packages/scrabble-solver/public/service-worker.js +1 -1
  153. package/packages/scrabble-solver/public/site.webmanifest +14 -0
  154. package/packages/scrabble-solver/scripts/generate-scss-types.ts +112 -0
  155. package/packages/scrabble-solver/src/@types/scss.d.ts +6 -9
  156. package/packages/scrabble-solver/src/@types/svg.d.ts +1 -1
  157. package/packages/scrabble-solver/src/components/Alert/Alert.module.scss.d.ts +13 -0
  158. package/packages/scrabble-solver/src/components/Alert/Alert.tsx +4 -1
  159. package/packages/scrabble-solver/src/components/Badge/Badge.module.scss.d.ts +6 -0
  160. package/packages/scrabble-solver/src/components/Board/Board.module.scss +10 -0
  161. package/packages/scrabble-solver/src/components/Board/Board.module.scss.d.ts +17 -0
  162. package/packages/scrabble-solver/src/components/Board/Board.tsx +14 -26
  163. package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +54 -49
  164. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +18 -18
  165. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss.d.ts +7 -0
  166. package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.tsx +6 -3
  167. package/packages/scrabble-solver/src/components/Board/components/Actions/lib.ts +3 -1
  168. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss.d.ts +13 -0
  169. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +28 -33
  170. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.module.scss.d.ts +10 -0
  171. package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.tsx +1 -1
  172. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss.d.ts +8 -0
  173. package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -2
  174. package/packages/scrabble-solver/src/components/Board/getBoardBackground.ts +175 -0
  175. package/packages/scrabble-solver/src/components/Board/hooks/index.ts +0 -1
  176. package/packages/scrabble-solver/src/components/Board/hooks/useBoardStyle.ts +10 -20
  177. package/packages/scrabble-solver/src/components/Board/hooks/useFloatingActions.ts +3 -6
  178. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +6 -3
  179. package/packages/scrabble-solver/src/components/Board/selectors.ts +1 -1
  180. package/packages/scrabble-solver/src/components/Button/Button.module.scss.d.ts +12 -0
  181. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +1 -0
  182. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss.d.ts +14 -0
  183. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +1 -3
  184. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.module.scss.d.ts +7 -0
  185. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.tsx +1 -1
  186. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.module.scss.d.ts +7 -0
  187. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.tsx +1 -1
  188. package/packages/scrabble-solver/src/components/IconButton/IconButton.module.scss.d.ts +8 -0
  189. package/packages/scrabble-solver/src/components/Key/Key.module.scss.d.ts +6 -0
  190. package/packages/scrabble-solver/src/components/Keys/Arrows/Arrows.module.scss.d.ts +11 -0
  191. package/packages/scrabble-solver/src/components/Keys/Arrows/Arrows.tsx +4 -1
  192. package/packages/scrabble-solver/src/components/Keys/index.tsx +1 -1
  193. package/packages/scrabble-solver/src/components/Loading/Loading.module.scss.d.ts +9 -0
  194. package/packages/scrabble-solver/src/components/Loading/Loading.tsx +1 -1
  195. package/packages/scrabble-solver/src/components/Logo/Logo.tsx +4 -9
  196. package/packages/scrabble-solver/src/components/Logo/LogoBlueprint.tsx +1 -0
  197. package/packages/scrabble-solver/src/components/Modal/Modal.module.scss.d.ts +15 -0
  198. package/packages/scrabble-solver/src/components/Modal/Modal.tsx +16 -3
  199. package/packages/scrabble-solver/src/components/Modal/components/Section/Section.module.scss.d.ts +7 -0
  200. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.module.scss +20 -0
  201. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.module.scss.d.ts +13 -0
  202. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +29 -45
  203. package/packages/scrabble-solver/src/components/NotFound/NotFound.module.scss.d.ts +9 -0
  204. package/packages/scrabble-solver/src/components/NotFound/NotFound.tsx +1 -1
  205. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.module.scss.d.ts +11 -0
  206. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.tsx +0 -1
  207. package/packages/scrabble-solver/src/components/PlainTiles/Tile.tsx +3 -4
  208. package/packages/scrabble-solver/src/components/PlainTiles/lib.ts +4 -19
  209. package/packages/scrabble-solver/src/components/PlainTiles/types.ts +0 -1
  210. package/packages/scrabble-solver/src/components/Progress/Progress.module.scss +7 -0
  211. package/packages/scrabble-solver/src/components/Progress/Progress.module.scss.d.ts +6 -0
  212. package/packages/scrabble-solver/src/components/Progress/Progress.tsx +2 -11
  213. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +3 -0
  214. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss.d.ts +10 -0
  215. package/packages/scrabble-solver/src/components/Rack/Rack.tsx +8 -11
  216. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +2 -0
  217. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss.d.ts +7 -0
  218. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.tsx +1 -13
  219. package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/lib.ts +1 -1
  220. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.module.scss.d.ts +6 -0
  221. package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.tsx +5 -4
  222. package/packages/scrabble-solver/src/components/Rack/selectors.ts +1 -1
  223. package/packages/scrabble-solver/src/components/Radio/Radio.module.scss.d.ts +12 -0
  224. package/packages/scrabble-solver/src/components/Results/Cell.tsx +3 -12
  225. package/packages/scrabble-solver/src/components/Results/Header.tsx +9 -18
  226. package/packages/scrabble-solver/src/components/Results/HeaderButton.tsx +4 -5
  227. package/packages/scrabble-solver/src/components/Results/Result.tsx +4 -12
  228. package/packages/scrabble-solver/src/components/Results/Results.module.scss +4 -6
  229. package/packages/scrabble-solver/src/components/Results/Results.module.scss.d.ts +27 -0
  230. package/packages/scrabble-solver/src/components/Results/Results.tsx +4 -3
  231. package/packages/scrabble-solver/src/components/Results/SolveButton.tsx +1 -1
  232. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.module.scss.d.ts +7 -0
  233. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.tsx +1 -1
  234. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +6 -5
  235. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss.d.ts +20 -0
  236. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +19 -21
  237. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.module.scss.d.ts +7 -0
  238. package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.tsx +1 -1
  239. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss.d.ts +18 -0
  240. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +3 -1
  241. package/packages/scrabble-solver/src/components/Spinner/Spinner.module.scss.d.ts +10 -0
  242. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +3 -0
  243. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss.d.ts +19 -0
  244. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +5 -14
  245. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +2 -12
  246. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.module.scss.d.ts +7 -0
  247. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.tsx +14 -3
  248. package/packages/scrabble-solver/src/components/Tooltip/useTooltip.ts +1 -0
  249. package/packages/scrabble-solver/src/hooks/useColumns.ts +4 -7
  250. package/packages/scrabble-solver/src/hooks/useDeferredRender.ts +20 -0
  251. package/packages/scrabble-solver/src/hooks/useDirection.ts +4 -3
  252. package/packages/scrabble-solver/src/hooks/useLanguage.ts +2 -2
  253. package/packages/scrabble-solver/src/hooks/useLocalStorage.ts +32 -8
  254. package/packages/scrabble-solver/src/hooks/useMedia.ts +26 -26
  255. package/packages/scrabble-solver/src/i18n/constants.ts +2 -15
  256. package/packages/scrabble-solver/src/i18n/i18n.ts +18 -17
  257. package/packages/scrabble-solver/src/i18n/languages/english.json +0 -1
  258. package/packages/scrabble-solver/src/i18n/languages/french.json +0 -1
  259. package/packages/scrabble-solver/src/i18n/languages/german.json +0 -1
  260. package/packages/scrabble-solver/src/i18n/languages/persian.json +0 -1
  261. package/packages/scrabble-solver/src/i18n/languages/polish.json +0 -1
  262. package/packages/scrabble-solver/src/i18n/languages/romanian.json +0 -1
  263. package/packages/scrabble-solver/src/i18n/languages/spanish.json +0 -1
  264. package/packages/scrabble-solver/src/i18n/languages/turkish.json +0 -1
  265. package/packages/scrabble-solver/src/i18n/localeIcons.ts +24 -0
  266. package/packages/scrabble-solver/src/lib/localeTransliterate.test.ts +5 -0
  267. package/packages/scrabble-solver/src/lib/localeTransliterate.ts +26 -6
  268. package/packages/scrabble-solver/src/lib/waitForIdleOrFirstIntent.ts +44 -0
  269. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.module.scss.d.ts +8 -0
  270. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.tsx +3 -1
  271. package/packages/scrabble-solver/src/modals/KeyMapModal/KeyMapModal.tsx +3 -1
  272. package/packages/scrabble-solver/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss.d.ts +11 -0
  273. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss +5 -0
  274. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss.d.ts +10 -0
  275. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.tsx +16 -5
  276. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss +4 -1
  277. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss.d.ts +9 -0
  278. package/packages/scrabble-solver/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -6
  279. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
  280. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss.d.ts +12 -0
  281. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.tsx +5 -12
  282. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss.d.ts +8 -0
  283. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.tsx +8 -4
  284. package/packages/scrabble-solver/src/modals/SettingsModal/SettingsModal.tsx +2 -2
  285. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.module.scss.d.ts +7 -0
  286. package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -1
  287. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss.d.ts +7 -0
  288. package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -1
  289. package/packages/scrabble-solver/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.module.scss.d.ts +7 -0
  290. package/packages/scrabble-solver/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.tsx +1 -1
  291. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss.d.ts +7 -0
  292. package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +1 -1
  293. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss.d.ts +8 -0
  294. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +24 -21
  295. package/packages/scrabble-solver/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.module.scss.d.ts +7 -0
  296. package/packages/scrabble-solver/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.tsx +1 -1
  297. package/packages/scrabble-solver/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.module.scss.d.ts +7 -0
  298. package/packages/scrabble-solver/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.tsx +1 -5
  299. package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.module.scss.d.ts +11 -0
  300. package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.tsx +4 -2
  301. package/packages/scrabble-solver/src/modals/preload.ts +24 -0
  302. package/packages/scrabble-solver/src/pages/_app.tsx +37 -76
  303. package/packages/scrabble-solver/src/pages/_document.tsx +112 -24
  304. package/packages/scrabble-solver/src/pages/api/solve.ts +1 -1
  305. package/packages/scrabble-solver/src/pages/index.module.scss +1 -0
  306. package/packages/scrabble-solver/src/pages/index.module.scss.d.ts +11 -0
  307. package/packages/scrabble-solver/src/pages/index.tsx +127 -40
  308. package/packages/scrabble-solver/src/pages/not-found.tsx +14 -3
  309. package/packages/scrabble-solver/src/parameters/index.ts +31 -93
  310. package/packages/scrabble-solver/src/serviceWorkerManager.ts +5 -2
  311. package/packages/scrabble-solver/src/state/actions.ts +1 -1
  312. package/packages/scrabble-solver/src/state/app/index.ts +4 -0
  313. package/packages/scrabble-solver/src/state/app/initialState.ts +6 -0
  314. package/packages/scrabble-solver/src/state/app/selectors.ts +5 -0
  315. package/packages/scrabble-solver/src/state/app/slice.ts +20 -0
  316. package/packages/scrabble-solver/src/state/app/types.ts +4 -0
  317. package/packages/scrabble-solver/src/state/board/initialState.ts +1 -2
  318. package/packages/scrabble-solver/src/state/i18n/index.ts +4 -0
  319. package/packages/scrabble-solver/src/state/i18n/initialState.ts +5 -0
  320. package/packages/scrabble-solver/src/state/i18n/selectors.ts +3 -0
  321. package/packages/scrabble-solver/src/state/i18n/slice.ts +16 -0
  322. package/packages/scrabble-solver/src/state/i18n/types.ts +7 -0
  323. package/packages/scrabble-solver/src/state/index.ts +2 -0
  324. package/packages/scrabble-solver/src/state/lib.ts +1 -1
  325. package/packages/scrabble-solver/src/state/localStorage.ts +86 -29
  326. package/packages/scrabble-solver/src/state/rack/initialState.ts +1 -2
  327. package/packages/scrabble-solver/src/state/rack/slice.ts +4 -1
  328. package/packages/scrabble-solver/src/state/results/lib.ts +5 -5
  329. package/packages/scrabble-solver/src/state/results/selectors.ts +1 -1
  330. package/packages/scrabble-solver/src/state/sagas.ts +125 -9
  331. package/packages/scrabble-solver/src/state/selectors.ts +1 -1
  332. package/packages/scrabble-solver/src/state/settings/initialState.ts +7 -10
  333. package/packages/scrabble-solver/src/state/settings/selectors.ts +7 -2
  334. package/packages/scrabble-solver/src/state/settings/slice.ts +5 -2
  335. package/packages/scrabble-solver/src/state/store.ts +4 -0
  336. package/packages/scrabble-solver/src/state/types.ts +4 -0
  337. package/packages/scrabble-solver/src/styles/_tokens.scss +1 -0
  338. package/packages/scrabble-solver/src/styles/global.scss +9 -0
  339. package/packages/scrabble-solver/src/styles/variables.module.scss +0 -21
  340. package/packages/scrabble-solver/src/styles/variables.module.scss.d.ts +27 -0
  341. package/packages/scrabble-solver/src/styles/variables.scss +46 -2
  342. package/packages/scrabble-solver/src/types/index.ts +1 -2
  343. package/packages/scrabble-solver/tsconfig.tsbuildinfo +1 -1
  344. package/packages/solver/package.json +14 -6
  345. package/packages/types/build/Direction.d.ts +1 -0
  346. package/packages/types/build/Direction.js +2 -0
  347. package/packages/types/build/ShowCoordinates.d.ts +1 -2
  348. package/packages/types/build/TextDirection.d.ts +1 -0
  349. package/packages/types/build/TextDirection.js +2 -0
  350. package/packages/types/build/index.d.ts +2 -0
  351. package/packages/types/package.json +10 -3
  352. package/packages/types/src/Direction.ts +1 -0
  353. package/packages/types/src/ShowCoordinates.ts +1 -2
  354. package/packages/types/src/TextDirection.ts +1 -0
  355. package/packages/types/src/index.ts +2 -0
  356. package/packages/word-definitions/package.json +9 -3
  357. package/packages/word-lists/package.json +11 -3
  358. package/playwright-report/index.html +49 -0
  359. package/playwright.config.ts +30 -0
  360. package/screencast.gif +0 -0
  361. package/scripts/record-screencast.ts +347 -0
  362. package/test-results/.last-run.json +4 -0
  363. package/tsconfig.json +1 -1
  364. package/babel.config.js +0 -4
  365. package/cypress/e2e/app.spec.cy.ts +0 -123
  366. package/cypress/e2e/bugs/112-character-bonus.ts +0 -40
  367. package/cypress/e2e/bugs/129-esc-sidebar.ts +0 -23
  368. package/cypress/e2e/bugs/2-q-tile-in-english.ts +0 -33
  369. package/cypress/e2e/bugs/3-x-tile-in-polish.ts +0 -33
  370. package/cypress/e2e/bugs/30-click-inside-radio-icon.ts +0 -26
  371. package/cypress/e2e/bugs/333-disabled-radio-cursor.ts +0 -24
  372. package/cypress/e2e/bugs/342-results-sorting.ts +0 -48
  373. package/cypress/e2e/bugs/348-results-word-padding.ts +0 -74
  374. package/cypress/e2e/bugs/352-dictionary-race-condition.ts +0 -29
  375. package/cypress/e2e/bugs/363-dictionary-input-unique-words.ts +0 -41
  376. package/cypress/e2e/bugs/380-transliteration-when-pasting.ts +0 -75
  377. package/cypress/e2e/bugs/398-digraphs-in-created-words.ts +0 -38
  378. package/cypress/e2e/bugs/401-board-state-on-game-change.ts +0 -36
  379. package/cypress/e2e/bugs.spec.cy.ts +0 -13
  380. package/cypress/e2e/features/198-highlight-unreachable-cells.ts +0 -101
  381. package/cypress/e2e/features/91-hover-remaining-tile-highlights.ts +0 -90
  382. package/cypress/e2e/features/solver-paths.ts +0 -66
  383. package/cypress/e2e/features.spec.cy.ts +0 -3
  384. package/cypress/support/commands.ts +0 -4
  385. package/cypress/support/e2e.ts +0 -1
  386. package/cypress/support/index.ts +0 -1
  387. package/cypress/support/lib/actions.ts +0 -104
  388. package/cypress/support/lib/selectors.ts +0 -70
  389. package/cypress/tsconfig.json +0 -10
  390. package/cypress.config.ts +0 -17
  391. package/packages/scrabble-solver/.next/server/chunks/129.js +0 -1
  392. package/packages/scrabble-solver/.next/server/chunks/318.js +0 -3
  393. package/packages/scrabble-solver/.next/server/chunks/791.js +0 -15
  394. package/packages/scrabble-solver/.next/static/OLE8_ZInlGH7ENU0NIOpf/_buildManifest.js +0 -1
  395. package/packages/scrabble-solver/.next/static/chunks/830.661816bf0de8c2f1.js +0 -1
  396. package/packages/scrabble-solver/.next/static/chunks/framework-3cf15b8041c59dd3.js +0 -1
  397. package/packages/scrabble-solver/.next/static/chunks/main-5b999f3f24ebfa84.js +0 -5
  398. package/packages/scrabble-solver/.next/static/chunks/pages/_app-7024e7a27ffbb2fa.js +0 -7
  399. package/packages/scrabble-solver/.next/static/chunks/pages/index-8b398b0a5d870773.js +0 -1
  400. package/packages/scrabble-solver/.next/static/chunks/pages/not-found-9e076427bad69d90.js +0 -1
  401. package/packages/scrabble-solver/.next/static/chunks/webpack-3943d624bbb47a64.js +0 -1
  402. package/packages/scrabble-solver/.next/static/css/a78e04fe95452380.css +0 -1
  403. package/packages/scrabble-solver/.next/static/css/aedeaada218d7280.css +0 -2
  404. package/packages/scrabble-solver/src/app-layout/AppLayoutContext.tsx +0 -25
  405. package/packages/scrabble-solver/src/app-layout/index.ts +0 -1
  406. package/packages/scrabble-solver/src/app-layout/useAppLayoutValue.ts +0 -112
  407. package/packages/scrabble-solver/src/components/Board/hooks/useBackgroundImage.tsx +0 -279
  408. package/packages/scrabble-solver/src/components/SeoMessage/SeoMessage.tsx +0 -18
  409. package/packages/scrabble-solver/src/components/SeoMessage/index.ts +0 -1
  410. package/packages/scrabble-solver/src/components/index.ts +0 -26
  411. package/packages/scrabble-solver/src/hooks/index.ts +0 -12
  412. package/packages/scrabble-solver/src/hooks/useOnWindowResize.ts +0 -11
  413. package/packages/scrabble-solver/src/hooks/useViewportSize.ts +0 -17
  414. package/packages/scrabble-solver/src/i18n/index.ts +0 -2
  415. package/packages/scrabble-solver/src/icons/Flag.svg +0 -4
  416. package/packages/scrabble-solver/src/icons/Star.svg +0 -4
  417. package/packages/scrabble-solver/src/icons/index.ts +0 -49
  418. package/packages/scrabble-solver/src/lib/getTileSizes.ts +0 -13
  419. package/packages/scrabble-solver/src/lib/index.ts +0 -24
  420. package/packages/scrabble-solver/src/modals/index.ts +0 -7
  421. /package/{cypress/support → e2e}/lib/index.ts +0 -0
  422. /package/packages/scrabble-solver/.next/static/{OLE8_ZInlGH7ENU0NIOpf → 5KOWzYqfY1Z2rjrzptr8D}/_ssgManifest.js +0 -0
@@ -40,6 +40,34 @@
40
40
  "packages/configs/package.json",
41
41
  "nx/core/package-json"
42
42
  ],
43
+ "tags.npm:scrabble": [
44
+ "packages/configs/package.json",
45
+ "nx/core/package-json"
46
+ ],
47
+ "tags.npm:board-game": [
48
+ "packages/configs/package.json",
49
+ "nx/core/package-json"
50
+ ],
51
+ "tags.npm:tile-distribution": [
52
+ "packages/configs/package.json",
53
+ "nx/core/package-json"
54
+ ],
55
+ "tags.npm:game-config": [
56
+ "packages/configs/package.json",
57
+ "nx/core/package-json"
58
+ ],
59
+ "tags.npm:literaki": [
60
+ "packages/configs/package.json",
61
+ "nx/core/package-json"
62
+ ],
63
+ "tags.npm:kelimelik": [
64
+ "packages/configs/package.json",
65
+ "nx/core/package-json"
66
+ ],
67
+ "tags.npm:super-scrabble": [
68
+ "packages/configs/package.json",
69
+ "nx/core/package-json"
70
+ ],
43
71
  "metadata.targetGroups": [
44
72
  "packages/configs/package.json",
45
73
  "nx/core/package-json"
@@ -174,6 +202,14 @@
174
202
  "packages/constants/package.json",
175
203
  "nx/core/package-json"
176
204
  ],
205
+ "tags.npm:scrabble": [
206
+ "packages/constants/package.json",
207
+ "nx/core/package-json"
208
+ ],
209
+ "tags.npm:constants": [
210
+ "packages/constants/package.json",
211
+ "nx/core/package-json"
212
+ ],
177
213
  "metadata.targetGroups": [
178
214
  "packages/constants/package.json",
179
215
  "nx/core/package-json"
@@ -308,6 +344,26 @@
308
344
  "packages/dictionaries/package.json",
309
345
  "nx/core/package-json"
310
346
  ],
347
+ "tags.npm:scrabble": [
348
+ "packages/dictionaries/package.json",
349
+ "nx/core/package-json"
350
+ ],
351
+ "tags.npm:dictionary": [
352
+ "packages/dictionaries/package.json",
353
+ "nx/core/package-json"
354
+ ],
355
+ "tags.npm:gaddag": [
356
+ "packages/dictionaries/package.json",
357
+ "nx/core/package-json"
358
+ ],
359
+ "tags.npm:word-list": [
360
+ "packages/dictionaries/package.json",
361
+ "nx/core/package-json"
362
+ ],
363
+ "tags.npm:cache": [
364
+ "packages/dictionaries/package.json",
365
+ "nx/core/package-json"
366
+ ],
311
367
  "metadata.targetGroups": [
312
368
  "packages/dictionaries/package.json",
313
369
  "nx/core/package-json"
@@ -538,6 +594,18 @@
538
594
  "packages/logger/package.json",
539
595
  "nx/core/package-json"
540
596
  ],
597
+ "tags.npm:logger": [
598
+ "packages/logger/package.json",
599
+ "nx/core/package-json"
600
+ ],
601
+ "tags.npm:winston": [
602
+ "packages/logger/package.json",
603
+ "nx/core/package-json"
604
+ ],
605
+ "tags.npm:scrabble": [
606
+ "packages/logger/package.json",
607
+ "nx/core/package-json"
608
+ ],
541
609
  "metadata.targetGroups": [
542
610
  "packages/logger/package.json",
543
611
  "nx/core/package-json"
@@ -704,6 +772,26 @@
704
772
  "packages/scrabble-solver/package.json",
705
773
  "nx/core/package-json"
706
774
  ],
775
+ "tags.npm:scrabble": [
776
+ "packages/scrabble-solver/package.json",
777
+ "nx/core/package-json"
778
+ ],
779
+ "tags.npm:word-finder": [
780
+ "packages/scrabble-solver/package.json",
781
+ "nx/core/package-json"
782
+ ],
783
+ "tags.npm:solver": [
784
+ "packages/scrabble-solver/package.json",
785
+ "nx/core/package-json"
786
+ ],
787
+ "tags.npm:nextjs": [
788
+ "packages/scrabble-solver/package.json",
789
+ "nx/core/package-json"
790
+ ],
791
+ "tags.npm:react": [
792
+ "packages/scrabble-solver/package.json",
793
+ "nx/core/package-json"
794
+ ],
707
795
  "metadata.targetGroups": [
708
796
  "packages/scrabble-solver/package.json",
709
797
  "nx/core/package-json"
@@ -740,6 +828,10 @@
740
828
  "packages/scrabble-solver/package.json",
741
829
  "nx/core/package-json"
742
830
  ],
831
+ "metadata.targetGroups.NPM Scripts.7": [
832
+ "packages/scrabble-solver/package.json",
833
+ "nx/core/package-json"
834
+ ],
743
835
  "metadata.description": [
744
836
  "packages/scrabble-solver/package.json",
745
837
  "nx/core/package-json"
@@ -872,6 +964,34 @@
872
964
  "packages/scrabble-solver/package.json",
873
965
  "nx/core/package-json"
874
966
  ],
967
+ "targets.generate-scss-types": [
968
+ "packages/scrabble-solver/package.json",
969
+ "nx/core/package-json"
970
+ ],
971
+ "targets.generate-scss-types.executor": [
972
+ "packages/scrabble-solver/package.json",
973
+ "nx/core/package-json"
974
+ ],
975
+ "targets.generate-scss-types.metadata": [
976
+ "packages/scrabble-solver/package.json",
977
+ "nx/core/package-json"
978
+ ],
979
+ "targets.generate-scss-types.options.script": [
980
+ "packages/scrabble-solver/package.json",
981
+ "nx/core/package-json"
982
+ ],
983
+ "targets.generate-scss-types.options": [
984
+ "packages/scrabble-solver/package.json",
985
+ "nx/core/package-json"
986
+ ],
987
+ "targets.generate-scss-types.metadata.scriptContent": [
988
+ "packages/scrabble-solver/package.json",
989
+ "nx/core/package-json"
990
+ ],
991
+ "targets.generate-scss-types.metadata.runCommand": [
992
+ "packages/scrabble-solver/package.json",
993
+ "nx/core/package-json"
994
+ ],
875
995
  "targets.start": [
876
996
  "packages/scrabble-solver/package.json",
877
997
  "nx/core/package-json"
@@ -998,6 +1118,30 @@
998
1118
  "packages/solver/package.json",
999
1119
  "nx/core/package-json"
1000
1120
  ],
1121
+ "tags.npm:scrabble": [
1122
+ "packages/solver/package.json",
1123
+ "nx/core/package-json"
1124
+ ],
1125
+ "tags.npm:solver": [
1126
+ "packages/solver/package.json",
1127
+ "nx/core/package-json"
1128
+ ],
1129
+ "tags.npm:gaddag": [
1130
+ "packages/solver/package.json",
1131
+ "nx/core/package-json"
1132
+ ],
1133
+ "tags.npm:move-generation": [
1134
+ "packages/solver/package.json",
1135
+ "nx/core/package-json"
1136
+ ],
1137
+ "tags.npm:word-finder": [
1138
+ "packages/solver/package.json",
1139
+ "nx/core/package-json"
1140
+ ],
1141
+ "tags.npm:anagram": [
1142
+ "packages/solver/package.json",
1143
+ "nx/core/package-json"
1144
+ ],
1001
1145
  "metadata.targetGroups": [
1002
1146
  "packages/solver/package.json",
1003
1147
  "nx/core/package-json"
@@ -1196,6 +1340,26 @@
1196
1340
  "packages/types/package.json",
1197
1341
  "nx/core/package-json"
1198
1342
  ],
1343
+ "tags.npm:scrabble": [
1344
+ "packages/types/package.json",
1345
+ "nx/core/package-json"
1346
+ ],
1347
+ "tags.npm:types": [
1348
+ "packages/types/package.json",
1349
+ "nx/core/package-json"
1350
+ ],
1351
+ "tags.npm:domain-model": [
1352
+ "packages/types/package.json",
1353
+ "nx/core/package-json"
1354
+ ],
1355
+ "tags.npm:board": [
1356
+ "packages/types/package.json",
1357
+ "nx/core/package-json"
1358
+ ],
1359
+ "tags.npm:tiles": [
1360
+ "packages/types/package.json",
1361
+ "nx/core/package-json"
1362
+ ],
1199
1363
  "metadata.targetGroups": [
1200
1364
  "packages/types/package.json",
1201
1365
  "nx/core/package-json"
@@ -1362,6 +1526,22 @@
1362
1526
  "packages/word-definitions/package.json",
1363
1527
  "nx/core/package-json"
1364
1528
  ],
1529
+ "tags.npm:word-definitions": [
1530
+ "packages/word-definitions/package.json",
1531
+ "nx/core/package-json"
1532
+ ],
1533
+ "tags.npm:dictionary": [
1534
+ "packages/word-definitions/package.json",
1535
+ "nx/core/package-json"
1536
+ ],
1537
+ "tags.npm:wiktionary": [
1538
+ "packages/word-definitions/package.json",
1539
+ "nx/core/package-json"
1540
+ ],
1541
+ "tags.npm:scrabble": [
1542
+ "packages/word-definitions/package.json",
1543
+ "nx/core/package-json"
1544
+ ],
1365
1545
  "metadata.targetGroups": [
1366
1546
  "packages/word-definitions/package.json",
1367
1547
  "nx/core/package-json"
@@ -1528,6 +1708,30 @@
1528
1708
  "packages/word-lists/package.json",
1529
1709
  "nx/core/package-json"
1530
1710
  ],
1711
+ "tags.npm:word-list": [
1712
+ "packages/word-lists/package.json",
1713
+ "nx/core/package-json"
1714
+ ],
1715
+ "tags.npm:scrabble": [
1716
+ "packages/word-lists/package.json",
1717
+ "nx/core/package-json"
1718
+ ],
1719
+ "tags.npm:dictionary": [
1720
+ "packages/word-lists/package.json",
1721
+ "nx/core/package-json"
1722
+ ],
1723
+ "tags.npm:sowpods": [
1724
+ "packages/word-lists/package.json",
1725
+ "nx/core/package-json"
1726
+ ],
1727
+ "tags.npm:twl06": [
1728
+ "packages/word-lists/package.json",
1729
+ "nx/core/package-json"
1730
+ ],
1731
+ "tags.npm:sjp": [
1732
+ "packages/word-lists/package.json",
1733
+ "nx/core/package-json"
1734
+ ],
1531
1735
  "metadata.targetGroups": [
1532
1736
  "packages/word-lists/package.json",
1533
1737
  "nx/core/package-json"
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",
@@ -262,7 +257,7 @@
262
257
  },
263
258
  "overrides": [
264
259
  {
265
- "files": ["packages/*/benchmarks/**"],
260
+ "files": ["packages/*/benchmarks/**", "packages/*/scripts/**"],
266
261
  "rules": {
267
262
  "no-console": "off"
268
263
  }
@@ -303,11 +298,13 @@
303
298
  }
304
299
  },
305
300
  {
306
- "files": ["**/*.spec.cy.ts", "cypress/**"],
301
+ "files": ["e2e/**"],
307
302
  "rules": {
308
303
  "max-statements": "off",
304
+ "max-lines": "off",
309
305
  "jest/valid-expect": "off",
310
- "jest/no-focused-tests": "off"
306
+ "jest/no-focused-tests": "off",
307
+ "playwright/no-focused-test": "error"
311
308
  }
312
309
  },
313
310
  {
package/CLAUDE.md CHANGED
@@ -30,15 +30,17 @@ The GADDAG itself lives outside this repo, in [`@kamilmielnik/gaddag`](https://g
30
30
  ### App package (`@scrabble-solver/scrabble-solver`)
31
31
 
32
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/{board,cellFilters,dictionary,hoveredTile,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`).
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.
34
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.
35
- - **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).
36
- - **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`).
37
- - **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.
38
- - **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.
39
- - **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`.
40
41
  - **SVGs**: imported as React components via `@svgr/webpack` (configured in `next.config.js`), typed by `src/@types/svg.d.ts`.
41
- - **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) — translations preload, modal chunk warming, and `visit()`; service worker registration is idle-only (`waitForIdle`), and the dictionary prefetch is intent-only (`waitForFirstIntent` — the multi-MB download is pointless for passive visitors and would land in Lighthouse's payload audit). Tooltips defer their floating-ui setup one task past hydration via `useDeferredRender`.
42
44
 
43
45
  ## Common commands
44
46
 
@@ -59,9 +61,9 @@ All commands run from the repo root unless noted.
59
61
  | One unit test file | `cd packages/solver && bun test src/solve.test.ts` |
60
62
  | One unit test by name | `cd packages/solver && bun test -t "pattern"` |
61
63
  | Solver benchmarks | `bun run benchmark` (rewrites the results table + chart in `packages/solver/README.md`) |
62
- | Cypress (interactive) | `bun run test-cypress` (expects dev server on :3000) |
63
- | Cypress (headless) | `bun run test-cypress:run` (expects server on :3333) |
64
- | 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 |
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 |
65
67
 
66
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.
67
69
 
@@ -71,15 +73,15 @@ Hot reload only works for the `scrabble-solver` package. Edits to any other pack
71
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.
72
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.
73
75
  - Each package's `tsconfig.json` excludes `**/*.test.ts` from the build output. Tests are not part of published packages.
74
- - 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`.
75
77
 
76
78
  ## Tooling specifics
77
79
 
78
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.
79
81
  - **Formatting**: `oxfmt` covers `*.{js,ts,tsx,scss}`.
80
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.
81
- - **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 `cypress` and `cypress.config.ts`; library packages extend it and additionally exclude `**/*.test.ts` from emitted output.
82
- - **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`.
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`.
83
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.
84
86
 
85
87
  ## CI workflows
@@ -88,9 +90,9 @@ Hot reload only works for the `scrabble-solver` package. Edits to any other pack
88
90
 
89
91
  - `build.yml` — `bun install --frozen-lockfile && bun run build`.
90
92
  - `unit.yml` — `bun run build && bun run test-unit`.
91
- - `e2e.yml` — Cypress (`cypress-io/github-action`) against the production server on :3333. Uploads screenshots on failure.
93
+ - `e2e.yml` — Playwright against the app on :3333 (via `start-server-and-test`). Uploads the HTML report and traces on failure.
92
94
  - `oxlint.yml` / `oxfmt.yml` — lint and format-check. `oxlint.yml` runs `bun run build` first because type-aware rules need built packages.
93
- - `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 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.
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.
94
96
  - `deploy.yml` — `workflow_dispatch` only (branch input, default `master`). SSHs into prod, pulls, builds, restarts `scrabble-solver.service`.
95
97
 
96
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`.
@@ -116,6 +118,7 @@ The `bunx scrabble-solver@latest` entry point (`bin/scrabble-solver.js`) just `c
116
118
 
117
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.
118
120
 
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.
119
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.
120
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`.
121
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.
package/README.md CHANGED
@@ -200,7 +200,7 @@ bun run --filter @scrabble-solver/word-lists build
200
200
  - [include-media](https://eduardoboucas.github.io/include-media/)
201
201
  - [Lerna](https://lerna.js.org/)
202
202
  - [Nx](https://nx.dev/)
203
- - [Cypress](https://www.cypress.io/)
203
+ - [Playwright](https://playwright.dev/)
204
204
  - [Oxlint](https://oxc.rs/docs/guide/usage/linter)
205
205
  - [Oxfmt](https://oxc.rs/docs/guide/usage/formatter)
206
206
 
@@ -220,6 +220,13 @@ The user interface and source code for the web front end of Lexatious is based o
220
220
  - Web: https://lexatious.com
221
221
  - GitHub: https://github.com/ericgjackson/lexatious_web
222
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
+
223
230
  ## Media
224
231
 
225
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)