From a03ea0ef7e73295c61c723e48a8a71406ac84ffa Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 18 Mar 2026 09:11:31 +0100
Subject: [PATCH 1/3] chore(deps): bump @sentry/browser from 9.14.0 to 10.43.0
(#7641)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript)
from 9.14.0 to 10.43.0.
Release notes
Sourced from @sentry/browser's
releases.
10.43.0
Important Changes
-
feat(nextjs): Add Turbopack support for React component name
annotation (#19604)
We added experimental support for React component name annotation in
Turbopack builds. When enabled, JSX elements
are annotated with data-sentry-component,
data-sentry-element, and
data-sentry-source-file attributes at build
time. This enables searching Replays by component name, seeing component
names in breadcrumbs, and performance
monitoring — previously only available with webpack builds.
This feature requires Next.js 16+ and is currently behind an
experimental flag:
// next.config.ts
import { withSentryConfig } from '@sentry/nextjs';
export default withSentryConfig(nextConfig, {
_experimental: {
turbopackReactComponentAnnotation: {
enabled: true,
ignoredComponents: ['Header', 'Footer'], // optional
},
},
});
feat(hono): Instrument middlewares app.use() (#19611)
Hono middleware registered via app.use() is now
automatically instrumented, creating spans for each middleware
invocation.
Other Changes
- feat(node-core,node): Add
tracePropagation option to
http and fetch integrations (#19712)
- feat(hono): Use parametrized names for errors (#19577)
- fix(browser): Fix missing traces for user feedback (#19660)
- fix(cloudflare): Use correct Proxy receiver in
instrumentDurableObjectStorage (#19662)
- fix(core): Standardize Vercel AI span descriptions to align with
GenAI semantic conventions (#19624)
- fix(deps): Bump hono to 4.12.5 to fix multiple vulnerabilities (#19653)
- fix(deps): Bump svgo to 4.0.1 to fix DoS via entity expansion (#19651)
- fix(deps): Bump tar to 7.5.10 to fix hardlink path traversal (#19650)
- fix(nextjs): Align Turbopack module metadata injection with webpack
behavior (#19645)
- fix(node): Prevent duplicate LangChain spans from double module
patching (#19684)
- fix(node-core,vercel-edge): Use HEROKU_BUILD_COMMIT env var for
default release (#19617)
- fix(sveltekit): Fix file system race condition in source map
cleaning (#19714)
- fix(tanstackstart-react): Add workerd and worker export conditions
(#19461)
- fix(vercel-ai): Prevent tool call span map memory leak (#19328)
- feat(deps): Bump
@sentry/rollup-plugin from 5.1.0 to
5.1.1 (#19658)
... (truncated)
Changelog
Sourced from @sentry/browser's
changelog.
10.43.0
Important Changes
-
feat(nextjs): Add Turbopack support for React component name
annotation (#19604)
We added experimental support for React component name annotation in
Turbopack builds. When enabled, JSX elements
are annotated with data-sentry-component,
data-sentry-element, and
data-sentry-source-file attributes at build
time. This enables searching Replays by component name, seeing component
names in breadcrumbs, and performance
monitoring — previously only available with webpack builds.
This feature requires Next.js 16+ and is currently behind an
experimental flag:
// next.config.ts
import { withSentryConfig } from '@sentry/nextjs';
export default withSentryConfig(nextConfig, {
_experimental: {
turbopackReactComponentAnnotation: {
enabled: true,
ignoredComponents: ['Header', 'Footer'], // optional
},
},
});
-
feat(hono): Instrument middlewares app.use() (#19611)
Hono middleware registered via app.use() is now
automatically instrumented, creating spans for each middleware
invocation.
Other Changes
- feat(node-core,node): Add
tracePropagation option to
http and fetch integrations (#19712)
- feat(hono): Use parametrized names for errors (#19577)
- fix(browser): Fix missing traces for user feedback (#19660)
- fix(cloudflare): Use correct Proxy receiver in
instrumentDurableObjectStorage (#19662)
- fix(core): Standardize Vercel AI span descriptions to align with
GenAI semantic conventions (#19624)
- fix(deps): Bump hono to 4.12.5 to fix multiple vulnerabilities (#19653)
- fix(deps): Bump svgo to 4.0.1 to fix DoS via entity expansion (#19651)
- fix(deps): Bump tar to 7.5.10 to fix hardlink path traversal (#19650)
- fix(nextjs): Align Turbopack module metadata injection with webpack
behavior (#19645)
- fix(node): Prevent duplicate LangChain spans from double module
patching (#19684)
- fix(node-core,vercel-edge): Use HEROKU_BUILD_COMMIT env var for
default release (#19617)
- fix(sveltekit): Fix file system race condition in source map
cleaning (#19714)
- fix(tanstackstart-react): Add workerd and worker export conditions
(#19461)
- fix(vercel-ai): Prevent tool call span map memory leak (#19328)
- feat(deps): Bump
@sentry/rollup-plugin from 5.1.0 to
5.1.1 (#19658)
... (truncated)
Commits
3fb8102
release: 10.43.0
8706e4e
Merge pull request #19716
from getsentry/prepare-release/10.43.0
61d7a84
meta(changelog): Update changelog for 10.43.0
f83f288
test(angular): Fix failing canary test (#19639)
2b3ce34
fix(sveltekit): Fix file system race condition in source map cleaning
(#19714)
98be6b0
chore(skills): Add bump-size-limit skill (#19715)
cdee7a9
chore(sourcemaps): Make sourcemaps e2e test more generic (#19678)
b26df86
feat(node-core,node): Add tracePropagation option to http and fetch
integrati...
7b69774
chore(ci): Allow triage action to run on issues from external users (#19701)
5651be2
fix(browser): Fix missing traces for user feedback (#19660)
- Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
frontend/package.json | 2 +-
pnpm-lock.yaml | 62 +++++++++++++++++++++----------------------
2 files changed, 32 insertions(+), 32 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index 3f446bd6b3b1..9ca160533a7c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -30,7 +30,7 @@
"@monkeytype/funbox": "workspace:*",
"@monkeytype/schemas": "workspace:*",
"@monkeytype/util": "workspace:*",
- "@sentry/browser": "9.14.0",
+ "@sentry/browser": "10.44.0",
"@sentry/vite-plugin": "3.3.1",
"@solid-devtools/overlay": "0.33.5",
"@solid-primitives/refs": "1.1.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 60434a5bd13a..193b7a252387 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -286,8 +286,8 @@ importers:
specifier: workspace:*
version: link:../packages/util
'@sentry/browser':
- specifier: 9.14.0
- version: 9.14.0
+ specifier: 10.44.0
+ version: 10.44.0
'@sentry/vite-plugin':
specifier: 3.3.1
version: 3.3.1(encoding@0.1.13)
@@ -3314,28 +3314,28 @@ packages:
cpu: [x64]
os: [win32]
- '@sentry-internal/browser-utils@9.14.0':
- resolution: {integrity: sha512-pDk9XUu9zf7lcT9QX0nTObPNp/y0xQyy1Dj+5/8TSB3vAfe0LQcooKGl/D1h7EoIXVHUozZk5JC/dH+gz6BXRg==}
+ '@sentry-internal/browser-utils@10.44.0':
+ resolution: {integrity: sha512-z9xz3T/v+MnfHY6kdUCmOZI8CiAl3LlKYtGH2p3rAsrxhwX+BTnUp01VhMVnEZIDgUXNt3AhJac+4kcDIPu1Hg==}
engines: {node: '>=18'}
- '@sentry-internal/feedback@9.14.0':
- resolution: {integrity: sha512-D+PiEUWbDT0vqmaTiOs6OzXwVRVFgf7BCkFs48qsN9sAPwUgT+5zh2oo/rU2r0NrmMcvJVtSY+ezwPMk8BgGsg==}
+ '@sentry-internal/feedback@10.44.0':
+ resolution: {integrity: sha512-yNS2EGK1bNm8YUI+Orzpa7yr05Da+b1VEe/9x7dl7gTjw/+tfutoXlG6Y+iFZBB3gQ9QU+nxZAhU+KcxiPEURw==}
engines: {node: '>=18'}
- '@sentry-internal/replay-canvas@9.14.0':
- resolution: {integrity: sha512-GhCSqc0oNzRiLhQsi9LCXgUmIwdHdvzVIsX4fihoFYWfgWSSj5YLqeEkb3CMM8htM6vheSFzIbPLlRS8fjCrPQ==}
+ '@sentry-internal/replay-canvas@10.44.0':
+ resolution: {integrity: sha512-RA7XgYZWHY7M+vaHvuMxDFT51wCs4puS2smElM5oh+j3YqbFXY7P16fOCwIAGoyI4gVsj8aTeBgVqUmrmzhAXQ==}
engines: {node: '>=18'}
- '@sentry-internal/replay@9.14.0':
- resolution: {integrity: sha512-wgt397/PtpfVQ9t779a0L+hGH3JN9doXv3+9Wj98MLWwhymvJBjpjCFUBLScO5iP6imewTbRqQHbq7XS7I+x1A==}
+ '@sentry-internal/replay@10.44.0':
+ resolution: {integrity: sha512-KDmoqBsRmkaoc+eKLR2CbScd2eBmLcw+1+D441lLttAO3WWhvYyCaYdu/HIGGUoybuSgt+IcpCJdi7hFuCvYqw==}
engines: {node: '>=18'}
'@sentry/babel-plugin-component-annotate@3.3.1':
resolution: {integrity: sha512-5GOxGT7lZN+I8A7Vp0rWY+726FDKEw8HnFiebe51rQrMbfGfCu2Aw9uSM0nT9OG6xhV6WvGccIcCszTPs4fUZQ==}
engines: {node: '>= 14'}
- '@sentry/browser@9.14.0':
- resolution: {integrity: sha512-acxFbFEei3hzKr/IW3OmkzHlwohRaRBG0872nIhLYV2f/BgZmR6eV5zrUoELMmt2cgoLmDYyfp1734OoplfDbw==}
+ '@sentry/browser@10.44.0':
+ resolution: {integrity: sha512-UpMx5forbVKieNULma3gT2SsLYqsYT4nLXa6s1io/Y8BFej9sH2dD5ExA8TrkQThQwAWFI3qKsQzYnF+EX/Bfg==}
engines: {node: '>=18'}
'@sentry/bundler-plugin-core@3.3.1':
@@ -3388,8 +3388,8 @@ packages:
engines: {node: '>= 10'}
hasBin: true
- '@sentry/core@9.14.0':
- resolution: {integrity: sha512-OLfucnP3LAL5bxVNWc2RVOHCX7fk9Er5bWPCS+O5cPjqNUUz0HQHhVh2Vhei5C0kYZZM4vy4BQit5T9LrlOaNA==}
+ '@sentry/core@10.44.0':
+ resolution: {integrity: sha512-aa7CiDaNFZvHpqd97LJhuskolfJ/4IH5xyuVVLnv7l6B0v9KTwskPUxb0tH1ej3FxuzfH+i8iTiTFuqpfHS3QA==}
engines: {node: '>=18'}
'@sentry/vite-plugin@3.3.1':
@@ -13430,33 +13430,33 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.52.5':
optional: true
- '@sentry-internal/browser-utils@9.14.0':
+ '@sentry-internal/browser-utils@10.44.0':
dependencies:
- '@sentry/core': 9.14.0
+ '@sentry/core': 10.44.0
- '@sentry-internal/feedback@9.14.0':
+ '@sentry-internal/feedback@10.44.0':
dependencies:
- '@sentry/core': 9.14.0
+ '@sentry/core': 10.44.0
- '@sentry-internal/replay-canvas@9.14.0':
+ '@sentry-internal/replay-canvas@10.44.0':
dependencies:
- '@sentry-internal/replay': 9.14.0
- '@sentry/core': 9.14.0
+ '@sentry-internal/replay': 10.44.0
+ '@sentry/core': 10.44.0
- '@sentry-internal/replay@9.14.0':
+ '@sentry-internal/replay@10.44.0':
dependencies:
- '@sentry-internal/browser-utils': 9.14.0
- '@sentry/core': 9.14.0
+ '@sentry-internal/browser-utils': 10.44.0
+ '@sentry/core': 10.44.0
'@sentry/babel-plugin-component-annotate@3.3.1': {}
- '@sentry/browser@9.14.0':
+ '@sentry/browser@10.44.0':
dependencies:
- '@sentry-internal/browser-utils': 9.14.0
- '@sentry-internal/feedback': 9.14.0
- '@sentry-internal/replay': 9.14.0
- '@sentry-internal/replay-canvas': 9.14.0
- '@sentry/core': 9.14.0
+ '@sentry-internal/browser-utils': 10.44.0
+ '@sentry-internal/feedback': 10.44.0
+ '@sentry-internal/replay': 10.44.0
+ '@sentry-internal/replay-canvas': 10.44.0
+ '@sentry/core': 10.44.0
'@sentry/bundler-plugin-core@3.3.1(encoding@0.1.13)':
dependencies:
@@ -13512,7 +13512,7 @@ snapshots:
- encoding
- supports-color
- '@sentry/core@9.14.0': {}
+ '@sentry/core@10.44.0': {}
'@sentry/vite-plugin@3.3.1(encoding@0.1.13)':
dependencies:
From e7a2cd9c9171953b19d1401e5303ef2fb169e142 Mon Sep 17 00:00:00 2001
From: Miodec
Date: Wed, 18 Mar 2026 09:11:05 +0100
Subject: [PATCH 2/3] chore: downgrade to sass 1.70
---
frontend/package.json | 2 +-
pnpm-lock.yaml | 133 +++++++++++++++++++++++++-----------------
2 files changed, 82 insertions(+), 53 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index 9ca160533a7c..2a081b30f334 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -107,7 +107,7 @@
"oxlint": "1.50.0",
"oxlint-tsgolint": "0.14.2",
"postcss": "8.5.8",
- "sass": "1.98.0",
+ "sass": "1.70.0",
"solid-devtools": "0.34.5",
"solid-js": "1.9.10",
"subset-font": "2.3.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 193b7a252387..2a8342af6b06 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -423,7 +423,7 @@ importers:
version: 0.8.10(solid-js@1.9.10)
'@tailwindcss/vite':
specifier: 4.2.1
- version: 4.2.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ version: 4.2.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
'@tanstack/eslint-plugin-query':
specifier: 5.91.4
version: 5.91.4(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.0-beta)
@@ -462,7 +462,7 @@ importers:
version: 5.0.2
'@vitest/coverage-v8':
specifier: 4.0.15
- version: 4.0.15(vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))
+ version: 4.0.15(vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))
autoprefixer:
specifier: 10.4.27
version: 10.4.27(postcss@8.5.8)
@@ -512,11 +512,11 @@ importers:
specifier: 8.5.8
version: 8.5.8
sass:
- specifier: 1.98.0
- version: 1.98.0
+ specifier: 1.70.0
+ version: 1.70.0
solid-devtools:
specifier: 0.34.5
- version: 0.34.5(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ version: 0.34.5(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
solid-js:
specifier: 1.9.10
version: 1.9.10
@@ -537,7 +537,7 @@ importers:
version: 3.0.0
vite:
specifier: 7.3.1
- version: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ version: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
vite-bundle-visualizer:
specifier: 1.2.1
version: 1.2.1(rollup@2.80.0)
@@ -546,19 +546,19 @@ importers:
version: 1.1.2
vite-plugin-inspect:
specifier: 11.3.3
- version: 11.3.3(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ version: 11.3.3(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
vite-plugin-minify:
specifier: 2.1.0
- version: 2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ version: 2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
vite-plugin-pwa:
specifier: 1.1.0
- version: 1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0)
+ version: 1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0)
vite-plugin-solid:
specifier: 2.11.11
- version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
vitest:
specifier: 4.1.0
- version: 4.1.0(@types/node@24.9.1)(@vitest/browser-playwright@4.0.18)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ version: 4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
frontend/storybook:
devDependencies:
@@ -649,7 +649,7 @@ importers:
version: 6.0.0-beta
vitest:
specifier: 4.1.0
- version: 4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ version: 4.1.0(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.0(@types/node@24.9.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
packages/funbox:
dependencies:
@@ -6606,6 +6606,9 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
+ immutable@4.3.8:
+ resolution: {integrity: sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==}
+
immutable@5.1.5:
resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==}
@@ -9066,6 +9069,11 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ sass@1.70.0:
+ resolution: {integrity: sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
sass@1.98.0:
resolution: {integrity: sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==}
engines: {node: '>=14.0.0'}
@@ -13899,6 +13907,13 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.1
'@tailwindcss/oxide-win32-x64-msvc': 4.2.1
+ '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
+ dependencies:
+ '@tailwindcss/node': 4.2.1
+ '@tailwindcss/oxide': 4.2.1
+ tailwindcss: 4.2.1
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+
'@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@tailwindcss/node': 4.2.1
@@ -14462,7 +14477,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@vitest/coverage-v8@4.0.15(vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))':
+ '@vitest/coverage-v8@4.0.15(vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))':
dependencies:
'@bcoe/v8-coverage': 1.0.2
'@vitest/utils': 4.0.15
@@ -14475,7 +14490,7 @@ snapshots:
obug: 2.1.1
std-env: 3.10.0
tinyrainbow: 3.0.3
- vitest: 4.1.0(@types/node@24.9.1)(@vitest/browser-playwright@4.0.18)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ vitest: 4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
transitivePeerDependencies:
- supports-color
@@ -14520,29 +14535,29 @@ snapshots:
optionalDependencies:
vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
- '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
+ '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 4.1.0
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
- '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
+ '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 4.1.0
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- vite: 8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
- '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
+ '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 4.1.0
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- vite: 8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite: 8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
'@vitest/mocker@4.1.0(vite@8.0.0(@types/node@24.9.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
@@ -17436,7 +17451,10 @@ snapshots:
ignore@5.3.2: {}
- immutable@5.1.5: {}
+ immutable@4.3.8: {}
+
+ immutable@5.1.5:
+ optional: true
import-fresh@3.3.1:
dependencies:
@@ -20207,6 +20225,12 @@ snapshots:
dependencies:
commander: 12.1.0
+ sass@1.70.0:
+ dependencies:
+ chokidar: 3.6.0
+ immutable: 4.3.8
+ source-map-js: 1.2.1
+
sass@1.98.0:
dependencies:
chokidar: 4.0.3
@@ -20214,6 +20238,7 @@ snapshots:
source-map-js: 1.2.1
optionalDependencies:
'@parcel/watcher': 2.5.6
+ optional: true
saxes@6.0.0:
dependencies:
@@ -20517,7 +20542,7 @@ snapshots:
ip-address: 9.0.5
smart-buffer: 4.2.0
- solid-devtools@0.34.5(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
+ solid-devtools@0.34.5(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
'@babel/core': 7.28.6
'@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6)
@@ -20526,7 +20551,7 @@ snapshots:
'@solid-devtools/shared': 0.20.0(solid-js@1.9.10)
solid-js: 1.9.10
optionalDependencies:
- vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
transitivePeerDependencies:
- supports-color
@@ -21559,19 +21584,19 @@ snapshots:
- rollup
- supports-color
- vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
+ vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
birpc: 2.6.1
- vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
- vite-hot-client: 2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite-hot-client: 2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
- vite-hot-client@2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
+ vite-hot-client@2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
- vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
vite-plugin-html-inject@1.1.2: {}
- vite-plugin-inspect@11.3.3(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
+ vite-plugin-inspect@11.3.3(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
ansis: 4.2.0
debug: 4.4.3(supports-color@5.5.0)
@@ -21581,23 +21606,23 @@ snapshots:
perfect-debounce: 2.0.0
sirv: 3.0.2
unplugin-utils: 0.3.1
- vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
- vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
transitivePeerDependencies:
- supports-color
- vite-plugin-minify@2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
+ vite-plugin-minify@2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
'@types/html-minifier-terser': 7.0.2
html-minifier-terser: 7.2.0
- vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
- vite-plugin-pwa@1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0):
+ vite-plugin-pwa@1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0):
dependencies:
debug: 4.4.3(supports-color@5.5.0)
pretty-bytes: 6.1.1
tinyglobby: 0.2.15
- vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
workbox-build: 7.1.1(@types/babel__core@7.20.5)
workbox-window: 7.1.0
transitivePeerDependencies:
@@ -21618,7 +21643,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
+ vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
'@babel/core': 7.28.6
'@types/babel__core': 7.20.5
@@ -21626,14 +21651,14 @@ snapshots:
merge-anything: 5.1.7
solid-js: 1.9.10
solid-refresh: 0.6.3(solid-js@1.9.10)
- vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
- vitefu: 1.1.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vitefu: 1.1.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
optionalDependencies:
'@testing-library/jest-dom': 6.9.1
transitivePeerDependencies:
- supports-color
- vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2):
+ vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
esbuild: 0.27.3
fdir: 6.5.0(picomatch@4.0.3)
@@ -21646,30 +21671,30 @@ snapshots:
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.32.0
- sass: 1.98.0
+ sass: 1.70.0
terser: 5.46.1
tsx: 4.21.0
yaml: 2.8.2
- vite@8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2):
+ vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
- '@oxc-project/runtime': 0.115.0
- lightningcss: 1.32.0
+ esbuild: 0.27.3
+ fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.8
- rolldown: 1.0.0-rc.9
+ rollup: 4.52.5
tinyglobby: 0.2.15
optionalDependencies:
- '@types/node': 20.5.1
- esbuild: 0.27.3
+ '@types/node': 24.9.1
fsevents: 2.3.3
jiti: 2.6.1
+ lightningcss: 1.32.0
sass: 1.98.0
terser: 5.46.1
tsx: 4.21.0
yaml: 2.8.2
- vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2):
+ vite@8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
'@oxc-project/runtime': 0.115.0
lightningcss: 1.32.0
@@ -21678,8 +21703,8 @@ snapshots:
rolldown: 1.0.0-rc.9
tinyglobby: 0.2.15
optionalDependencies:
- '@types/node': 24.9.1
- esbuild: 0.25.11
+ '@types/node': 20.5.1
+ esbuild: 0.27.3
fsevents: 2.3.3
jiti: 2.6.1
sass: 1.98.0
@@ -21705,6 +21730,10 @@ snapshots:
tsx: 4.21.0
yaml: 2.8.2
+ vitefu@1.1.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
+ optionalDependencies:
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+
vitefu@1.1.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
optionalDependencies:
vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
@@ -21798,10 +21827,10 @@ snapshots:
transitivePeerDependencies:
- msw
- vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
+ vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
'@vitest/expect': 4.1.0
- '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
'@vitest/pretty-format': 4.1.0
'@vitest/runner': 4.1.0
'@vitest/snapshot': 4.1.0
@@ -21818,7 +21847,7 @@ snapshots:
tinyexec: 1.0.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
- vite: 8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 24.9.1
From f3f0e9ed60e2244601756e273b55874ef4471c44 Mon Sep 17 00:00:00 2001
From: Jack
Date: Wed, 18 Mar 2026 11:49:23 +0100
Subject: [PATCH 3/3] refactor: config split (@miodec) (#7659)
### Description
### Checks
- [ ] Adding quotes?
- [ ] Make sure to include translations for the quotes in the
description (or another comment) so we can verify their content.
- [ ] Adding a language?
- Make sure to follow the [languages
documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LANGUAGES.md)
- [ ] Add language to `packages/schemas/src/languages.ts`
- [ ] Add language to exactly one group in
`frontend/src/ts/constants/languages.ts`
- [ ] Add language json file to `frontend/static/languages`
- [ ] Adding a theme?
- Make sure to follow the [themes
documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/THEMES.md)
- [ ] Add theme to `packages/schemas/src/themes.ts`
- [ ] Add theme to `frontend/src/ts/constants/themes.ts`
- [ ] (optional) Add theme css file to `frontend/static/themes`
- [ ] Add some screenshots of the theme, especially with different test
settings (colorful, flip colors) to your pull request
- [ ] Adding a layout?
- [ ] Make sure to follow the [layouts
documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LAYOUTS.md)
- [ ] Add layout to `packages/schemas/src/layouts.ts`
- [ ] Add layout json file to `frontend/static/layouts`
- [ ] Adding a font?
- Make sure to follow the [fonts
documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/FONTS.md)
- [ ] Add font file to `frontend/static/webfonts`
- [ ] Add font to `packages/schemas/src/fonts.ts`
- [ ] Add font to `frontend/src/ts/constants/fonts.ts`
- [ ] Check if any open issues are related to this PR; if so, be sure to
tag them below.
- [ ] Make sure the PR title follows the Conventional Commits standard.
(https://www.conventionalcommits.org for more info)
- [ ] Make sure to include your GitHub username prefixed with @ inside
parentheses at the end of the PR title.
Closes #
---
frontend/__tests__/commandline/util.spec.ts | 6 +-
.../controllers/preset-controller.spec.ts | 16 +-
.../__tests__/controllers/url-handler.spec.ts | 2 +-
.../input/helpers/fail-or-finish.spec.ts | 2 +-
.../input/helpers/validation.spec.ts | 2 +-
.../__tests__/root/config-metadata.spec.ts | 13 +-
frontend/__tests__/root/config.spec.ts | 25 +-
.../__tests__/test/british-english.spec.ts | 2 +-
frontend/__tests__/utils/config.spec.ts | 2 +-
frontend/src/ts/auth.tsx | 2 +-
.../ts/commandline/commandline-metadata.ts | 2 +-
frontend/src/ts/commandline/commandline.ts | 2 +-
frontend/src/ts/commandline/lists.ts | 4 +-
.../lists/add-or-remove-theme-to-favorites.ts | 3 +-
.../ts/commandline/lists/background-filter.ts | 3 +-
frontend/src/ts/commandline/lists/bail-out.ts | 2 +-
.../ts/commandline/lists/custom-background.ts | 3 +-
.../commandline/lists/custom-themes-list.ts | 2 +-
.../src/ts/commandline/lists/font-family.ts | 4 +-
.../src/ts/commandline/lists/min-burst.ts | 3 +-
.../ts/commandline/lists/quote-favorites.ts | 2 +-
.../src/ts/commandline/lists/result-screen.ts | 2 +-
frontend/src/ts/commandline/lists/tags.ts | 2 +-
frontend/src/ts/commandline/lists/themes.ts | 3 +-
frontend/src/ts/commandline/util.ts | 5 +-
.../ts/components/layout/footer/Keytips.tsx | 2 +-
.../layout/footer/ThemeIndicator.tsx | 3 +-
.../src/ts/components/pages/AboutPage.tsx | 2 +-
.../ts/components/pages/leaderboard/Table.tsx | 2 +-
.../components/pages/leaderboard/UserRank.tsx | 2 +-
.../components/pages/profile/UserProfile.tsx | 2 +-
frontend/src/ts/config.ts | 422 ------------------
frontend/src/ts/config/lifecycle.ts | 123 +++++
.../metadata.ts} | 16 +-
frontend/src/ts/config/persistence.ts | 66 +++
frontend/src/ts/config/remote.ts | 50 +++
frontend/src/ts/config/setters.ts | 184 ++++++++
frontend/src/ts/config/store.ts | 10 +
frontend/src/ts/config/testing.ts | 18 +
.../ts/{utils/config.ts => config/utils.ts} | 28 +-
.../validation.ts} | 5 +-
frontend/src/ts/controllers/ad-controller.ts | 2 +-
.../ts/controllers/challenge-controller.ts | 4 +-
.../src/ts/controllers/chart-controller.ts | 2 +-
.../src/ts/controllers/preset-controller.ts | 5 +-
.../src/ts/controllers/pw-ad-controller.ts | 2 +-
.../src/ts/controllers/sound-controller.ts | 2 +-
.../src/ts/controllers/theme-controller.ts | 4 +-
frontend/src/ts/controllers/url-handler.tsx | 3 +-
.../src/ts/elements/account/result-filters.ts | 2 +-
frontend/src/ts/elements/caret.ts | 2 +-
.../ts/elements/custom-background-filter.ts | 2 +-
frontend/src/ts/elements/input-validation.ts | 3 +-
frontend/src/ts/elements/keymap.ts | 2 +-
frontend/src/ts/elements/last-10-average.ts | 2 +-
frontend/src/ts/elements/modes-notice.ts | 2 +-
frontend/src/ts/elements/monkey-power.ts | 2 +-
.../ts/elements/settings/settings-group.ts | 4 +-
.../src/ts/elements/settings/theme-picker.ts | 4 +-
frontend/src/ts/event-handlers/global.ts | 2 +-
frontend/src/ts/event-handlers/test.ts | 2 +-
frontend/src/ts/index.ts | 3 +-
.../src/ts/input/handlers/before-delete.ts | 2 +-
.../ts/input/handlers/before-insert-text.ts | 2 +-
frontend/src/ts/input/handlers/delete.ts | 2 +-
frontend/src/ts/input/handlers/insert-text.ts | 2 +-
frontend/src/ts/input/handlers/keydown.ts | 2 +-
frontend/src/ts/input/handlers/keyup.ts | 2 +-
.../src/ts/input/helpers/fail-or-finish.ts | 2 +-
frontend/src/ts/input/helpers/validation.ts | 2 +-
.../src/ts/input/helpers/word-navigation.ts | 2 +-
.../src/ts/modals/custom-test-duration.ts | 3 +-
frontend/src/ts/modals/custom-text.ts | 4 +-
frontend/src/ts/modals/custom-word-amount.ts | 3 +-
frontend/src/ts/modals/edit-preset.ts | 9 +-
.../src/ts/modals/import-export-settings.ts | 2 +-
frontend/src/ts/modals/mini-result-chart.ts | 3 +-
frontend/src/ts/modals/mobile-test-config.ts | 3 +-
frontend/src/ts/modals/pb-tables.ts | 2 +-
frontend/src/ts/modals/quote-report.ts | 3 +-
frontend/src/ts/modals/quote-search.ts | 3 +-
frontend/src/ts/modals/quote-submit.ts | 2 +-
frontend/src/ts/modals/share-custom-theme.ts | 2 +-
frontend/src/ts/modals/share-test-settings.ts | 2 +-
frontend/src/ts/modals/simple-modals.ts | 3 +-
frontend/src/ts/pages/account.ts | 4 +-
frontend/src/ts/pages/settings.ts | 7 +-
frontend/src/ts/ready.ts | 2 +-
frontend/src/ts/singletons/format.ts | 3 +-
frontend/src/ts/states/config.ts | 22 -
frontend/src/ts/test/break-ligatures.ts | 2 +-
frontend/src/ts/test/british-english.ts | 2 +-
frontend/src/ts/test/caps-warning.ts | 2 +-
frontend/src/ts/test/caret.ts | 2 +-
.../src/ts/test/funbox/funbox-functions.ts | 3 +-
frontend/src/ts/test/funbox/funbox-memory.ts | 2 +-
frontend/src/ts/test/funbox/funbox.ts | 7 +-
frontend/src/ts/test/funbox/list.ts | 2 +-
frontend/src/ts/test/layout-emulator.ts | 2 +-
frontend/src/ts/test/live-acc.ts | 2 +-
frontend/src/ts/test/live-burst.ts | 2 +-
frontend/src/ts/test/live-speed.ts | 2 +-
frontend/src/ts/test/monkey.ts | 2 +-
frontend/src/ts/test/out-of-focus.ts | 2 +-
frontend/src/ts/test/pace-caret.ts | 2 +-
frontend/src/ts/test/practise-words.ts | 4 +-
frontend/src/ts/test/replay.ts | 5 +-
frontend/src/ts/test/result.ts | 4 +-
frontend/src/ts/test/shift-tracker.ts | 2 +-
frontend/src/ts/test/test-config.ts | 2 +-
frontend/src/ts/test/test-input.ts | 2 +-
frontend/src/ts/test/test-logic.ts | 4 +-
frontend/src/ts/test/test-stats.ts | 2 +-
frontend/src/ts/test/test-timer.ts | 3 +-
frontend/src/ts/test/test-ui.ts | 4 +-
frontend/src/ts/test/timer-progress.ts | 2 +-
frontend/src/ts/test/tts.ts | 2 +-
frontend/src/ts/test/words-generator.ts | 3 +-
frontend/src/ts/ui.ts | 2 +-
119 files changed, 675 insertions(+), 607 deletions(-)
delete mode 100644 frontend/src/ts/config.ts
create mode 100644 frontend/src/ts/config/lifecycle.ts
rename frontend/src/ts/{config-metadata.ts => config/metadata.ts} (98%)
create mode 100644 frontend/src/ts/config/persistence.ts
create mode 100644 frontend/src/ts/config/remote.ts
create mode 100644 frontend/src/ts/config/setters.ts
create mode 100644 frontend/src/ts/config/store.ts
create mode 100644 frontend/src/ts/config/testing.ts
rename frontend/src/ts/{utils/config.ts => config/utils.ts} (90%)
rename frontend/src/ts/{config-validation.ts => config/validation.ts} (90%)
delete mode 100644 frontend/src/ts/states/config.ts
diff --git a/frontend/__tests__/commandline/util.spec.ts b/frontend/__tests__/commandline/util.spec.ts
index 27e7b988df62..0cab39f0e3b6 100644
--- a/frontend/__tests__/commandline/util.spec.ts
+++ b/frontend/__tests__/commandline/util.spec.ts
@@ -1,17 +1,17 @@
-//import type { ConfigMetadata } from "../../src/ts/config-metadata";
+//import type { ConfigMetadata } from "../../src/ts/config/metadata";
import { describe, it, expect, afterAll, vi } from "vitest";
import * as Util from "../../src/ts/commandline/util";
import type { CommandlineConfigMetadata } from "../../src/ts/commandline/commandline-metadata";
import type { ConfigKey } from "@monkeytype/schemas/configs";
-import type { ConfigMetadata } from "../../src/ts/config-metadata";
+import type { ConfigMetadata } from "../../src/ts/config/metadata";
import { z, ZodSchema } from "zod";
const buildCommandForConfigKey = Util.__testing._buildCommandForConfigKey;
describe("CommandlineUtils", () => {
- vi.mock("../../src/ts/config-metadata", () => ({ configMetadata: [] }));
+ vi.mock("../../src/ts/config/metadata", () => ({ configMetadata: [] }));
vi.mock("../../src/ts/commandline/commandline-metadata", () => ({
commandlineConfigMetadata: [],
}));
diff --git a/frontend/__tests__/controllers/preset-controller.spec.ts b/frontend/__tests__/controllers/preset-controller.spec.ts
index 24f21a517194..baef3596b82f 100644
--- a/frontend/__tests__/controllers/preset-controller.spec.ts
+++ b/frontend/__tests__/controllers/preset-controller.spec.ts
@@ -2,7 +2,11 @@ import { describe, it, expect, beforeEach, vi } from "vitest";
import * as PresetController from "../../src/ts/controllers/preset-controller";
import { Preset } from "@monkeytype/schemas/presets";
import * as DB from "../../src/ts/db";
-import * as UpdateConfig from "../../src/ts/config";
+import { setConfig } from "../../src/ts/config/setters";
+import { Config } from "../../src/ts/config/store";
+import * as Lifecycle from "../../src/ts/config/lifecycle";
+import * as ConfigUtils from "../../src/ts/config/utils";
+import * as Persistence from "../../src/ts/config/persistence";
import * as Notifications from "../../src/ts/states/notifications";
import * as TestLogic from "../../src/ts/test/test-logic";
import * as TagController from "../../src/ts/controllers/tag-controller";
@@ -16,13 +20,13 @@ describe("PresetController", () => {
//
}));
const dbGetSnapshotMock = vi.spyOn(DB, "getSnapshot");
- const configApplyMock = vi.spyOn(UpdateConfig, "applyConfig");
+ const configApplyMock = vi.spyOn(Lifecycle, "applyConfig");
const configSaveFullConfigMock = vi.spyOn(
- UpdateConfig,
+ Persistence,
"saveFullConfigToLocalStorage",
);
const configGetConfigChangesMock = vi.spyOn(
- UpdateConfig,
+ ConfigUtils,
"getConfigChanges",
);
const notificationAddMock = vi.spyOn(
@@ -111,8 +115,8 @@ describe("PresetController", () => {
settingGroups: ["test"],
});
- UpdateConfig.setConfig("numbers", true);
- const oldConfig = structuredClone(UpdateConfig.default);
+ setConfig("numbers", true);
+ const oldConfig = structuredClone(Config);
//WHEN
await PresetController.apply(preset._id);
diff --git a/frontend/__tests__/controllers/url-handler.spec.ts b/frontend/__tests__/controllers/url-handler.spec.ts
index 43681e2713dc..10b5804969cf 100644
--- a/frontend/__tests__/controllers/url-handler.spec.ts
+++ b/frontend/__tests__/controllers/url-handler.spec.ts
@@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { Difficulty, Mode, Mode2 } from "@monkeytype/schemas/shared";
import { compressToURI } from "lz-ts";
-import * as UpdateConfig from "../../src/ts/config";
+import * as UpdateConfig from "../../src/ts/config/setters";
import * as Notifications from "../../src/ts/states/notifications";
import * as TestLogic from "../../src/ts/test/test-logic";
import * as TestState from "../../src/ts/test/test-state";
diff --git a/frontend/__tests__/input/helpers/fail-or-finish.spec.ts b/frontend/__tests__/input/helpers/fail-or-finish.spec.ts
index c45977d7e1b9..2893e07d0dcb 100644
--- a/frontend/__tests__/input/helpers/fail-or-finish.spec.ts
+++ b/frontend/__tests__/input/helpers/fail-or-finish.spec.ts
@@ -4,7 +4,7 @@ import {
checkIfFailedDueToDifficulty,
checkIfFinished,
} from "../../../src/ts/input/helpers/fail-or-finish";
-import { __testing } from "../../../src/ts/config";
+import { __testing } from "../../../src/ts/config/testing";
import * as Misc from "../../../src/ts/utils/misc";
import * as TestLogic from "../../../src/ts/test/test-logic";
import * as Strings from "../../../src/ts/utils/strings";
diff --git a/frontend/__tests__/input/helpers/validation.spec.ts b/frontend/__tests__/input/helpers/validation.spec.ts
index 08920a5a1465..94b3b2416230 100644
--- a/frontend/__tests__/input/helpers/validation.spec.ts
+++ b/frontend/__tests__/input/helpers/validation.spec.ts
@@ -3,7 +3,7 @@ import {
isCharCorrect,
shouldInsertSpaceCharacter,
} from "../../../src/ts/input/helpers/validation";
-import { __testing } from "../../../src/ts/config";
+import { __testing } from "../../../src/ts/config/testing";
import * as FunboxList from "../../../src/ts/test/funbox/list";
import * as Strings from "../../../src/ts/utils/strings";
diff --git a/frontend/__tests__/root/config-metadata.spec.ts b/frontend/__tests__/root/config-metadata.spec.ts
index 01af2874b82d..d8db8f348d75 100644
--- a/frontend/__tests__/root/config-metadata.spec.ts
+++ b/frontend/__tests__/root/config-metadata.spec.ts
@@ -1,9 +1,10 @@
import { describe, it, expect, afterAll, vi } from "vitest";
-import { configMetadata } from "../../src/ts/config-metadata";
-import * as Config from "../../src/ts/config";
+import { configMetadata } from "../../src/ts/config/metadata";
+import { __testing } from "../../src/ts/config/testing";
+import { setConfig } from "../../src/ts/config/setters";
import { ConfigKey, Config as ConfigType } from "@monkeytype/schemas/configs";
-const { replaceConfig, getConfig } = Config.__testing;
+const { replaceConfig, getConfig } = __testing;
type TestsByConfig = Partial<{
[K in keyof ConfigType]: (T & { value: ConfigType[K] })[];
@@ -138,7 +139,7 @@ describe("ConfigMeta", () => {
replaceConfig(given ?? {});
//WHEN
- Config.setConfig(key, value as any);
+ setConfig(key, value as any);
//THEN
expect(getConfig()).toMatchObject(expected);
@@ -175,7 +176,7 @@ describe("ConfigMeta", () => {
replaceConfig(given ?? {});
//WHEN
- const applied = Config.setConfig(key, value as any);
+ const applied = setConfig(key, value as any);
//THEN
expect(applied).toEqual(!fail);
@@ -335,7 +336,7 @@ describe("ConfigMeta", () => {
replaceConfig(given);
//WHEN
- Config.setConfig(key, value as any);
+ setConfig(key, value as any);
//THEN
expect(getConfig()).toMatchObject(expected ?? {});
diff --git a/frontend/__tests__/root/config.spec.ts b/frontend/__tests__/root/config.spec.ts
index fd738d9a9d2e..90eb7ca8fa44 100644
--- a/frontend/__tests__/root/config.spec.ts
+++ b/frontend/__tests__/root/config.spec.ts
@@ -1,5 +1,8 @@
import { describe, it, expect, beforeEach, afterAll, vi } from "vitest";
-import * as Config from "../../src/ts/config";
+import * as Config from "../../src/ts/config/setters";
+import * as Lifecycle from "../../src/ts/config/lifecycle";
+import * as ConfigUtils from "../../src/ts/config/utils";
+import { __testing } from "../../src/ts/config/testing";
import * as Misc from "../../src/ts/utils/misc";
import * as Env from "../../src/ts/utils/env";
import {
@@ -8,11 +11,11 @@ import {
CaretStyleSchema,
} from "@monkeytype/schemas/configs";
import * as FunboxValidation from "../../src/ts/test/funbox/funbox-validation";
-import * as ConfigValidation from "../../src/ts/config-validation";
+import * as ConfigValidation from "../../src/ts/config/validation";
import * as ConfigEvent from "../../src/ts/observables/config-event";
import * as ApeConfig from "../../src/ts/ape/config";
import * as Notifications from "../../src/ts/states/notifications";
-const { replaceConfig, getConfig } = Config.__testing;
+const { replaceConfig, getConfig } = __testing;
describe("Config", () => {
const isDevEnvironmentMock = vi.spyOn(Env, "isDevEnvironment");
@@ -281,7 +284,7 @@ describe("Config", () => {
replaceConfig({
mode: "words",
});
- await Config.applyConfig({
+ await Lifecycle.applyConfig({
numbers: true,
punctuation: true,
});
@@ -326,7 +329,7 @@ describe("Config", () => {
];
it.each(testCases)("$display", async ({ value, expected }) => {
- await Config.applyConfig(value);
+ await Lifecycle.applyConfig(value);
const config = getConfig();
const applied = Object.fromEntries(
@@ -363,7 +366,7 @@ describe("Config", () => {
];
it.each(testCases)("$display", async ({ value, expected }) => {
- await Config.applyConfig(value);
+ await Lifecycle.applyConfig(value);
const config = getConfig();
const applied = Object.fromEntries(
Object.entries(config).filter(([key]) =>
@@ -378,8 +381,8 @@ describe("Config", () => {
replaceConfig({
numbers: true,
});
- await Config.applyConfig({
- ...Config.getConfigChanges(),
+ await Lifecycle.applyConfig({
+ ...ConfigUtils.getConfigChanges(),
punctuation: true,
});
const config = getConfig();
@@ -390,7 +393,7 @@ describe("Config", () => {
replaceConfig({
minWpm: "off",
});
- await Config.applyConfig({
+ await Lifecycle.applyConfig({
minWpmCustomSpeed: 100,
});
const config = getConfig();
@@ -402,7 +405,7 @@ describe("Config", () => {
replaceConfig({
minWpm: "off",
});
- await Config.applyConfig({
+ await Lifecycle.applyConfig({
minWpm: "custom",
minWpmCustomSpeed: 100,
});
@@ -413,7 +416,7 @@ describe("Config", () => {
it("should keep the keymap off when applying keymapLayout", async () => {
replaceConfig({});
- await Config.applyConfig({
+ await Lifecycle.applyConfig({
keymapLayout: "qwerty",
});
const config = getConfig();
diff --git a/frontend/__tests__/test/british-english.spec.ts b/frontend/__tests__/test/british-english.spec.ts
index 12a7e53f4915..e52bf74131f6 100644
--- a/frontend/__tests__/test/british-english.spec.ts
+++ b/frontend/__tests__/test/british-english.spec.ts
@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach } from "vitest";
import { replace } from "../../src/ts/test/british-english";
-import Config from "../../src/ts/config";
+import { Config } from "../../src/ts/config/store";
describe("british-english", () => {
describe("replace", () => {
diff --git a/frontend/__tests__/utils/config.spec.ts b/frontend/__tests__/utils/config.spec.ts
index 085cab2cf46c..3c29fde39b9d 100644
--- a/frontend/__tests__/utils/config.spec.ts
+++ b/frontend/__tests__/utils/config.spec.ts
@@ -1,6 +1,6 @@
import { describe, it, expect } from "vitest";
import { getDefaultConfig } from "../../src/ts/constants/default-config";
-import { migrateConfig } from "../../src/ts/utils/config";
+import { migrateConfig } from "../../src/ts/config/utils";
import { PartialConfig } from "@monkeytype/schemas/configs";
const defaultConfig = getDefaultConfig();
diff --git a/frontend/src/ts/auth.tsx b/frontend/src/ts/auth.tsx
index 5b25cd7c4fb8..5fc6c39b58d5 100644
--- a/frontend/src/ts/auth.tsx
+++ b/frontend/src/ts/auth.tsx
@@ -10,7 +10,7 @@ import {
import Ape from "./ape";
import { showRegisterCaptchaModal } from "./components/modals/RegisterCaptchaModal";
-import { updateFromServer as updateConfigFromServer } from "./config";
+import { updateFromServer as updateConfigFromServer } from "./config/remote";
import * as DB from "./db";
import {
isAuthAvailable,
diff --git a/frontend/src/ts/commandline/commandline-metadata.ts b/frontend/src/ts/commandline/commandline-metadata.ts
index a1cdb9c2d1a2..9744c9ee16ab 100644
--- a/frontend/src/ts/commandline/commandline-metadata.ts
+++ b/frontend/src/ts/commandline/commandline-metadata.ts
@@ -5,7 +5,7 @@ import { getLanguageDisplayString } from "../utils/strings";
import * as ModesNotice from "../elements/modes-notice";
import { isAuthenticated } from "../firebase";
import { areUnsortedArraysEqual } from "../utils/arrays";
-import Config from "../config";
+import { Config } from "../config/store";
import { get as getTypingSpeedUnit } from "../utils/typing-speed-units";
import { getActivePage } from "../states/core";
import { Fonts } from "../constants/fonts";
diff --git a/frontend/src/ts/commandline/commandline.ts b/frontend/src/ts/commandline/commandline.ts
index 61959f04b023..baddf180a06d 100644
--- a/frontend/src/ts/commandline/commandline.ts
+++ b/frontend/src/ts/commandline/commandline.ts
@@ -1,6 +1,6 @@
import * as Focus from "../test/focus";
import * as CommandlineLists from "./lists";
-import Config from "../config";
+import { Config } from "../config/store";
import * as AnalyticsController from "../controllers/analytics-controller";
import * as ThemeController from "../controllers/theme-controller";
import { clearFontPreview } from "../ui";
diff --git a/frontend/src/ts/commandline/lists.ts b/frontend/src/ts/commandline/lists.ts
index 0a59523967b3..2af52dec0463 100644
--- a/frontend/src/ts/commandline/lists.ts
+++ b/frontend/src/ts/commandline/lists.ts
@@ -16,7 +16,8 @@ import LoadChallengeCommands, {
update as updateLoadChallengeCommands,
} from "./lists/load-challenge";
-import Config, { applyConfigFromJson, setConfig } from "../config";
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as getErrorMessage from "../utils/error";
import * as JSONData from "../utils/json-data";
import { randomizeTheme } from "../controllers/theme-controller";
@@ -39,6 +40,7 @@ import {
hideFpsCounter,
showFpsCounter,
} from "../components/layout/overlays/FpsCounter";
+import { applyConfigFromJson } from "../config/lifecycle";
const challengesPromise = JSONData.getChallengeList();
challengesPromise
diff --git a/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts b/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts
index f311f8fc05bd..c687c693e142 100644
--- a/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts
+++ b/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts
@@ -1,5 +1,6 @@
import { ThemeName } from "@monkeytype/schemas/configs";
-import Config, { setConfig } from "../../config";
+import { Config } from "../../config/store";
+import { setConfig } from "../../config/setters";
import { randomTheme } from "../../controllers/theme-controller";
import { Command } from "../types";
diff --git a/frontend/src/ts/commandline/lists/background-filter.ts b/frontend/src/ts/commandline/lists/background-filter.ts
index 2958b4447e61..01bd3ecfdc02 100644
--- a/frontend/src/ts/commandline/lists/background-filter.ts
+++ b/frontend/src/ts/commandline/lists/background-filter.ts
@@ -1,4 +1,5 @@
-import Config, { setConfig } from "../../config";
+import { Config } from "../../config/store";
+import { setConfig } from "../../config/setters";
import { Command, CommandsSubgroup } from "../types";
const subgroup: CommandsSubgroup = {
diff --git a/frontend/src/ts/commandline/lists/bail-out.ts b/frontend/src/ts/commandline/lists/bail-out.ts
index c750312f2ac6..319701b3c6df 100644
--- a/frontend/src/ts/commandline/lists/bail-out.ts
+++ b/frontend/src/ts/commandline/lists/bail-out.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import * as CustomText from "../../test/custom-text";
import * as TestLogic from "../../test/test-logic";
import * as TestState from "../../test/test-state";
diff --git a/frontend/src/ts/commandline/lists/custom-background.ts b/frontend/src/ts/commandline/lists/custom-background.ts
index 3f4624410657..795f20929695 100644
--- a/frontend/src/ts/commandline/lists/custom-background.ts
+++ b/frontend/src/ts/commandline/lists/custom-background.ts
@@ -4,7 +4,8 @@ import FileStorage from "../../utils/file-storage";
import { applyCustomBackground } from "../../controllers/theme-controller";
import { updateUI } from "../../elements/settings/custom-background-picker";
import { showNoticeNotification } from "../../states/notifications";
-import Config, { setConfig } from "../../config";
+import { Config } from "../../config/store";
+import { setConfig } from "../../config/setters";
const fromMeta = buildCommandForConfigKey("customBackground");
diff --git a/frontend/src/ts/commandline/lists/custom-themes-list.ts b/frontend/src/ts/commandline/lists/custom-themes-list.ts
index fe112c258ac9..aacf492df125 100644
--- a/frontend/src/ts/commandline/lists/custom-themes-list.ts
+++ b/frontend/src/ts/commandline/lists/custom-themes-list.ts
@@ -1,4 +1,4 @@
-import { setConfig } from "../../config";
+import { setConfig } from "../../config/setters";
import { isAuthenticated } from "../../firebase";
import * as DB from "../../db";
import * as ThemeController from "../../controllers/theme-controller";
diff --git a/frontend/src/ts/commandline/lists/font-family.ts b/frontend/src/ts/commandline/lists/font-family.ts
index 3ef441151cda..acfc08ca9a37 100644
--- a/frontend/src/ts/commandline/lists/font-family.ts
+++ b/frontend/src/ts/commandline/lists/font-family.ts
@@ -4,8 +4,8 @@ import FileStorage from "../../utils/file-storage";
import { applyFontFamily } from "../../controllers/theme-controller";
import { updateUI } from "../../elements/settings/custom-font-picker";
import { showNoticeNotification } from "../../states/notifications";
-import Config, { setConfig } from "../../config";
-
+import { Config } from "../../config/store";
+import { setConfig } from "../../config/setters";
const fromMeta = buildCommandForConfigKey("fontFamily");
if (fromMeta.subgroup) {
diff --git a/frontend/src/ts/commandline/lists/min-burst.ts b/frontend/src/ts/commandline/lists/min-burst.ts
index bf8480f3987b..1152e476c68e 100644
--- a/frontend/src/ts/commandline/lists/min-burst.ts
+++ b/frontend/src/ts/commandline/lists/min-burst.ts
@@ -1,4 +1,5 @@
-import Config, { setConfig } from "../../config";
+import { Config } from "../../config/store";
+import { setConfig } from "../../config/setters";
import { get as getTypingSpeedUnit } from "../../utils/typing-speed-units";
import { Command, CommandsSubgroup } from "../types";
diff --git a/frontend/src/ts/commandline/lists/quote-favorites.ts b/frontend/src/ts/commandline/lists/quote-favorites.ts
index adb329968f6a..ecfe6bf8876b 100644
--- a/frontend/src/ts/commandline/lists/quote-favorites.ts
+++ b/frontend/src/ts/commandline/lists/quote-favorites.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import QuotesController, { Quote } from "../../controllers/quotes-controller";
import {
showErrorNotification,
diff --git a/frontend/src/ts/commandline/lists/result-screen.ts b/frontend/src/ts/commandline/lists/result-screen.ts
index 0ab77c979907..1debee3ac804 100644
--- a/frontend/src/ts/commandline/lists/result-screen.ts
+++ b/frontend/src/ts/commandline/lists/result-screen.ts
@@ -8,7 +8,7 @@ import {
import * as TestInput from "../../test/test-input";
import * as TestState from "../../test/test-state";
import * as TestWords from "../../test/test-words";
-import Config from "../../config";
+import { Config } from "../../config/store";
import * as PractiseWords from "../../test/practise-words";
import { Command, CommandsSubgroup } from "../types";
import * as TestScreenshot from "../../test/test-screenshot";
diff --git a/frontend/src/ts/commandline/lists/tags.ts b/frontend/src/ts/commandline/lists/tags.ts
index 41d676c6576b..33a18be8abf9 100644
--- a/frontend/src/ts/commandline/lists/tags.ts
+++ b/frontend/src/ts/commandline/lists/tags.ts
@@ -2,7 +2,7 @@ import * as DB from "../../db";
import * as EditTagsPopup from "../../modals/edit-tag";
import * as ModesNotice from "../../elements/modes-notice";
import * as TagController from "../../controllers/tag-controller";
-import Config from "../../config";
+import { Config } from "../../config/store";
import * as PaceCaret from "../../test/pace-caret";
import { isAuthenticated } from "../../firebase";
import { Command, CommandsSubgroup } from "../types";
diff --git a/frontend/src/ts/commandline/lists/themes.ts b/frontend/src/ts/commandline/lists/themes.ts
index ec4d4d296008..0b4874f72bdb 100644
--- a/frontend/src/ts/commandline/lists/themes.ts
+++ b/frontend/src/ts/commandline/lists/themes.ts
@@ -1,4 +1,5 @@
-import Config, { setConfig } from "../../config";
+import { Config } from "../../config/store";
+import { setConfig } from "../../config/setters";
import { capitalizeFirstLetterOfEachWord } from "../../utils/strings";
import * as ThemeController from "../../controllers/theme-controller";
import { Command, CommandsSubgroup } from "../types";
diff --git a/frontend/src/ts/commandline/util.ts b/frontend/src/ts/commandline/util.ts
index 714233901f33..ec65be67ff1f 100644
--- a/frontend/src/ts/commandline/util.ts
+++ b/frontend/src/ts/commandline/util.ts
@@ -1,5 +1,6 @@
-import Config, { setConfig } from "../config";
-import { ConfigMetadata, configMetadata } from "../config-metadata";
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
+import { ConfigMetadata, configMetadata } from "../config/metadata";
import { capitalizeFirstLetter } from "../utils/strings";
import {
CommandlineConfigMetadata,
diff --git a/frontend/src/ts/components/layout/footer/Keytips.tsx b/frontend/src/ts/components/layout/footer/Keytips.tsx
index 071a972bd304..3c703888f71f 100644
--- a/frontend/src/ts/components/layout/footer/Keytips.tsx
+++ b/frontend/src/ts/components/layout/footer/Keytips.tsx
@@ -1,6 +1,6 @@
import { JSXElement, Show } from "solid-js";
-import { getConfig } from "../../../states/config";
+import { getConfig } from "../../../config/store";
import { getFocus } from "../../../states/core";
import { Conditional } from "../../common/Conditional";
diff --git a/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx b/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx
index 58902d1b2ca8..9b91c232e84b 100644
--- a/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx
+++ b/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx
@@ -1,6 +1,7 @@
import { JSXElement, Show } from "solid-js";
-import Config, { setConfig } from "../../../config";
+import { setConfig } from "../../../config/setters";
+import { Config } from "../../../config/store";
import { isAuthenticated } from "../../../firebase";
import {
getThemeIndicator,
diff --git a/frontend/src/ts/components/pages/AboutPage.tsx b/frontend/src/ts/components/pages/AboutPage.tsx
index 11e26d157e1a..3296d2ebf3e6 100644
--- a/frontend/src/ts/components/pages/AboutPage.tsx
+++ b/frontend/src/ts/components/pages/AboutPage.tsx
@@ -1,6 +1,7 @@
import { useQuery } from "@tanstack/solid-query";
import { For, JSXElement, Show } from "solid-js";
+import { getConfig } from "../../config/store";
import { queryClient } from "../../queries";
import {
getContributorsQueryOptions,
@@ -8,7 +9,6 @@ import {
getSupportersQueryOptions,
getTypingStatsQueryOptions,
} from "../../queries/public";
-import { getConfig } from "../../states/config";
import { getActivePage } from "../../states/core";
import { showModal } from "../../states/modals";
import { getTheme } from "../../states/theme";
diff --git a/frontend/src/ts/components/pages/leaderboard/Table.tsx b/frontend/src/ts/components/pages/leaderboard/Table.tsx
index 8a74b8c0a5ea..27c1d8cb8b68 100644
--- a/frontend/src/ts/components/pages/leaderboard/Table.tsx
+++ b/frontend/src/ts/components/pages/leaderboard/Table.tsx
@@ -7,10 +7,10 @@ import { format as dateFormat } from "date-fns/format";
import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
import { Accessor, createMemo, JSXElement } from "solid-js";
+import { getConfig } from "../../../config/store";
import { isFriend } from "../../../db";
import { createEffectOn } from "../../../hooks/effects";
import { bp, BreakpointKey } from "../../../states/breakpoints";
-import { getConfig } from "../../../states/config";
import { getUserId } from "../../../states/core";
import { cn } from "../../../utils/cn";
import { secondsToString } from "../../../utils/date-and-time";
diff --git a/frontend/src/ts/components/pages/leaderboard/UserRank.tsx b/frontend/src/ts/components/pages/leaderboard/UserRank.tsx
index 6baa112c1b1c..77ab276bea08 100644
--- a/frontend/src/ts/components/pages/leaderboard/UserRank.tsx
+++ b/frontend/src/ts/components/pages/leaderboard/UserRank.tsx
@@ -5,7 +5,7 @@ import {
import { formatDuration, intervalToDuration } from "date-fns";
import { createMemo, JSXElement, Match, Show, Switch } from "solid-js";
-import { getConfig } from "../../../states/config";
+import { getConfig } from "../../../config/store";
import { Formatting } from "../../../utils/format";
import { Conditional } from "../../common/Conditional";
import { Fa } from "../../common/Fa";
diff --git a/frontend/src/ts/components/pages/profile/UserProfile.tsx b/frontend/src/ts/components/pages/profile/UserProfile.tsx
index f88517e95d1d..3bc907fb4b1e 100644
--- a/frontend/src/ts/components/pages/profile/UserProfile.tsx
+++ b/frontend/src/ts/components/pages/profile/UserProfile.tsx
@@ -6,8 +6,8 @@ import {
import { formatDate } from "date-fns/format";
import { createMemo, For, JSXElement, Show } from "solid-js";
+import { getConfig } from "../../../config/store";
import * as PbTablesModal from "../../../modals/pb-tables";
-import { getConfig } from "../../../states/config";
import { Formatting } from "../../../utils/format";
import { formatTopPercentage } from "../../../utils/misc";
import { Button } from "../../common/Button";
diff --git a/frontend/src/ts/config.ts b/frontend/src/ts/config.ts
deleted file mode 100644
index a890466ec441..000000000000
--- a/frontend/src/ts/config.ts
+++ /dev/null
@@ -1,422 +0,0 @@
-import {
- showNoticeNotification,
- showErrorNotification,
- showSuccessNotification,
-} from "./states/notifications";
-import { isConfigValueValid } from "./config-validation";
-import * as ConfigEvent from "./observables/config-event";
-import { debounce } from "throttle-debounce";
-import {
- canSetConfigWithCurrentFunboxes,
- canSetFunboxWithConfig,
-} from "./test/funbox/funbox-validation";
-import {
- isObject,
- promiseWithResolvers,
- triggerResize,
- typedKeys,
-} from "./utils/misc";
-import * as ConfigSchemas from "@monkeytype/schemas/configs";
-import { Config, FunboxName } from "@monkeytype/schemas/configs";
-import { LocalStorageWithSchema } from "./utils/local-storage-with-schema";
-import { migrateConfig } from "./utils/config";
-import { getDefaultConfig } from "./constants/default-config";
-import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
-import { ZodSchema } from "zod";
-import * as TestState from "./test/test-state";
-import { ConfigMetadataObject, configMetadata } from "./config-metadata";
-import { setAccountButtonSpinner } from "./states/header";
-import { deleteConfig, saveConfig } from "./ape/config";
-import Ape from "./ape";
-import { SnapshotInitError } from "./db";
-
-const configLS = new LocalStorageWithSchema({
- key: "config",
- schema: ConfigSchemas.ConfigSchema,
- fallback: getDefaultConfig(),
- migrate: (value, _issues) => {
- if (!isObject(value)) {
- return getDefaultConfig();
- }
- //todo maybe send a full config to db so that it removes legacy values
-
- return migrateConfig(value);
- },
-});
-
-let config: Config = {
- ...getDefaultConfig(),
-};
-
-let configToSend: Partial = {};
-const saveToDatabase = debounce(1000, () => {
- if (Object.keys(configToSend).length > 0) {
- setAccountButtonSpinner(true);
- void saveConfig(configToSend).finally(() => {
- setAccountButtonSpinner(false);
- });
- }
- configToSend = {} as Config;
-});
-
-function saveToLocalStorage(
- key: keyof Config,
- nosave = false,
- noDbCheck = false,
-): void {
- if (nosave) return;
- configLS.set(config);
- if (!noDbCheck) {
- //@ts-expect-error this is fine
- configToSend[key] = config[key];
- saveToDatabase();
- }
-}
-
-export function saveFullConfigToLocalStorage(noDbCheck = false): void {
- console.log("saving full config to localStorage");
- configLS.set(config);
- if (!noDbCheck) {
- setAccountButtonSpinner(true);
- void saveConfig(config).finally(() => {
- setAccountButtonSpinner(false);
- });
- }
-}
-
-function isConfigChangeBlocked(): boolean {
- if (TestState.isActive && config.funbox.includes("no_quit")) {
- showNoticeNotification(
- "No quit funbox is active. Please finish the test.",
- {
- important: true,
- },
- );
- return true;
- }
- return false;
-}
-
-export function setConfig(
- key: T,
- value: Config[T],
- options?: {
- nosave?: boolean;
- },
-): boolean {
- const metadata = configMetadata[key] as ConfigMetadataObject[T];
- if (metadata === undefined) {
- throw new Error(`Config metadata for key "${key}" is not defined.`);
- }
-
- if (metadata.overrideValue) {
- value = metadata.overrideValue({
- value,
- currentValue: config[key],
- currentConfig: config,
- });
- }
-
- const previousValue = config[key];
-
- if (
- metadata.changeRequiresRestart &&
- TestState.isActive &&
- config.funbox.includes("no_quit")
- ) {
- showNoticeNotification(
- "No quit funbox is active. Please finish the test.",
- {
- important: true,
- },
- );
- console.warn(
- `Could not set config key "${key}" with value "${JSON.stringify(
- value,
- )}" - no quit funbox active.`,
- );
- return false;
- }
-
- if (metadata.isBlocked?.({ value, currentConfig: config })) {
- console.warn(
- `Could not set config key "${key}" with value "${JSON.stringify(
- value,
- )}" - blocked.`,
- );
- return false;
- }
-
- const schema = ConfigSchemas.ConfigSchema.shape[key] as ZodSchema;
-
- if (!isConfigValueValid(metadata.displayString ?? key, value, schema)) {
- console.warn(
- `Could not set config key "${key}" with value "${JSON.stringify(
- value,
- )}" - invalid value.`,
- );
- return false;
- }
-
- if (!canSetConfigWithCurrentFunboxes(key, value, config.funbox)) {
- console.warn(
- `Could not set config key "${key}" with value "${JSON.stringify(
- value,
- )}" - funbox conflict.`,
- );
- return false;
- }
-
- if (metadata.overrideConfig) {
- const targetConfig = metadata.overrideConfig({
- value,
- currentConfig: config,
- });
-
- for (const targetKey of typedKeys(targetConfig)) {
- const targetValue = targetConfig[
- targetKey
- ] as ConfigSchemas.Config[keyof typeof configMetadata];
-
- if (config[targetKey] === targetValue) {
- continue; // no need to set if the value is already the same
- }
-
- const set = setConfig(targetKey, targetValue, options);
- if (!set) {
- throw new Error(
- `Failed to set config key "${targetKey}" with value "${targetValue}" for ${metadata.displayString} config override.`,
- );
- }
- }
- }
-
- config[key] = value;
- if (!options?.nosave) saveToLocalStorage(key, options?.nosave);
-
- // @ts-expect-error i can't figure this out
- ConfigEvent.dispatch({
- key: key,
- newValue: value,
- nosave: options?.nosave ?? false,
- previousValue: previousValue as Config[T],
- });
-
- if (metadata.triggerResize && !options?.nosave) {
- triggerResize();
- }
-
- metadata.afterSet?.({
- nosave: options?.nosave ?? false,
- currentConfig: config,
- });
- return true;
-}
-
-export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean {
- if (isConfigChangeBlocked()) return false;
-
- if (!canSetFunboxWithConfig(funbox, config)) {
- return false;
- }
-
- const previousValue = config.funbox;
-
- let newConfig: FunboxName[] = config.funbox;
-
- if (newConfig.includes(funbox)) {
- newConfig = newConfig.filter((it) => it !== funbox);
- } else {
- newConfig.push(funbox);
- newConfig.sort();
- }
-
- if (!isConfigValueValid("funbox", newConfig, ConfigSchemas.FunboxSchema)) {
- return false;
- }
-
- config.funbox = newConfig;
- saveToLocalStorage("funbox", nosave);
- ConfigEvent.dispatch({
- key: "funbox",
- newValue: config.funbox,
- nosave,
- previousValue,
- });
-
- return true;
-}
-
-export function setQuoteLengthAll(nosave?: boolean): boolean {
- return setConfig("quoteLength", [0, 1, 2, 3], {
- nosave,
- });
-}
-
-const lastConfigsToApply: Set = new Set([
- "keymapMode",
- "minWpm",
- "minAcc",
- "minBurst",
- "paceCaret",
- "quoteLength", //quote length sets mode,
- "words",
- "time",
- "mode", // mode sets punctuation and numbers
- "numbers",
- "punctuation",
- "funbox",
-]);
-
-export async function applyConfig(
- partialConfig: Partial,
-): Promise {
- if (partialConfig === undefined || partialConfig === null) return;
-
- //migrate old values if needed, remove additional keys and merge with default config
- const fullConfig: Config = migrateConfig(partialConfig);
-
- ConfigEvent.dispatch({ key: "fullConfigChange" });
-
- const defaultConfig = getDefaultConfig();
- for (const key of typedKeys(fullConfig)) {
- //@ts-expect-error this is fine, both are of type config
- config[key] = defaultConfig[key];
- }
-
- const configKeysToReset: (keyof Config)[] = [];
-
- const firstKeys = typedKeys(fullConfig).filter(
- (key) => !lastConfigsToApply.has(key),
- );
-
- for (const configKey of [...firstKeys, ...lastConfigsToApply]) {
- const configValue = fullConfig[configKey];
-
- const set = setConfig(configKey, configValue, { nosave: true });
-
- if (!set) {
- configKeysToReset.push(configKey);
- }
- }
-
- for (const key of configKeysToReset) {
- saveToLocalStorage(key);
- }
-
- ConfigEvent.dispatch({ key: "fullConfigChangeFinished" });
-}
-
-export async function resetConfig(): Promise {
- await applyConfig(getDefaultConfig());
- await deleteConfig();
- saveFullConfigToLocalStorage(true);
-}
-
-export async function loadFromLocalStorage(): Promise {
- console.log("loading localStorage config");
- const newConfig = configLS.get();
- if (newConfig === undefined) {
- await resetConfig();
- } else {
- await applyConfig(newConfig);
- saveFullConfigToLocalStorage(true);
- }
- loadDone();
-}
-
-export function getConfigChanges(): Partial {
- const configChanges: Partial = {};
- typedKeys(config)
- .filter((key) => {
- return config[key] !== getDefaultConfig()[key];
- })
- .forEach((key) => {
- //@ts-expect-error this is fine
- configChanges[key] = config[key];
- });
- return configChanges;
-}
-
-export async function applyConfigFromJson(json: string): Promise {
- try {
- const parsedConfig = parseJsonWithSchema(
- json,
- ConfigSchemas.PartialConfigSchema.strip(),
- {
- migrate: (value) => {
- if (Array.isArray(value)) {
- throw new Error("Invalid config");
- }
- return migrateConfig(value);
- },
- },
- );
- await applyConfig(parsedConfig);
- saveFullConfigToLocalStorage();
- showSuccessNotification("Done");
- } catch (e) {
- console.error(e);
- showErrorNotification("Failed to import settings", { error: e });
- }
-}
-
-export async function updateFromServer(): Promise {
- const remoteConfig = await getRemoteConfig();
-
- const areConfigsEqual =
- JSON.stringify(config) === JSON.stringify(remoteConfig);
-
- if (config === undefined || !areConfigsEqual) {
- console.log(
- "no local config or local and db configs are different - applying db",
- );
- await applyConfig(remoteConfig);
- saveFullConfigToLocalStorage(true);
- }
-}
-
-async function getRemoteConfig(): Promise {
- const response = await Ape.configs.get();
-
- if (response.status !== 200) {
- throw new SnapshotInitError(
- `${response.body.message} (config)`,
- response.status,
- );
- }
-
- const configData = response.body.data;
- if (configData !== null && "config" in configData) {
- throw new Error(
- "Config data is not in the correct format. Please refresh the page or contact support.",
- );
- }
-
- if (configData === undefined || configData === null) {
- return {
- ...getDefaultConfig(),
- };
- } else {
- return migrateConfig(configData);
- }
-}
-
-const { promise: configLoadPromise, resolve: loadDone } =
- promiseWithResolvers();
-
-export const getConfig = (): Config => config;
-export { configLoadPromise };
-export default config;
-export const __testing = {
- configMetadata,
- replaceConfig: (setConfig: Partial): void => {
- const newConfig = { ...getDefaultConfig(), ...setConfig };
- for (const key of Object.keys(config)) {
- Reflect.deleteProperty(config, key);
- }
- Object.assign(config, newConfig);
- configToSend = {} as Config;
- },
- getConfig: () => config,
-};
diff --git a/frontend/src/ts/config/lifecycle.ts b/frontend/src/ts/config/lifecycle.ts
new file mode 100644
index 000000000000..636c38481bb6
--- /dev/null
+++ b/frontend/src/ts/config/lifecycle.ts
@@ -0,0 +1,123 @@
+import * as ConfigSchemas from "@monkeytype/schemas/configs";
+import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
+import {
+ showSuccessNotification,
+ showErrorNotification,
+} from "../states/notifications";
+import {
+ configLS,
+ saveToLocalStorage,
+ saveFullConfigToLocalStorage,
+} from "./persistence";
+import { Config, setConfigStore } from "./store";
+import { getDefaultConfig } from "../constants/default-config";
+import * as ConfigEvent from "../observables/config-event";
+import { migrateConfig } from "./utils";
+import { promiseWithResolvers, typedKeys } from "../utils/misc";
+import { setConfig } from "./setters";
+import { deleteConfig } from "../ape/config";
+import { reconcile } from "solid-js/store";
+
+export async function applyConfigFromJson(json: string): Promise {
+ try {
+ const parsedConfig = parseJsonWithSchema(
+ json,
+ ConfigSchemas.PartialConfigSchema.strip(),
+ {
+ migrate: (value) => {
+ if (Array.isArray(value)) {
+ throw new Error("Invalid config");
+ }
+ return migrateConfig(value);
+ },
+ },
+ );
+ await applyConfig(parsedConfig);
+ saveFullConfigToLocalStorage();
+ showSuccessNotification("Done");
+ } catch (e) {
+ console.error(e);
+ showErrorNotification("Failed to import settings", { error: e });
+ }
+}
+
+export async function loadFromLocalStorage(): Promise {
+ console.log("loading localStorage config");
+ const newConfig = configLS.get();
+ if (newConfig === undefined) {
+ await resetConfig();
+ } else {
+ await applyConfig(newConfig);
+ saveFullConfigToLocalStorage(true);
+ }
+ loadDone();
+}
+
+const lastConfigsToApply: Set = new Set([
+ "keymapMode",
+ "minWpm",
+ "minAcc",
+ "minBurst",
+ "paceCaret",
+ "quoteLength", //quote length sets mode,
+ "words",
+ "time",
+ "mode", // mode sets punctuation and numbers
+ "numbers",
+ "punctuation",
+ "funbox",
+]);
+
+export async function applyConfig(
+ partialConfig: Partial,
+): Promise {
+ if (partialConfig === undefined || partialConfig === null) return;
+
+ //migrate old values if needed, remove additional keys and merge with default config
+ const fullConfig: ConfigSchemas.Config = migrateConfig(partialConfig);
+
+ ConfigEvent.dispatch({ key: "fullConfigChange" });
+
+ const defaultConfig = getDefaultConfig();
+ for (const key of typedKeys(fullConfig)) {
+ //@ts-expect-error this is fine, both are of type config
+ Config[key] = defaultConfig[key];
+ }
+
+ const configKeysToReset: (keyof ConfigSchemas.Config)[] = [];
+
+ const firstKeys = typedKeys(fullConfig).filter(
+ (key) => !lastConfigsToApply.has(key),
+ );
+
+ for (const configKey of [...firstKeys, ...lastConfigsToApply]) {
+ const configValue = fullConfig[configKey];
+
+ const set = setConfig(configKey, configValue, {
+ nosave: true,
+ partOfFullConfigChange: true,
+ });
+
+ if (!set) {
+ configKeysToReset.push(configKey);
+ }
+ }
+
+ for (const key of configKeysToReset) {
+ saveToLocalStorage(key);
+ }
+
+ ConfigEvent.dispatch({ key: "fullConfigChangeFinished" });
+ setConfigStore(reconcile(Config));
+}
+
+export async function resetConfig(): Promise {
+ await applyConfig(getDefaultConfig());
+ await deleteConfig();
+ saveFullConfigToLocalStorage(true);
+}
+
+const { promise: configLoadPromise, resolve: loadDone } =
+ promiseWithResolvers();
+
+export { configLoadPromise };
diff --git a/frontend/src/ts/config-metadata.ts b/frontend/src/ts/config/metadata.ts
similarity index 98%
rename from frontend/src/ts/config-metadata.ts
rename to frontend/src/ts/config/metadata.ts
index 120f4d34da23..d3947ae9bec6 100644
--- a/frontend/src/ts/config-metadata.ts
+++ b/frontend/src/ts/config/metadata.ts
@@ -1,14 +1,14 @@
import { checkCompatibility } from "@monkeytype/funbox";
-import * as DB from "./db";
-import { showNoticeNotification } from "./states/notifications";
-import { isAuthenticated } from "./firebase";
-import { canSetFunboxWithConfig } from "./test/funbox/funbox-validation";
-import { reloadAfter } from "./utils/misc";
-import { isDevEnvironment } from "./utils/env";
+import * as DB from "../db";
+import { showNoticeNotification } from "../states/notifications";
+import { isAuthenticated } from "../firebase";
+import { canSetFunboxWithConfig } from "../test/funbox/funbox-validation";
+import { reloadAfter } from "../utils/misc";
+import { isDevEnvironment } from "../utils/env";
import * as ConfigSchemas from "@monkeytype/schemas/configs";
import { roundTo1 } from "@monkeytype/util/numbers";
-import { capitalizeFirstLetter } from "./utils/strings";
-import { getDefaultConfig } from "./constants/default-config";
+import { capitalizeFirstLetter } from "../utils/strings";
+import { getDefaultConfig } from "../constants/default-config";
// type SetBlock = {
// [K in keyof ConfigSchemas.Config]?: ConfigSchemas.Config[K][];
// };
diff --git a/frontend/src/ts/config/persistence.ts b/frontend/src/ts/config/persistence.ts
new file mode 100644
index 000000000000..30914714f2e5
--- /dev/null
+++ b/frontend/src/ts/config/persistence.ts
@@ -0,0 +1,66 @@
+import { Config as ConfigSchema } from "@monkeytype/schemas/configs";
+import { saveConfig } from "../ape/config";
+import { setAccountButtonSpinner } from "../states/header";
+import { Config } from "./store";
+import * as ConfigSchemas from "@monkeytype/schemas/configs";
+import { getDefaultConfig } from "../constants/default-config";
+import { migrateConfig } from "./utils";
+import { LocalStorageWithSchema } from "../utils/local-storage-with-schema";
+import { isObject } from "../utils/misc";
+import { debounce } from "throttle-debounce";
+
+let configToSend: Partial = {};
+
+export const configLS = new LocalStorageWithSchema({
+ key: "config",
+ schema: ConfigSchemas.ConfigSchema,
+ fallback: getDefaultConfig(),
+ migrate: (value, _issues) => {
+ if (!isObject(value)) {
+ return getDefaultConfig();
+ }
+ //todo maybe send a full config to db so that it removes legacy values
+ return migrateConfig(value);
+ },
+});
+
+export function saveToLocalStorage(
+ key: keyof ConfigSchema,
+ nosave = false,
+ noDbCheck = false,
+): void {
+ if (nosave) return;
+ configLS.set(Config);
+ if (!noDbCheck) {
+ //@ts-expect-error this is fine
+ configToSend[key] = Config[key];
+ saveToDatabase();
+ }
+}
+
+export function saveFullConfigToLocalStorage(noDbCheck = false): void {
+ console.log("saving full config to localStorage");
+ configLS.set(Config);
+ if (!noDbCheck) {
+ setAccountButtonSpinner(true);
+ void saveConfig(Config).finally(() => {
+ setAccountButtonSpinner(false);
+ });
+ }
+}
+
+const saveToDatabase = debounce(1000, () => {
+ if (Object.keys(configToSend).length > 0) {
+ setAccountButtonSpinner(true);
+ void saveConfig(configToSend).finally(() => {
+ setAccountButtonSpinner(false);
+ });
+ }
+ configToSend = {};
+});
+
+export function resetPendingConfigSync(
+ newConfigToSend: Partial,
+): void {
+ configToSend = newConfigToSend;
+}
diff --git a/frontend/src/ts/config/remote.ts b/frontend/src/ts/config/remote.ts
new file mode 100644
index 000000000000..2fb74f71400b
--- /dev/null
+++ b/frontend/src/ts/config/remote.ts
@@ -0,0 +1,50 @@
+import * as ConfigSchemas from "@monkeytype/schemas/configs";
+
+import { migrateConfig } from "./utils";
+import { applyConfig } from "./lifecycle";
+import { saveFullConfigToLocalStorage } from "./persistence";
+import Ape from "../ape";
+import { SnapshotInitError } from "../db";
+import { getDefaultConfig } from "../constants/default-config";
+import { Config } from "./store";
+
+export async function updateFromServer(): Promise {
+ const remoteConfig = await getRemoteConfig();
+
+ const areConfigsEqual =
+ JSON.stringify(Config) === JSON.stringify(remoteConfig);
+
+ if (Config === undefined || !areConfigsEqual) {
+ console.log(
+ "no local config or local and db configs are different - applying db",
+ );
+ await applyConfig(remoteConfig);
+ saveFullConfigToLocalStorage(true);
+ }
+}
+
+async function getRemoteConfig(): Promise {
+ const response = await Ape.configs.get();
+
+ if (response.status !== 200) {
+ throw new SnapshotInitError(
+ `${response.body.message} (config)`,
+ response.status,
+ );
+ }
+
+ const configData = response.body.data;
+ if (configData !== null && "config" in configData) {
+ throw new Error(
+ "Config data is not in the correct format. Please refresh the page or contact support.",
+ );
+ }
+
+ if (configData === undefined || configData === null) {
+ return {
+ ...getDefaultConfig(),
+ };
+ } else {
+ return migrateConfig(configData);
+ }
+}
diff --git a/frontend/src/ts/config/setters.ts b/frontend/src/ts/config/setters.ts
new file mode 100644
index 000000000000..84fb44e08dd2
--- /dev/null
+++ b/frontend/src/ts/config/setters.ts
@@ -0,0 +1,184 @@
+import * as ConfigSchemas from "@monkeytype/schemas/configs";
+import { ZodType as ZodSchema } from "zod";
+import { saveToLocalStorage } from "../config/persistence";
+import { configMetadata, ConfigMetadataObject } from "./metadata";
+import { isConfigValueValid } from "./validation";
+import * as ConfigEvent from "../observables/config-event";
+import { showNoticeNotification } from "../states/notifications";
+import {
+ canSetConfigWithCurrentFunboxes,
+ canSetFunboxWithConfig,
+} from "../test/funbox/funbox-validation";
+import * as TestState from "../test/test-state";
+import { typedKeys, triggerResize } from "../utils/misc";
+import { Config, setConfigStore } from "./store";
+import { FunboxName } from "@monkeytype/schemas/configs";
+
+export function setConfig(
+ key: T,
+ value: ConfigSchemas.Config[T],
+ options?: {
+ nosave?: boolean;
+ partOfFullConfigChange?: boolean;
+ },
+): boolean {
+ const metadata = configMetadata[key] as ConfigMetadataObject[T];
+ if (metadata === undefined) {
+ throw new Error(`Config metadata for key "${key}" is not defined.`);
+ }
+
+ if (metadata.overrideValue) {
+ value = metadata.overrideValue({
+ value,
+ currentValue: Config[key],
+ currentConfig: Config,
+ });
+ }
+
+ const previousValue = Config[key];
+
+ if (
+ metadata.changeRequiresRestart &&
+ TestState.isActive &&
+ Config.funbox.includes("no_quit")
+ ) {
+ showNoticeNotification(
+ "No quit funbox is active. Please finish the test.",
+ {
+ important: true,
+ },
+ );
+ console.warn(
+ `Could not set config key "${key}" with value "${JSON.stringify(
+ value,
+ )}" - no quit funbox active.`,
+ );
+ return false;
+ }
+
+ if (metadata.isBlocked?.({ value, currentConfig: Config })) {
+ console.warn(
+ `Could not set config key "${key}" with value "${JSON.stringify(
+ value,
+ )}" - blocked.`,
+ );
+ return false;
+ }
+
+ const schema = ConfigSchemas.ConfigSchema.shape[key] as ZodSchema;
+
+ if (!isConfigValueValid(metadata.displayString ?? key, value, schema)) {
+ console.warn(
+ `Could not set config key "${key}" with value "${JSON.stringify(
+ value,
+ )}" - invalid value.`,
+ );
+ return false;
+ }
+
+ if (!canSetConfigWithCurrentFunboxes(key, value, Config.funbox)) {
+ console.warn(
+ `Could not set config key "${key}" with value "${JSON.stringify(
+ value,
+ )}" - funbox conflict.`,
+ );
+ return false;
+ }
+
+ if (metadata.overrideConfig) {
+ const targetConfig = metadata.overrideConfig({
+ value,
+ currentConfig: Config,
+ });
+
+ for (const targetKey of typedKeys(targetConfig)) {
+ const targetValue = targetConfig[
+ targetKey
+ ] as ConfigSchemas.Config[keyof typeof configMetadata];
+
+ if (Config[targetKey] === targetValue) {
+ continue; // no need to set if the value is already the same
+ }
+
+ const set = setConfig(targetKey, targetValue, options);
+ if (!set) {
+ throw new Error(
+ `Failed to set config key "${targetKey}" with value "${targetValue}" for ${metadata.displayString} config override.`,
+ );
+ }
+ }
+ }
+
+ Config[key] = value;
+ if (!options?.nosave) saveToLocalStorage(key, options?.nosave);
+
+ // @ts-expect-error i can't figure this out
+ ConfigEvent.dispatch({
+ key: key,
+ newValue: value,
+ nosave: options?.nosave ?? false,
+ previousValue: previousValue as ConfigSchemas.Config[T],
+ });
+
+ if (!options?.partOfFullConfigChange) {
+ setConfigStore(key, value);
+ }
+
+ if (metadata.triggerResize && !options?.nosave) {
+ triggerResize();
+ }
+
+ metadata.afterSet?.({
+ nosave: options?.nosave ?? false,
+ currentConfig: Config,
+ });
+ return true;
+}
+
+export function setQuoteLengthAll(nosave?: boolean): boolean {
+ return setConfig("quoteLength", [0, 1, 2, 3], {
+ nosave,
+ });
+}
+
+export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean {
+ if (TestState.isActive && Config.funbox.includes("no_quit")) {
+ showNoticeNotification(
+ "No quit funbox is active. Please finish the test.",
+ {
+ important: true,
+ },
+ );
+ return false;
+ }
+
+ if (!canSetFunboxWithConfig(funbox, Config)) {
+ return false;
+ }
+
+ const previousValue = Config.funbox;
+
+ let newConfig: FunboxName[] = Config.funbox;
+
+ if (newConfig.includes(funbox)) {
+ newConfig = newConfig.filter((it) => it !== funbox);
+ } else {
+ newConfig.push(funbox);
+ newConfig.sort();
+ }
+
+ if (!isConfigValueValid("funbox", newConfig, ConfigSchemas.FunboxSchema)) {
+ return false;
+ }
+
+ Config.funbox = newConfig;
+ saveToLocalStorage("funbox", nosave);
+ ConfigEvent.dispatch({
+ key: "funbox",
+ newValue: Config.funbox,
+ nosave,
+ previousValue,
+ });
+
+ return true;
+}
diff --git a/frontend/src/ts/config/store.ts b/frontend/src/ts/config/store.ts
new file mode 100644
index 000000000000..1e73844649c3
--- /dev/null
+++ b/frontend/src/ts/config/store.ts
@@ -0,0 +1,10 @@
+import type { Config as ConfigSchema } from "@monkeytype/schemas/configs";
+import { getDefaultConfig } from "../constants/default-config";
+import { createStore } from "solid-js/store";
+
+export const Config: ConfigSchema = {
+ ...getDefaultConfig(),
+};
+
+export const [getConfig, setConfigStore] =
+ createStore(getDefaultConfig());
diff --git a/frontend/src/ts/config/testing.ts b/frontend/src/ts/config/testing.ts
new file mode 100644
index 000000000000..48ef33e8ce8b
--- /dev/null
+++ b/frontend/src/ts/config/testing.ts
@@ -0,0 +1,18 @@
+import type { Config as ConfigSchema } from "@monkeytype/schemas/configs";
+import { configMetadata } from "./metadata";
+import { getDefaultConfig } from "../constants/default-config";
+import { resetPendingConfigSync } from "./persistence";
+import { Config } from "./store";
+
+export const __testing = {
+ configMetadata,
+ replaceConfig: (setConfig: Partial): void => {
+ const newConfig = { ...getDefaultConfig(), ...setConfig };
+ for (const key of Object.keys(Config)) {
+ Reflect.deleteProperty(Config, key);
+ }
+ Object.assign(Config, newConfig);
+ resetPendingConfigSync({});
+ },
+ getConfig: () => Config,
+};
diff --git a/frontend/src/ts/utils/config.ts b/frontend/src/ts/config/utils.ts
similarity index 90%
rename from frontend/src/ts/utils/config.ts
rename to frontend/src/ts/config/utils.ts
index c8ccd6feac54..9621b2e717a3 100644
--- a/frontend/src/ts/utils/config.ts
+++ b/frontend/src/ts/config/utils.ts
@@ -1,25 +1,26 @@
-import {
- Config,
+import type {
+ Config as ConfigSchema,
ConfigValue,
PartialConfig,
FunboxName,
} from "@monkeytype/schemas/configs";
-import { typedKeys } from "./misc";
-import { sanitize } from "./sanitize";
+import { typedKeys } from "../utils/misc";
+import { sanitize } from "../utils/sanitize";
import * as ConfigSchemas from "@monkeytype/schemas/configs";
import { getDefaultConfig } from "../constants/default-config";
+import { Config } from "./store";
/**
* migrates possible outdated config and merges with the default config values
* @param config partial or possible outdated config
* @returns
*/
-export function migrateConfig(config: PartialConfig | object): Config {
+export function migrateConfig(config: PartialConfig | object): ConfigSchema {
return mergeWithDefaultConfig(sanitizeConfig(replaceLegacyValues(config)));
}
-function mergeWithDefaultConfig(config: PartialConfig): Config {
+function mergeWithDefaultConfig(config: PartialConfig): ConfigSchema {
const defaultConfig = getDefaultConfig();
- const mergedConfig = {} as Config;
+ const mergedConfig = {} as ConfigSchema;
for (const key of typedKeys(defaultConfig)) {
const newValue = config[key] ?? (defaultConfig[key] as ConfigValue);
//@ts-expect-error cant be bothered to deal with this
@@ -220,3 +221,16 @@ function replaceLegacyValues(
return configObj;
}
+
+export function getConfigChanges(): Partial {
+ const configChanges: Partial = {};
+ typedKeys(Config)
+ .filter((key) => {
+ return Config[key] !== getDefaultConfig()[key];
+ })
+ .forEach((key) => {
+ //@ts-expect-error this is fine
+ configChanges[key] = Config[key];
+ });
+ return configChanges;
+}
diff --git a/frontend/src/ts/config-validation.ts b/frontend/src/ts/config/validation.ts
similarity index 90%
rename from frontend/src/ts/config-validation.ts
rename to frontend/src/ts/config/validation.ts
index 0249262523a3..f39d6cd28a1f 100644
--- a/frontend/src/ts/config-validation.ts
+++ b/frontend/src/ts/config/validation.ts
@@ -1,6 +1,6 @@
-import { showErrorNotification } from "./states/notifications";
+import { showErrorNotification } from "../states/notifications";
import { ZodSchema, z } from "zod";
-import * as Sentry from "./sentry";
+import * as Sentry from "../sentry";
// function isConfigKeyValid(name: string): boolean {
// if (name === null || name === undefined || name === "") return false;
@@ -34,6 +34,7 @@ export function isConfigValueValid(
return isValid;
}
+
export function isConfigValueValidBoolean(key: string, val: boolean): boolean {
return isConfigValueValid(key, val, z.boolean());
}
diff --git a/frontend/src/ts/controllers/ad-controller.ts b/frontend/src/ts/controllers/ad-controller.ts
index 0c56463804eb..85bd1efe6255 100644
--- a/frontend/src/ts/controllers/ad-controller.ts
+++ b/frontend/src/ts/controllers/ad-controller.ts
@@ -1,7 +1,7 @@
/* oxlint-disable no-unsafe-member-access */
import { debounce } from "throttle-debounce";
import * as ConfigEvent from "../observables/config-event";
-import Config from "../config";
+import { Config } from "../config/store";
import * as TestState from "../test/test-state";
import * as EG from "./eg-ad-controller";
import * as PW from "./pw-ad-controller";
diff --git a/frontend/src/ts/controllers/challenge-controller.ts b/frontend/src/ts/controllers/challenge-controller.ts
index 403bf63518ac..acb7864f0c29 100644
--- a/frontend/src/ts/controllers/challenge-controller.ts
+++ b/frontend/src/ts/controllers/challenge-controller.ts
@@ -7,7 +7,9 @@ import {
} from "../states/notifications";
import * as CustomText from "../test/custom-text";
import * as Funbox from "../test/funbox/funbox";
-import Config, { setConfig } from "../config";
+
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as ConfigEvent from "../observables/config-event";
import * as TestState from "../test/test-state";
diff --git a/frontend/src/ts/controllers/chart-controller.ts b/frontend/src/ts/controllers/chart-controller.ts
index febeef2c76bb..afc75ff6c85a 100644
--- a/frontend/src/ts/controllers/chart-controller.ts
+++ b/frontend/src/ts/controllers/chart-controller.ts
@@ -59,7 +59,7 @@ Chart.defaults.elements.line.fill = "origin";
import "chartjs-adapter-date-fns";
import { format } from "date-fns/format";
-import Config from "../config";
+import { Config } from "../config/store";
import * as ConfigEvent from "../observables/config-event";
import * as TestInput from "../test/test-input";
import * as DateTime from "../utils/date-and-time";
diff --git a/frontend/src/ts/controllers/preset-controller.ts b/frontend/src/ts/controllers/preset-controller.ts
index e752891ab369..fce7a6c02732 100644
--- a/frontend/src/ts/controllers/preset-controller.ts
+++ b/frontend/src/ts/controllers/preset-controller.ts
@@ -1,5 +1,7 @@
import { Preset } from "@monkeytype/schemas/presets";
-import Config, { applyConfig, saveFullConfigToLocalStorage } from "../config";
+
+import { Config } from "../config/store";
+import { applyConfig } from "../config/lifecycle";
import * as DB from "../db";
import {
showNoticeNotification,
@@ -8,6 +10,7 @@ import {
import * as TestLogic from "../test/test-logic";
import * as TagController from "./tag-controller";
import { SnapshotPreset } from "../constants/default-snapshot";
+import { saveFullConfigToLocalStorage } from "../config/persistence";
export async function apply(_id: string): Promise {
const snapshot = DB.getSnapshot();
diff --git a/frontend/src/ts/controllers/pw-ad-controller.ts b/frontend/src/ts/controllers/pw-ad-controller.ts
index d94c142d4a52..5b17c9841466 100644
--- a/frontend/src/ts/controllers/pw-ad-controller.ts
+++ b/frontend/src/ts/controllers/pw-ad-controller.ts
@@ -3,7 +3,7 @@
// oxlint-disable ban-ts-comment
//@ts-nocheck too many errors from 3rd party ad code
-import Config from "../config";
+import { Config } from "../config/store";
import { getActivePage } from "../states/core";
import * as TestState from "../test/test-state";
diff --git a/frontend/src/ts/controllers/sound-controller.ts b/frontend/src/ts/controllers/sound-controller.ts
index 89725400d6b7..b3103789a720 100644
--- a/frontend/src/ts/controllers/sound-controller.ts
+++ b/frontend/src/ts/controllers/sound-controller.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import * as ConfigEvent from "../observables/config-event";
import { randomElementFromArray } from "../utils/arrays";
import { randomIntFromRange } from "@monkeytype/util/numbers";
diff --git a/frontend/src/ts/controllers/theme-controller.ts b/frontend/src/ts/controllers/theme-controller.ts
index 5fdfa1e3a5ae..bca26542ad9d 100644
--- a/frontend/src/ts/controllers/theme-controller.ts
+++ b/frontend/src/ts/controllers/theme-controller.ts
@@ -1,6 +1,8 @@
import * as Arrays from "../utils/arrays";
import { isColorDark, isColorLight } from "../utils/colors";
-import Config, { setConfig } from "../config";
+
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as BackgroundFilter from "../elements/custom-background-filter";
import * as ConfigEvent from "../observables/config-event";
import * as DB from "../db";
diff --git a/frontend/src/ts/controllers/url-handler.tsx b/frontend/src/ts/controllers/url-handler.tsx
index d5e4bef53438..acd5a586828b 100644
--- a/frontend/src/ts/controllers/url-handler.tsx
+++ b/frontend/src/ts/controllers/url-handler.tsx
@@ -21,7 +21,8 @@ import { decompressFromURI } from "lz-ts";
import { z } from "zod";
import Ape from "../ape";
-import Config, { setConfig } from "../config";
+import { setConfig } from "../config/setters";
+import { Config } from "../config/store";
import * as DB from "../db";
import * as AuthEvent from "../observables/auth-event";
import { showLoaderBar, hideLoaderBar } from "../states/loader-bar";
diff --git a/frontend/src/ts/elements/account/result-filters.ts b/frontend/src/ts/elements/account/result-filters.ts
index 25906eb85106..35410cc996b7 100644
--- a/frontend/src/ts/elements/account/result-filters.ts
+++ b/frontend/src/ts/elements/account/result-filters.ts
@@ -1,7 +1,7 @@
import * as Misc from "../../utils/misc";
import * as Strings from "../../utils/strings";
import * as DB from "../../db";
-import Config from "../../config";
+import { Config } from "../../config/store";
import {
showNoticeNotification,
showErrorNotification,
diff --git a/frontend/src/ts/elements/caret.ts b/frontend/src/ts/elements/caret.ts
index 836a1d047421..ff11ed3a983f 100644
--- a/frontend/src/ts/elements/caret.ts
+++ b/frontend/src/ts/elements/caret.ts
@@ -1,5 +1,5 @@
import { CaretStyle } from "@monkeytype/schemas/configs";
-import Config from "../config";
+import { Config } from "../config/store";
import * as TestWords from "../test/test-words";
import { getTotalInlineMargin } from "../utils/misc";
import { isWordRightToLeft } from "../utils/strings";
diff --git a/frontend/src/ts/elements/custom-background-filter.ts b/frontend/src/ts/elements/custom-background-filter.ts
index 5fc35e809b79..21f5e9973d89 100644
--- a/frontend/src/ts/elements/custom-background-filter.ts
+++ b/frontend/src/ts/elements/custom-background-filter.ts
@@ -1,5 +1,5 @@
import { CustomBackgroundFilter } from "@monkeytype/schemas/configs";
-import { setConfig } from "../config";
+import { setConfig } from "../config/setters";
import * as ConfigEvent from "../observables/config-event";
import { debounce } from "throttle-debounce";
import { qs, qsr } from "../utils/dom";
diff --git a/frontend/src/ts/elements/input-validation.ts b/frontend/src/ts/elements/input-validation.ts
index d6c7008ce47c..a870c322810d 100644
--- a/frontend/src/ts/elements/input-validation.ts
+++ b/frontend/src/ts/elements/input-validation.ts
@@ -6,7 +6,8 @@ import {
ConfigSchema,
Config as ConfigType,
} from "@monkeytype/schemas/configs";
-import Config, { setConfig } from "../config";
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import { showSuccessNotification } from "../states/notifications";
import { ElementWithUtils } from "../utils/dom";
import { Validation, ValidationResult } from "../types/validation";
diff --git a/frontend/src/ts/elements/keymap.ts b/frontend/src/ts/elements/keymap.ts
index 267e5f5982cf..114acdb8f8e3 100644
--- a/frontend/src/ts/elements/keymap.ts
+++ b/frontend/src/ts/elements/keymap.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import * as ConfigEvent from "../observables/config-event";
import * as KeymapEvent from "../observables/keymap-event";
import * as Misc from "../utils/misc";
diff --git a/frontend/src/ts/elements/last-10-average.ts b/frontend/src/ts/elements/last-10-average.ts
index 6e2e846638c8..df296a69afdd 100644
--- a/frontend/src/ts/elements/last-10-average.ts
+++ b/frontend/src/ts/elements/last-10-average.ts
@@ -1,7 +1,7 @@
import * as DB from "../db";
import * as Misc from "../utils/misc";
import * as Numbers from "@monkeytype/util/numbers";
-import Config from "../config";
+import { Config } from "../config/store";
import * as TestWords from "../test/test-words";
let averageWPM = 0;
diff --git a/frontend/src/ts/elements/modes-notice.ts b/frontend/src/ts/elements/modes-notice.ts
index d4fda472c3d3..a1ef87f20660 100644
--- a/frontend/src/ts/elements/modes-notice.ts
+++ b/frontend/src/ts/elements/modes-notice.ts
@@ -2,7 +2,7 @@ import * as PaceCaret from "../test/pace-caret";
import * as TestState from "../test/test-state";
import * as DB from "../db";
import * as Last10Average from "../elements/last-10-average";
-import Config from "../config";
+import { Config } from "../config/store";
import * as TestWords from "../test/test-words";
import * as ConfigEvent from "../observables/config-event";
import { isAuthenticated } from "../firebase";
diff --git a/frontend/src/ts/elements/monkey-power.ts b/frontend/src/ts/elements/monkey-power.ts
index 6730e5653b83..71d2bd9a9064 100644
--- a/frontend/src/ts/elements/monkey-power.ts
+++ b/frontend/src/ts/elements/monkey-power.ts
@@ -1,5 +1,5 @@
import * as SlowTimer from "../legacy-states/slow-timer";
-import Config from "../config";
+import { Config } from "../config/store";
import { isSafeNumber } from "@monkeytype/util/numbers";
import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame";
import { ElementWithUtils, qsr } from "../utils/dom";
diff --git a/frontend/src/ts/elements/settings/settings-group.ts b/frontend/src/ts/elements/settings/settings-group.ts
index 2a2e6bb6289b..aaea67ca5ec8 100644
--- a/frontend/src/ts/elements/settings/settings-group.ts
+++ b/frontend/src/ts/elements/settings/settings-group.ts
@@ -1,6 +1,6 @@
import { Config as ConfigType, ConfigKey } from "@monkeytype/schemas/configs";
-
-import Config, { setConfig } from "../../config";
+import { Config } from "../../config/store";
+import { setConfig } from "../../config/setters";
import { showErrorNotification } from "../../states/notifications";
import SlimSelect from "slim-select";
import { debounce } from "throttle-debounce";
diff --git a/frontend/src/ts/elements/settings/theme-picker.ts b/frontend/src/ts/elements/settings/theme-picker.ts
index 95cabf236e62..5e88f52b0972 100644
--- a/frontend/src/ts/elements/settings/theme-picker.ts
+++ b/frontend/src/ts/elements/settings/theme-picker.ts
@@ -1,4 +1,5 @@
-import Config, { setConfig, saveFullConfigToLocalStorage } from "../../config";
+import { Config } from "../../config/store";
+import { setConfig } from "../../config/setters";
import * as ThemeController from "../../controllers/theme-controller";
import * as Misc from "../../utils/misc";
import * as Colors from "../../utils/colors";
@@ -17,6 +18,7 @@ import { captureException } from "../../sentry";
import { ColorName, ThemesList, ThemeWithName } from "../../constants/themes";
import { qs, qsa, qsr } from "../../utils/dom";
import { getTheme, updateThemeColor } from "../../states/theme";
+import { saveFullConfigToLocalStorage } from "../../config/persistence";
export const sortedThemes: ThemeWithName[] = [...ThemesList].sort((a, b) => {
const b1 = Colors.hexToHSL(a.bg);
diff --git a/frontend/src/ts/event-handlers/global.ts b/frontend/src/ts/event-handlers/global.ts
index f7b3806d7a91..34262c190d5c 100644
--- a/frontend/src/ts/event-handlers/global.ts
+++ b/frontend/src/ts/event-handlers/global.ts
@@ -1,6 +1,6 @@
import * as Misc from "../utils/misc";
import * as PageTransition from "../legacy-states/page-transition";
-import Config from "../config";
+import { Config } from "../config/store";
import * as TestWords from "../test/test-words";
import * as Commandline from "../commandline/commandline";
import { showErrorNotification } from "../states/notifications";
diff --git a/frontend/src/ts/event-handlers/test.ts b/frontend/src/ts/event-handlers/test.ts
index 4ebd783417f6..1fc30c519a2b 100644
--- a/frontend/src/ts/event-handlers/test.ts
+++ b/frontend/src/ts/event-handlers/test.ts
@@ -1,6 +1,6 @@
import * as Commandline from "../commandline/commandline";
import * as CustomWordAmount from "../modals/custom-word-amount";
-import Config from "../config";
+import { Config } from "../config/store";
import * as DB from "../db";
import * as EditResultTagsModal from "../modals/edit-result-tags";
import * as MobileTestConfigModal from "../modals/mobile-test-config";
diff --git a/frontend/src/ts/index.ts b/frontend/src/ts/index.ts
index 6a8a46646450..3dfc3859b2d0 100644
--- a/frontend/src/ts/index.ts
+++ b/frontend/src/ts/index.ts
@@ -14,7 +14,7 @@ import * as DB from "./db";
import "./ui";
import "./elements/settings/account-settings-notice";
import "./controllers/ad-controller";
-import Config, { loadFromLocalStorage } from "./config";
+import { Config } from "./config/store";
import * as TestStats from "./test/test-stats";
import * as Replay from "./test/replay";
import * as TestTimer from "./test/test-timer";
@@ -44,6 +44,7 @@ import { qs, qsa, qsr } from "./utils/dom";
import { mountComponents } from "./components/mount";
import "./ready";
import { setVersion } from "./states/core";
+import { loadFromLocalStorage } from "./config/lifecycle";
// Lock Math.random
Object.defineProperty(Math, "random", {
diff --git a/frontend/src/ts/input/handlers/before-delete.ts b/frontend/src/ts/input/handlers/before-delete.ts
index 79fb28d77d50..12784dfdacb0 100644
--- a/frontend/src/ts/input/handlers/before-delete.ts
+++ b/frontend/src/ts/input/handlers/before-delete.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import * as TestInput from "../../test/test-input";
import * as TestState from "../../test/test-state";
import * as TestWords from "../../test/test-words";
diff --git a/frontend/src/ts/input/handlers/before-insert-text.ts b/frontend/src/ts/input/handlers/before-insert-text.ts
index 158771e3d7b6..d06b90e35da1 100644
--- a/frontend/src/ts/input/handlers/before-insert-text.ts
+++ b/frontend/src/ts/input/handlers/before-insert-text.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import * as TestInput from "../../test/test-input";
import * as TestState from "../../test/test-state";
import * as TestUI from "../../test/test-ui";
diff --git a/frontend/src/ts/input/handlers/delete.ts b/frontend/src/ts/input/handlers/delete.ts
index b6551c643636..0ee4213d8e3f 100644
--- a/frontend/src/ts/input/handlers/delete.ts
+++ b/frontend/src/ts/input/handlers/delete.ts
@@ -4,7 +4,7 @@ import * as TestInput from "../../test/test-input";
import { getInputElementValue, setInputElementValue } from "../input-element";
import * as Replay from "../../test/replay";
-import Config from "../../config";
+import { Config } from "../../config/store";
import { goToPreviousWord } from "../helpers/word-navigation";
import { DeleteInputType } from "../helpers/input-type";
diff --git a/frontend/src/ts/input/handlers/insert-text.ts b/frontend/src/ts/input/handlers/insert-text.ts
index 9a8e5212278a..190783c66227 100644
--- a/frontend/src/ts/input/handlers/insert-text.ts
+++ b/frontend/src/ts/input/handlers/insert-text.ts
@@ -20,7 +20,7 @@ import {
isFunboxActiveWithProperty,
} from "../../test/funbox/list";
import * as Replay from "../../test/replay";
-import Config from "../../config";
+import { Config } from "../../config/store";
import * as KeymapEvent from "../../observables/keymap-event";
import * as WeakSpot from "../../test/weak-spot";
import * as CompositionState from "../../legacy-states/composition";
diff --git a/frontend/src/ts/input/handlers/keydown.ts b/frontend/src/ts/input/handlers/keydown.ts
index cea4ffb1cefc..d27373dff7ff 100644
--- a/frontend/src/ts/input/handlers/keydown.ts
+++ b/frontend/src/ts/input/handlers/keydown.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import * as TestInput from "../../test/test-input";
import * as TestLogic from "../../test/test-logic";
import { getCharFromEvent } from "../../test/layout-emulator";
diff --git a/frontend/src/ts/input/handlers/keyup.ts b/frontend/src/ts/input/handlers/keyup.ts
index 995331c8265f..97c3321db8ee 100644
--- a/frontend/src/ts/input/handlers/keyup.ts
+++ b/frontend/src/ts/input/handlers/keyup.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import * as TestInput from "../../test/test-input";
import * as Monkey from "../../test/monkey";
diff --git a/frontend/src/ts/input/helpers/fail-or-finish.ts b/frontend/src/ts/input/helpers/fail-or-finish.ts
index 4b89e0768d5c..6c096a42c10b 100644
--- a/frontend/src/ts/input/helpers/fail-or-finish.ts
+++ b/frontend/src/ts/input/helpers/fail-or-finish.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import { whorf } from "../../utils/misc";
/**
diff --git a/frontend/src/ts/input/helpers/validation.ts b/frontend/src/ts/input/helpers/validation.ts
index 408b5416eba7..862bc80132bc 100644
--- a/frontend/src/ts/input/helpers/validation.ts
+++ b/frontend/src/ts/input/helpers/validation.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import { isSpace } from "../../utils/strings";
/**
diff --git a/frontend/src/ts/input/helpers/word-navigation.ts b/frontend/src/ts/input/helpers/word-navigation.ts
index ce57130ec818..fdb0e298bca2 100644
--- a/frontend/src/ts/input/helpers/word-navigation.ts
+++ b/frontend/src/ts/input/helpers/word-navigation.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import * as TestInput from "../../test/test-input";
import * as TestUI from "../../test/test-ui";
import * as PaceCaret from "../../test/pace-caret";
diff --git a/frontend/src/ts/modals/custom-test-duration.ts b/frontend/src/ts/modals/custom-test-duration.ts
index a7285a4233ce..16fe8551f931 100644
--- a/frontend/src/ts/modals/custom-test-duration.ts
+++ b/frontend/src/ts/modals/custom-test-duration.ts
@@ -1,4 +1,5 @@
-import Config, { setConfig } from "../config";
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as TestLogic from "../test/test-logic";
import { showNoticeNotification } from "../states/notifications";
import AnimatedModal, { ShowOptions } from "../utils/animated-modal";
diff --git a/frontend/src/ts/modals/custom-text.ts b/frontend/src/ts/modals/custom-text.ts
index 88a0cc9ba040..d9b1c8679c3e 100644
--- a/frontend/src/ts/modals/custom-text.ts
+++ b/frontend/src/ts/modals/custom-text.ts
@@ -2,7 +2,9 @@ import * as CustomText from "../test/custom-text";
import * as CustomTextState from "../legacy-states/custom-text-name";
import * as TestLogic from "../test/test-logic";
import * as ChallengeController from "../controllers/challenge-controller";
-import Config, { setConfig } from "../config";
+
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as Strings from "../utils/strings";
import * as WordFilterPopup from "./word-filter";
import * as CustomGeneratorPopup from "./custom-generator";
diff --git a/frontend/src/ts/modals/custom-word-amount.ts b/frontend/src/ts/modals/custom-word-amount.ts
index 4ab797f6b45f..6e4fd7aee5ca 100644
--- a/frontend/src/ts/modals/custom-word-amount.ts
+++ b/frontend/src/ts/modals/custom-word-amount.ts
@@ -1,4 +1,5 @@
-import Config, { setConfig } from "../config";
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as TestLogic from "../test/test-logic";
import { showNoticeNotification } from "../states/notifications";
import AnimatedModal, { ShowOptions } from "../utils/animated-modal";
diff --git a/frontend/src/ts/modals/edit-preset.ts b/frontend/src/ts/modals/edit-preset.ts
index 0fc66bc70373..de8d82d4b574 100644
--- a/frontend/src/ts/modals/edit-preset.ts
+++ b/frontend/src/ts/modals/edit-preset.ts
@@ -1,7 +1,5 @@
import Ape from "../ape";
import * as DB from "../db";
-import * as Config from "../config";
-
import { showLoaderBar, hideLoaderBar } from "../states/loader-bar";
import * as Settings from "../pages/settings";
import {
@@ -26,7 +24,8 @@ import { getDefaultConfig } from "../constants/default-config";
import { SnapshotPreset } from "../constants/default-snapshot";
import { ValidatedHtmlInputElement } from "../elements/input-validation";
import { ElementWithUtils } from "../utils/dom";
-import { configMetadata } from "../config-metadata";
+import { configMetadata } from "../config/metadata";
+import { getConfigChanges as getConfigChangesFromConfig } from "../config/utils";
const state = {
presetType: "full" as PresetType,
@@ -388,8 +387,8 @@ function getActiveSettingGroupsFromState(): ConfigGroupName[] {
function getConfigChanges(): Partial {
const activeConfigChanges =
state.presetType === "partial"
- ? getPartialConfigChanges(Config.getConfigChanges())
- : Config.getConfigChanges();
+ ? getPartialConfigChanges(getConfigChangesFromConfig())
+ : getConfigChangesFromConfig();
const tags = DB.getSnapshot()?.tags ?? [];
const activeTagIds: string[] = tags
diff --git a/frontend/src/ts/modals/import-export-settings.ts b/frontend/src/ts/modals/import-export-settings.ts
index 1a28159b646b..f2be415871d1 100644
--- a/frontend/src/ts/modals/import-export-settings.ts
+++ b/frontend/src/ts/modals/import-export-settings.ts
@@ -1,4 +1,4 @@
-import { applyConfigFromJson } from "../config";
+import { applyConfigFromJson } from "../config/lifecycle";
import AnimatedModal from "../utils/animated-modal";
type State = {
diff --git a/frontend/src/ts/modals/mini-result-chart.ts b/frontend/src/ts/modals/mini-result-chart.ts
index 14a090d215d9..e30925d79b62 100644
--- a/frontend/src/ts/modals/mini-result-chart.ts
+++ b/frontend/src/ts/modals/mini-result-chart.ts
@@ -1,8 +1,7 @@
import { ChartData } from "@monkeytype/schemas/results";
import AnimatedModal from "../utils/animated-modal";
import * as ChartController from "../controllers/chart-controller";
-import Config from "../config";
-
+import { Config } from "../config/store";
function updateData(data: ChartData): void {
// let data = filteredResults[filteredId].chartData;
let labels = [];
diff --git a/frontend/src/ts/modals/mobile-test-config.ts b/frontend/src/ts/modals/mobile-test-config.ts
index ba6bceeb639f..7dd85ccc5d79 100644
--- a/frontend/src/ts/modals/mobile-test-config.ts
+++ b/frontend/src/ts/modals/mobile-test-config.ts
@@ -1,5 +1,6 @@
import * as TestLogic from "../test/test-logic";
-import Config, { setConfig, setQuoteLengthAll } from "../config";
+import { Config } from "../config/store";
+import { setConfig, setQuoteLengthAll } from "../config/setters";
import * as CustomWordAmountPopup from "./custom-word-amount";
import * as CustomTestDurationPopup from "./custom-test-duration";
import * as QuoteSearchModal from "./quote-search";
diff --git a/frontend/src/ts/modals/pb-tables.ts b/frontend/src/ts/modals/pb-tables.ts
index a55c96204c9c..fabd0ace6ab7 100644
--- a/frontend/src/ts/modals/pb-tables.ts
+++ b/frontend/src/ts/modals/pb-tables.ts
@@ -1,7 +1,7 @@
import * as DB from "../db";
import { format } from "date-fns/format";
import { getLanguageDisplayString } from "../utils/strings";
-import Config from "../config";
+import { Config } from "../config/store";
import Format from "../singletons/format";
import AnimatedModal from "../utils/animated-modal";
import { Mode, Mode2, PersonalBest } from "@monkeytype/schemas/shared";
diff --git a/frontend/src/ts/modals/quote-report.ts b/frontend/src/ts/modals/quote-report.ts
index 0ec75aae44ef..d977becbe5a6 100644
--- a/frontend/src/ts/modals/quote-report.ts
+++ b/frontend/src/ts/modals/quote-report.ts
@@ -1,7 +1,6 @@
import { ElementWithUtils, qsr } from "../utils/dom";
import Ape from "../ape";
-import Config from "../config";
-
+import { Config } from "../config/store";
import { showLoaderBar, hideLoaderBar } from "../states/loader-bar";
import {
showNoticeNotification,
diff --git a/frontend/src/ts/modals/quote-search.ts b/frontend/src/ts/modals/quote-search.ts
index 234f72578356..1b7d6d910a76 100644
--- a/frontend/src/ts/modals/quote-search.ts
+++ b/frontend/src/ts/modals/quote-search.ts
@@ -1,4 +1,5 @@
-import Config, { setConfig } from "../config";
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as DB from "../db";
import {
showNoticeNotification,
diff --git a/frontend/src/ts/modals/quote-submit.ts b/frontend/src/ts/modals/quote-submit.ts
index b158ac63c7f6..e75854ee6182 100644
--- a/frontend/src/ts/modals/quote-submit.ts
+++ b/frontend/src/ts/modals/quote-submit.ts
@@ -9,7 +9,7 @@ import {
} from "../states/notifications";
import * as CaptchaController from "../controllers/captcha-controller";
import * as Strings from "../utils/strings";
-import Config from "../config";
+import { Config } from "../config/store";
import SlimSelect from "slim-select";
import AnimatedModal, { ShowOptions } from "../utils/animated-modal";
import { CharacterCounter } from "../elements/character-counter";
diff --git a/frontend/src/ts/modals/share-custom-theme.ts b/frontend/src/ts/modals/share-custom-theme.ts
index 8aa1a738fb39..b95c1251e12b 100644
--- a/frontend/src/ts/modals/share-custom-theme.ts
+++ b/frontend/src/ts/modals/share-custom-theme.ts
@@ -1,5 +1,5 @@
import * as ThemeController from "../controllers/theme-controller";
-import Config from "../config";
+import { Config } from "../config/store";
import {
showNoticeNotification,
showSuccessNotification,
diff --git a/frontend/src/ts/modals/share-test-settings.ts b/frontend/src/ts/modals/share-test-settings.ts
index 101d54245a29..86d3324a9ca4 100644
--- a/frontend/src/ts/modals/share-test-settings.ts
+++ b/frontend/src/ts/modals/share-test-settings.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import { currentQuote } from "../test/test-words";
import { getMode2 } from "../utils/misc";
import * as CustomText from "../test/custom-text";
diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts
index 16875cdf10e4..873b785ea424 100644
--- a/frontend/src/ts/modals/simple-modals.ts
+++ b/frontend/src/ts/modals/simple-modals.ts
@@ -1,7 +1,8 @@
import Ape from "../ape";
import * as AccountController from "../auth";
import * as DB from "../db";
-import { resetConfig, setConfig } from "../config";
+import { resetConfig } from "../config/lifecycle";
+import { setConfig } from "../config/setters";
import { showNoticeNotification } from "../states/notifications";
import * as Settings from "../pages/settings";
import * as ThemePicker from "../elements/settings/theme-picker";
diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts
index 6d15a69be241..080ab29e4336 100644
--- a/frontend/src/ts/pages/account.ts
+++ b/frontend/src/ts/pages/account.ts
@@ -1,7 +1,9 @@
import * as DB from "../db";
import * as ResultFilters from "../elements/account/result-filters";
import * as ChartController from "../controllers/chart-controller";
-import Config, { setConfig } from "../config";
+
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as MiniResultChartModal from "../modals/mini-result-chart";
import * as Focus from "../test/focus";
import * as TodayTracker from "../test/today-tracker";
diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts
index 43ee21f7ef0c..a40605db8e8e 100644
--- a/frontend/src/ts/pages/settings.ts
+++ b/frontend/src/ts/pages/settings.ts
@@ -1,5 +1,8 @@
import SettingsGroup from "../elements/settings/settings-group";
-import Config, { setConfig, configLoadPromise } from "../config";
+
+import { Config } from "../config/store";
+import { configLoadPromise } from "../config/lifecycle";
+import { setConfig } from "../config/setters";
import * as Sound from "../controllers/sound-controller";
import * as Misc from "../utils/misc";
import * as Strings from "../utils/strings";
@@ -1065,7 +1068,7 @@ export const page = new PageWithUrlParams({
},
beforeShow: async (options): Promise => {
Skeleton.append("pageSettings", "main");
- await configLoadPromise;
+ await configLoadPromise; //todo: is this actually needed here if we await it in ready?
await fillSettingsPage();
await update();
// theme UI updates manually to avoid duplication
diff --git a/frontend/src/ts/ready.ts b/frontend/src/ts/ready.ts
index 2d0ef5a46493..9acc9a79e53e 100644
--- a/frontend/src/ts/ready.ts
+++ b/frontend/src/ts/ready.ts
@@ -4,7 +4,7 @@ import * as MerchBanner from "./elements/merch-banner";
//@ts-expect-error no types for this package
import Konami from "konami";
import * as ServerConfiguration from "./ape/server-configuration";
-import { configLoadPromise } from "./config";
+import { configLoadPromise } from "./config/lifecycle";
import { authPromise } from "./firebase";
import { animate } from "animejs";
import { onDOMReady, qs } from "./utils/dom";
diff --git a/frontend/src/ts/singletons/format.ts b/frontend/src/ts/singletons/format.ts
index ae447cb31db8..f1943de48c65 100644
--- a/frontend/src/ts/singletons/format.ts
+++ b/frontend/src/ts/singletons/format.ts
@@ -1,4 +1,3 @@
import { Formatting } from "../utils/format";
-import Config from "../config";
-
+import { Config } from "../config/store";
export default new Formatting(Config);
diff --git a/frontend/src/ts/states/config.ts b/frontend/src/ts/states/config.ts
deleted file mode 100644
index b86f6d24f006..000000000000
--- a/frontend/src/ts/states/config.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { subscribe } from "../observables/config-event";
-import { Config as ConfigType } from "@monkeytype/schemas/configs";
-import { createStore } from "solid-js/store";
-import { getDefaultConfig } from "../constants/default-config";
-import * as Config from "../config";
-
-const [getConfig, setConfigStore] = createStore(getDefaultConfig());
-export { getConfig };
-
-let fullConfigChange = false;
-subscribe(({ key, newValue }) => {
- if (key === "fullConfigChange") {
- fullConfigChange = true;
- } else if (key === "fullConfigChangeFinished") {
- fullConfigChange = false;
- setConfigStore(Config.getConfig());
- } else if (fullConfigChange) {
- return;
- } else {
- setConfigStore(key, newValue);
- }
-});
diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts
index 8ce3ea569677..7d6c5b1d19a4 100644
--- a/frontend/src/ts/test/break-ligatures.ts
+++ b/frontend/src/ts/test/break-ligatures.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import { ElementWithUtils } from "../utils/dom";
function canBreak(wordEl: ElementWithUtils): boolean {
diff --git a/frontend/src/ts/test/british-english.ts b/frontend/src/ts/test/british-english.ts
index 3875d82e3bb8..35d0bf7e1dca 100644
--- a/frontend/src/ts/test/british-english.ts
+++ b/frontend/src/ts/test/british-english.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import britishEnglishReplacements from "../constants/british-english";
import { capitalizeFirstLetterOfEachWord } from "../utils/strings";
diff --git a/frontend/src/ts/test/caps-warning.ts b/frontend/src/ts/test/caps-warning.ts
index df67b4501b40..962fed5acc4f 100644
--- a/frontend/src/ts/test/caps-warning.ts
+++ b/frontend/src/ts/test/caps-warning.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import { qsr } from "../utils/dom";
import { onCapsLockChange } from "@leonabcd123/modern-caps-lock";
diff --git a/frontend/src/ts/test/caret.ts b/frontend/src/ts/test/caret.ts
index 0e48fb60531a..04a2fd7dc74a 100644
--- a/frontend/src/ts/test/caret.ts
+++ b/frontend/src/ts/test/caret.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import * as TestInput from "./test-input";
import * as TestState from "../test/test-state";
import { subscribe } from "../observables/config-event";
diff --git a/frontend/src/ts/test/funbox/funbox-functions.ts b/frontend/src/ts/test/funbox/funbox-functions.ts
index cd1a1d17d19b..db4927a82797 100644
--- a/frontend/src/ts/test/funbox/funbox-functions.ts
+++ b/frontend/src/ts/test/funbox/funbox-functions.ts
@@ -1,6 +1,7 @@
import { FunboxWordsFrequency, Wordset } from "../wordset";
import * as GetText from "../../utils/generate";
-import Config, { setConfig, toggleFunbox } from "../../config";
+import { Config } from "../../config/store";
+import { setConfig, toggleFunbox } from "../../config/setters";
import * as Misc from "../../utils/misc";
import * as Strings from "../../utils/strings";
import { randomIntFromRange } from "@monkeytype/util/numbers";
diff --git a/frontend/src/ts/test/funbox/funbox-memory.ts b/frontend/src/ts/test/funbox/funbox-memory.ts
index 5fc953eeb945..605fa49aab9f 100644
--- a/frontend/src/ts/test/funbox/funbox-memory.ts
+++ b/frontend/src/ts/test/funbox/funbox-memory.ts
@@ -1,6 +1,6 @@
import { Config, ConfigKey, ConfigValue } from "@monkeytype/schemas/configs";
-import { setConfig } from "../../config";
+import { setConfig } from "../../config/setters";
type SetFunction = (param: T, nosave?: boolean) => boolean;
diff --git a/frontend/src/ts/test/funbox/funbox.ts b/frontend/src/ts/test/funbox/funbox.ts
index e3443d0ce35d..716723ea3449 100644
--- a/frontend/src/ts/test/funbox/funbox.ts
+++ b/frontend/src/ts/test/funbox/funbox.ts
@@ -4,10 +4,11 @@ import {
} from "../../states/notifications";
import * as JSONData from "../../utils/json-data";
import * as Strings from "../../utils/strings";
-import Config, {
- setConfig,
+import { Config } from "../../config/store";
+import {
toggleFunbox as configToggleFunbox,
-} from "../../config";
+ setConfig,
+} from "../../config/setters";
import * as MemoryTimer from "./memory-funbox-timer";
import * as FunboxMemory from "./funbox-memory";
import { HighlightMode, FunboxName } from "@monkeytype/schemas/configs";
diff --git a/frontend/src/ts/test/funbox/list.ts b/frontend/src/ts/test/funbox/list.ts
index 88220d8badb8..6447d89eb3fc 100644
--- a/frontend/src/ts/test/funbox/list.ts
+++ b/frontend/src/ts/test/funbox/list.ts
@@ -1,4 +1,4 @@
-import Config from "../../config";
+import { Config } from "../../config/store";
import {
FunboxMetadata,
getFunboxObject,
diff --git a/frontend/src/ts/test/layout-emulator.ts b/frontend/src/ts/test/layout-emulator.ts
index 29449b0b8abc..4b8593780ae0 100644
--- a/frontend/src/ts/test/layout-emulator.ts
+++ b/frontend/src/ts/test/layout-emulator.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import * as JSONData from "../utils/json-data";
import { capsState } from "./caps-warning";
import { showErrorNotification } from "../states/notifications";
diff --git a/frontend/src/ts/test/live-acc.ts b/frontend/src/ts/test/live-acc.ts
index e780c94f5a76..cda6654997a2 100644
--- a/frontend/src/ts/test/live-acc.ts
+++ b/frontend/src/ts/test/live-acc.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import * as TestState from "../test/test-state";
import * as ConfigEvent from "../observables/config-event";
import { applyReducedMotion } from "../utils/misc";
diff --git a/frontend/src/ts/test/live-burst.ts b/frontend/src/ts/test/live-burst.ts
index 38170e179ece..3f1b6c308d64 100644
--- a/frontend/src/ts/test/live-burst.ts
+++ b/frontend/src/ts/test/live-burst.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import * as TestState from "../test/test-state";
import * as ConfigEvent from "../observables/config-event";
import Format from "../singletons/format";
diff --git a/frontend/src/ts/test/live-speed.ts b/frontend/src/ts/test/live-speed.ts
index fdc4d0cf027a..a35090a2beac 100644
--- a/frontend/src/ts/test/live-speed.ts
+++ b/frontend/src/ts/test/live-speed.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import * as TestState from "./test-state";
import * as ConfigEvent from "../observables/config-event";
import Format from "../singletons/format";
diff --git a/frontend/src/ts/test/monkey.ts b/frontend/src/ts/test/monkey.ts
index 713b8002f1ce..2299f2df752b 100644
--- a/frontend/src/ts/test/monkey.ts
+++ b/frontend/src/ts/test/monkey.ts
@@ -1,5 +1,5 @@
import { mapRange } from "@monkeytype/util/numbers";
-import Config from "../config";
+import { Config } from "../config/store";
import * as ConfigEvent from "../observables/config-event";
import * as TestState from "../test/test-state";
import * as KeyConverter from "../utils/key-converter";
diff --git a/frontend/src/ts/test/out-of-focus.ts b/frontend/src/ts/test/out-of-focus.ts
index 05d4ee05db5b..3a7e295bf16f 100644
--- a/frontend/src/ts/test/out-of-focus.ts
+++ b/frontend/src/ts/test/out-of-focus.ts
@@ -1,5 +1,5 @@
import * as Misc from "../utils/misc";
-import Config from "../config";
+import { Config } from "../config/store";
import { qs, qsa } from "../utils/dom";
const outOfFocusTimeouts: (number | NodeJS.Timeout)[] = [];
diff --git a/frontend/src/ts/test/pace-caret.ts b/frontend/src/ts/test/pace-caret.ts
index 0319f9a8e270..5dd45a69ba84 100644
--- a/frontend/src/ts/test/pace-caret.ts
+++ b/frontend/src/ts/test/pace-caret.ts
@@ -1,5 +1,5 @@
import * as TestWords from "./test-words";
-import Config from "../config";
+import { Config } from "../config/store";
import * as DB from "../db";
import * as Misc from "../utils/misc";
import * as TestState from "./test-state";
diff --git a/frontend/src/ts/test/practise-words.ts b/frontend/src/ts/test/practise-words.ts
index e4ddbb55b652..f84b178bfed7 100644
--- a/frontend/src/ts/test/practise-words.ts
+++ b/frontend/src/ts/test/practise-words.ts
@@ -1,6 +1,8 @@
import * as TestWords from "./test-words";
import { showNoticeNotification } from "../states/notifications";
-import Config, { setConfig } from "../config";
+
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as CustomText from "./custom-text";
import * as TestInput from "./test-input";
import * as ConfigEvent from "../observables/config-event";
diff --git a/frontend/src/ts/test/replay.ts b/frontend/src/ts/test/replay.ts
index b6dee822a5dc..05a19e649155 100644
--- a/frontend/src/ts/test/replay.ts
+++ b/frontend/src/ts/test/replay.ts
@@ -1,9 +1,8 @@
-import config from "../config";
import * as Sound from "../controllers/sound-controller";
import * as TestInput from "./test-input";
import * as Arrays from "../utils/arrays";
import { qs, qsr } from "../utils/dom";
-
+import { Config } from "../config/store";
type ReplayAction =
| "correctLetter"
| "incorrectLetter"
@@ -91,7 +90,7 @@ export function pauseReplay(): void {
function playSound(error = false): void {
if (error) {
- if (config.playSoundOnError !== "off") {
+ if (Config.playSoundOnError !== "off") {
void Sound.playError();
} else {
void Sound.playClick();
diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts
index 565e263c6d95..98d63f85f2eb 100644
--- a/frontend/src/ts/test/result.ts
+++ b/frontend/src/ts/test/result.ts
@@ -1,6 +1,8 @@
//TODO: use Format
import { Chart, type PluginChartOptions } from "chart.js";
-import Config, { setConfig } from "../config";
+
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as AdController from "../controllers/ad-controller";
import * as ChartController from "../controllers/chart-controller";
import QuotesController, { Quote } from "../controllers/quotes-controller";
diff --git a/frontend/src/ts/test/shift-tracker.ts b/frontend/src/ts/test/shift-tracker.ts
index 9e9120e21c37..cb803bf0532a 100644
--- a/frontend/src/ts/test/shift-tracker.ts
+++ b/frontend/src/ts/test/shift-tracker.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import { Keycode } from "../constants/keys";
import * as KeyConverter from "../utils/key-converter";
diff --git a/frontend/src/ts/test/test-config.ts b/frontend/src/ts/test/test-config.ts
index 391b96eca2e9..3a1ea2b6e679 100644
--- a/frontend/src/ts/test/test-config.ts
+++ b/frontend/src/ts/test/test-config.ts
@@ -1,6 +1,6 @@
import { ConfigValue, QuoteLength } from "@monkeytype/schemas/configs";
import { Mode } from "@monkeytype/schemas/shared";
-import Config from "../config";
+import { Config } from "../config/store";
import * as ConfigEvent from "../observables/config-event";
import { getActivePage } from "../states/core";
import { applyReducedMotion } from "../utils/misc";
diff --git a/frontend/src/ts/test/test-input.ts b/frontend/src/ts/test/test-input.ts
index 4d7a9373139b..8b18f17efd61 100644
--- a/frontend/src/ts/test/test-input.ts
+++ b/frontend/src/ts/test/test-input.ts
@@ -1,7 +1,7 @@
import { lastElementFromArray } from "../utils/arrays";
import { mean, roundTo2 } from "@monkeytype/util/numbers";
import * as TestState from "./test-state";
-import Config from "../config";
+import { Config } from "../config/store";
import { getInputElementValue } from "../input/input-element";
const keysToTrack = new Set([
diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts
index 294ac32ea28d..fc56ef4aa938 100644
--- a/frontend/src/ts/test/test-logic.ts
+++ b/frontend/src/ts/test/test-logic.ts
@@ -1,6 +1,5 @@
import Ape from "../ape";
import * as TestUI from "./test-ui";
-import Config, { setConfig, setQuoteLengthAll, toggleFunbox } from "../config";
import * as Strings from "../utils/strings";
import * as Misc from "../utils/misc";
import * as Arrays from "../utils/arrays";
@@ -72,7 +71,8 @@ import { debounce } from "throttle-debounce";
import * as Time from "../legacy-states/time";
import { qs } from "../utils/dom";
import { setAccountButtonSpinner } from "../states/header";
-
+import { Config } from "../config/store";
+import { setQuoteLengthAll, toggleFunbox, setConfig } from "../config/setters";
let failReason = "";
export async function syncNotSignedInLastResult(uid: string): Promise {
diff --git a/frontend/src/ts/test/test-stats.ts b/frontend/src/ts/test/test-stats.ts
index 558a197acfde..16078cf172c1 100644
--- a/frontend/src/ts/test/test-stats.ts
+++ b/frontend/src/ts/test/test-stats.ts
@@ -1,5 +1,5 @@
import Hangul from "hangul-js";
-import Config from "../config";
+import { Config } from "../config/store";
import * as Strings from "../utils/strings";
import * as TestInput from "./test-input";
import * as TestWords from "./test-words";
diff --git a/frontend/src/ts/test/test-timer.ts b/frontend/src/ts/test/test-timer.ts
index 612f48ab8d9b..1ce1314d1039 100644
--- a/frontend/src/ts/test/test-timer.ts
+++ b/frontend/src/ts/test/test-timer.ts
@@ -1,7 +1,8 @@
//most of the code is thanks to
//https://stackoverflow.com/questions/29971898/how-to-create-an-accurate-timer-in-javascript
-import Config, { setConfig } from "../config";
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as CustomText from "./custom-text";
import * as TimerProgress from "./timer-progress";
import * as LiveSpeed from "./live-speed";
diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts
index ae7034e234a3..ef58d4823ba1 100644
--- a/frontend/src/ts/test/test-ui.ts
+++ b/frontend/src/ts/test/test-ui.ts
@@ -2,7 +2,9 @@ import {
showNoticeNotification,
showErrorNotification,
} from "../states/notifications";
-import Config, { setConfig } from "../config";
+
+import { Config } from "../config/store";
+import { setConfig } from "../config/setters";
import * as TestWords from "./test-words";
import * as TestInput from "./test-input";
import * as CustomText from "./custom-text";
diff --git a/frontend/src/ts/test/timer-progress.ts b/frontend/src/ts/test/timer-progress.ts
index 1a53a710d267..90f4ce76840e 100644
--- a/frontend/src/ts/test/timer-progress.ts
+++ b/frontend/src/ts/test/timer-progress.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import * as CustomText from "./custom-text";
import * as DateTime from "../utils/date-and-time";
import * as TestWords from "./test-words";
diff --git a/frontend/src/ts/test/tts.ts b/frontend/src/ts/test/tts.ts
index ab62b05954ea..a32d61947ab6 100644
--- a/frontend/src/ts/test/tts.ts
+++ b/frontend/src/ts/test/tts.ts
@@ -1,4 +1,4 @@
-import Config from "../config";
+import { Config } from "../config/store";
import * as JSONData from "../utils/json-data";
import * as ConfigEvent from "../observables/config-event";
import * as TTSEvent from "../observables/tts-event";
diff --git a/frontend/src/ts/test/words-generator.ts b/frontend/src/ts/test/words-generator.ts
index 88d175db9e20..4302df510bfe 100644
--- a/frontend/src/ts/test/words-generator.ts
+++ b/frontend/src/ts/test/words-generator.ts
@@ -1,4 +1,5 @@
-import Config, { setConfig, setQuoteLengthAll, toggleFunbox } from "../config";
+import { Config } from "../config/store";
+import { setConfig, setQuoteLengthAll, toggleFunbox } from "../config/setters";
import * as CustomText from "./custom-text";
import { Wordset, FunboxWordsFrequency, withWords } from "./wordset";
import QuotesController, {
diff --git a/frontend/src/ts/ui.ts b/frontend/src/ts/ui.ts
index 689b910a0369..73991df8112b 100644
--- a/frontend/src/ts/ui.ts
+++ b/frontend/src/ts/ui.ts
@@ -1,4 +1,4 @@
-import Config from "./config";
+import { Config } from "./config/store";
import * as Caret from "./test/caret";
import * as CustomText from "./test/custom-text";
import * as TestState from "./test/test-state";