Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
658390a
impr: enable dots typed effect for ligature languages (@byseif21) (#7…
byseif21 Feb 28, 2026
8056eb5
feat(layout): add vitrimak layout (@aoieop) (#7488)
aoieop Feb 28, 2026
abf708f
docs: update formatter info to oxc (@vinayaksodar) (#7492)
vinayaksodar Feb 28, 2026
baad1b8
chore: typo in parameter name (@Leonabcd123) (#7499)
Leonabcd123 Feb 28, 2026
a5af40c
fix(test-config): some layout and state issues (@byseif21) (#7500)
byseif21 Feb 28, 2026
39d522a
fix(presets): spaces not being replaced when editing preset with unde…
Leonabcd123 Feb 28, 2026
a6c1e6b
impr: add details to speed histogram (@fehmer) (#7503)
fehmer Feb 28, 2026
427e9de
fix(account): filter buttons not working (@fehmer) (#7505)
fehmer Feb 28, 2026
763b600
impr: remove connection guards (@fehmer) (#7508)
fehmer Feb 28, 2026
17674eb
chore: do some todos (@Leonabcd123) (#7502)
Leonabcd123 Feb 28, 2026
0f3bc7a
fix(theme): name still visible after test start in some themes (@Leon…
Leonabcd123 Feb 28, 2026
751b715
fix(settings): quick nav fails to open hide elements & danger zone (@…
byseif21 Feb 28, 2026
fb700f5
impr(quotes): add two new English quotes (@damarpas) (#7514)
damarpas Feb 28, 2026
2eda3e1
impr(sound): all sounds normalized to -10dB (@razorree) (#7515)
razorree Feb 28, 2026
3fe1fdf
fix(test): prioritize "too short" over AFK detection for very short t…
openvaibhav Feb 28, 2026
9cd1275
feat(layout): add miligram layout (@kazeno-uta) (#7523)
kazeno-uta Feb 28, 2026
0bf2ba3
refactor: convert to hide and show (@Leonabcd123) (#7524)
Leonabcd123 Feb 28, 2026
ff3b58b
impr(languages): expand kinyarwanda word list (@cedrick13bienvenue) (…
cedrick13bienvenue Feb 28, 2026
3bda4e5
impr(quotes): add esperanto quotes (@norwd) (#7531)
norwd Feb 28, 2026
29b2a09
impr(quotes): add Java quotes (@K87lk) (#7533)
K87lk Feb 28, 2026
a0551e9
impr(quotes): add minimum quote length check (@Leonabcd123) (#7534)
Leonabcd123 Feb 28, 2026
c9e6f71
feat: Add 1,000 Irish word list (@aindriu80) (#7535)
aindriu80 Feb 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions backend/src/utils/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@ type FirebaseErrorParent = {
errorInfo: FirebaseError;
};

// oxlint-disable-next-line no-explicit-any
export function isFirebaseError(err: any): err is FirebaseErrorParent {
export function isFirebaseError(err: unknown): err is FirebaseErrorParent {
return (
err !== null &&
typeof err === "object" &&
"code" in err &&
"errorInfo" in err &&
"codePrefix" in err &&
// oxlint-disable-next-line no-unsafe-member-access
typeof err.errorInfo === "object" &&
// oxlint-disable-next-line no-unsafe-member-access
err.errorInfo !== null &&
"code" in err.errorInfo &&
// oxlint-disable-next-line no-unsafe-member-access
"message" in err.errorInfo
);
}
Expand Down
2 changes: 1 addition & 1 deletion docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

## Getting Started

When contributing to Monkeytype, it's good to know our best practices, tips, and tricks. First, Monkeytype is written in ~~JavaScript~~ TypeScript, HTML, and CSS (in order of language usage within the project); thus, we assume you are comfortable with these languages or have basic knowledge of them. Our backend is in NodeJS and we use MongoDB to store our user data. Firebase is used for authentication. Redis is used to store ephemeral data (daily leaderboards, jobs via BullMQ, OAuth state parameters). Furthermore, we use Prettier to format our code.
When contributing to Monkeytype, it's good to know our best practices, tips, and tricks. First, Monkeytype is written in ~~JavaScript~~ TypeScript, HTML, and CSS (in order of language usage within the project); thus, we assume you are comfortable with these languages or have basic knowledge of them. Our backend is in NodeJS and we use MongoDB to store our user data. Firebase is used for authentication. Redis is used to store ephemeral data (daily leaderboards, jobs via BullMQ, OAuth state parameters). Furthermore, we use Oxc (Oxfmt and Oxlint) to format and lint our code.

## How to Contribute

Expand Down
2 changes: 1 addition & 1 deletion docs/CONTRIBUTING_ADVANCED.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ If you are on a UNIX system and you get a spawn error, run npm with `sudo`.

## Standards and Guidelines

Code formatting is enforced by [Prettier](https://prettier.io/docs/en/install.html), which automatically runs every time you make a commit.
Code formatting and linting is enforced by [Oxc (Oxfmt and Oxlint)](https://github.com/oxc-project/oxc), which automatically runs every time you make a commit.

For guidelines on commit messages, adding themes, languages, or quotes, please refer to [CONTRIBUTING.md](./CONTRIBUTING.md). Following these guidelines will increase the chances of getting your change accepted.

Expand Down
19 changes: 13 additions & 6 deletions frontend/scripts/check-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ async function validateQuotes(): Promise<void> {
//check schema
const schema = QuoteDataSchema.extend({
language: LanguageSchema
//icelandic only exists as icelandic_1k, language in quote file is stipped of its size
//icelandic only exists as icelandic_1k, language in quote file is stripped of its size
.or(z.literal("icelandic")),
});
problems.addValidation(quotefilename, schema.safeParse(quoteData));
Expand All @@ -198,14 +198,21 @@ async function validateQuotes(): Promise<void> {
}

//check quote length
quoteData.quotes
.filter((quote) => quote.text.length !== quote.length)
.forEach((quote) =>
quoteData.quotes.forEach((quote) => {
if (quote.text.length !== quote.length) {
problems.add(
quotefilename,
`ID ${quote.id}: expected length ${quote.text.length}`,
),
);
);
}

if (quote.text.length < 60) {
problems.add(
quotefilename,
`ID ${quote.id}: length too short (under 60 characters)`,
);
}
});

//check groups
let last = -1;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/styles/nav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ header {

.textButton,
button {
color: transparent;
color: transparent !important;
}
.avatar,
.levelAndBar {
Expand Down
78 changes: 51 additions & 27 deletions frontend/src/styles/test.scss
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@
&.blind {
.word {
& letter.extra {
display: none;
display: none !important;
}
& letter.incorrect {
color: var(--correct-letter-color);
Expand Down Expand Up @@ -533,45 +533,69 @@
}
}

&.typed-effect-dots:not(.withLigatures) {
&.typed-effect-dots {
/* transform already typed letters into appropriately colored dots */

.word letter {
position: relative;
&::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 1em;
aspect-ratio: 1;
border-radius: 50%;
opacity: 0;
&:not(.withLigatures) .word,
&.withLigatures .word.broken-ligatures {
letter {
position: relative;
display: inline-block;
&::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 1em;
aspect-ratio: 1;
border-radius: 50%;
opacity: 0;
}
}
}
.typed letter {
color: var(--bg-color);
animation: typedEffectToDust 200ms ease-out 0ms 1 forwards !important;
&::after {
animation: typedEffectFadeIn 100ms ease-in 100ms 1 forwards;
background: var(--c-dot);
// unify dot spacing
&.withLigatures .word.broken-ligatures {
letter {
width: 0.4em;
}
}
.word.broken-ligatures:not(.needs-wrap) {
white-space: nowrap;
}

&:not(.withLigatures) .word.typed,
&.withLigatures .word.broken-ligatures.typed {
letter {
color: var(--bg-color);
animation: typedEffectToDust 200ms ease-out 0ms 1 forwards !important;
&::after {
animation: typedEffectFadeIn 100ms ease-in 100ms 1 forwards;
background: var(--c-dot);
}
}
}
&:not(.blind) {

&:not(.withLigatures):not(.blind) {
.word letter.incorrect::after {
background: var(--c-dot--error);
}
}
&.withLigatures:not(.blind) .word.broken-ligatures letter.incorrect::after {
background: var(--c-dot--error);
}

@media (prefers-reduced-motion) {
.typed letter {
animation: none !important;
transform: scale(0.4);
color: transparent;
&::after {
&:not(.withLigatures) .word.typed,
&.withLigatures .word.broken-ligatures.typed {
letter {
animation: none !important;
opacity: 1;
transform: scale(0.4);
color: transparent;
&::after {
animation: none !important;
opacity: 1;
}
}
}
}
Expand Down
7 changes: 0 additions & 7 deletions frontend/src/ts/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { showPopup } from "./modals/simple-modals-base";
import * as AuthEvent from "./observables/auth-event";
import * as Sentry from "./sentry";
import { showLoaderBar, hideLoaderBar } from "./signals/loader-bar";
import * as ConnectionState from "./states/connection";
import { addBanner } from "./stores/banners";
import * as Misc from "./utils/misc";

Expand Down Expand Up @@ -244,12 +243,6 @@ async function addAuthProvider(
providerName: string,
provider: AuthProvider,
): Promise<void> {
if (!ConnectionState.get()) {
Notifications.add("You are offline", 0, {
duration: 2,
});
return;
}
if (!isAuthAvailable()) {
Notifications.add("Authentication uninitialized", -1, {
duration: 3,
Expand Down
14 changes: 6 additions & 8 deletions frontend/src/ts/commandline/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "./commandline-metadata";
import { Command } from "./types";
import * as ConfigSchemas from "@monkeytype/schemas/configs";
import { z, ZodSchema } from "zod";
import { z, ZodSchema, ZodFirstPartySchemaTypes } from "zod";

function getOptions<T extends ZodSchema>(schema: T): undefined | z.infer<T>[] {
if (schema instanceof z.ZodLiteral) {
Expand All @@ -28,7 +28,6 @@ function getOptions<T extends ZodSchema>(schema: T): undefined | z.infer<T>[] {
}

export function buildCommandForConfigKey<
// oxlint-disable-next-line no-unnecessary-type-parameters
K extends keyof CommandlineConfigMetadataObject,
>(key: K): Command {
const configMeta = configMetadata[key];
Expand Down Expand Up @@ -69,7 +68,7 @@ function _buildCommandForConfigKey<

const inputCommand = buildInputCommand({
key: "secondKey" in inputProps ? inputProps.secondKey : key,
isPartOfSubgruop: "subgroup" in commandMeta,
isPartOfSubgroup: "subgroup" in commandMeta,
inputProps: inputProps as InputProps<keyof ConfigSchemas.Config>,
configMeta: configMeta as unknown as ConfigMetadata<
keyof ConfigSchemas.Config
Expand Down Expand Up @@ -120,8 +119,7 @@ function buildCommandWithSubgroup<K extends keyof ConfigSchemas.Config>(

if (values === undefined) {
throw new Error(
//@ts-expect-error todo
`Unsupported schema type for key "${key}": ${schema._def.typeName}`,
`Unsupported schema type for key "${key}": ${(schema as ZodFirstPartySchemaTypes)._def.typeName}`,
);
}
const list = values.map((value) =>
Expand Down Expand Up @@ -201,13 +199,13 @@ function buildSubgroupCommand<K extends keyof ConfigSchemas.Config>(

function buildInputCommand<K extends keyof ConfigSchemas.Config>({
key,
isPartOfSubgruop,
isPartOfSubgroup,
inputProps,
configMeta,
schema,
}: {
key: K;
isPartOfSubgruop: boolean;
isPartOfSubgroup: boolean;
inputProps?: InputProps<K>;
configMeta: ConfigMetadata<K>;
schema?: ZodSchema;
Expand All @@ -216,7 +214,7 @@ function buildInputCommand<K extends keyof ConfigSchemas.Config>({

const displayString =
inputProps?.display ??
(isPartOfSubgruop
(isPartOfSubgroup
? "custom..."
: `${capitalizeFirstLetter(configMeta.displayString ?? key)}...`);

Expand Down
8 changes: 4 additions & 4 deletions frontend/src/ts/components/common/AnimatedModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function AnimatedModal(props: AnimatedModalProps): JSXElement {
await props.beforeShow?.();

// Open the dialog
dialogEl()?.removeClass("hidden");
dialogEl()?.show();
if (props.mode === "dialog") {
dialogEl()?.native.show();
} else {
Expand Down Expand Up @@ -188,13 +188,13 @@ export function AnimatedModal(props: AnimatedModalProps): JSXElement {
duration: wrapperDuration,
onComplete: async () => {
dialogEl()?.native.close();
dialogEl()?.addClass("hidden");
dialogEl()?.hide();
await handleAfterHide();
},
});
} else {
dialogEl()?.native.close();
dialogEl()?.addClass("hidden");
dialogEl()?.hide();
await handleAfterHide();
}
} else if (animMode === "modalOnly") {
Expand All @@ -204,7 +204,7 @@ export function AnimatedModal(props: AnimatedModalProps): JSXElement {
duration: modalAnimDuration,
onComplete: async () => {
dialogEl()?.native.close();
dialogEl()?.addClass("hidden");
dialogEl()?.hide();
await handleAfterHide();
},
});
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/ts/components/common/ChartJs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ export function ChartJs<T extends ChartType, TData = DefaultDataPoint<T>>(
let chart: ChartWithUpdateColors<T, TData> | undefined;

onMount(() => {
//oxlint-disable-next-line no-non-null-assertion
chart = new ChartWithUpdateColors(canvasEl()!.native, {
const canvas = canvasEl();
if (canvas === undefined) return;
chart = new ChartWithUpdateColors(canvas.native, {
type: props.type,
data: props.data,
options: props.options,
Expand Down
Loading
Loading