@scrabble-solver/scrabble-solver 2.17.0 → 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 (328) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +22 -20
  3. package/.next/cache/.previewinfo +1 -1
  4. package/.next/cache/.rscinfo +1 -1
  5. package/.next/cache/.tsbuildinfo +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  11. package/.next/cache/webpack/server-production/0.pack +0 -0
  12. package/.next/cache/webpack/server-production/index.pack +0 -0
  13. package/.next/cache/webpack/server-production/index.pack.old +0 -0
  14. package/.next/diagnostics/framework.json +1 -1
  15. package/.next/dynamic-css-manifest.json +8 -1
  16. package/.next/next-minimal-server.js.nft.json +1 -1
  17. package/.next/next-server.js.nft.json +1 -1
  18. package/.next/prerender-manifest.json +4 -4
  19. package/.next/react-loadable-manifest.json +176 -1
  20. package/.next/routes-manifest.json +1 -1
  21. package/.next/server/chunks/309.js +1 -0
  22. package/.next/server/chunks/{785.js → 316.js} +1 -1
  23. package/.next/server/chunks/344.js +1 -0
  24. package/.next/server/chunks/371.js +1 -0
  25. package/.next/server/chunks/465.js +1 -0
  26. package/.next/server/chunks/546.js +1 -0
  27. package/.next/server/chunks/623.js +1 -0
  28. package/.next/server/chunks/624.js +1 -0
  29. package/.next/server/chunks/641.js +1 -0
  30. package/.next/server/chunks/671.js +1 -1
  31. package/.next/server/chunks/692.js +1 -0
  32. package/.next/server/chunks/705.js +1 -0
  33. package/.next/server/chunks/712.js +1 -1
  34. package/.next/server/chunks/846.js +1 -0
  35. package/.next/server/chunks/853.js +15 -0
  36. package/.next/server/chunks/886.js +1 -0
  37. package/.next/server/chunks/916.js +1 -0
  38. package/.next/server/chunks/953.js +1 -0
  39. package/.next/server/chunks/961.js +1 -0
  40. package/.next/server/chunks/968.js +1 -0
  41. package/.next/server/dynamic-css-manifest.js +1 -1
  42. package/.next/server/middleware-build-manifest.js +1 -1
  43. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  44. package/.next/server/pages/404.html +28 -1
  45. package/.next/server/pages/500.html +28 -1
  46. package/.next/server/pages/_app.js +1 -1
  47. package/.next/server/pages/_app.js.nft.json +1 -1
  48. package/.next/server/pages/_document.js +28 -1
  49. package/.next/server/pages/_document.js.nft.json +1 -1
  50. package/.next/server/pages/_error.js +28 -1
  51. package/.next/server/pages/_error.js.nft.json +1 -1
  52. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  53. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  54. package/.next/server/pages/api/solve.js +1 -1
  55. package/.next/server/pages/api/solve.js.nft.json +1 -1
  56. package/.next/server/pages/api/verify.js.nft.json +1 -1
  57. package/.next/server/pages/api/visit.js.nft.json +1 -1
  58. package/.next/server/pages/index.html +28 -1
  59. package/.next/server/pages/index.js +30 -1
  60. package/.next/server/pages/index.js.nft.json +1 -1
  61. package/.next/server/pages/index.json +1 -1
  62. package/.next/server/pages/not-found.html +28 -1
  63. package/.next/server/pages/not-found.js.nft.json +1 -1
  64. package/.next/server/pages-manifest.json +1 -1
  65. package/.next/server/webpack-api-runtime.js +1 -1
  66. package/.next/server/webpack-runtime.js +1 -1
  67. package/.next/static/FL82obwzFHRzvV6z76I-U/_buildManifest.js +1 -0
  68. package/.next/static/chunks/101-829ab8a8d310e7a0.js +3 -0
  69. package/.next/static/chunks/106.e414e88694add27f.js +1 -0
  70. package/.next/static/chunks/127.125d896a19061d71.js +1 -0
  71. package/.next/static/chunks/18.8b91e137d239e15c.js +1 -0
  72. package/.next/static/chunks/277.6785dfaf22fdb0b7.js +1 -0
  73. package/.next/static/chunks/307.67f52f13e3d60d4e.js +1 -0
  74. package/.next/static/chunks/341.79535ba85bf1986f.js +1 -0
  75. package/.next/static/chunks/363.01eed3233f92b6a4.js +1 -0
  76. package/.next/static/chunks/422.1559ae6756912ca5.js +1 -0
  77. package/.next/static/chunks/541.dcd9e8b804f88da5.js +1 -0
  78. package/.next/static/chunks/631.98d42abad0a60af6.js +1 -0
  79. package/.next/static/chunks/663.d2c9ca36b26695b8.js +1 -0
  80. package/.next/static/chunks/711.ba7b8f900dfd40d0.js +1 -0
  81. package/.next/static/chunks/744.b06c9a3de4a1d877.js +1 -0
  82. package/.next/static/chunks/820.97b82f751ca49579.js +1 -0
  83. package/.next/static/chunks/865.a9b563a45848a83c.js +1 -0
  84. package/.next/static/chunks/912.ff8037ba9d4db5c8.js +1 -0
  85. package/.next/static/chunks/939.19b33a3d3c351d73.js +1 -0
  86. package/.next/static/chunks/990.61296e5870b91f11.js +1 -0
  87. package/.next/static/chunks/framework-626cbd48ac5aca57.js +1 -0
  88. package/.next/static/chunks/main-b92e08f3fc96dd8d.js +5 -0
  89. package/.next/static/chunks/pages/_app-ba5d628e81a15461.js +1 -0
  90. package/.next/static/chunks/pages/{_error-afeda7c5231b6c3c.js → _error-f0290a98eb3ed0ec.js} +1 -1
  91. package/.next/static/chunks/pages/index-5f9fa6ac3cd3d97b.js +1 -0
  92. package/.next/static/chunks/pages/not-found-23d61fd3ed791cad.js +1 -0
  93. package/.next/static/chunks/webpack-e34c3ae12275e978.js +1 -0
  94. package/.next/static/css/2d03f0da2c678dfc.css +1 -0
  95. package/.next/static/css/7b6724d2df70f48f.css +1 -0
  96. package/.next/static/css/abc2425d3ba607bc.css +1 -0
  97. package/.next/static/css/b736be3bd02b5d99.css +1 -0
  98. package/.next/static/css/b74cd7de82dabcdd.css +1 -0
  99. package/.next/static/css/c403ce739a9c0d83.css +1 -0
  100. package/.next/static/css/ca311e6c030663d4.css +1 -0
  101. package/.next/static/css/dee878eb64099d92.css +1 -0
  102. package/.next/static/css/fbfdce70731d0d2b.css +1 -0
  103. package/.next/trace +21 -21
  104. package/.next/trace-build +1 -1
  105. package/empty-module.js +2 -0
  106. package/next.config.js +13 -2
  107. package/package.json +34 -15
  108. package/public/llms.txt +11 -0
  109. package/public/robots.txt +4 -0
  110. package/scripts/generate-scss-types.ts +112 -0
  111. package/src/@types/scss.d.ts +6 -9
  112. package/src/@types/svg.d.ts +1 -1
  113. package/src/components/Alert/Alert.module.scss.d.ts +13 -0
  114. package/src/components/Alert/Alert.tsx +4 -1
  115. package/src/components/Badge/Badge.module.scss.d.ts +6 -0
  116. package/src/components/Board/Board.module.scss +10 -0
  117. package/src/components/Board/Board.module.scss.d.ts +17 -0
  118. package/src/components/Board/Board.tsx +14 -26
  119. package/src/components/Board/BoardPure.tsx +54 -49
  120. package/src/components/Board/components/Actions/Actions.module.scss +18 -18
  121. package/src/components/Board/components/Actions/Actions.module.scss.d.ts +7 -0
  122. package/src/components/Board/components/Actions/Actions.tsx +6 -3
  123. package/src/components/Board/components/Actions/lib.ts +3 -1
  124. package/src/components/Board/components/Cell/Cell.module.scss.d.ts +13 -0
  125. package/src/components/Board/components/Cell/Cell.tsx +28 -33
  126. package/src/components/Board/components/InputPrompt/InputPrompt.module.scss.d.ts +10 -0
  127. package/src/components/Board/components/InputPrompt/InputPrompt.tsx +1 -1
  128. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss.d.ts +8 -0
  129. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -2
  130. package/src/components/Board/getBoardBackground.ts +175 -0
  131. package/src/components/Board/hooks/index.ts +0 -1
  132. package/src/components/Board/hooks/useBoardStyle.ts +10 -20
  133. package/src/components/Board/hooks/useFloatingActions.ts +3 -6
  134. package/src/components/Board/hooks/useGrid.ts +6 -3
  135. package/src/components/Board/selectors.ts +1 -1
  136. package/src/components/Button/Button.module.scss.d.ts +12 -0
  137. package/src/components/Dictionary/Dictionary.module.scss +1 -0
  138. package/src/components/Dictionary/Dictionary.module.scss.d.ts +14 -0
  139. package/src/components/Dictionary/Dictionary.tsx +1 -3
  140. package/src/components/DictionaryInput/DictionaryInput.module.scss.d.ts +7 -0
  141. package/src/components/DictionaryInput/DictionaryInput.tsx +1 -1
  142. package/src/components/EmptyState/EmptyState.module.scss.d.ts +7 -0
  143. package/src/components/EmptyState/EmptyState.tsx +1 -1
  144. package/src/components/IconButton/IconButton.module.scss.d.ts +8 -0
  145. package/src/components/Key/Key.module.scss.d.ts +6 -0
  146. package/src/components/Keys/Arrows/Arrows.module.scss.d.ts +11 -0
  147. package/src/components/Keys/Arrows/Arrows.tsx +4 -1
  148. package/src/components/Keys/index.tsx +1 -1
  149. package/src/components/Loading/Loading.module.scss.d.ts +9 -0
  150. package/src/components/Loading/Loading.tsx +1 -1
  151. package/src/components/Logo/Logo.tsx +4 -9
  152. package/src/components/Logo/LogoBlueprint.tsx +1 -0
  153. package/src/components/Modal/Modal.module.scss.d.ts +15 -0
  154. package/src/components/Modal/Modal.tsx +16 -3
  155. package/src/components/Modal/components/Section/Section.module.scss.d.ts +7 -0
  156. package/src/components/NavButtons/NavButtons.module.scss +20 -0
  157. package/src/components/NavButtons/NavButtons.module.scss.d.ts +13 -0
  158. package/src/components/NavButtons/NavButtons.tsx +29 -45
  159. package/src/components/NotFound/NotFound.module.scss.d.ts +9 -0
  160. package/src/components/NotFound/NotFound.tsx +1 -1
  161. package/src/components/PlainTiles/PlainTiles.module.scss.d.ts +11 -0
  162. package/src/components/PlainTiles/PlainTiles.tsx +0 -1
  163. package/src/components/PlainTiles/Tile.tsx +3 -4
  164. package/src/components/PlainTiles/lib.ts +4 -19
  165. package/src/components/PlainTiles/types.ts +0 -1
  166. package/src/components/Progress/Progress.module.scss +7 -0
  167. package/src/components/Progress/Progress.module.scss.d.ts +6 -0
  168. package/src/components/Progress/Progress.tsx +2 -11
  169. package/src/components/Rack/Rack.module.scss +3 -0
  170. package/src/components/Rack/Rack.module.scss.d.ts +10 -0
  171. package/src/components/Rack/Rack.tsx +8 -11
  172. package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +2 -0
  173. package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss.d.ts +7 -0
  174. package/src/components/Rack/components/InputPrompt/InputPrompt.tsx +1 -13
  175. package/src/components/Rack/components/InputPrompt/lib.ts +1 -1
  176. package/src/components/Rack/components/RackTile/RackTile.module.scss.d.ts +6 -0
  177. package/src/components/Rack/components/RackTile/RackTile.tsx +5 -4
  178. package/src/components/Rack/selectors.ts +1 -1
  179. package/src/components/Radio/Radio.module.scss.d.ts +12 -0
  180. package/src/components/Results/Cell.tsx +3 -12
  181. package/src/components/Results/Header.tsx +9 -18
  182. package/src/components/Results/HeaderButton.tsx +4 -5
  183. package/src/components/Results/Result.tsx +4 -12
  184. package/src/components/Results/Results.module.scss +4 -6
  185. package/src/components/Results/Results.module.scss.d.ts +27 -0
  186. package/src/components/Results/Results.tsx +4 -3
  187. package/src/components/Results/SolveButton.tsx +1 -1
  188. package/src/components/ResultsInput/ResultsInput.module.scss.d.ts +7 -0
  189. package/src/components/ResultsInput/ResultsInput.tsx +1 -1
  190. package/src/components/Solver/Solver.module.scss +6 -5
  191. package/src/components/Solver/Solver.module.scss.d.ts +20 -0
  192. package/src/components/Solver/Solver.tsx +19 -21
  193. package/src/components/Solver/components/InsertButton/InsertButton.module.scss.d.ts +7 -0
  194. package/src/components/Solver/components/InsertButton/InsertButton.tsx +1 -1
  195. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss.d.ts +18 -0
  196. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +3 -1
  197. package/src/components/Spinner/Spinner.module.scss.d.ts +10 -0
  198. package/src/components/Tile/Tile.module.scss +3 -0
  199. package/src/components/Tile/Tile.module.scss.d.ts +19 -0
  200. package/src/components/Tile/Tile.tsx +5 -14
  201. package/src/components/Tile/TilePure.tsx +2 -12
  202. package/src/components/Tooltip/Tooltip.module.scss.d.ts +7 -0
  203. package/src/components/Tooltip/Tooltip.tsx +14 -3
  204. package/src/components/Tooltip/useTooltip.ts +1 -0
  205. package/src/hooks/useColumns.ts +4 -7
  206. package/src/hooks/useDeferredRender.ts +20 -0
  207. package/src/hooks/useDirection.ts +4 -3
  208. package/src/hooks/useLanguage.ts +2 -2
  209. package/src/hooks/useLocalStorage.ts +32 -8
  210. package/src/hooks/useMedia.ts +26 -26
  211. package/src/i18n/constants.ts +2 -15
  212. package/src/i18n/i18n.ts +18 -17
  213. package/src/i18n/languages/english.json +0 -1
  214. package/src/i18n/languages/french.json +0 -1
  215. package/src/i18n/languages/german.json +0 -1
  216. package/src/i18n/languages/persian.json +0 -1
  217. package/src/i18n/languages/polish.json +0 -1
  218. package/src/i18n/languages/romanian.json +0 -1
  219. package/src/i18n/languages/spanish.json +0 -1
  220. package/src/i18n/languages/turkish.json +0 -1
  221. package/src/i18n/localeIcons.ts +24 -0
  222. package/src/lib/localeTransliterate.test.ts +5 -0
  223. package/src/lib/localeTransliterate.ts +26 -6
  224. package/src/lib/waitForIdleOrFirstIntent.ts +33 -0
  225. package/src/modals/DictionaryModal/DictionaryModal.module.scss.d.ts +8 -0
  226. package/src/modals/DictionaryModal/DictionaryModal.tsx +3 -1
  227. package/src/modals/KeyMapModal/KeyMapModal.tsx +3 -1
  228. package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss.d.ts +11 -0
  229. package/src/modals/MenuModal/MenuModal.module.scss +5 -0
  230. package/src/modals/MenuModal/MenuModal.module.scss.d.ts +10 -0
  231. package/src/modals/MenuModal/MenuModal.tsx +16 -5
  232. package/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss +4 -1
  233. package/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss.d.ts +9 -0
  234. package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -6
  235. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
  236. package/src/modals/RemainingTilesModal/components/Character/Character.module.scss.d.ts +12 -0
  237. package/src/modals/RemainingTilesModal/components/Character/Character.tsx +5 -12
  238. package/src/modals/ResultsModal/ResultsModal.module.scss.d.ts +8 -0
  239. package/src/modals/ResultsModal/ResultsModal.tsx +8 -4
  240. package/src/modals/SettingsModal/SettingsModal.tsx +2 -2
  241. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.module.scss.d.ts +7 -0
  242. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -1
  243. package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss.d.ts +7 -0
  244. package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -1
  245. package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.module.scss.d.ts +7 -0
  246. package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.tsx +1 -1
  247. package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss.d.ts +7 -0
  248. package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +1 -1
  249. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss.d.ts +8 -0
  250. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +24 -21
  251. package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.module.scss.d.ts +7 -0
  252. package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.tsx +1 -1
  253. package/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.module.scss.d.ts +7 -0
  254. package/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.tsx +1 -5
  255. package/src/modals/WordsModal/WordsModal.module.scss.d.ts +11 -0
  256. package/src/modals/WordsModal/WordsModal.tsx +4 -2
  257. package/src/modals/preload.ts +24 -0
  258. package/src/pages/_app.tsx +5 -8
  259. package/src/pages/_document.tsx +91 -5
  260. package/src/pages/api/solve.ts +1 -1
  261. package/src/pages/index.module.scss +1 -0
  262. package/src/pages/index.module.scss.d.ts +11 -0
  263. package/src/pages/index.tsx +98 -38
  264. package/src/pages/not-found.tsx +1 -1
  265. package/src/parameters/index.ts +24 -93
  266. package/src/serviceWorkerManager.ts +5 -2
  267. package/src/state/actions.ts +1 -1
  268. package/src/state/app/index.ts +4 -0
  269. package/src/state/app/initialState.ts +6 -0
  270. package/src/state/app/selectors.ts +5 -0
  271. package/src/state/app/slice.ts +20 -0
  272. package/src/state/app/types.ts +4 -0
  273. package/src/state/board/initialState.ts +1 -2
  274. package/src/state/i18n/index.ts +4 -0
  275. package/src/state/i18n/initialState.ts +5 -0
  276. package/src/state/i18n/selectors.ts +3 -0
  277. package/src/state/i18n/slice.ts +16 -0
  278. package/src/state/i18n/types.ts +7 -0
  279. package/src/state/index.ts +2 -0
  280. package/src/state/lib.ts +1 -1
  281. package/src/state/localStorage.ts +86 -29
  282. package/src/state/rack/initialState.ts +1 -2
  283. package/src/state/rack/slice.ts +4 -1
  284. package/src/state/results/lib.ts +5 -5
  285. package/src/state/results/selectors.ts +1 -1
  286. package/src/state/sagas.ts +125 -9
  287. package/src/state/selectors.ts +1 -1
  288. package/src/state/settings/initialState.ts +7 -10
  289. package/src/state/settings/selectors.ts +7 -2
  290. package/src/state/settings/slice.ts +5 -2
  291. package/src/state/store.ts +4 -0
  292. package/src/state/types.ts +4 -0
  293. package/src/styles/_tokens.scss +1 -0
  294. package/src/styles/global.scss +9 -0
  295. package/src/styles/variables.module.scss +0 -21
  296. package/src/styles/variables.module.scss.d.ts +27 -0
  297. package/src/styles/variables.scss +46 -2
  298. package/src/types/index.ts +1 -2
  299. package/tsconfig.tsbuildinfo +1 -1
  300. package/.next/server/chunks/129.js +0 -1
  301. package/.next/server/chunks/318.js +0 -3
  302. package/.next/server/chunks/791.js +0 -15
  303. package/.next/static/OLE8_ZInlGH7ENU0NIOpf/_buildManifest.js +0 -1
  304. package/.next/static/chunks/830.661816bf0de8c2f1.js +0 -1
  305. package/.next/static/chunks/framework-3cf15b8041c59dd3.js +0 -1
  306. package/.next/static/chunks/main-5b999f3f24ebfa84.js +0 -5
  307. package/.next/static/chunks/pages/_app-7024e7a27ffbb2fa.js +0 -7
  308. package/.next/static/chunks/pages/index-8b398b0a5d870773.js +0 -1
  309. package/.next/static/chunks/pages/not-found-9e076427bad69d90.js +0 -1
  310. package/.next/static/chunks/webpack-3943d624bbb47a64.js +0 -1
  311. package/.next/static/css/a78e04fe95452380.css +0 -1
  312. package/.next/static/css/aedeaada218d7280.css +0 -2
  313. package/src/app-layout/AppLayoutContext.tsx +0 -25
  314. package/src/app-layout/index.ts +0 -1
  315. package/src/app-layout/useAppLayoutValue.ts +0 -112
  316. package/src/components/Board/hooks/useBackgroundImage.tsx +0 -279
  317. package/src/components/index.ts +0 -26
  318. package/src/hooks/index.ts +0 -12
  319. package/src/hooks/useOnWindowResize.ts +0 -11
  320. package/src/hooks/useViewportSize.ts +0 -17
  321. package/src/i18n/index.ts +0 -2
  322. package/src/icons/Flag.svg +0 -4
  323. package/src/icons/Star.svg +0 -4
  324. package/src/icons/index.ts +0 -49
  325. package/src/lib/getTileSizes.ts +0 -13
  326. package/src/lib/index.ts +0 -24
  327. package/src/modals/index.ts +0 -7
  328. /package/.next/static/{OLE8_ZInlGH7ENU0NIOpf → FL82obwzFHRzvV6z76I-U}/_ssgManifest.js +0 -0
@@ -5,18 +5,23 @@
5
5
 
6
6
  import { type PayloadAction } from '@reduxjs/toolkit';
7
7
  import { hasConfig, languages } from '@scrabble-solver/configs';
8
- import { Board, type Locale, type Result } from '@scrabble-solver/types';
8
+ import { Board, Locale, type Result } from '@scrabble-solver/types';
9
9
  import { call, delay, put, select, spawn, takeEvery, takeLatest } from 'redux-saga/effects';
10
10
 
11
- import { LOCALE_FEATURES } from '@/i18n';
12
- import { memoize } from '@/lib';
11
+ import { LOCALE_FEATURES } from '@/i18n/constants';
12
+ import { loadTranslations } from '@/i18n/i18n';
13
+ import { memoize } from '@/lib/memoize';
14
+ import { waitForIdleOrFirstIntent } from '@/lib/waitForIdleOrFirstIntent';
13
15
  import { findWordDefinitions, solve, verify, visit } from '@/sdk';
14
16
  import { prefetchDictionary } from '@/solver-worker';
15
17
 
16
18
  import { initialize, reset } from './actions';
19
+ import { appSlice, selectVersion } from './app';
17
20
  import { boardSlice, selectBoard } from './board';
18
21
  import { cellFiltersSlice, selectCellFilter } from './cellFilters';
19
22
  import { dictionarySlice, selectDictionary } from './dictionary';
23
+ import { i18nSlice, selectLoadedTranslations } from './i18n';
24
+ import { localStorage } from './localStorage';
20
25
  import { rackSlice, selectCharacters, selectRack } from './rack';
21
26
  import { resultsSlice } from './results';
22
27
  import {
@@ -25,8 +30,11 @@ import {
25
30
  selectLocale,
26
31
  selectLocaleAutoGroupTiles,
27
32
  selectRemoveCellFilters,
33
+ settingsInitialState,
28
34
  settingsSlice,
35
+ type SettingsState,
29
36
  } from './settings';
37
+ import { guessLocale } from './settings/lib';
30
38
  import { solveSlice } from './solve';
31
39
  import { verifySlice } from './verify';
32
40
 
@@ -119,13 +127,22 @@ function* onDictionarySubmit(): AnyGenerator {
119
127
  }
120
128
  }
121
129
 
122
- function* onInitialize(): AnyGenerator {
130
+ function* onInitialize({ payload }: PayloadAction<{ version: string }>): AnyGenerator {
131
+ try {
132
+ yield* hydratePersistedState(payload.version);
133
+ // oxlint-disable-next-line no-empty
134
+ } catch {
135
+ } finally {
136
+ yield put(appSlice.actions.hydrated());
137
+ }
138
+
123
139
  const board = yield select(selectBoard);
124
140
  const locale = yield select(selectLocale);
125
141
 
126
- yield spawn(prefetchDictionary, locale);
127
- // Detached: offline, a failed visit would otherwise tear down the whole root saga.
128
- yield spawn(visit);
142
+ yield spawn(prefetchDictionaryWhenIdle);
143
+ yield spawn(loadLocaleTranslations, locale);
144
+ yield spawn(preloadTranslationsWhenIdle);
145
+ yield spawn(visitWhenIdle);
129
146
 
130
147
  if (!board.isEmpty()) {
131
148
  yield* resetRack();
@@ -133,6 +150,100 @@ function* onInitialize(): AnyGenerator {
133
150
  }
134
151
  }
135
152
 
153
+ function* hydratePersistedState(version: string): AnyGenerator {
154
+ const isTouchScreen = globalThis.matchMedia?.('(hover: none)').matches ?? false;
155
+ const settings: Pick<SettingsState, 'game' | 'inputMode' | 'locale'> & Partial<SettingsState> = {
156
+ game: settingsInitialState.game,
157
+ inputMode: isTouchScreen ? ('touchscreen' as const) : ('keyboard' as const),
158
+ locale: guessLocale(),
159
+ ...localStorage.getSettings(),
160
+ };
161
+
162
+ if (!hasConfig(settings.game, settings.locale)) {
163
+ const localeDefault = Object.values(languages).find((config) => config.locale === settings.locale);
164
+ settings.game = localeDefault?.game ?? settingsInitialState.game;
165
+ settings.locale = localeDefault?.locale ?? guessLocale();
166
+ }
167
+
168
+ yield put(settingsSlice.actions.init(settings));
169
+ yield* hydratePersistedTranslations(settings.locale, version);
170
+
171
+ const config = yield select(selectConfig);
172
+ const currentBoard: Board = yield select(selectBoard);
173
+ const board = localStorage.getBoard();
174
+
175
+ if (board) {
176
+ yield put(boardSlice.actions.init(board));
177
+ } else if (currentBoard.rows.length !== config.boardHeight || currentBoard.rows[0].length !== config.boardWidth) {
178
+ yield put(boardSlice.actions.init(Board.create(config.boardWidth, config.boardHeight)));
179
+ }
180
+
181
+ const currentRack = yield select(selectRack);
182
+ const rack = localStorage.getRack();
183
+
184
+ if (rack) {
185
+ yield put(rackSlice.actions.init(rack));
186
+ } else if (currentRack.length !== config.rackSize) {
187
+ yield put(rackSlice.actions.init(Array(config.rackSize).fill(null)));
188
+ }
189
+ }
190
+
191
+ // Applied synchronously so the first post-hydration paint is already translated
192
+ function* hydratePersistedTranslations(locale: Locale, version: string): AnyGenerator {
193
+ const translations = localStorage.getTranslations(locale, version);
194
+
195
+ if (translations) {
196
+ yield put(i18nSlice.actions.loaded({ locale, translations }));
197
+ }
198
+ }
199
+
200
+ function* visitWhenIdle(): AnyGenerator {
201
+ yield call(waitForIdleOrFirstIntent);
202
+ yield call(visit);
203
+ }
204
+
205
+ function* prefetchDictionaryWhenIdle(): AnyGenerator {
206
+ yield call(waitForIdleOrFirstIntent);
207
+ const locale = yield select(selectLocale);
208
+ yield call(prefetchDictionary, locale);
209
+ }
210
+
211
+ function* loadLocaleTranslations(locale: Locale): AnyGenerator {
212
+ const loaded = yield select(selectLoadedTranslations);
213
+
214
+ if (loaded[locale]) {
215
+ return;
216
+ }
217
+
218
+ const translations = yield call(loadTranslations, locale);
219
+ yield put(i18nSlice.actions.loaded({ locale, translations }));
220
+
221
+ const activeLocale = yield select(selectLocale);
222
+
223
+ if (locale === activeLocale) {
224
+ const version = yield select(selectVersion);
225
+ localStorage.setTranslations(locale, version, translations);
226
+ }
227
+ }
228
+
229
+ // The cache makes the next boot's first paint translated without waiting for a chunk download
230
+ function* persistTranslationsCache(locale: Locale): AnyGenerator {
231
+ const loaded = yield select(selectLoadedTranslations);
232
+ const version = yield select(selectVersion);
233
+
234
+ if (loaded[locale]) {
235
+ localStorage.setTranslations(locale, version, loaded[locale]);
236
+ }
237
+ }
238
+
239
+ function* preloadTranslationsWhenIdle(): AnyGenerator {
240
+ yield call(waitForIdleOrFirstIntent);
241
+
242
+ for (const locale of Object.values(Locale)) {
243
+ yield spawn(loadLocaleTranslations, locale);
244
+ }
245
+ }
246
+
136
247
  function* onReset(): AnyGenerator {
137
248
  const config = yield select(selectConfig);
138
249
 
@@ -146,7 +257,9 @@ function* onReset(): AnyGenerator {
146
257
  }
147
258
 
148
259
  function* onLocaleChange({ payload: locale }: PayloadAction<Locale>): AnyGenerator {
260
+ yield spawn(loadLocaleTranslations, locale);
149
261
  yield spawn(prefetchDictionary, locale);
262
+ yield* persistTranslationsCache(locale);
150
263
 
151
264
  const game = yield select(selectGame);
152
265
 
@@ -176,11 +289,14 @@ function* onResultCandidateChange({ payload: result }: PayloadAction<Result | nu
176
289
  return;
177
290
  }
178
291
 
179
- yield delay(SUBMIT_DELAY);
180
-
181
292
  const locale: Locale = yield select(selectLocale);
182
293
  const uniqueWords = Array.from(new Set(result.words));
183
294
  const input = uniqueWords.join(LOCALE_FEATURES[locale].separator);
295
+
296
+ if (!memoizedFindWordDefinitions.hasCache(locale, input)) {
297
+ yield delay(SUBMIT_DELAY);
298
+ }
299
+
184
300
  yield put(dictionarySlice.actions.changeInput(input));
185
301
  yield put(dictionarySlice.actions.submit());
186
302
  }
@@ -1,6 +1,6 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
 
3
- import { unorderedArraysEqual } from '@/lib';
3
+ import { unorderedArraysEqual } from '@/lib/unorderedArraysEqual';
4
4
 
5
5
  import { selectBoard } from './board';
6
6
  import { getRemainingTiles } from './lib';
@@ -1,19 +1,16 @@
1
- import { Game } from '@scrabble-solver/types';
1
+ import { Game, Locale } from '@scrabble-solver/types';
2
2
 
3
- import { localStorage } from '../localStorage';
4
-
5
- import { guessLocale } from './lib';
6
3
  import type { SettingsState } from './types';
7
4
 
8
- const isTouchScreen = typeof globalThis.matchMedia !== 'undefined' && globalThis.matchMedia('(hover: none)').matches;
9
-
5
+ /**
6
+ * Deterministic defaults shared by the server render and the client's hydration render.
7
+ */
10
8
  export const settingsInitialState: SettingsState = {
11
9
  autoGroupTiles: null,
12
10
  game: Game.Scrabble,
13
11
  highlightUnreachableCells: false,
14
- inputMode: isTouchScreen ? 'touchscreen' : 'keyboard',
15
- locale: guessLocale(),
12
+ inputMode: 'keyboard',
13
+ locale: Locale.EN_US,
16
14
  removeCellFilters: 'always',
17
- showCoordinates: 'hidden',
18
- ...localStorage.getSettings(),
15
+ showCoordinates: 'original',
19
16
  };
@@ -3,8 +3,10 @@ import { getConfig } from '@scrabble-solver/configs';
3
3
  import { BLANK } from '@scrabble-solver/constants';
4
4
  import { type Cell, type Tile } from '@scrabble-solver/types';
5
5
 
6
- import { i18n, LOCALE_FEATURES } from '@/i18n';
6
+ import { LOCALE_FEATURES } from '@/i18n/constants';
7
+ import { englishTranslations } from '@/i18n/i18n';
7
8
 
9
+ import { selectLoadedTranslations } from '../i18n';
8
10
  import type { RootState } from '../types';
9
11
 
10
12
  const selectCell = (_: unknown, cell: Cell): Cell => cell;
@@ -68,6 +70,9 @@ export const selectCellIsValid = createSelector([selectConfig, selectCell], (con
68
70
  return config.tiles.some((tile) => tile.character === cell.tile.character);
69
71
  });
70
72
 
71
- export const selectTranslations = createSelector([selectLocale], (locale) => i18n[locale]);
73
+ export const selectTranslations = createSelector(
74
+ [selectLocale, selectLoadedTranslations],
75
+ (locale, translations) => translations[locale] ?? englishTranslations,
76
+ );
72
77
 
73
78
  export const selectRemoveCellFilters = createSelector([selectSettings], (settings) => settings.removeCellFilters);
@@ -44,8 +44,11 @@ export const settingsSlice = createSlice({
44
44
  return { ...state, removeCellFilters };
45
45
  },
46
46
 
47
- init: (state, action: PayloadAction<Partial<Pick<typeof settingsInitialState, 'game' | 'locale'>>>) => {
48
- return { ...state, ...action.payload };
47
+ init: (state, action: PayloadAction<Partial<typeof settingsInitialState>>) => {
48
+ const settings = { ...state, ...action.payload };
49
+ const keys = Object.keys(settings) as (keyof typeof settingsInitialState)[];
50
+ const hasDiff = keys.some((key) => settings[key] !== state[key]);
51
+ return hasDiff ? settings : state;
49
52
  },
50
53
  },
51
54
  });
@@ -1,10 +1,12 @@
1
1
  import { configureStore } from '@reduxjs/toolkit';
2
2
  import reduxSaga from 'redux-saga';
3
3
 
4
+ import { appSlice } from './app';
4
5
  import { boardSlice } from './board';
5
6
  import { cellFiltersSlice } from './cellFilters';
6
7
  import { dictionarySlice } from './dictionary';
7
8
  import { hoveredTileSlice } from './hoveredTile';
9
+ import { i18nSlice } from './i18n';
8
10
  import { rackSlice } from './rack';
9
11
  import { resultsSlice } from './results';
10
12
  import { rootSaga } from './sagas';
@@ -16,10 +18,12 @@ const sagaMiddleware = reduxSaga();
16
18
 
17
19
  export const store = configureStore({
18
20
  reducer: {
21
+ app: appSlice.reducer,
19
22
  board: boardSlice.reducer,
20
23
  cellFilters: cellFiltersSlice.reducer,
21
24
  dictionary: dictionarySlice.reducer,
22
25
  hoveredTile: hoveredTileSlice.reducer,
26
+ i18n: i18nSlice.reducer,
23
27
  rack: rackSlice.reducer,
24
28
  results: resultsSlice.reducer,
25
29
  settings: settingsSlice.reducer,
@@ -1,7 +1,9 @@
1
+ import type { AppState } from './app';
1
2
  import type { BoardState } from './board';
2
3
  import type { CellFiltersState } from './cellFilters';
3
4
  import type { DictionaryState } from './dictionary';
4
5
  import type { HoveredTileState } from './hoveredTile';
6
+ import type { I18nState } from './i18n';
5
7
  import type { RackState } from './rack';
6
8
  import type { ResultsState } from './results';
7
9
  import type { SettingsState } from './settings';
@@ -9,10 +11,12 @@ import type { SolveState } from './solve';
9
11
  import type { VerifyState } from './verify';
10
12
 
11
13
  export type RootState = {
14
+ app: AppState;
12
15
  board: BoardState;
13
16
  cellFilters: CellFiltersState;
14
17
  dictionary: DictionaryState;
15
18
  hoveredTile: HoveredTileState;
19
+ i18n: I18nState;
16
20
  rack: RackState;
17
21
  results: ResultsState;
18
22
  settings: SettingsState;
@@ -57,3 +57,4 @@ $modal-width: 370px;
57
57
  $results-item-height: 40px;
58
58
  $solver-column-width: 580px;
59
59
  $text-input-height: 40px;
60
+ $tile-size-max: 80px;
@@ -50,3 +50,12 @@ h4 {
50
50
  nextjs-portal {
51
51
  display: none;
52
52
  }
53
+
54
+ /**
55
+ * Added pre-paint by _document's inline script when the persisted game differs from the
56
+ * prerendered default board; removed after hydration applies the persisted config.
57
+ * The class name must match CONFIG_PENDING_CLASS in parameters/index.ts.
58
+ */
59
+ html.config-pending main {
60
+ visibility: hidden;
61
+ }
@@ -7,23 +7,16 @@
7
7
  breakpointL: strip-unit($breakpoint-l);
8
8
  breakpointXl: strip-unit($breakpoint-xl);
9
9
 
10
- borderColor: $border-color;
11
10
  borderColorLight: $border-color-light;
12
- borderRadius: strip-unit($border-radius);
13
11
  borderWidth: strip-unit($border-width);
14
12
 
15
- colorBlue: $color-blue;
16
13
  colorBlueLight: $color-blue-light;
17
- colorGreen: $color-green;
18
14
  colorGreenLight: $color-green-light;
19
- colorRed: $color-red;
20
15
  colorRedLight: $color-red-light;
21
16
  colorVioletLight: $color-violet-light;
22
- colorYellow: $color-yellow;
23
17
  colorYellowLight: $color-yellow-light;
24
18
 
25
19
  colorBackground: $color-background;
26
- colorForegroundSecondary: $color-foreground-secondary;
27
20
 
28
21
  colorBonusCharacterMultiplier2: $color-bonus-character-multiplier-2;
29
22
  colorBonusCharacterMultiplier3: $color-bonus-character-multiplier-3;
@@ -32,22 +25,8 @@
32
25
  colorBonusWordMultiplier3: $color-bonus-word-multiplier-3;
33
26
  colorBonusWordMultiplier4: $color-bonus-word-multiplier-4;
34
27
 
35
- spacingXs: strip-unit($spacing-xs);
36
- spacingS: strip-unit($spacing-s);
37
- spacingM: strip-unit($spacing-m);
38
- spacingL: strip-unit($spacing-l);
39
- spacingXl: strip-unit($spacing-xl);
40
-
41
28
  transitionDuration: strip-unit($transition-duration);
42
29
  transitionDurationLong: strip-unit($transition-duration-long);
43
30
 
44
- buttonIconSize: strip-unit($button-icon-size);
45
- dictionaryHeight: strip-unit($dictionary-height);
46
- dictionaryHeightMobile: strip-unit($dictionary-height-mobile);
47
- logoHeight: strip-unit($logo-height);
48
- logoHeightSmall: strip-unit($logo-height-small);
49
- modalWidth: strip-unit($modal-width);
50
31
  resultsItemHeight: strip-unit($results-item-height);
51
- solverColumnWidth: strip-unit($solver-column-width);
52
- textInputHeight: strip-unit($text-input-height);
53
32
  }
@@ -0,0 +1,27 @@
1
+ // Generated by scripts/generate-scss-types.ts - do not edit.
2
+ declare const styles: {
3
+ readonly borderColorLight: string;
4
+ readonly borderWidth: string;
5
+ readonly breakpointL: string;
6
+ readonly breakpointM: string;
7
+ readonly breakpointS: string;
8
+ readonly breakpointXl: string;
9
+ readonly breakpointXs: string;
10
+ readonly colorBackground: string;
11
+ readonly colorBlueLight: string;
12
+ readonly colorBonusCharacterMultiplier2: string;
13
+ readonly colorBonusCharacterMultiplier3: string;
14
+ readonly colorBonusCharacterMultiplier4: string;
15
+ readonly colorBonusWordMultiplier2: string;
16
+ readonly colorBonusWordMultiplier3: string;
17
+ readonly colorBonusWordMultiplier4: string;
18
+ readonly colorGreenLight: string;
19
+ readonly colorRedLight: string;
20
+ readonly colorVioletLight: string;
21
+ readonly colorYellowLight: string;
22
+ readonly resultsItemHeight: string;
23
+ readonly transitionDuration: string;
24
+ readonly transitionDurationLong: string;
25
+ };
26
+
27
+ export default styles;
@@ -61,10 +61,12 @@ $easeOutSine: cubic-bezier(0.61, 1, 0.88, 1);
61
61
  --color--bonus--word-multiplier--2: #{$color-bonus-word-multiplier-2};
62
62
  --color--bonus--word-multiplier--3: #{$color-bonus-word-multiplier-3};
63
63
  --color--bonus--word-multiplier--4: #{$color-bonus-word-multiplier-4};
64
+ --color--progress: var(--color--primary--light);
65
+ --color--progress--background: var(--color--inactive);
64
66
  --color--tooltip--background: #{$color-foreground-secondary};
65
67
  --color--tooltip--foreground: var(--color--white);
66
68
 
67
- --font--family: system-ui, sans-serif;
69
+ --font--family: Ubuntu, system-ui, sans-serif;
68
70
  --font--family--monospace:
69
71
  ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
70
72
  --font--size--h1: 30px;
@@ -101,13 +103,14 @@ $easeOutSine: cubic-bezier(0.61, 1, 0.88, 1);
101
103
  --z-index--tooltip: 102;
102
104
 
103
105
  --button--icon--size: #{$button-icon-size};
106
+ --button--height: calc(var(--button--icon--size) + 2 * var(--spacing--m) + 2 * var(--border--width));
104
107
  --dictionary--height: #{$dictionary-height};
105
108
  --dictionary--height--mobile: #{$dictionary-height-mobile};
106
109
  --key--height: 36px;
107
110
  --key--icon--size: 16px;
108
111
  --logo--height: #{$logo-height};
109
112
  --modal--width: #{$modal-width};
110
- --nav--height: calc(var(--logo--height) + var(--nav--padding));
113
+ --nav--height: calc(2 * var(--nav--padding) + var(--logo--height));
111
114
  --nav--padding: var(--spacing--l);
112
115
  --results--icon--size: 18px;
113
116
  --results--item--height: #{$results-item-height};
@@ -116,9 +119,50 @@ $easeOutSine: cubic-bezier(0.61, 1, 0.88, 1);
116
119
  --text-input--height: #{$text-input-height};
117
120
  --tooltip--max-width: 500px;
118
121
 
122
+ /**
123
+ * Sizes of everything visible before hydration.
124
+ * Defaults are of classic Scrabble config.
125
+ */
126
+ --board-cols: 15;
127
+ --board-rows: 15;
128
+ --rack-size: 7;
129
+
130
+ --components-spacing: var(--spacing--xl);
131
+ --solver-column-total: calc(var(--solver-column--width) + var(--components-spacing));
132
+ --candidate-picker-height: 0px;
133
+ --max-board-width: calc(100vw - var(--solver-column-total) - 2 * var(--components-spacing));
134
+ --rack-tile-size: min(
135
+ calc((var(--max-board-width) - 2 * var(--border--width)) / var(--rack-size)),
136
+ #{$tile-size-max}
137
+ );
138
+ --bottom-container-height: calc(
139
+ var(--candidate-picker-height) + var(--rack-tile-size) + 2 * var(--components-spacing)
140
+ );
141
+ --max-board-height: max(calc(100dvh - var(--nav--height) - var(--bottom-container-height)), 0px);
142
+ --cell-from-width: calc(
143
+ (var(--max-board-width) - (var(--board-cols) + 2) * var(--border--width)) / (var(--board-cols) + 0.5)
144
+ );
145
+ --cell-from-height: calc(
146
+ (var(--max-board-height) - (var(--board-rows) + 2) * var(--border--width)) / (var(--board-rows) + 0.5)
147
+ );
148
+ --cell-size: min(var(--cell-from-width), var(--cell-from-height), #{$tile-size-max});
149
+ --coordinate-size: calc(0.5 * var(--cell-size));
150
+ --controls-max-width: calc(var(--rack-size) * var(--rack-tile-size) + 2 * var(--border--width));
151
+
152
+ @include media('<xl') {
153
+ --components-spacing: var(--spacing--l);
154
+ }
155
+
119
156
  @include media('<l') {
120
157
  --dictionary--height: var(--dictionary--height--mobile);
121
158
  --logo--height: #{$logo-height-small};
159
+ --solver-column-total: 0px;
160
+ --candidate-picker-height: calc(var(--button--height) + var(--components-spacing));
161
+ }
162
+
163
+ @include media('<m') {
164
+ // Full-width board: height no longer constrains the cell size.
165
+ --cell-size: min(var(--cell-from-width), #{$tile-size-max});
122
166
  }
123
167
 
124
168
  @include media('<s') {
@@ -15,7 +15,7 @@ export type CellFilter = {
15
15
 
16
16
  export type RemoveCellFilters = 'always' | 'never' | null;
17
17
 
18
- export type Direction = 'horizontal' | 'vertical';
18
+ export { type Direction } from '@scrabble-solver/types';
19
19
 
20
20
  export type InputMode = 'keyboard' | 'touchscreen';
21
21
 
@@ -152,7 +152,6 @@ export type TranslationKey =
152
152
  | 'settings.language'
153
153
  | 'settings.showCoordinates'
154
154
  | 'settings.showCoordinates.alternative'
155
- | 'settings.showCoordinates.hidden'
156
155
  | 'settings.showCoordinates.original'
157
156
  | 'words'
158
157
  | 'words.invalid'