From e019e7900cb5da78b69f95d438ab6bb69b41e7f0 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 17:20:41 +0100
Subject: [PATCH 01/17] lint
---
client/src/Navigation.tsx | 90 +++++++++++++++++------------
client/src/Popups/Impressum.tsx | 8 +--
client/src/Popups/LoadUrl.tsx | 10 +++-
client/src/Popups/LoadZulip.tsx | 10 +++-
client/src/Popups/PrivacyPolicy.tsx | 8 +--
client/src/Popups/Tools.tsx | 18 +++---
6 files changed, 82 insertions(+), 62 deletions(-)
diff --git a/client/src/Navigation.tsx b/client/src/Navigation.tsx
index d9dc59bd..bfdc252b 100644
--- a/client/src/Navigation.tsx
+++ b/client/src/Navigation.tsx
@@ -45,13 +45,19 @@ import { useAtom } from 'jotai';
import { SettingsPopup } from './settings/SettingsPopup';
/** A button to appear in the hamburger menu or to navigation bar. */
-export const NavButton: FC<{
+export function NavButton({
+ icon,
+ iconElement,
+ text,
+ onClick = () => {},
+ href = undefined,
+}: {
icon?: IconDefinition;
iconElement?: JSX.Element;
text: string;
onClick?: MouseEventHandler;
href?: string;
-}> = ({ icon, iconElement, text, onClick = () => {}, href = null }) => {
+}) {
// note: it seems that we can just leave the `target="_blank"` and it has no
// effect on links without a `href`. If not, add `if (href)` statement here...
return (
@@ -59,10 +65,18 @@ export const NavButton: FC<{
{iconElement ?? } {text}
);
-};
+}
/** A button to appear in the hamburger menu or to navigation bar. */
-export const Dropdown: FC<{
+export function Dropdown({
+ open,
+ setOpen,
+ icon,
+ text,
+ useOverlay = false,
+ onClick,
+ children,
+}: {
open: boolean;
setOpen: Dispatch>;
icon?: IconDefinition;
@@ -70,7 +84,7 @@ export const Dropdown: FC<{
useOverlay?: boolean;
onClick?: MouseEventHandler;
children?: ReactNode;
-}> = ({ open, setOpen, icon, text, useOverlay = false, onClick, children }) => {
+}) {
return (
<>
@@ -100,14 +114,18 @@ export const Dropdown: FC<{
)}
>
);
-};
+}
/** A popup which overlays the entire screen. */
-export const Popup: FC<{
+export function Popup({
+ open,
+ handleClose,
+ children,
+}: {
open: boolean;
handleClose: () => void; // TODO: what's the correct type?
children?: ReactNode;
-}> = ({ open, handleClose, children }) => {
+}) {
return (
@@ -117,21 +135,10 @@ export const Popup: FC<{
);
-};
+}
/** The menu items either appearing inside the dropdown or outside */
-const FlexibleMenu: FC<{
- isInDropdown: boolean;
- setOpenNav: Dispatch>;
- openExample: boolean;
- setOpenExample: Dispatch>;
- openLoad: boolean;
- setOpenLoad: Dispatch>;
- loadFromUrl: (url: string, project?: string | undefined) => void;
- setContent: (code: string) => void;
- setLoadUrlOpen: Dispatch>;
- setLoadZulipOpen: Dispatch>;
-}> = ({
+function FlexibleMenu({
isInDropdown = false,
setOpenNav,
openExample,
@@ -142,7 +149,18 @@ const FlexibleMenu: FC<{
setContent,
setLoadUrlOpen,
setLoadZulipOpen,
-}) => {
+}: {
+ isInDropdown: boolean;
+ setOpenNav: Dispatch>;
+ openExample: boolean;
+ setOpenExample: Dispatch>;
+ openLoad: boolean;
+ setOpenLoad: Dispatch>;
+ loadFromUrl: (url: string, project?: string | undefined) => void;
+ setContent: (code: string) => void;
+ setLoadUrlOpen: Dispatch>;
+ setLoadZulipOpen: Dispatch>;
+}) {
const loadFileFromDisk = (event: ChangeEvent) => {
console.debug('Loading file from disk');
const fileToLoad = event.target.files![0];
@@ -225,20 +243,10 @@ const FlexibleMenu: FC<{
>
);
-};
+}
/** The Navigation menu */
-export const Menu: FC<{
- code: string;
- setContent: (code: string) => void;
- project: string;
- setProject: Dispatch>;
- setUrl: Dispatch>;
- codeFromUrl: string;
- restart?: () => void;
- codeMirror: boolean;
- setCodeMirror: Dispatch>;
-}> = ({
+export function Menu({
code,
setContent,
project,
@@ -248,7 +256,17 @@ export const Menu: FC<{
restart,
codeMirror,
setCodeMirror,
-}) => {
+}: {
+ code: string;
+ setContent: (code: string) => void;
+ project: string;
+ setProject: Dispatch>;
+ setUrl: Dispatch>;
+ codeFromUrl: string;
+ restart?: () => void;
+ codeMirror: boolean;
+ setCodeMirror: Dispatch>;
+}) {
// state for handling the dropdown menus
const [openNav, setOpenNav] = useState(false);
const [openExample, setOpenExample] = useState(false);
@@ -408,4 +426,4 @@ export const Menu: FC<{
/>
);
-};
+}
diff --git a/client/src/Popups/Impressum.tsx b/client/src/Popups/Impressum.tsx
index 00203ed9..e5db76f8 100644
--- a/client/src/Popups/Impressum.tsx
+++ b/client/src/Popups/Impressum.tsx
@@ -1,12 +1,8 @@
-import { FC } from 'react';
import { Popup } from '../Navigation';
import lean4webConfig from '../config/config';
/** The popup with the privacy policy. */
-const ImpressumPopup: FC<{
- open: boolean;
- handleClose: () => void;
-}> = ({ open, handleClose }) => {
+function ImpressumPopup({ open, handleClose }: { open: boolean; handleClose: () => void }) {
return (
Impressum
@@ -22,6 +18,6 @@ const ImpressumPopup: FC<{
{lean4webConfig.impressum}
);
-};
+}
export default ImpressumPopup;
diff --git a/client/src/Popups/LoadUrl.tsx b/client/src/Popups/LoadUrl.tsx
index e9c70d6b..e16ac111 100644
--- a/client/src/Popups/LoadUrl.tsx
+++ b/client/src/Popups/LoadUrl.tsx
@@ -1,11 +1,15 @@
import { Popup } from '../Navigation';
import { FC, FormEvent, useRef, useState } from 'react';
-const LoadUrlPopup: FC<{
+function LoadUrlPopup({
+ open,
+ handleClose,
+ loadFromUrl,
+}: {
open: boolean;
handleClose: () => void;
loadFromUrl: (url: string) => void;
-}> = ({ open, handleClose, loadFromUrl }) => {
+}) {
const [error, setError] = useState('');
const urlRef = useRef(null);
@@ -33,6 +37,6 @@ const LoadUrlPopup: FC<{
);
-};
+}
export default LoadUrlPopup;
diff --git a/client/src/Popups/LoadZulip.tsx b/client/src/Popups/LoadZulip.tsx
index 471c5c3e..1a78c02c 100644
--- a/client/src/Popups/LoadZulip.tsx
+++ b/client/src/Popups/LoadZulip.tsx
@@ -1,11 +1,15 @@
import { Popup } from '../Navigation';
import { FC, FormEvent, useRef, useState } from 'react';
-const LoadZulipPopup: FC<{
+function LoadZulipPopup({
+ open,
+ handleClose,
+ setContent,
+}: {
open: boolean;
handleClose: () => void;
setContent: (code: string) => void;
-}> = ({ open, handleClose, setContent }) => {
+}) {
const [error, setError] = useState('');
const textInputRef = useRef(null);
@@ -57,6 +61,6 @@ const LoadZulipPopup: FC<{
);
-};
+}
export default LoadZulipPopup;
diff --git a/client/src/Popups/PrivacyPolicy.tsx b/client/src/Popups/PrivacyPolicy.tsx
index 95573766..a6ce2183 100644
--- a/client/src/Popups/PrivacyPolicy.tsx
+++ b/client/src/Popups/PrivacyPolicy.tsx
@@ -1,12 +1,8 @@
-import { FC } from 'react';
import { Popup } from '../Navigation';
import lean4webConfig from '../config/config';
/** The popup with the privacy policy. */
-const PrivacyPopup: FC<{
- open: boolean;
- handleClose: () => void;
-}> = ({ open, handleClose }) => {
+function PrivacyPopup({ open, handleClose }: { open: boolean; handleClose: () => void }) {
return (
Privacy Policy
@@ -35,6 +31,6 @@ const PrivacyPopup: FC<{
)}
);
-};
+}
export default PrivacyPopup;
diff --git a/client/src/Popups/Tools.tsx b/client/src/Popups/Tools.tsx
index ff5b4cf3..a8b85bbc 100644
--- a/client/src/Popups/Tools.tsx
+++ b/client/src/Popups/Tools.tsx
@@ -1,5 +1,5 @@
import { Popup } from '../Navigation';
-import { FC, useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
// TODO: Do these interfaces exist somewhere in vscode-lean4?
// They might need to be updated manually if changes to `lake` occur.
@@ -68,9 +68,7 @@ interface CommitInfo {
* Note that github has a rate limit (60 requests/h), but since this should be a
* rarely used feature, it might be fine for now.
*/
-const ToolTip: FC<{
- pkg: LakePackage;
-}> = ({ pkg }) => {
+function ToolTip({ pkg }: { pkg: LakePackage }) {
const [loaded, setLoaded] = useState(false);
const linkRef = useRef(null);
const [commit, setCommit] = useState();
@@ -152,14 +150,18 @@ const ToolTip: FC<{
);
-};
+}
/** Shows important information about the Lean project loaded in the web editor */
-const ToolsPopup: FC<{
+function ToolsPopup({
+ open,
+ handleClose,
+ project,
+}: {
open: boolean;
project: string;
handleClose: () => void;
-}> = ({ open, handleClose, project }) => {
+}) {
const [manifest, setManifest] = useState(emptyManifest);
const [toolchain, setToolchain] = useState('');
// The last time `lake-manifest.json` has been modified
@@ -260,6 +262,6 @@ const ToolsPopup: FC<{
);
-};
+}
export default ToolsPopup;
From caaa570d78a1e09b493889cc99ac70191a62b26a Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:09:28 +0100
Subject: [PATCH 02/17] eslint
---
.eslintrc.cjs | 18 -
.vscode/settings.json | 15 +-
client/src/.prettierrc.json | 2 +-
client/src/App.tsx | 274 +-
client/src/Navigation.tsx | 272 +-
client/src/Popups/Impressum.tsx | 8 +-
client/src/Popups/LoadUrl.tsx | 35 +-
client/src/Popups/LoadZulip.tsx | 43 +-
client/src/Popups/PrivacyPolicy.tsx | 8 +-
client/src/Popups/Tools.tsx | 179 +-
client/src/config/config.tsx | 6 +-
client/src/config/docs.tsx | 22 +-
client/src/index.tsx | 12 +-
client/src/navigation/Dropdown.tsx | 53 +
client/src/navigation/NavButton.tsx | 26 +
client/src/settings/SettingsPopup.tsx | 74 +-
client/src/settings/settings-atoms.ts | 63 +-
client/src/settings/settings-types.ts | 32 +-
.../src/settings/settings-url-converters.ts | 46 +-
client/src/store/location-atoms.ts | 4 +-
client/src/store/window-atoms.ts | 4 +-
client/src/utils/Entries.ts | 4 +-
client/src/utils/SaveToFile.tsx | 8 +-
client/src/utils/UrlParsing.tsx | 28 +-
client/src/utils/WindowWidth.tsx | 18 +-
client/src/utils/cleanObject.ts | 2 +-
client/src/utils/shallowEqual.ts | 4 +-
client/src/vite-env.d.ts | 10 +-
eslint.config.js | 61 +
package-lock.json | 2252 ++++++++++++++++-
package.json | 16 +-
tsconfig.json | 2 +-
vite.config.ts | 13 +-
33 files changed, 2883 insertions(+), 731 deletions(-)
delete mode 100644 .eslintrc.cjs
create mode 100644 client/src/navigation/Dropdown.tsx
create mode 100644 client/src/navigation/NavButton.tsx
create mode 100644 eslint.config.js
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
deleted file mode 100644
index 6e8698b7..00000000
--- a/.eslintrc.cjs
+++ /dev/null
@@ -1,18 +0,0 @@
-module.exports = {
- root: true,
- env: { browser: true, es2020: true },
- extends: [
- "eslint:recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:react-hooks/recommended",
- ],
- ignorePatterns: ["dist", ".eslintrc.cjs"],
- parser: "@typescript-eslint/parser",
- plugins: ["react-refresh"],
- rules: {
- "react-refresh/only-export-components": [
- "warn",
- { allowConstantExport: true },
- ],
- },
-};
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d1b4edb2..5dcf9686 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,15 @@
{
"editor.formatOnSave": true,
- "editor.defaultFormatter": "esbenp.prettier-vscode"
-}
\ No newline at end of file
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "eslint.enable": true,
+ "eslint.validate": [
+ "javascript",
+ "javascriptreact",
+ "typescript",
+ "typescriptreact"
+ ],
+ "eslint.workingDirectories": [{ "mode": "auto" }],
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ }
+}
diff --git a/client/src/.prettierrc.json b/client/src/.prettierrc.json
index 70ae7996..7ef6ab72 100644
--- a/client/src/.prettierrc.json
+++ b/client/src/.prettierrc.json
@@ -2,7 +2,7 @@
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
- "semi": true,
+ "semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 01bcd32f..f68587b2 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -1,107 +1,109 @@
-import { useCallback, useEffect, useRef, useState } from 'react';
-import Split from 'react-split';
-import * as monaco from 'monaco-editor';
-import CodeMirror, { EditorView } from '@uiw/react-codemirror';
-import { LeanMonaco, LeanMonacoEditor, LeanMonacoOptions } from 'lean4monaco';
-import LZString from 'lz-string';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faCode } from '@fortawesome/free-solid-svg-icons';
-import * as path from 'path';
-import { mobileAtom, settingsAtom, settingsUrlAtom } from './settings/settings-atoms';
-import { useAtom } from 'jotai/react';
-import { screenWidthAtom } from './store/window-atoms';
-import LeanLogo from './assets/logo.svg';
-import { lightThemes } from './settings/settings-types';
-import { Menu } from './Navigation';
-import { save } from './utils/SaveToFile';
-import { fixedEncodeURIComponent, formatArgs, lookupUrl, parseArgs } from './utils/UrlParsing';
-import './css/App.css';
-import './css/Editor.css';
+import './css/App.css'
+import './css/Editor.css'
+
+import { faCode } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import CodeMirror, { EditorView } from '@uiw/react-codemirror'
+import { useAtom } from 'jotai/react'
+import { LeanMonaco, LeanMonacoEditor, LeanMonacoOptions } from 'lean4monaco'
+import LZString from 'lz-string'
+import * as monaco from 'monaco-editor'
+import * as path from 'path'
+import { useCallback, useEffect, useRef, useState } from 'react'
+import Split from 'react-split'
+
+import LeanLogo from './assets/logo.svg'
+import { Menu } from './Navigation'
+import { mobileAtom, settingsAtom, settingsUrlAtom } from './settings/settings-atoms'
+import { lightThemes } from './settings/settings-types'
+import { screenWidthAtom } from './store/window-atoms'
+import { save } from './utils/SaveToFile'
+import { fixedEncodeURIComponent, formatArgs, lookupUrl, parseArgs } from './utils/UrlParsing'
/** Returns true if the browser wants dark mode */
function isBrowserDefaultDark() {
- return window.matchMedia('(prefers-color-scheme: dark)').matches;
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
}
function App() {
- const editorRef = useRef(null);
- const infoviewRef = useRef(null);
- const [dragging, setDragging] = useState(false);
- const [editor, setEditor] = useState();
- const [leanMonaco, setLeanMonaco] = useState();
- const [settings] = useAtom(settingsAtom);
- const [mobile] = useAtom(mobileAtom);
- const [, setScreenWidth] = useAtom(screenWidthAtom);
- const [searchParams] = useAtom(settingsUrlAtom);
+ const editorRef = useRef(null)
+ const infoviewRef = useRef(null)
+ const [dragging, setDragging] = useState(false)
+ const [editor, setEditor] = useState()
+ const [leanMonaco, setLeanMonaco] = useState()
+ const [settings] = useAtom(settingsAtom)
+ const [mobile] = useAtom(mobileAtom)
+ const [, setScreenWidth] = useAtom(screenWidthAtom)
+ const [searchParams] = useAtom(settingsUrlAtom)
// Lean4monaco options
const [options, setOptions] = useState({
// placeholder, updated below
websocket: { url: '' },
- });
+ })
// Because of Monaco's missing mobile support we add a codeMirror editor
// which can be enabled to do editing.
// TODO: It would be nice to integrate Lean into CodeMirror better.
// first step could be to pass the cursor selection to the underlying monaco editor
- const [codeMirror, setCodeMirror] = useState(false);
+ const [codeMirror, setCodeMirror] = useState(false)
// the user data
- const [code, setCode] = useState('');
- const [project, setProject] = useState('MathlibDemo');
- const [url, setUrl] = useState(null);
- const [codeFromUrl, setCodeFromUrl] = useState('');
+ const [code, setCode] = useState('')
+ const [project, setProject] = useState('MathlibDemo')
+ const [url, setUrl] = useState(null)
+ const [codeFromUrl, setCodeFromUrl] = useState('')
/** Monaco editor requires the code to be set manually. */
function setContent(code: string) {
- editor?.getModel()?.setValue(code);
- setCode(code);
+ editor?.getModel()?.setValue(code)
+ setCode(code)
}
// Read the URL arguments
useEffect(() => {
// Parse args
- console.log('[Lean4web] Parsing URL');
- let args = parseArgs();
+ console.log('[Lean4web] Parsing URL')
+ let args = parseArgs()
if (args.code) {
- let _code = decodeURIComponent(args.code);
- setContent(_code);
+ let _code = decodeURIComponent(args.code)
+ setContent(_code)
} else if (args.codez) {
- let _code = LZString.decompressFromBase64(args.codez);
- setContent(_code);
+ let _code = LZString.decompressFromBase64(args.codez)
+ setContent(_code)
}
if (args.url) {
- setUrl(lookupUrl(decodeURIComponent(args.url)));
+ setUrl(lookupUrl(decodeURIComponent(args.url)))
}
// if no project provided, use default
- let project = args.project ?? 'MathlibDemo';
+ let project = args.project ?? 'MathlibDemo'
- console.log(`[Lean4web] Setting project to ${project}`);
- setProject(project);
- }, []);
+ console.log(`[Lean4web] Setting project to ${project}`)
+ setProject(project)
+ }, [setContent])
// save the screen width in jotai
useEffect(() => {
- const handleResize = () => setScreenWidth(window.innerWidth);
- window.addEventListener('resize', handleResize);
- return () => window.removeEventListener('resize', handleResize);
- }, [setScreenWidth]);
+ const handleResize = () => setScreenWidth(window.innerWidth)
+ window.addEventListener('resize', handleResize)
+ return () => window.removeEventListener('resize', handleResize)
+ }, [setScreenWidth])
// Update LeanMonaco options when preferences are loaded or change
useEffect(() => {
if (!project) {
- return;
+ return
}
- console.log('[Lean4web] Update lean4monaco options');
+ console.log('[Lean4web] Update lean4monaco options')
var socketUrl =
(window.location.protocol === 'https:' ? 'wss://' : 'ws://') +
window.location.host +
'/websocket/' +
- project;
- console.log(`[Lean4web] Socket url is ${socketUrl}`);
+ project
+ console.log(`[Lean4web] Socket url is ${socketUrl}`)
var _options: LeanMonacoOptions = {
websocket: { url: socketUrl },
// Restrict monaco's extend (e.g. context menu) to the editor itself
@@ -126,28 +128,28 @@ function App() {
'lean4.infoview.showTooltipOnHover': false,
'lean4.input.leader': settings.abbreviationCharacter,
},
- };
- setOptions(_options);
- }, [editorRef, project, settings]);
+ }
+ setOptions(_options)
+ }, [editorRef, project, settings])
// Setting up the editor and infoview
useEffect(() => {
- if (project === undefined) return;
- console.debug('[Lean4web] Restarting editor');
- var _leanMonaco = new LeanMonaco();
- var leanMonacoEditor = new LeanMonacoEditor();
-
- _leanMonaco.setInfoviewElement(infoviewRef.current!);
- (async () => {
- await _leanMonaco.start(options);
+ if (project === undefined) return
+ console.debug('[Lean4web] Restarting editor')
+ var _leanMonaco = new LeanMonaco()
+ var leanMonacoEditor = new LeanMonacoEditor()
+
+ _leanMonaco.setInfoviewElement(infoviewRef.current!)
+ ;(async () => {
+ await _leanMonaco.start(options)
await leanMonacoEditor.start(
editorRef.current!,
path.join(project, `${project}.lean`),
code ?? '',
- );
+ )
- setEditor(leanMonacoEditor.editor);
- setLeanMonaco(_leanMonaco);
+ setEditor(leanMonacoEditor.editor)
+ setLeanMonaco(_leanMonaco)
// Add a `Paste` option to the context menu on mobile.
// Monaco does not support clipboard pasting as all browsers block it
@@ -160,9 +162,9 @@ function App() {
// keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_V],
contextMenuGroupId: '9_cutcopypaste',
run: (_editor) => {
- setCodeMirror(true);
+ setCodeMirror(true)
},
- });
+ })
}
// // TODO: This was an approach to create a new definition provider, but it
@@ -188,11 +190,11 @@ function App() {
// TODO: Implement Go-To-Definition better
// This approach only gives us the file on the server (plus line number) it wants
// to open, is there a better approach?
- const editorService = (leanMonacoEditor.editor as any)?._codeEditorService;
+ const editorService = (leanMonacoEditor.editor as any)?._codeEditorService
if (editorService) {
- const openEditorBase = editorService.openCodeEditor.bind(editorService);
+ const openEditorBase = editorService.openCodeEditor.bind(editorService)
editorService.openCodeEditor = async (input: any, source: any) => {
- const result = await openEditorBase(input, source);
+ const result = await openEditorBase(input, source)
if (result === null) {
// found this out with `console.debug(input)`:
// `resource.path` is the file go-to-def tries to open on the disk
@@ -201,7 +203,7 @@ function App() {
// call `...${path}.html#${declaration}`
let path = input.resource.path
.replace(new RegExp('^.*/(?:lean|\.lake/packages/[^/]+/)'), '')
- .replace(new RegExp('\.lean$'), '');
+ .replace(new RegExp('\.lean$'), '')
if (
window.confirm(
@@ -211,27 +213,27 @@ function App() {
let newTab = window.open(
`https://leanprover-community.github.io/mathlib4_docs/${path}.html`,
'_blank',
- );
+ )
if (newTab) {
- newTab.focus();
+ newTab.focus()
}
}
}
- return null;
+ return null
// return result // always return the base result
- };
+ }
}
// Keeping the `code` state up-to-date with the changes in the editor
leanMonacoEditor.editor?.onDidChangeModelContent(() => {
- setCode(leanMonacoEditor.editor?.getModel()?.getValue()!);
- });
- })();
+ setCode(leanMonacoEditor.editor?.getModel()?.getValue()!)
+ })
+ })()
return () => {
- leanMonacoEditor.dispose();
- _leanMonaco.dispose();
- };
- }, [project, settings, options, infoviewRef, editorRef]);
+ leanMonacoEditor.dispose()
+ _leanMonaco.dispose()
+ }
+ }, [project, settings, options, infoviewRef, editorRef, code, mobile])
// Load content from source URL.
// Once the editor is loaded, this reads the content of any provided `url=` in the URL and
@@ -240,20 +242,20 @@ function App() {
// changes, otherwise it might overwrite local changes too often.
useEffect(() => {
if (!editor || !url) {
- return;
+ return
}
- console.debug(`[Lean4web] Loading from ${url}`);
+ console.debug(`[Lean4web] Loading from ${url}`)
fetch(url)
.then((response) => response.text())
.then((code) => {
- setCodeFromUrl(code);
+ setCodeFromUrl(code)
})
.catch((err) => {
- let errorTxt = `ERROR: ${err.toString()}`;
- console.error(errorTxt);
- setCodeFromUrl(errorTxt);
- });
- }, [url, editor]);
+ let errorTxt = `ERROR: ${err.toString()}`
+ console.error(errorTxt)
+ setCodeFromUrl(errorTxt)
+ })
+ }, [url, editor])
// Sets the editors content to the content from the loaded URL.
// As described above, this requires the editor is loaded, but we do not want to
@@ -261,42 +263,42 @@ function App() {
// we would constantly overwrite the user's local changes
useEffect(() => {
if (!codeFromUrl) {
- return;
+ return
}
- setContent(codeFromUrl);
- }, [codeFromUrl]);
+ setContent(codeFromUrl)
+ }, [codeFromUrl])
// Keep the URL updated on change
useEffect(() => {
if (!editor) {
- return;
+ return
}
- let _project = project == 'MathlibDemo' ? null : (project ?? null);
+ let _project = project == 'MathlibDemo' ? null : (project ?? null)
let args: {
- project: string | null;
- url: string | null;
- code: string | null;
- codez: string | null;
- };
+ project: string | null
+ url: string | null
+ code: string | null
+ codez: string | null
+ }
if (code === '') {
args = {
project: _project,
url: null,
code: null,
codez: null,
- };
+ }
} else if (url != null && code == codeFromUrl) {
args = {
project: _project,
url: encodeURIComponent(url),
code: null,
codez: null,
- };
+ }
} else if (settings.compress) {
// LZ padds the string with trailing `=`, which mess up the argument parsing
// and aren't needed for LZ encoding, so we remove them.
- const compressed = LZString.compressToBase64(code).replace(/=*$/, '');
+ const compressed = LZString.compressToBase64(code).replace(/=*$/, '')
// // Note: probably temporary; might be worth to always compress as with whitespace encoding
// // it needs very little for the compressed version to be shorter
// const encodedCode = fixedEncodeURIComponent(code)
@@ -307,7 +309,7 @@ function App() {
url: null,
code: null,
codez: compressed,
- };
+ }
// } else {
// args = {
// project: _project,
@@ -322,51 +324,51 @@ function App() {
url: null,
code: fixedEncodeURIComponent(code),
codez: null,
- };
+ }
}
- history.replaceState(undefined, undefined!, formatArgs(args));
- }, [editor, project, code, codeFromUrl]);
+ history.replaceState(undefined, undefined!, formatArgs(args))
+ }, [editor, project, code, codeFromUrl, url, settings.compress])
// Disable monaco context menu outside the editor
useEffect(() => {
const handleContextMenu = (event: MouseEvent) => {
- const editorContainer = document.querySelector('.editor');
+ const editorContainer = document.querySelector('.editor')
if (editorContainer && !editorContainer.contains(event.target as Node)) {
- event.stopPropagation();
+ event.stopPropagation()
}
- };
- document.addEventListener('contextmenu', handleContextMenu, true);
+ }
+ document.addEventListener('contextmenu', handleContextMenu, true)
return () => {
- document.removeEventListener('contextmenu', handleContextMenu, true);
- };
- }, []);
+ document.removeEventListener('contextmenu', handleContextMenu, true)
+ }
+ }, [])
// Stop the browser's save dialog on Ctrl+S
const handleKeyDown = useCallback((event: KeyboardEvent) => {
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
- event.preventDefault();
+ event.preventDefault()
}
- }, []);
+ }, [])
// Actually save the file on Ctrl+S
const handleKeyUp = useCallback(
(event: KeyboardEvent) => {
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
- event.preventDefault();
- save(code);
+ event.preventDefault()
+ save(code)
}
},
[code],
- );
+ )
useEffect(() => {
- document.addEventListener('keydown', handleKeyDown);
- document.addEventListener('keyup', handleKeyUp);
+ document.addEventListener('keydown', handleKeyDown)
+ document.addEventListener('keyup', handleKeyUp)
return () => {
- document.removeEventListener('keydown', handleKeyDown);
- document.removeEventListener('keyup', handleKeyUp);
- };
- }, [handleKeyDown, handleKeyUp]);
+ document.removeEventListener('keydown', handleKeyDown)
+ document.removeEventListener('keyup', handleKeyUp)
+ }
+ }, [handleKeyDown, handleKeyUp])
return (
@@ -387,9 +389,9 @@ function App() {
{
- const gutter = document.createElement('div');
- gutter.className = `gutter`; // no `gutter-${direction}` as it might change
- return gutter;
+ const gutter = document.createElement('div')
+ gutter.className = `gutter` // no `gutter-${direction}` as it might change
+ return gutter
}}
gutterStyle={(_dimension, gutterSize, _index) => {
return {
@@ -399,7 +401,7 @@ function App() {
'margin-left': mobile ? 0 : `-${gutterSize}px`,
'margin-top': mobile ? `-${gutterSize}px` : 0,
'z-index': 0,
- };
+ }
}}
gutterSize={5}
onDragStart={() => setDragging(true)}
@@ -437,7 +439,7 @@ function App() {
- );
+ )
}
-export default App;
+export default App
diff --git a/client/src/Navigation.tsx b/client/src/Navigation.tsx
index bfdc252b..d8988594 100644
--- a/client/src/Navigation.tsx
+++ b/client/src/Navigation.tsx
@@ -1,120 +1,36 @@
-import {
- ChangeEvent,
- Dispatch,
- FC,
- JSX,
- MouseEventHandler,
- ReactNode,
- SetStateAction,
- useState,
-} from 'react';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import {
- IconDefinition,
- faArrowRotateRight,
- faCode,
- faInfoCircle,
-} from '@fortawesome/free-solid-svg-icons';
-import ZulipIcon from './assets/zulip.svg';
+import './css/Modal.css'
+import './css/Navigation.css'
+
+import { faArrowRotateRight, faCode, faInfoCircle } from '@fortawesome/free-solid-svg-icons'
import {
faArrowUpRightFromSquare,
- faDownload,
faBars,
- faXmark,
- faShield,
- faHammer,
+ faCloudArrowUp,
+ faDownload,
faGear,
+ faHammer,
+ faShield,
faStar,
faUpload,
- faCloudArrowUp,
-} from '@fortawesome/free-solid-svg-icons';
-
-import PrivacyPopup from './Popups/PrivacyPolicy';
-import ImpressumPopup from './Popups/Impressum';
-import ToolsPopup from './Popups/Tools';
-import LoadUrlPopup from './Popups/LoadUrl';
-import LoadZulipPopup from './Popups/LoadZulip';
-
-import lean4webConfig from './config/config';
-import './css/Modal.css';
-import './css/Navigation.css';
-import { save } from './utils/SaveToFile';
-import { lookupUrl } from './utils/UrlParsing';
-import { mobileAtom } from './settings/settings-atoms';
-import { useAtom } from 'jotai';
-import { SettingsPopup } from './settings/SettingsPopup';
-
-/** A button to appear in the hamburger menu or to navigation bar. */
-export function NavButton({
- icon,
- iconElement,
- text,
- onClick = () => {},
- href = undefined,
-}: {
- icon?: IconDefinition;
- iconElement?: JSX.Element;
- text: string;
- onClick?: MouseEventHandler;
- href?: string;
-}) {
- // note: it seems that we can just leave the `target="_blank"` and it has no
- // effect on links without a `href`. If not, add `if (href)` statement here...
- return (
-
- {iconElement ?? } {text}
-
- );
-}
+ faXmark,
+} from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { useAtom } from 'jotai'
+import { ChangeEvent, Dispatch, ReactNode, SetStateAction, useState } from 'react'
-/** A button to appear in the hamburger menu or to navigation bar. */
-export function Dropdown({
- open,
- setOpen,
- icon,
- text,
- useOverlay = false,
- onClick,
- children,
-}: {
- open: boolean;
- setOpen: Dispatch>;
- icon?: IconDefinition;
- text?: string;
- useOverlay?: boolean;
- onClick?: MouseEventHandler;
- children?: ReactNode;
-}) {
- return (
- <>
-
-
{
- setOpen(!open);
- onClick!(ev);
- ev.stopPropagation();
- }}
- />
- {open && (
- setOpen(false)}>
- {children}
-
- )}
-
- {useOverlay && open && (
- {
- setOpen(false);
- ev.stopPropagation();
- }}
- />
- )}
- >
- );
-}
+import ZulipIcon from './assets/zulip.svg'
+import lean4webConfig from './config/config'
+import { Dropdown } from './navigation/Dropdown'
+import { NavButton } from './navigation/NavButton'
+import ImpressumPopup from './Popups/Impressum'
+import LoadUrlPopup from './Popups/LoadUrl'
+import LoadZulipPopup from './Popups/LoadZulip'
+import PrivacyPopup from './Popups/PrivacyPolicy'
+import ToolsPopup from './Popups/Tools'
+import { mobileAtom } from './settings/settings-atoms'
+import { SettingsPopup } from './settings/SettingsPopup'
+import { save } from './utils/SaveToFile'
+import { lookupUrl } from './utils/UrlParsing'
/** A popup which overlays the entire screen. */
export function Popup({
@@ -122,9 +38,9 @@ export function Popup({
handleClose,
children,
}: {
- open: boolean;
- handleClose: () => void; // TODO: what's the correct type?
- children?: ReactNode;
+ open: boolean
+ handleClose: () => void // TODO: what's the correct type?
+ children?: ReactNode
}) {
return (
@@ -134,7 +50,7 @@ export function Popup({
{children}
- );
+ )
}
/** The menu items either appearing inside the dropdown or outside */
@@ -150,29 +66,29 @@ function FlexibleMenu({
setLoadUrlOpen,
setLoadZulipOpen,
}: {
- isInDropdown: boolean;
- setOpenNav: Dispatch>;
- openExample: boolean;
- setOpenExample: Dispatch>;
- openLoad: boolean;
- setOpenLoad: Dispatch>;
- loadFromUrl: (url: string, project?: string | undefined) => void;
- setContent: (code: string) => void;
- setLoadUrlOpen: Dispatch>;
- setLoadZulipOpen: Dispatch>;
+ isInDropdown: boolean
+ setOpenNav: Dispatch>
+ openExample: boolean
+ setOpenExample: Dispatch>
+ openLoad: boolean
+ setOpenLoad: Dispatch>
+ loadFromUrl: (url: string, project?: string | undefined) => void
+ setContent: (code: string) => void
+ setLoadUrlOpen: Dispatch>
+ setLoadZulipOpen: Dispatch>
}) {
const loadFileFromDisk = (event: ChangeEvent) => {
- console.debug('Loading file from disk');
- const fileToLoad = event.target.files![0];
- var fileReader = new FileReader();
+ console.debug('Loading file from disk')
+ const fileToLoad = event.target.files![0]
+ var fileReader = new FileReader()
fileReader.onload = (fileLoadedEvent) => {
- var textFromFileLoaded = fileLoadedEvent.target!.result as string;
- setContent(textFromFileLoaded);
- };
- fileReader.readAsText(fileToLoad, 'UTF-8');
+ var textFromFileLoaded = fileLoadedEvent.target!.result as string
+ setContent(textFromFileLoaded)
+ }
+ fileReader.readAsText(fileToLoad, 'UTF-8')
// Manually close the menu as we prevent it closing below.
- setOpenLoad(false);
- };
+ setOpenLoad(false)
+ }
return (
<>
@@ -183,8 +99,8 @@ function FlexibleMenu({
text="Examples"
useOverlay={isInDropdown}
onClick={() => {
- setOpenLoad(false);
- !isInDropdown && setOpenNav(false);
+ setOpenLoad(false)
+ !isInDropdown && setOpenNav(false)
}}
>
{lean4webConfig.projects.map((proj) =>
@@ -197,8 +113,8 @@ function FlexibleMenu({
loadFromUrl(
`${window.location.origin}/api/example/${proj.folder}/${example.file}`,
proj.folder,
- );
- setOpenExample(false);
+ )
+ setOpenExample(false)
}}
/>
)),
@@ -211,8 +127,8 @@ function FlexibleMenu({
text="Load"
useOverlay={isInDropdown}
onClick={() => {
- setOpenExample(false);
- !isInDropdown && setOpenNav(false);
+ setOpenExample(false)
+ !isInDropdown && setOpenNav(false)
}}
>
{
- setLoadUrlOpen(true);
+ setLoadUrlOpen(true)
}}
/>
}
text="Load Zulip Message"
onClick={() => {
- setLoadZulipOpen(true);
+ setLoadZulipOpen(true)
}}
/>
>
- );
+ )
}
/** The Navigation menu */
@@ -257,46 +173,46 @@ export function Menu({
codeMirror,
setCodeMirror,
}: {
- code: string;
- setContent: (code: string) => void;
- project: string;
- setProject: Dispatch>;
- setUrl: Dispatch>;
- codeFromUrl: string;
- restart?: () => void;
- codeMirror: boolean;
- setCodeMirror: Dispatch>;
+ code: string
+ setContent: (code: string) => void
+ project: string
+ setProject: Dispatch>
+ setUrl: Dispatch>
+ codeFromUrl: string
+ restart?: () => void
+ codeMirror: boolean
+ setCodeMirror: Dispatch>
}) {
// state for handling the dropdown menus
- const [openNav, setOpenNav] = useState(false);
- const [openExample, setOpenExample] = useState(false);
- const [openLoad, setOpenLoad] = useState(false);
- const [loadUrlOpen, setLoadUrlOpen] = useState(false);
- const [loadZulipOpen, setLoadZulipOpen] = useState(false);
+ const [openNav, setOpenNav] = useState(false)
+ const [openExample, setOpenExample] = useState(false)
+ const [openLoad, setOpenLoad] = useState(false)
+ const [loadUrlOpen, setLoadUrlOpen] = useState(false)
+ const [loadZulipOpen, setLoadZulipOpen] = useState(false)
// state for the popups
- const [privacyOpen, setPrivacyOpen] = useState(false);
- const [impressumOpen, setImpressumOpen] = useState(false);
- const [toolsOpen, setToolsOpen] = useState(false);
- const [settingsOpen, setSettingsOpen] = useState(false);
+ const [privacyOpen, setPrivacyOpen] = useState(false)
+ const [impressumOpen, setImpressumOpen] = useState(false)
+ const [toolsOpen, setToolsOpen] = useState(false)
+ const [settingsOpen, setSettingsOpen] = useState(false)
- const [mobile] = useAtom(mobileAtom);
+ const [mobile] = useAtom(mobileAtom)
const loadFromUrl = (url: string, project: string | undefined = undefined) => {
- url = lookupUrl(url);
- console.debug('load code from url');
+ url = lookupUrl(url)
+ console.debug('load code from url')
setUrl((oldUrl: string | null) => {
if (oldUrl === url) {
- setContent(codeFromUrl);
+ setContent(codeFromUrl)
}
- return url;
- });
+ return url
+ })
if (project) {
- setProject(project);
+ setProject(project)
}
- };
+ }
- const hasImpressum = lean4webConfig.impressum || lean4webConfig.contactDetails;
+ const hasImpressum = lean4webConfig.impressum || lean4webConfig.contactDetails
return (
@@ -304,8 +220,8 @@ export function Menu({
name="leanVersion"
value={project}
onChange={(ev) => {
- setProject(ev.target.value);
- console.log(`set Lean project to: ${ev.target.value}`);
+ setProject(ev.target.value)
+ console.log(`set Lean project to: ${ev.target.value}`)
}}
>
{lean4webConfig.projects.map((proj) => (
@@ -319,7 +235,7 @@ export function Menu({
icon={faCode}
text={codeMirror ? 'Lean' : 'Text'}
onClick={() => {
- setCodeMirror(!codeMirror);
+ setCodeMirror(!codeMirror)
}}
/>
)}
@@ -342,8 +258,8 @@ export function Menu({
setOpen={setOpenNav}
icon={openNav ? faXmark : faBars}
onClick={() => {
- setOpenExample(false);
- setOpenLoad(false);
+ setOpenExample(false)
+ setOpenLoad(false)
}}
>
{mobile && (
@@ -364,7 +280,7 @@ export function Menu({
icon={faGear}
text="Settings"
onClick={() => {
- setSettingsOpen(true);
+ setSettingsOpen(true)
}}
/>
setToolsOpen(true)} />
@@ -374,7 +290,7 @@ export function Menu({
icon={faShield}
text={'Privacy policy'}
onClick={() => {
- setPrivacyOpen(true);
+ setPrivacyOpen(true)
}}
/>
{hasImpressum && (
@@ -382,7 +298,7 @@ export function Menu({
icon={faInfoCircle}
text={'Impressum'}
onClick={() => {
- setImpressumOpen(true);
+ setImpressumOpen(true)
}}
/>
)}
@@ -425,5 +341,5 @@ export function Menu({
setContent={setContent}
/>
- );
+ )
}
diff --git a/client/src/Popups/Impressum.tsx b/client/src/Popups/Impressum.tsx
index e5db76f8..539f8ae8 100644
--- a/client/src/Popups/Impressum.tsx
+++ b/client/src/Popups/Impressum.tsx
@@ -1,5 +1,5 @@
-import { Popup } from '../Navigation';
-import lean4webConfig from '../config/config';
+import lean4webConfig from '../config/config'
+import { Popup } from '../Navigation'
/** The popup with the privacy policy. */
function ImpressumPopup({ open, handleClose }: { open: boolean; handleClose: () => void }) {
@@ -17,7 +17,7 @@ function ImpressumPopup({ open, handleClose }: { open: boolean; handleClose: ()
{lean4webConfig.impressum}
- );
+ )
}
-export default ImpressumPopup;
+export default ImpressumPopup
diff --git a/client/src/Popups/LoadUrl.tsx b/client/src/Popups/LoadUrl.tsx
index e16ac111..14553221 100644
--- a/client/src/Popups/LoadUrl.tsx
+++ b/client/src/Popups/LoadUrl.tsx
@@ -1,31 +1,32 @@
-import { Popup } from '../Navigation';
-import { FC, FormEvent, useRef, useState } from 'react';
+import { FormEvent, useRef, useState } from 'react'
+
+import { Popup } from '../Navigation'
function LoadUrlPopup({
open,
handleClose,
loadFromUrl,
}: {
- open: boolean;
- handleClose: () => void;
- loadFromUrl: (url: string) => void;
+ open: boolean
+ handleClose: () => void
+ loadFromUrl: (url: string) => void
}) {
- const [error, setError] = useState('');
- const urlRef = useRef(null);
+ const [error, setError] = useState('')
+ const urlRef = useRef(null)
const handleLoad = (ev: FormEvent) => {
- ev.preventDefault();
- let url = urlRef.current?.value;
+ ev.preventDefault()
+ let url = urlRef.current?.value
if (!url) {
- setError(`Please specify a URL.`);
- return;
+ setError(`Please specify a URL.`)
+ return
}
if (!url.startsWith('http://') && !url.startsWith('https://')) {
- url = 'https://' + url;
+ url = 'https://' + url
}
- loadFromUrl(url);
- handleClose();
- };
+ loadFromUrl(url)
+ handleClose()
+ }
return (
@@ -36,7 +37,7 @@ function LoadUrlPopup({
- );
+ )
}
-export default LoadUrlPopup;
+export default LoadUrlPopup
diff --git a/client/src/Popups/LoadZulip.tsx b/client/src/Popups/LoadZulip.tsx
index 1a78c02c..200fe607 100644
--- a/client/src/Popups/LoadZulip.tsx
+++ b/client/src/Popups/LoadZulip.tsx
@@ -1,46 +1,47 @@
-import { Popup } from '../Navigation';
-import { FC, FormEvent, useRef, useState } from 'react';
+import { FormEvent, useRef, useState } from 'react'
+
+import { Popup } from '../Navigation'
function LoadZulipPopup({
open,
handleClose,
setContent,
}: {
- open: boolean;
- handleClose: () => void;
- setContent: (code: string) => void;
+ open: boolean
+ handleClose: () => void
+ setContent: (code: string) => void
}) {
- const [error, setError] = useState('');
- const textInputRef = useRef(null);
+ const [error, setError] = useState('')
+ const textInputRef = useRef(null)
const handleLoad = (ev: FormEvent) => {
- ev.preventDefault();
- let md = textInputRef.current?.value; // TODO: not a URL but text, update the var names
+ ev.preventDefault()
+ let md = textInputRef.current?.value // TODO: not a URL but text, update the var names
- console.log(`received: ${md}`);
+ console.log(`received: ${md}`)
// regex 1 finds the code-blocks
- let regex1 = /(`{3,})\s*(lean)?\s*\n(.+?)\1/gs;
+ let regex1 = /(`{3,})\s*(lean)?\s*\n(.+?)\1/gs
// regex 2 extracts the code from a codeblock
- let regex2 = /^(`{3,})\s*(?:lean)?\s*\n\s*(.+)\s*\1$/s;
+ let regex2 = /^(`{3,})\s*(?:lean)?\s*\n\s*(.+)\s*\1$/s
- let res = md?.match(regex1);
+ let res = md?.match(regex1)
if (res) {
let code =
res
.map((s) => {
- return s.match(regex2)![2];
+ return s.match(regex2)![2]
})
.join('\n\n-- new codeblock\n\n')
- .trim() + '\n';
- setContent(code);
+ .trim() + '\n'
+ setContent(code)
//setError('')
- handleClose();
+ handleClose()
} else {
- setError('Could not find a code-block in the message');
+ setError('Could not find a code-block in the message')
}
- };
+ }
return (
@@ -60,7 +61,7 @@ function LoadZulipPopup({
- );
+ )
}
-export default LoadZulipPopup;
+export default LoadZulipPopup
diff --git a/client/src/Popups/PrivacyPolicy.tsx b/client/src/Popups/PrivacyPolicy.tsx
index a6ce2183..c5f93841 100644
--- a/client/src/Popups/PrivacyPolicy.tsx
+++ b/client/src/Popups/PrivacyPolicy.tsx
@@ -1,5 +1,5 @@
-import { Popup } from '../Navigation';
-import lean4webConfig from '../config/config';
+import lean4webConfig from '../config/config'
+import { Popup } from '../Navigation'
/** The popup with the privacy policy. */
function PrivacyPopup({ open, handleClose }: { open: boolean; handleClose: () => void }) {
@@ -30,7 +30,7 @@ function PrivacyPopup({ open, handleClose }: { open: boolean; handleClose: () =>
)}
- );
+ )
}
-export default PrivacyPopup;
+export default PrivacyPopup
diff --git a/client/src/Popups/Tools.tsx b/client/src/Popups/Tools.tsx
index a8b85bbc..91e3eee7 100644
--- a/client/src/Popups/Tools.tsx
+++ b/client/src/Popups/Tools.tsx
@@ -1,36 +1,37 @@
-import { Popup } from '../Navigation';
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react'
+
+import { Popup } from '../Navigation'
// TODO: Do these interfaces exist somewhere in vscode-lean4?
// They might need to be updated manually if changes to `lake` occur.
interface LakePackage {
- url: string;
- type: 'git';
- subDir: string;
- scope: string;
- rev: string;
- name: string;
- manifestFile: string;
- inputRev: string;
- inherited: string;
- configFile: string;
+ url: string
+ type: 'git'
+ subDir: string
+ scope: string
+ rev: string
+ name: string
+ manifestFile: string
+ inputRev: string
+ inherited: string
+ configFile: string
}
interface LocalLakePackage {
- type: 'path';
- name: string;
- manifestFile: string;
- inherited: false;
- dir: string;
- configFile: string;
+ type: 'path'
+ name: string
+ manifestFile: string
+ inherited: false
+ dir: string
+ configFile: string
}
interface LakeManifest {
- version: string;
- packagesDir: string;
- packages: (LakePackage | LocalLakePackage)[];
- name: string;
- lakeDir: string;
+ version: string
+ packagesDir: string
+ packages: (LakePackage | LocalLakePackage)[]
+ name: string
+ lakeDir: string
}
/** Default. Should never actually be visible to the user as it will be overwritten immediately */
@@ -40,26 +41,26 @@ const emptyManifest: LakeManifest = {
packages: [],
name: '',
lakeDir: '',
-};
+}
/** These are just a few relevant fields the data fetched from github comprises. */
interface CommitInfo {
- sha: string;
+ sha: string
commit: {
author: {
- name: string;
- date: string;
- };
- message: string;
- };
+ name: string
+ date: string
+ }
+ message: string
+ }
author: {
- avatar_url: string;
- };
+ avatar_url: string
+ }
stats: {
- total: number;
- additions: number;
- deletions: number;
- };
+ total: number
+ additions: number
+ deletions: number
+ }
}
/** Displays a link of the specified commit together with a hover-tooltip showing the
@@ -69,59 +70,59 @@ interface CommitInfo {
* rarely used feature, it might be fine for now.
*/
function ToolTip({ pkg }: { pkg: LakePackage }) {
- const [loaded, setLoaded] = useState(false);
- const linkRef = useRef(null);
- const [commit, setCommit] = useState();
- const [error, setError] = useState();
-
- useEffect(() => {
- linkRef.current?.addEventListener('mouseover', handleHover);
- return () => {
- linkRef.current?.removeEventListener('mouseover', handleHover);
- };
- }, [linkRef, loaded]);
+ const [loaded, setLoaded] = useState(false)
+ const linkRef = useRef(null)
+ const [commit, setCommit] = useState()
+ const [error, setError] = useState()
// Load commit info on hovering the first time
const handleHover = (_event: any) => {
// Do not fetch twice
if (loaded) {
- return;
+ return
}
- setLoaded(true);
+ setLoaded(true)
// construct github api URL from repo URL
- let m = pkg.url.match(/github.com\/([^\/]+)\/([^\/\.]+)/i); // exclude '\.' to strip potential '.git' at the end
+ let m = pkg.url.match(/github.com\/([^\/]+)\/([^\/\.]+)/i) // exclude '\.' to strip potential '.git' at the end
if (!m || m.length < 2) {
- console.warn(`[LeanWeb]: cannot parse package url`, pkg.url);
- setError('Not Found');
- return;
+ console.warn(`[LeanWeb]: cannot parse package url`, pkg.url)
+ setError('Not Found')
+ return
}
- let githubUrl = `https://api.github.com/repos/${m![1]}/${m![2]}/commits/${pkg.rev}`;
+ let githubUrl = `https://api.github.com/repos/${m![1]}/${m![2]}/commits/${pkg.rev}`
- pkg.url.replace('github.com/', 'api.github.com/repos/') + `/commits/${pkg.rev}`;
- console.debug(`[LeanWeb]: fetch from ${githubUrl}`);
+ pkg.url.replace('github.com/', 'api.github.com/repos/') + `/commits/${pkg.rev}`
+ console.debug(`[LeanWeb]: fetch from ${githubUrl}`)
fetch(githubUrl)
.then((response) => {
if (!response.ok) {
- console.warn(`[LeanWeb]: failed request (${response.status})`, response);
+ console.warn(`[LeanWeb]: failed request (${response.status})`, response)
}
- return response.json();
+ return response.json()
})
.then((data) => {
if (data.message) {
// e.g. when reaching rate limit
- setError(data.message);
+ setError(data.message)
} else {
- setCommit(data);
+ setCommit(data)
}
})
.catch((error) => {
- setError(error);
- console.error(error);
- });
- };
+ setError(error)
+ console.error(error)
+ })
+ }
+
+ useEffect(() => {
+ linkRef.current?.addEventListener('mouseover', handleHover)
+ return () => {
+ linkRef.current?.removeEventListener('mouseover', handleHover)
+ }
+ }, [linkRef, loaded])
return (
@@ -149,7 +150,7 @@ function ToolTip({ pkg }: { pkg: LakePackage }) {
)}
- );
+ )
}
/** Shows important information about the Lean project loaded in the web editor */
@@ -158,48 +159,48 @@ function ToolsPopup({
handleClose,
project,
}: {
- open: boolean;
- project: string;
- handleClose: () => void;
+ open: boolean
+ project: string
+ handleClose: () => void
}) {
- const [manifest, setManifest] = useState(emptyManifest);
- const [toolchain, setToolchain] = useState('');
+ const [manifest, setManifest] = useState(emptyManifest)
+ const [toolchain, setToolchain] = useState('')
// The last time `lake-manifest.json` has been modified
// Experimental: This might somewhat agree with the last update of the project
// I couldn't think of a better way to determine this.
- const [lastModified, setLastModified] = useState(null);
+ const [lastModified, setLastModified] = useState(null)
// Load the new manifest & toolchain
useEffect(() => {
if (!project) {
- return;
+ return
}
- const urlManifest = `${window.location.origin}/api/manifest/${project}`;
+ const urlManifest = `${window.location.origin}/api/manifest/${project}`
fetch(urlManifest)
.then((response) => {
- var _lastModified = response.headers.get('Last-Modified');
+ var _lastModified = response.headers.get('Last-Modified')
response.json().then((manifest) => {
- setManifest(manifest);
- setLastModified(_lastModified);
- });
+ setManifest(manifest)
+ setLastModified(_lastModified)
+ })
})
.catch((err) => {
- console.error('Error reading manifest.');
- console.error(err);
- });
+ console.error('Error reading manifest.')
+ console.error(err)
+ })
- const urlToolchain = `${window.location.origin}/api/toolchain/${project}`;
+ const urlToolchain = `${window.location.origin}/api/toolchain/${project}`
fetch(urlToolchain)
.then((response) => {
response.text().then((toolchain) => {
- setToolchain(toolchain);
- });
+ setToolchain(toolchain)
+ })
})
.catch((err) => {
- console.error('Error reading toolchain.');
- console.error(err);
- });
- }, [project]);
+ console.error('Error reading toolchain.')
+ console.error(err)
+ })
+ }, [project])
return (
@@ -261,7 +262,7 @@ function ToolsPopup({
#eval Lean.versionString
- );
+ )
}
-export default ToolsPopup;
+export default ToolsPopup
diff --git a/client/src/config/config.tsx b/client/src/config/config.tsx
index 93b85edd..93125e58 100644
--- a/client/src/config/config.tsx
+++ b/client/src/config/config.tsx
@@ -1,4 +1,4 @@
-import { LeanWebConfig } from './docs'; // look here for documentation of the individual config options
+import { LeanWebConfig } from './docs' // look here for documentation of the individual config options
const lean4webConfig: LeanWebConfig = {
projects: [
@@ -17,6 +17,6 @@ const lean4webConfig: LeanWebConfig = {
serverCountry: null,
contactDetails: null,
impressum: null,
-};
+}
-export default lean4webConfig;
+export default lean4webConfig
diff --git a/client/src/config/docs.tsx b/client/src/config/docs.tsx
index c70f14a9..2632a6d8 100644
--- a/client/src/config/docs.tsx
+++ b/client/src/config/docs.tsx
@@ -2,16 +2,16 @@
* This file contains the documentation of the existing `config` options
*/
-import { JSX } from 'react';
+import { JSX } from 'react'
/** An example can be any Lean file which belongs to the project.
* The editor just reads the file content, but it makes sense for maintainability
* that you ensure the Lean project actually builds the file. */
interface LeanWebExample {
/** File to load; relative path in `lean4web/Projects//` */
- file: string;
+ file: string
/** Display name used in the `Examples` menu */
- name: string;
+ name: string
}
/** You can add any Lean Project under `lean4web/Projects/` and add it here to use it in the
@@ -23,27 +23,27 @@ interface LeanWebProject {
/** The folder containing the Lean project; the folder is expected to be in `lean4web/Projects/`.
* The folder name will appear in the URL.
*/
- folder: string;
+ folder: string
/** Display name; shown in selection menu */
- name: string;
+ name: string
/** A list of examples which are added under the menu `Examples` */
- examples?: LeanWebExample[];
+ examples?: LeanWebExample[]
}
interface LeanWebConfig {
- projects: LeanWebProject[];
+ projects: LeanWebProject[]
/** Where the server is located. Use `null` to not display this information. */
- serverCountry: string | null;
+ serverCountry: string | null
/** Contact details of the server maintainer. Used in Privacy Policy and Impressum.
* Use `null` to not display this information.
* Note: This will be embedded inside a `` tag! (example: `<>Hans Muster
hans@nowhere.com>`)
*/
- contactDetails: JSX.Element | null;
+ contactDetails: JSX.Element | null
/** Additional legal information shown in impressum alongside the contact details.
* Use `null` to not display this information.
* (example: `<>
vat number: 000
<>`)
*/
- impressum: JSX.Element | null;
+ impressum: JSX.Element | null
}
-export type { LeanWebExample, LeanWebProject, LeanWebConfig };
+export type { LeanWebConfig, LeanWebExample, LeanWebProject }
diff --git a/client/src/index.tsx b/client/src/index.tsx
index c2818776..25e51d79 100644
--- a/client/src/index.tsx
+++ b/client/src/index.tsx
@@ -1,10 +1,12 @@
-import ReactDOM from 'react-dom/client';
-import App from './App.tsx';
-import './css/index.css';
-import { StrictMode } from 'react';
+import './css/index.css'
+
+import { StrictMode } from 'react'
+import ReactDOM from 'react-dom/client'
+
+import App from './App.tsx'
ReactDOM.createRoot(document.getElementById('root')!).render(
,
-);
+)
diff --git a/client/src/navigation/Dropdown.tsx b/client/src/navigation/Dropdown.tsx
new file mode 100644
index 00000000..f3f922da
--- /dev/null
+++ b/client/src/navigation/Dropdown.tsx
@@ -0,0 +1,53 @@
+import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
+import { MouseEventHandler, ReactNode } from 'react'
+
+import { NavButton } from './NavButton'
+
+/** A button to appear in the hamburger menu or to navigation bar. */
+export function Dropdown({
+ open,
+ setOpen,
+ icon,
+ text,
+ useOverlay = false,
+ onClick,
+ children,
+}: {
+ open: boolean
+ setOpen: (open: boolean) => void
+ icon?: IconDefinition
+ text?: string
+ useOverlay?: boolean
+ onClick?: MouseEventHandler
+ children?: ReactNode
+}) {
+ return (
+ <>
+
+
{
+ setOpen(!open)
+ onClick!(ev)
+ ev.stopPropagation()
+ }}
+ />
+ {open && (
+ setOpen(false)}>
+ {children}
+
+ )}
+
+ {useOverlay && open && (
+ {
+ setOpen(false)
+ ev.stopPropagation()
+ }}
+ />
+ )}
+ >
+ )
+}
diff --git a/client/src/navigation/NavButton.tsx b/client/src/navigation/NavButton.tsx
new file mode 100644
index 00000000..ad403b03
--- /dev/null
+++ b/client/src/navigation/NavButton.tsx
@@ -0,0 +1,26 @@
+import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { JSX, MouseEventHandler } from 'react'
+
+/** A button to appear in the hamburger menu or to navigation bar. */
+export function NavButton({
+ icon,
+ iconElement,
+ text,
+ onClick = () => {},
+ href = undefined,
+}: {
+ icon?: IconDefinition
+ iconElement?: JSX.Element
+ text: string
+ onClick?: MouseEventHandler
+ href?: string
+}) {
+ // note: it seems that we can just leave the `target="_blank"` and it has no
+ // effect on links without a `href`. If not, add `if (href)` statement here...
+ return (
+
+ {iconElement ?? } {text}
+
+ )
+}
diff --git a/client/src/settings/SettingsPopup.tsx b/client/src/settings/SettingsPopup.tsx
index a34eac48..6a0f1451 100644
--- a/client/src/settings/SettingsPopup.tsx
+++ b/client/src/settings/SettingsPopup.tsx
@@ -1,14 +1,14 @@
-import Box from '@mui/material/Box';
-import Slider from '@mui/material/Slider';
-import Switch from '@mui/material/Switch';
-import { useAtom } from 'jotai/react';
-import { useState } from 'react';
-import { Popup } from '../Navigation';
-import { applySettingsAtom, settingsAtom } from './settings-atoms';
-import { defaultSettings, Settings } from './settings-types';
-import type { MobileValues, Theme } from './settings-types';
-import { shallowEqualSubset } from '../utils/shallowEqual';
-import { inputAdornmentClasses } from '@mui/material/InputAdornment';
+import Box from '@mui/material/Box'
+import Slider from '@mui/material/Slider'
+import Switch from '@mui/material/Switch'
+import { useAtom } from 'jotai/react'
+import { useState } from 'react'
+
+import { Popup } from '../Navigation'
+import { shallowEqualSubset } from '../utils/shallowEqual'
+import { applySettingsAtom, settingsAtom } from './settings-atoms'
+import type { MobileValues, Theme } from './settings-types'
+import { defaultSettings, Settings } from './settings-types'
export function SettingsPopup({
open,
@@ -17,27 +17,27 @@ export function SettingsPopup({
project,
setProject,
}: {
- open: boolean;
- handleClose: () => void;
- closeNav: () => void;
- project: string;
- setProject: (project: string) => void;
+ open: boolean
+ handleClose: () => void
+ closeNav: () => void
+ project: string
+ setProject: (project: string) => void
}) {
- const [settings, setSettings] = useAtom(settingsAtom);
- const [, applySettings] = useAtom(applySettingsAtom);
- const [newSettings, setNewSettings] = useState(settings);
+ const [settings, setSettings] = useAtom(settingsAtom)
+ const [, applySettings] = useAtom(applySettingsAtom)
+ const [newSettings, setNewSettings] = useState(settings)
function updateSetting(key: K, value: Settings[K]) {
- setNewSettings((prev) => ({ ...prev, [key]: value }));
+ setNewSettings((prev) => ({ ...prev, [key]: value }))
}
return (
- );
+ )
}
const mobileSliderMarks: { value: number; label: string; key: MobileValues }[] = [
@@ -224,4 +224,4 @@ const mobileSliderMarks: { value: number; label: string; key: MobileValues }[] =
label: 'Desktop',
key: false,
},
-];
+]
diff --git a/client/src/settings/settings-atoms.ts b/client/src/settings/settings-atoms.ts
index 7f7976c1..3402f722 100644
--- a/client/src/settings/settings-atoms.ts
+++ b/client/src/settings/settings-atoms.ts
@@ -1,25 +1,26 @@
-import { atomWithStorage, createJSONStorage } from 'jotai/utils';
-import { defaultSettings, Settings, PartialUserSettings, Theme } from './settings-types';
-import { atom } from 'jotai/vanilla';
-import { screenWidthAtom } from '../store/window-atoms';
-import { locationAtom } from '../store/location-atoms';
-import { cleanObject } from '../utils/cleanObject';
-import { decodeSettingsFromURL, encodeSettingsToURL } from './settings-url-converters';
+import { atomWithStorage } from 'jotai/utils'
+import { atom } from 'jotai/vanilla'
+
+import { locationAtom } from '../store/location-atoms'
+import { screenWidthAtom } from '../store/window-atoms'
+import { cleanObject } from '../utils/cleanObject'
+import { defaultSettings, PartialUserSettings, Settings } from './settings-types'
+import { decodeSettingsFromURL, encodeSettingsToURL } from './settings-url-converters'
/** User settings as they are stored in storage */
const settingsStoreAtom = atomWithStorage('lean4web:settings', {}, undefined, {
getOnInit: true,
-});
+})
/** The settings which are set in the searchParams of the opened URL */
export const settingsUrlAtom = atom((get) => {
- const searchParams = get(locationAtom).searchParams;
- if (!searchParams) return {};
- return decodeSettingsFromURL(searchParams);
-});
+ const searchParams = get(locationAtom).searchParams
+ if (!searchParams) return {}
+ return decodeSettingsFromURL(searchParams)
+})
/** The settings which apply for the current session */
-const settingsBaseAtom = atom({ saved: false, inUrl: false, ...defaultSettings });
+const settingsBaseAtom = atom({ saved: false, inUrl: false, ...defaultSettings })
/**
* The user settings combined from different sources, with decreasing priority:
@@ -29,41 +30,41 @@ const settingsBaseAtom = atom({ saved: false, inUrl: false, ...default
* - default values (base)
*/
export const settingsAtom = atom((get) => {
- const base = get(settingsBaseAtom);
- const store = cleanObject(get(settingsStoreAtom));
- const url = cleanObject(get(settingsUrlAtom));
+ const base = get(settingsBaseAtom)
+ const store = cleanObject(get(settingsStoreAtom))
+ const url = cleanObject(get(settingsUrlAtom))
return {
...base,
...store,
...url,
saved: Object.entries(store).length > 0,
inUrl: Object.entries(url).length > 0,
- } as Settings;
-});
+ } as Settings
+})
/** Set the new settings, and write them to browser storage or URL if desired */
export const applySettingsAtom = atom(null, (get, set, val: Settings) => {
- const { saved, inUrl, ...settingsToStore } = val;
+ const { saved, inUrl, ...settingsToStore } = val
- set(settingsBaseAtom, val);
+ set(settingsBaseAtom, val)
if (saved) {
- set(settingsStoreAtom, settingsToStore);
+ set(settingsStoreAtom, settingsToStore)
} else {
- localStorage.removeItem('lean4web:settings');
+ localStorage.removeItem('lean4web:settings')
}
- const newSearchParams = inUrl ? encodeSettingsToURL(settingsToStore) : new URLSearchParams();
- const location = get(locationAtom);
- set(locationAtom, { ...location, searchParams: newSearchParams });
-});
+ const newSearchParams = inUrl ? encodeSettingsToURL(settingsToStore) : new URLSearchParams()
+ const location = get(locationAtom)
+ set(locationAtom, { ...location, searchParams: newSearchParams })
+})
/** Indicates whether mobile layout should be used */
export const mobileAtom = atom((get) => {
- const mobile_setting = get(settingsAtom).mobile;
+ const mobile_setting = get(settingsAtom).mobile
if (mobile_setting === 'auto') {
- const width = get(screenWidthAtom);
- return width < 800;
+ const width = get(screenWidthAtom)
+ return width < 800
}
- return mobile_setting;
-});
+ return mobile_setting
+})
diff --git a/client/src/settings/settings-types.ts b/client/src/settings/settings-types.ts
index db8f8d5b..163c86fb 100644
--- a/client/src/settings/settings-types.ts
+++ b/client/src/settings/settings-types.ts
@@ -4,42 +4,42 @@ export type Theme =
| 'Visual Studio Light'
| 'Visual Studio Dark'
| 'Default High Contrast'
- | 'Cobalt';
+ | 'Cobalt'
-export type MobileValues = boolean | 'auto';
+export type MobileValues = boolean | 'auto'
/** Type for the settings, including internal ones */
export interface Settings {
/** Lead character to trigger unicode input mode */
- abbreviationCharacter: string;
+ abbreviationCharacter: string
/** Accept code editors suggestions on Enter */
- acceptSuggestionOnEnter: boolean;
+ acceptSuggestionOnEnter: boolean
/** Show goal names in Lean infoview box */
- showGoalNames: boolean;
+ showGoalNames: boolean
/** Show expected type in Lean infoview box */
- showExpectedType: boolean;
+ showExpectedType: boolean
/** Compress the `code=` in the URL into `codez=` using LZ-string */
- compress: boolean;
+ compress: boolean
/** Display code editor and infoview in narrow, vertically stacked, mobile-friendly mode.
* Usually inferred from window width. */
- mobile: MobileValues;
+ mobile: MobileValues
/** `light` or `dark` or name of existing theme.
* Usually inferred from browser dark mode preferences.
*/
- theme: Theme;
+ theme: Theme
/** Wrap code */
- wordWrap: boolean;
+ wordWrap: boolean
// internal: saved to browser storage
- saved: boolean;
+ saved: boolean
// internal: written to search params
- inUrl: boolean;
+ inUrl: boolean
}
/** The settings which are not internal */
-export type UserSettings = Omit;
+export type UserSettings = Omit
/** Same as `UserSettings` but everything is optional, since single keys might be missing in the browser store */
-export type PartialUserSettings = Partial;
+export type PartialUserSettings = Partial
/** The default settings. */
export const defaultSettings: UserSettings = {
@@ -51,11 +51,11 @@ export const defaultSettings: UserSettings = {
mobile: 'auto',
theme: 'Visual Studio Light', // TODO: introduce "auto" which takes the browser setting.
wordWrap: true,
-};
+}
/**
* For CodeMirror (on mobile only)
* If you add a Monaco theme, the mobile code-mirror editor will default to its dark theme,
* unless the theme is in this list.
*/
-export const lightThemes: Theme[] = ['Visual Studio Light'];
+export const lightThemes: Theme[] = ['Visual Studio Light']
diff --git a/client/src/settings/settings-url-converters.ts b/client/src/settings/settings-url-converters.ts
index a0f1d862..33792332 100644
--- a/client/src/settings/settings-url-converters.ts
+++ b/client/src/settings/settings-url-converters.ts
@@ -1,4 +1,4 @@
-import { lightThemes, PartialUserSettings, Theme, UserSettings } from './settings-types';
+import { lightThemes, PartialUserSettings, Theme, UserSettings } from './settings-types'
export function decodeSettingsFromURL(searchParams: URLSearchParams): PartialUserSettings {
return {
@@ -10,49 +10,49 @@ export function decodeSettingsFromURL(searchParams: URLSearchParams): PartialUse
showExpectedType: parseBooleanSearchParam(searchParams, 'showExpectedType'),
theme: decodeTheme(searchParams.get('theme') ?? undefined),
wordWrap: parseBooleanSearchParam(searchParams, 'wordWrap'),
- };
+ }
}
function decodeTheme(val?: string): Theme | undefined {
- if (val === undefined) return;
+ if (val === undefined) return
switch (val.toLowerCase()) {
case 'light':
- return 'Visual Studio Light';
- break;
+ return 'Visual Studio Light'
+ break
case 'dark':
- return 'Visual Studio Dark';
- break;
+ return 'Visual Studio Dark'
+ break
default:
- console.warn(`expected search param 'theme' to be 'light' or 'dark'.`);
+ console.warn(`expected search param 'theme' to be 'light' or 'dark'.`)
}
}
function parseBooleanSearchParam(searchParams: URLSearchParams, name: string) {
- const param = searchParams.get(name) ?? undefined;
- return convertToBoolean(name, param);
+ const param = searchParams.get(name) ?? undefined
+ return convertToBoolean(name, param)
}
/** `name` is only used for the error message */
function convertToBoolean(name: string, val?: string) {
- if (val === undefined) return;
+ if (val === undefined) return
switch (val.toLowerCase()) {
case 'true':
- return true;
+ return true
case 'false':
- return false;
+ return false
default:
- console.warn(`expected search param '${name}' to be 'true' or 'false'.`);
+ console.warn(`expected search param '${name}' to be 'true' or 'false'.`)
}
}
export function encodeSettingsToURL(val: UserSettings): URLSearchParams {
- const searchParams = new URLSearchParams();
+ const searchParams = new URLSearchParams()
Object.entries(val)
.filter(([_, v]) => v !== undefined)
.forEach(([key, v]) => {
- setParam(searchParams, key as keyof UserSettings, v);
- });
- return searchParams;
+ setParam(searchParams, key as keyof UserSettings, v)
+ })
+ return searchParams
}
function setParam(
@@ -62,14 +62,14 @@ function setParam(
) {
switch (key) {
case 'theme':
- searchParams.set(String(key), lightThemes.includes(value as Theme) ? 'light' : 'dark');
- break;
+ searchParams.set(String(key), lightThemes.includes(value as Theme) ? 'light' : 'dark')
+ break
case 'mobile':
if (value !== 'auto') {
- searchParams.set(String(key), String(value as Boolean));
+ searchParams.set(String(key), String(value as Boolean))
}
- break;
+ break
default:
- searchParams.set(String(key), String(value));
+ searchParams.set(String(key), String(value))
}
}
diff --git a/client/src/store/location-atoms.ts b/client/src/store/location-atoms.ts
index bf5a5fcd..8acded4c 100644
--- a/client/src/store/location-atoms.ts
+++ b/client/src/store/location-atoms.ts
@@ -1,3 +1,3 @@
-import { atomWithLocation } from 'jotai-location';
+import { atomWithLocation } from 'jotai-location'
-export const locationAtom = atomWithLocation();
+export const locationAtom = atomWithLocation()
diff --git a/client/src/store/window-atoms.ts b/client/src/store/window-atoms.ts
index e0795cc8..8a669d36 100644
--- a/client/src/store/window-atoms.ts
+++ b/client/src/store/window-atoms.ts
@@ -1,7 +1,7 @@
-import { atom } from 'jotai';
+import { atom } from 'jotai'
/**
* Atom to store the screen width. Note that this atom needs to be updated with a `useEffect`
* in `App.tsx`.
*/
-export const screenWidthAtom = atom(window?.innerWidth ?? 1024);
+export const screenWidthAtom = atom(window?.innerWidth ?? 1024)
diff --git a/client/src/utils/Entries.ts b/client/src/utils/Entries.ts
index 512ad991..76766573 100644
--- a/client/src/utils/Entries.ts
+++ b/client/src/utils/Entries.ts
@@ -8,5 +8,5 @@ So `{[K in keyof T]-?: [K, T[K]]}` is the type of objects with keys of T
We index into this object with `[keyof T]` to get the pairs of key,value,
and finally with a `[]` to get the array of pairs. */
export type Entries = {
- [K in keyof T]-?: [K, T[K]];
-}[keyof T][];
+ [K in keyof T]-?: [K, T[K]]
+}[keyof T][]
diff --git a/client/src/utils/SaveToFile.tsx b/client/src/utils/SaveToFile.tsx
index 92b51e4d..dedf730c 100644
--- a/client/src/utils/SaveToFile.tsx
+++ b/client/src/utils/SaveToFile.tsx
@@ -1,6 +1,6 @@
-import saveAs from 'file-saver';
+import saveAs from 'file-saver'
export const save = (content: string) => {
- var blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
- saveAs(blob, 'Lean4WebDownload.lean');
-};
+ var blob = new Blob([content], { type: 'text/plain;charset=utf-8' })
+ saveAs(blob, 'Lean4WebDownload.lean')
+}
diff --git a/client/src/utils/UrlParsing.tsx b/client/src/utils/UrlParsing.tsx
index abf53342..d40bc257 100644
--- a/client/src/utils/UrlParsing.tsx
+++ b/client/src/utils/UrlParsing.tsx
@@ -1,16 +1,16 @@
/** Expected arguments which can be provided in the URL. */
interface UrlArgs {
- project: string | null;
- url: string | null;
- code: string | null;
- codez: string | null;
+ project: string | null
+ url: string | null
+ code: string | null
+ codez: string | null
}
/** Escape `(` and `)` in URL. */
export function fixedEncodeURIComponent(str: string) {
return encodeURIComponent(str).replace(/[()]/g, function (c) {
- return '%' + c.charCodeAt(0).toString(16);
- });
+ return '%' + c.charCodeAt(0).toString(16)
+ })
}
/**
@@ -23,11 +23,11 @@ export function formatArgs(args: UrlArgs): string {
Object.entries(args)
.filter(([_key, val]) => val !== null && val.trim().length > 0)
.map(([key, val]) => `${key}=${val}`)
- .join('&');
+ .join('&')
if (out == '#') {
- return ' ';
+ return ' '
}
- return out;
+ return out
}
/**
@@ -38,8 +38,8 @@ export function parseArgs(): UrlArgs {
.replace('#', '')
.split('&')
.map((s) => s.split('='))
- .filter((x) => x[0]);
- return Object.fromEntries(_args);
+ .filter((x) => x[0])
+ return Object.fromEntries(_args)
}
/**
@@ -48,11 +48,11 @@ export function parseArgs(): UrlArgs {
* - change link to github file to its raw content.
*/
export function lookupUrl(url: string): string {
- const regex = RegExp('https://github.com/(.+)/blob/(.+)');
+ const regex = RegExp('https://github.com/(.+)/blob/(.+)')
if (regex.test(url)) {
- url = url.replace(regex, 'https://raw.githubusercontent.com/$1/refs/heads/$2');
+ url = url.replace(regex, 'https://raw.githubusercontent.com/$1/refs/heads/$2')
}
- return url;
+ return url
}
diff --git a/client/src/utils/WindowWidth.tsx b/client/src/utils/WindowWidth.tsx
index f3f3104f..f6295d7c 100644
--- a/client/src/utils/WindowWidth.tsx
+++ b/client/src/utils/WindowWidth.tsx
@@ -1,20 +1,20 @@
-import { useState, useEffect } from 'react';
+import { useEffect, useState } from 'react'
function getWindowDimensions() {
- const { innerWidth: width, innerHeight: height } = window;
- return { width, height };
+ const { innerWidth: width, innerHeight: height } = window
+ return { width, height }
}
export function useWindowDimensions() {
- const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
+ const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions())
useEffect(() => {
function handleResize() {
- setWindowDimensions(getWindowDimensions());
+ setWindowDimensions(getWindowDimensions())
}
- window.addEventListener('resize', handleResize);
- return () => window.removeEventListener('resize', handleResize);
- }, []);
+ window.addEventListener('resize', handleResize)
+ return () => window.removeEventListener('resize', handleResize)
+ }, [])
- return windowDimensions;
+ return windowDimensions
}
diff --git a/client/src/utils/cleanObject.ts b/client/src/utils/cleanObject.ts
index 18f835c2..baf2ac2e 100644
--- a/client/src/utils/cleanObject.ts
+++ b/client/src/utils/cleanObject.ts
@@ -1,4 +1,4 @@
/** Removes all keys which have `undefined` as value */
export function cleanObject(obj: T): Partial {
- return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v !== undefined)) as Partial;
+ return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v !== undefined)) as Partial
}
diff --git a/client/src/utils/shallowEqual.ts b/client/src/utils/shallowEqual.ts
index 1ec3dcdb..0fa45c1a 100644
--- a/client/src/utils/shallowEqual.ts
+++ b/client/src/utils/shallowEqual.ts
@@ -1,5 +1,5 @@
/** Every key of `A` is in `B` and they have the same value. This is not syymmetric */
export function shallowEqualSubset>(A: T, B: T): boolean {
- const keysA = Object.keys(A);
- return keysA.every((key) => A[key] === B[key]);
+ const keysA = Object.keys(A)
+ return keysA.every((key) => A[key] === B[key])
}
diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts
index 0951d9c0..7f2dd5b1 100644
--- a/client/src/vite-env.d.ts
+++ b/client/src/vite-env.d.ts
@@ -1,12 +1,12 @@
///
declare module '*.svg?react' {
- import { FC, SVGProps } from 'react';
- const content: FC>;
- export default content;
+ import { FC, SVGProps } from 'react'
+ const content: FC>
+ export default content
}
declare module '*.svg' {
- const content: string;
- export default content;
+ const content: string
+ export default content
}
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 00000000..d4753523
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,61 @@
+import js from "@eslint/js";
+import react from "eslint-plugin-react";
+import reactHooks from "eslint-plugin-react-hooks";
+import reactRefresh from "eslint-plugin-react-refresh";
+import tseslint from "@typescript-eslint/eslint-plugin";
+import tsParser from "@typescript-eslint/parser";
+import unusedImports from "eslint-plugin-unused-imports";
+import simpleImportSort from "eslint-plugin-simple-import-sort";
+import prettier from "eslint-config-prettier";
+import globals from "globals";
+
+export default [
+ {
+ ignores: ["dist", "node_modules"],
+ },
+ {
+ files: ["client/src/**/*.{ts,tsx}"],
+ ...js.configs.recommended,
+ languageOptions: {
+ parser: tsParser,
+ parserOptions: {
+ ecmaVersion: "latest",
+ sourceType: "module",
+ },
+ globals: globals.browser,
+ },
+ plugins: {
+ react,
+ "react-hooks": reactHooks,
+ "react-refresh": reactRefresh,
+ "@typescript-eslint": tseslint,
+ "unused-imports": unusedImports,
+ "simple-import-sort": simpleImportSort,
+ },
+ settings: {
+ react: { version: "detect" },
+ },
+ rules: {
+ "react/react-in-jsx-scope": "off",
+ "react-refresh/only-export-components": [
+ "warn",
+ { allowConstantExport: true },
+ ],
+ ...reactHooks.configs.recommended.rules,
+ "unused-imports/no-unused-imports": "warn",
+ "unused-imports/no-unused-vars": [
+ "warn",
+ {
+ varsIgnorePattern: "^_",
+ argsIgnorePattern: "^_",
+ },
+ ],
+ "simple-import-sort/imports": "warn",
+ "simple-import-sort/exports": "warn",
+
+ "@typescript-eslint/no-unused-vars": "off",
+ },
+ },
+
+ prettier,
+];
diff --git a/package-lock.json b/package-lock.json
index ae073249..0b680abc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43,14 +43,27 @@
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
+ "@typescript-eslint/parser": "^8.50.1",
"@vitejs/plugin-react-swc": "^4.2.2",
"cypress": "^15.7.0",
"cypress-iframe": "^1.0.1",
"cypress-real-events": "^1.15.0",
- "eslint": "^9.21.0",
+ "eslint": "^9.39.2",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-import": "^2.32.0",
+ "eslint-plugin-jsx-a11y": "^6.10.2",
+ "eslint-plugin-react": "^7.37.5",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.26",
+ "eslint-plugin-simple-import-sort": "^12.1.1",
+ "eslint-plugin-unused-imports": "^4.3.0",
+ "globals": "^16.5.0",
"prettier": "^3.7.4",
"typescript": "^5.9.3",
+ "typescript-eslint": "^8.50.1",
"vite": "^7.2.4",
+ "vite-plugin-eslint": "^1.8.1",
"vite-plugin-node-polyfills": "0.24.0",
"vite-plugin-static-copy": "^3.1.4",
"vite-plugin-svgr": "^4.5.0",
@@ -320,6 +333,15 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/traverse/node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
@@ -1434,9 +1456,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
- "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2462,6 +2484,13 @@
"win32"
]
},
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
@@ -3216,6 +3245,17 @@
"cypress": "*"
}
},
+ "node_modules/@types/eslint": {
+ "version": "8.56.12",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
+ "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -3236,6 +3276,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
@@ -3324,6 +3371,264 @@
"@types/node": "*"
}
},
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz",
+ "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.50.1",
+ "@typescript-eslint/type-utils": "8.50.1",
+ "@typescript-eslint/utils": "8.50.1",
+ "@typescript-eslint/visitor-keys": "8.50.1",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.50.1",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz",
+ "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.50.1",
+ "@typescript-eslint/types": "8.50.1",
+ "@typescript-eslint/typescript-estree": "8.50.1",
+ "@typescript-eslint/visitor-keys": "8.50.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz",
+ "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.50.1",
+ "@typescript-eslint/types": "^8.50.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz",
+ "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.50.1",
+ "@typescript-eslint/visitor-keys": "8.50.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz",
+ "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz",
+ "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.50.1",
+ "@typescript-eslint/typescript-estree": "8.50.1",
+ "@typescript-eslint/utils": "8.50.1",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz",
+ "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz",
+ "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.50.1",
+ "@typescript-eslint/tsconfig-utils": "8.50.1",
+ "@typescript-eslint/types": "8.50.1",
+ "@typescript-eslint/visitor-keys": "8.50.1",
+ "debug": "^4.3.4",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz",
+ "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.50.1",
+ "@typescript-eslint/types": "8.50.1",
+ "@typescript-eslint/typescript-estree": "8.50.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz",
+ "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.50.1",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
"node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.25.3",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.3.tgz",
@@ -3618,71 +3923,258 @@
"dev": true,
"license": "Python-2.0"
},
- "node_modules/asn1": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
- "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": "~2.1.0"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/asn1.js": {
- "version": "4.10.1",
- "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
- "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "bn.js": "^4.0.0",
- "inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0"
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/asn1.js/node_modules/bn.js": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
- "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/assert": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
- "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
+ "node_modules/array-includes": {
+ "version": "3.1.9",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
+ "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.2",
- "is-nan": "^1.3.2",
- "object-is": "^1.1.5",
- "object.assign": "^4.1.4",
- "util": "^0.12.5"
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.0",
+ "es-object-atoms": "^1.1.1",
+ "get-intrinsic": "^1.3.0",
+ "is-string": "^1.1.1",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/assert-plus": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
+ "node_modules/array.prototype.findlast": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
"engines": {
- "node": ">=0.8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/astral-regex": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
- "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz",
+ "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-shim-unscopables": "^1.1.0"
+ },
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/asynckit": {
- "version": "0.4.0",
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
+ "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
+ "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
+ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/asn1": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "node_modules/asn1.js": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "node_modules/asn1.js/node_modules/bn.js": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz",
+ "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/assert": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
+ "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "is-nan": "^1.3.2",
+ "object-is": "^1.1.5",
+ "object.assign": "^4.1.4",
+ "util": "^0.12.5"
+ }
+ },
+ "node_modules/assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/ast-types-flow": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
+ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async-function": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
+ "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
@@ -3731,6 +4223,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/axe-core": {
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz",
+ "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
@@ -3750,6 +4252,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/babel-plugin-macros": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
@@ -4790,6 +5302,13 @@
"cypress": "^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x || ^13.x || ^14.x || ^15.x"
}
},
+ "node_modules/damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
"node_modules/dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -4803,6 +5322,60 @@
"node": ">=0.10"
}
},
+ "node_modules/data-view-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
+ "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
+ "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/inspect-js"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
+ "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -4944,6 +5517,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@@ -5114,6 +5700,75 @@
"is-arrayish": "^0.2.1"
}
},
+ "node_modules/es-abstract": {
+ "version": "1.24.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
+ "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.2.1",
+ "is-set": "^2.0.3",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.1",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.4",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.4",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "stop-iteration-iterator": "^1.1.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.19"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -5132,6 +5787,34 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-iterator-helpers": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz",
+ "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.1",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.1.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.3.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "iterator.prototype": "^1.1.5",
+ "safe-array-concat": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-module-lexer": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
@@ -5170,6 +5853,37 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-shim-unscopables": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
+ "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
@@ -5238,9 +5952,9 @@
}
},
"node_modules/eslint": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
- "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -5251,7 +5965,7 @@
"@eslint/config-helpers": "^0.4.2",
"@eslint/core": "^0.17.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.1",
+ "@eslint/js": "9.39.2",
"@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -5298,6 +6012,280 @@
}
}
},
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
+ "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.32.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
+ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rtsao/scc": "^1.1.0",
+ "array-includes": "^3.1.9",
+ "array.prototype.findlastindex": "^1.2.6",
+ "array.prototype.flat": "^1.3.3",
+ "array.prototype.flatmap": "^1.3.3",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.12.1",
+ "hasown": "^2.0.2",
+ "is-core-module": "^2.16.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "object.groupby": "^1.0.3",
+ "object.values": "^1.2.1",
+ "semver": "^6.3.1",
+ "string.prototype.trimend": "^1.0.9",
+ "tsconfig-paths": "^3.15.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y": {
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
+ "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "aria-query": "^5.3.2",
+ "array-includes": "^3.1.8",
+ "array.prototype.flatmap": "^1.3.2",
+ "ast-types-flow": "^0.0.8",
+ "axe-core": "^4.10.0",
+ "axobject-query": "^4.1.0",
+ "damerau-levenshtein": "^1.0.8",
+ "emoji-regex": "^9.2.2",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^3.3.5",
+ "language-tags": "^1.0.9",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "safe-regex-test": "^1.0.3",
+ "string.prototype.includes": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.37.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
+ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.8",
+ "array.prototype.findlast": "^1.2.5",
+ "array.prototype.flatmap": "^1.3.3",
+ "array.prototype.tosorted": "^1.1.4",
+ "doctrine": "^2.1.0",
+ "es-iterator-helpers": "^1.2.1",
+ "estraverse": "^5.3.0",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.9",
+ "object.fromentries": "^2.0.8",
+ "object.values": "^1.2.1",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.5",
+ "semver": "^6.3.1",
+ "string.prototype.matchall": "^4.0.12",
+ "string.prototype.repeat": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.26",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
+ "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/resolve": {
+ "version": "2.0.0-next.5",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
+ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/eslint-plugin-simple-import-sort": {
+ "version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz",
+ "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=5.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-unused-imports": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz",
+ "integrity": "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0",
+ "eslint": "^9.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/eslint-plugin": {
+ "optional": true
+ }
+ }
+ },
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
@@ -5848,6 +6836,37 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -5920,6 +6939,24 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/get-symbol-description": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
+ "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@@ -5976,12 +7013,33 @@
}
},
"node_modules/globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">=4"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gopd": {
@@ -6002,6 +7060,19 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -6024,6 +7095,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -6116,6 +7203,23 @@
"node": ">= 0.4"
}
},
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
"node_modules/hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -6319,6 +7423,21 @@
"node": ">=10"
}
},
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/ip-anonymize": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/ip-anonymize/-/ip-anonymize-0.1.0.tgz",
@@ -6351,12 +7470,66 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"license": "MIT"
},
+ "node_modules/is-async-function": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
+ "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-function": "^1.0.0",
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -6369,6 +7542,23 @@
"node": ">=8"
}
},
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -6397,6 +7587,41 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -6406,6 +7631,22 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-finalizationregistry": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
+ "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -6463,6 +7704,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-nan": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
@@ -6480,6 +7734,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -6489,6 +7756,23 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
@@ -6524,6 +7808,35 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@@ -6537,6 +7850,41 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-typed-array": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
@@ -6573,6 +7921,52 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
+ "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -6604,6 +7998,24 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/iterator.prototype": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
+ "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "get-proto": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
@@ -6806,6 +8218,22 @@
"verror": "1.10.0"
}
},
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -6816,6 +8244,26 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/language-subtag-registry": {
+ "version": "0.3.23",
+ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
+ "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/language-tags": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
+ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "language-subtag-registry": "^0.3.20"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/lean4monaco": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/lean4monaco/-/lean4monaco-1.1.5.tgz",
@@ -7864,6 +9312,75 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object.entries": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz",
+ "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+ "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
+ "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
+ "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -7933,6 +9450,24 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -8509,6 +10044,50 @@
"node": ">=8.10.0"
}
},
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+ "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.1",
+ "which-builtin-type": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/request-progress": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
@@ -8715,6 +10294,33 @@
"tslib": "^2.1.0"
}
},
+ "node_modules/safe-array-concat": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-array-concat/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -8736,6 +10342,30 @@
],
"license": "MIT"
},
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-push-apply/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/safe-regex-test": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
@@ -8858,6 +10488,37 @@
"node": ">= 0.4"
}
},
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-proto": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
+ "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -9105,55 +10766,182 @@
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
- "node": ">= 0.8"
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/stream-browserify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
+ "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "~2.0.4",
+ "readable-stream": "^3.5.0"
+ }
+ },
+ "node_modules/stream-http": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz",
+ "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "builtin-status-codes": "^3.0.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.6.0",
+ "xtend": "^4.0.2"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string.prototype.includes": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
+ "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
+ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "regexp.prototype.flags": "^1.5.3",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/stream-browserify": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
- "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "inherits": "~2.0.4",
- "readable-stream": "^3.5.0"
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
}
},
- "node_modules/stream-http": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz",
- "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==",
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "builtin-status-codes": "^3.0.0",
- "inherits": "^2.0.4",
- "readable-stream": "^3.6.0",
- "xtend": "^4.0.2"
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "safe-buffer": "~5.2.0"
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/strip-ansi": {
@@ -9168,6 +10956,16 @@
"node": ">=8"
}
},
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -9500,6 +11298,45 @@
"tree-kill": "cli.js"
}
},
+ "node_modules/ts-api-utils": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.2.0.tgz",
+ "integrity": "sha512-L6f5oQRAoLU1RwXz0Ab9mxsE7LtxeVB6AIR1lpkZMsOyg/JXeaxBaXa/FVCBZyNr9S9I4wkHrlZTklX+im+WMw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -9614,6 +11451,69 @@
"node": ">= 0.4"
}
},
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -9629,6 +11529,49 @@
"node": ">=14.17"
}
},
+ "node_modules/typescript-eslint": {
+ "version": "8.50.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.1.tgz",
+ "integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.50.1",
+ "@typescript-eslint/parser": "8.50.1",
+ "@typescript-eslint/typescript-estree": "8.50.1",
+ "@typescript-eslint/utils": "8.50.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -9863,6 +11806,52 @@
}
}
},
+ "node_modules/vite-plugin-eslint": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz",
+ "integrity": "sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^4.2.1",
+ "@types/eslint": "^8.4.5",
+ "rollup": "^2.77.2"
+ },
+ "peerDependencies": {
+ "eslint": ">=7",
+ "vite": ">=2"
+ }
+ },
+ "node_modules/vite-plugin-eslint/node_modules/@rollup/pluginutils": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+ "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "estree-walker": "^2.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/vite-plugin-eslint/node_modules/rollup": {
+ "version": "2.79.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
+ "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/vite-plugin-node-polyfills": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.24.0.tgz",
@@ -10118,17 +12107,92 @@
"node": ">= 8"
}
},
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
+ "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.1.0",
+ "is-finalizationregistry": "^1.1.0",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.2.1",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.1.0",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/which-typed-array": {
- "version": "1.1.18",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz",
- "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==",
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
+ "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
"dev": true,
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "for-each": "^0.3.3",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2"
},
@@ -10274,9 +12338,23 @@
"version": "3.25.57",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.57.tgz",
"integrity": "sha512-6tgzLuwVST5oLUxXTmBqoinKMd3JeesgbgseXeFasKKj8Q1FCZrHnbqJOyiEvr4cVAlbug+CgIsmJ8cl/pU5FA==",
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index 5a2eaba8..0f5840fb 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"production": "NODE_ENV=production node server/index.mjs",
"dev": "vite",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "format": "prettier --write .",
"preview": "vite preview",
"test:dev:bare": "wait-on \"http://localhost:8080\" \"http://localhost:3000\" && cypress run",
"test:dev": "concurrently -k -s first -n server,client,cypress -c blue,green,cyan \"npm run start:server\" \"npm run start:client\" \"npm run test:dev:bare\" --hide server",
@@ -62,14 +63,27 @@
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
+ "@typescript-eslint/parser": "^8.50.1",
"@vitejs/plugin-react-swc": "^4.2.2",
"cypress": "^15.7.0",
"cypress-iframe": "^1.0.1",
"cypress-real-events": "^1.15.0",
- "eslint": "^9.21.0",
+ "eslint": "^9.39.2",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-import": "^2.32.0",
+ "eslint-plugin-jsx-a11y": "^6.10.2",
+ "eslint-plugin-react": "^7.37.5",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.26",
+ "eslint-plugin-simple-import-sort": "^12.1.1",
+ "eslint-plugin-unused-imports": "^4.3.0",
+ "globals": "^16.5.0",
"prettier": "^3.7.4",
"typescript": "^5.9.3",
+ "typescript-eslint": "^8.50.1",
"vite": "^7.2.4",
+ "vite-plugin-eslint": "^1.8.1",
"vite-plugin-node-polyfills": "0.24.0",
"vite-plugin-static-copy": "^3.1.4",
"vite-plugin-svgr": "^4.5.0",
diff --git a/tsconfig.json b/tsconfig.json
index fe2ba757..e4d1ee87 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -24,6 +24,6 @@
/* Performance */
"skipLibCheck": true
},
- "include": ["client/src"],
+ "include": ["client/src", "eslint.config.js"],
"exclude": ["server", "node_modules"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 572e5b45..6862c3d2 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,12 +1,14 @@
-import { defineConfig } from "vite";
+import path from "node:path";
+
+import importMetaUrlPlugin from "@codingame/esbuild-import-meta-url-plugin";
+import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react-swc";
+import { defineConfig } from "vite";
+import { normalizePath } from "vite";
import { nodePolyfills } from "vite-plugin-node-polyfills";
-import importMetaUrlPlugin from "@codingame/esbuild-import-meta-url-plugin";
import { viteStaticCopy } from "vite-plugin-static-copy";
-import { normalizePath } from "vite";
-import path from "node:path";
import svgr from "vite-plugin-svgr";
-import tailwindcss from "@tailwindcss/vite";
+import eslint from "vite-plugin-eslint";
// https://vitejs.dev/config/
export default defineConfig({
@@ -25,6 +27,7 @@ export default defineConfig({
plugins: [
react(),
tailwindcss(),
+ eslint(),
svgr({
include: ["**/*.svg?react", "**/*.svg"],
svgrOptions: {
From 2a75a75f9f8b16611f9c874f69cb4180be251861 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:14:32 +0100
Subject: [PATCH 03/17] lint in CI
---
.github/workflows/build.yml | 3 +++
package.json | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a041f5c9..8c06c5b0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -133,6 +133,9 @@ jobs:
name: cypress-screenshots-production-windows-${{ matrix.os }}-${{ matrix.browser }}
path: cypress/screenshots
+ - name: lint
+ run: npm run lint
+
all-tests-pass:
name: All Tests Pass
runs-on: ubuntu-latest
diff --git a/package.json b/package.json
index 0f5840fb..8ace571c 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"build:client": "tsc -b && NODE_ENV=production vite build",
"production": "NODE_ENV=production node server/index.mjs",
"dev": "vite",
- "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "lint": "eslint .",
"format": "prettier --write .",
"preview": "vite preview",
"test:dev:bare": "wait-on \"http://localhost:8080\" \"http://localhost:3000\" && cypress run",
From 0de81aca7cd28eac00dee9e3962a028e1c6f0644 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:15:47 +0100
Subject: [PATCH 04/17] ci
---
.github/workflows/build.yml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8c06c5b0..0960eb3d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -132,10 +132,12 @@ jobs:
with:
name: cypress-screenshots-production-windows-${{ matrix.os }}-${{ matrix.browser }}
path: cypress/screenshots
-
+ lint:
+ name: All Tests Pass
+ runs-on: ubuntu-latest
+ steps:
- name: lint
run: npm run lint
-
all-tests-pass:
name: All Tests Pass
runs-on: ubuntu-latest
From d557d2da15cc859e37412b78465dcaaa290455a7 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:16:59 +0100
Subject: [PATCH 05/17] ci
---
.github/workflows/build.yml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0960eb3d..78a65702 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -133,9 +133,16 @@ jobs:
name: cypress-screenshots-production-windows-${{ matrix.os }}-${{ matrix.browser }}
path: cypress/screenshots
lint:
- name: All Tests Pass
+ name: Lint
runs-on: ubuntu-latest
steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "25"
- name: lint
run: npm run lint
all-tests-pass:
From 172a23aedf8934f64bc68b9c77c6422118acb2f8 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:18:19 +0100
Subject: [PATCH 06/17] ci
---
.github/workflows/build.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 78a65702..a0bec496 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -138,11 +138,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
-
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "25"
+ - name: Install dependencies
+ run: npm ci
- name: lint
run: npm run lint
all-tests-pass:
From 0ed16f755de3f37e2802e1f2346507e55466e02f Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:20:47 +0100
Subject: [PATCH 07/17] ci
---
.github/workflows/build.yml | 9 ++++-----
package.json | 2 +-
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a0bec496..6ae4f329 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -7,15 +7,14 @@ on:
- "main"
- "dev"
pull_request:
-
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
-
defaults:
run:
shell: bash
-
+env:
+ NODE_VERSION: 25
jobs:
test:
runs-on: ${{ matrix.os }}
@@ -34,7 +33,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: "25"
+ node-version: ${{ env.NODE_VERSION }}
- name: Install elan
uses: leanprover/lean-action@v1
@@ -141,7 +140,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: "25"
+ node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: lint
diff --git a/package.json b/package.json
index 8ace571c..f071cb28 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"build:client": "tsc -b && NODE_ENV=production vite build",
"production": "NODE_ENV=production node server/index.mjs",
"dev": "vite",
- "lint": "eslint .",
+ "lint": "eslint --max-warnings=0 .",
"format": "prettier --write .",
"preview": "vite preview",
"test:dev:bare": "wait-on \"http://localhost:8080\" \"http://localhost:3000\" && cypress run",
From f3f640c19bcf6942a0edcb3ee8d7facba9dd2d57 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:21:42 +0100
Subject: [PATCH 08/17] ci
---
.github/workflows/build.yml | 154 ------------------------------------
1 file changed, 154 deletions(-)
delete mode 100644 .github/workflows/build.yml
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
deleted file mode 100644
index 6ae4f329..00000000
--- a/.github/workflows/build.yml
+++ /dev/null
@@ -1,154 +0,0 @@
-name: Test
-run-name: test
-on:
- workflow_dispatch:
- push:
- branches:
- - "main"
- - "dev"
- pull_request:
-concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
- cancel-in-progress: true
-defaults:
- run:
- shell: bash
-env:
- NODE_VERSION: 25
-jobs:
- test:
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, macos-latest]
- browser: [electron, chrome]
- include:
- - os: ubuntu-latest
- browser: chromium
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: ${{ env.NODE_VERSION }}
-
- - name: Install elan
- uses: leanprover/lean-action@v1
- with:
- lake-package-directory: Projects/MathlibDemo
- use-mathlib-cache: false
- use-github-cache: false
- auto-config: false
- build: false
- test: false
- lint: false
-
- - name: Build sample projects
- run: npm run build:server
-
- - name: Ensure sample project 'MathlibDemo' is built
- run: |
- cd Projects/MathlibDemo
- lake build --no-build
-
- - name: Ensure sample project 'Stable' is built
- run: |
- cd Projects/Stable
- lake build --no-build
-
- - name: Install dependencies
- run: npm ci
-
- - name: Build client for production
- run: npm run build:client
-
- - name: Run Cypress tests Development (No Video)
- id: cypress_no_video
- run: npm run test:dev
- env:
- CYPRESS_DEFAULT_BROWSER: ${{ matrix.browser }}
- CYPRESS_VIDEO: false
-
- - name: Upload screenshots on failure
- if: failure() && steps.cypress_no_video.conclusion == 'failure'
- uses: actions/upload-artifact@v4
- with:
- name: cypress-screenshots-${{ matrix.os }}-${{ matrix.browser }}
- path: cypress/screenshots
-
- - name: Run Cypress tests Development (With Video)
- id: cypress_with_video
- if: failure() && steps.cypress_no_video.conclusion == 'failure'
- run: npm run test:dev
- env:
- CYPRESS_DEFAULT_BROWSER: ${{ matrix.browser }}
- CYPRESS_VIDEO: true
- CYPRESS_VIDEO_COMPRESSION: true
-
- - name: Upload videos
- if: failure() && steps.cypress_no_video.conclusion == 'failure'
- uses: actions/upload-artifact@v4
- with:
- name: cypress-videos-${{ matrix.os }}-${{ matrix.browser }}
- path: cypress/videos
-
- - name: Install bubblewrap
- if: runner.os == 'Linux'
- run: |
- sudo apt-get install bubblewrap
- echo -e "abi ,\ninclude \n\nprofile bwrap /usr/bin/bwrap flags=(unconfined) {\n userns,\n\n include if exists \n}" | sudo tee /etc/apparmor.d/bwrap
- sudo systemctl reload apparmor
-
- - name: Run Cypress tests Production (No Video)
- if: runner.os == 'Linux'
- id: cypress_production_no_video
- run: npm run test:prod
- env:
- CYPRESS_DEFAULT_BROWSER: ${{ matrix.browser }}
- CYPRESS_VIDEO: false
-
- - name: Upload screenshots on failure
- if: runner.os == 'Linux' && failure() && steps.cypress_production_no_video.conclusion == 'failure'
- uses: actions/upload-artifact@v4
- with:
- name: cypress-screenshots-production-${{ matrix.os }}-${{ matrix.browser }}
- path: cypress/screenshots
-
- - name: Run Cypress tests Production with Windows User Agent (No Video)
- if: matrix.browser == 'chromium'
- id: cypress_production_windows_no_video
- run: npm run test:prod:windows
- env:
- CYPRESS_DEFAULT_BROWSER: ${{ matrix.browser }}
- CYPRESS_VIDEO: false
-
- - name: Upload screenshots on failure
- if: matrix.browser == 'chromium' && failure() && steps.cypress_production_windows_no_video.conclusion == 'failure'
- uses: actions/upload-artifact@v4
- with:
- name: cypress-screenshots-production-windows-${{ matrix.os }}-${{ matrix.browser }}
- path: cypress/screenshots
- lint:
- name: Lint
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: ${{ env.NODE_VERSION }}
- - name: Install dependencies
- run: npm ci
- - name: lint
- run: npm run lint
- all-tests-pass:
- name: All Tests Pass
- runs-on: ubuntu-latest
- needs: test
- if: success()
- steps:
- - run: echo "All matrix jobs passed"
From 6e3ba7f3e349ce81cb230cb3fbd116f8306d1445 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:21:55 +0100
Subject: [PATCH 09/17] add workflow
---
.github/workflows/ci.yml | 154 +++++++++++++++++++++++++++++++++++++++
1 file changed, 154 insertions(+)
create mode 100644 .github/workflows/ci.yml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..98b14dee
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,154 @@
+name: CI
+run-name: ci
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - "main"
+ - "dev"
+ pull_request:
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+defaults:
+ run:
+ shell: bash
+env:
+ NODE_VERSION: 25
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ browser: [electron, chrome]
+ include:
+ - os: ubuntu-latest
+ browser: chromium
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: Install elan
+ uses: leanprover/lean-action@v1
+ with:
+ lake-package-directory: Projects/MathlibDemo
+ use-mathlib-cache: false
+ use-github-cache: false
+ auto-config: false
+ build: false
+ test: false
+ lint: false
+
+ - name: Build sample projects
+ run: npm run build:server
+
+ - name: Ensure sample project 'MathlibDemo' is built
+ run: |
+ cd Projects/MathlibDemo
+ lake build --no-build
+
+ - name: Ensure sample project 'Stable' is built
+ run: |
+ cd Projects/Stable
+ lake build --no-build
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build client for production
+ run: npm run build:client
+
+ - name: Run Cypress tests Development (No Video)
+ id: cypress_no_video
+ run: npm run test:dev
+ env:
+ CYPRESS_DEFAULT_BROWSER: ${{ matrix.browser }}
+ CYPRESS_VIDEO: false
+
+ - name: Upload screenshots on failure
+ if: failure() && steps.cypress_no_video.conclusion == 'failure'
+ uses: actions/upload-artifact@v4
+ with:
+ name: cypress-screenshots-${{ matrix.os }}-${{ matrix.browser }}
+ path: cypress/screenshots
+
+ - name: Run Cypress tests Development (With Video)
+ id: cypress_with_video
+ if: failure() && steps.cypress_no_video.conclusion == 'failure'
+ run: npm run test:dev
+ env:
+ CYPRESS_DEFAULT_BROWSER: ${{ matrix.browser }}
+ CYPRESS_VIDEO: true
+ CYPRESS_VIDEO_COMPRESSION: true
+
+ - name: Upload videos
+ if: failure() && steps.cypress_no_video.conclusion == 'failure'
+ uses: actions/upload-artifact@v4
+ with:
+ name: cypress-videos-${{ matrix.os }}-${{ matrix.browser }}
+ path: cypress/videos
+
+ - name: Install bubblewrap
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get install bubblewrap
+ echo -e "abi ,\ninclude \n\nprofile bwrap /usr/bin/bwrap flags=(unconfined) {\n userns,\n\n include if exists \n}" | sudo tee /etc/apparmor.d/bwrap
+ sudo systemctl reload apparmor
+
+ - name: Run Cypress tests Production (No Video)
+ if: runner.os == 'Linux'
+ id: cypress_production_no_video
+ run: npm run test:prod
+ env:
+ CYPRESS_DEFAULT_BROWSER: ${{ matrix.browser }}
+ CYPRESS_VIDEO: false
+
+ - name: Upload screenshots on failure
+ if: runner.os == 'Linux' && failure() && steps.cypress_production_no_video.conclusion == 'failure'
+ uses: actions/upload-artifact@v4
+ with:
+ name: cypress-screenshots-production-${{ matrix.os }}-${{ matrix.browser }}
+ path: cypress/screenshots
+
+ - name: Run Cypress tests Production with Windows User Agent (No Video)
+ if: matrix.browser == 'chromium'
+ id: cypress_production_windows_no_video
+ run: npm run test:prod:windows
+ env:
+ CYPRESS_DEFAULT_BROWSER: ${{ matrix.browser }}
+ CYPRESS_VIDEO: false
+
+ - name: Upload screenshots on failure
+ if: matrix.browser == 'chromium' && failure() && steps.cypress_production_windows_no_video.conclusion == 'failure'
+ uses: actions/upload-artifact@v4
+ with:
+ name: cypress-screenshots-production-windows-${{ matrix.os }}-${{ matrix.browser }}
+ path: cypress/screenshots
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ - name: Install dependencies
+ run: npm ci
+ - name: lint
+ run: npm run lint
+ all-tests-pass:
+ name: All Tests Pass
+ runs-on: ubuntu-latest
+ needs: test
+ if: success()
+ steps:
+ - run: echo "All matrix jobs passed"
From beffee300fe9f97a185fba3bd17dc3c511ff8161 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:27:06 +0100
Subject: [PATCH 10/17] add workflow
---
.github/workflows/ci.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 98b14dee..132d6cc1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -134,6 +134,7 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
+ continue-on-error: true # TODO: remove again
steps:
- name: Checkout repository
uses: actions/checkout@v4
From 92dec3890327e79043f86038864c872662dd24d9 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:29:46 +0100
Subject: [PATCH 11/17] ci
---
.github/workflows/ci.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 132d6cc1..98b14dee 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -134,7 +134,6 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
- continue-on-error: true # TODO: remove again
steps:
- name: Checkout repository
uses: actions/checkout@v4
From 88388ebe7b7b2a94a4b2e5ecf2135b9b6e702f2f Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:34:01 +0100
Subject: [PATCH 12/17] cleanup navigation
---
client/src/App.tsx | 2 +-
client/src/Popups/Impressum.tsx | 2 +-
client/src/Popups/LoadUrl.tsx | 2 +-
client/src/Popups/LoadZulip.tsx | 2 +-
client/src/Popups/PrivacyPolicy.tsx | 2 +-
client/src/Popups/Tools.tsx | 2 +-
client/src/{ => navigation}/Navigation.tsx | 47 ++++++----------------
client/src/navigation/Popup.tsx | 22 ++++++++++
client/src/settings/SettingsPopup.tsx | 2 +-
9 files changed, 42 insertions(+), 41 deletions(-)
rename client/src/{ => navigation}/Navigation.tsx (88%)
create mode 100644 client/src/navigation/Popup.tsx
diff --git a/client/src/App.tsx b/client/src/App.tsx
index f68587b2..ede11e3a 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -13,7 +13,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import Split from 'react-split'
import LeanLogo from './assets/logo.svg'
-import { Menu } from './Navigation'
+import { Menu } from './navigation/Navigation'
import { mobileAtom, settingsAtom, settingsUrlAtom } from './settings/settings-atoms'
import { lightThemes } from './settings/settings-types'
import { screenWidthAtom } from './store/window-atoms'
diff --git a/client/src/Popups/Impressum.tsx b/client/src/Popups/Impressum.tsx
index 539f8ae8..887471ec 100644
--- a/client/src/Popups/Impressum.tsx
+++ b/client/src/Popups/Impressum.tsx
@@ -1,5 +1,5 @@
import lean4webConfig from '../config/config'
-import { Popup } from '../Navigation'
+import { Popup } from '../navigation/Popup'
/** The popup with the privacy policy. */
function ImpressumPopup({ open, handleClose }: { open: boolean; handleClose: () => void }) {
diff --git a/client/src/Popups/LoadUrl.tsx b/client/src/Popups/LoadUrl.tsx
index 14553221..ae6f5ef8 100644
--- a/client/src/Popups/LoadUrl.tsx
+++ b/client/src/Popups/LoadUrl.tsx
@@ -1,6 +1,6 @@
import { FormEvent, useRef, useState } from 'react'
-import { Popup } from '../Navigation'
+import { Popup } from '../navigation/Popup'
function LoadUrlPopup({
open,
diff --git a/client/src/Popups/LoadZulip.tsx b/client/src/Popups/LoadZulip.tsx
index 200fe607..40ea15fd 100644
--- a/client/src/Popups/LoadZulip.tsx
+++ b/client/src/Popups/LoadZulip.tsx
@@ -1,6 +1,6 @@
import { FormEvent, useRef, useState } from 'react'
-import { Popup } from '../Navigation'
+import { Popup } from '../navigation/Popup'
function LoadZulipPopup({
open,
diff --git a/client/src/Popups/PrivacyPolicy.tsx b/client/src/Popups/PrivacyPolicy.tsx
index c5f93841..bf06b4f0 100644
--- a/client/src/Popups/PrivacyPolicy.tsx
+++ b/client/src/Popups/PrivacyPolicy.tsx
@@ -1,5 +1,5 @@
import lean4webConfig from '../config/config'
-import { Popup } from '../Navigation'
+import { Popup } from '../navigation/Popup'
/** The popup with the privacy policy. */
function PrivacyPopup({ open, handleClose }: { open: boolean; handleClose: () => void }) {
diff --git a/client/src/Popups/Tools.tsx b/client/src/Popups/Tools.tsx
index 91e3eee7..8e00db34 100644
--- a/client/src/Popups/Tools.tsx
+++ b/client/src/Popups/Tools.tsx
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react'
-import { Popup } from '../Navigation'
+import { Popup } from '../navigation/Popup'
// TODO: Do these interfaces exist somewhere in vscode-lean4?
// They might need to be updated manually if changes to `lake` occur.
diff --git a/client/src/Navigation.tsx b/client/src/navigation/Navigation.tsx
similarity index 88%
rename from client/src/Navigation.tsx
rename to client/src/navigation/Navigation.tsx
index d8988594..574fe476 100644
--- a/client/src/Navigation.tsx
+++ b/client/src/navigation/Navigation.tsx
@@ -16,42 +16,21 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useAtom } from 'jotai'
-import { ChangeEvent, Dispatch, ReactNode, SetStateAction, useState } from 'react'
+import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react'
+import lean4webConfig from '../config/config'
+import ImpressumPopup from '../Popups/Impressum'
+import LoadUrlPopup from '../Popups/LoadUrl'
+import LoadZulipPopup from '../Popups/LoadZulip'
+import PrivacyPopup from '../Popups/PrivacyPolicy'
+import ToolsPopup from '../Popups/Tools'
+import { mobileAtom } from '../settings/settings-atoms'
+import { SettingsPopup } from '../settings/SettingsPopup'
+import { save } from '../utils/SaveToFile'
+import { lookupUrl } from '../utils/UrlParsing'
import ZulipIcon from './assets/zulip.svg'
-import lean4webConfig from './config/config'
-import { Dropdown } from './navigation/Dropdown'
-import { NavButton } from './navigation/NavButton'
-import ImpressumPopup from './Popups/Impressum'
-import LoadUrlPopup from './Popups/LoadUrl'
-import LoadZulipPopup from './Popups/LoadZulip'
-import PrivacyPopup from './Popups/PrivacyPolicy'
-import ToolsPopup from './Popups/Tools'
-import { mobileAtom } from './settings/settings-atoms'
-import { SettingsPopup } from './settings/SettingsPopup'
-import { save } from './utils/SaveToFile'
-import { lookupUrl } from './utils/UrlParsing'
-
-/** A popup which overlays the entire screen. */
-export function Popup({
- open,
- handleClose,
- children,
-}: {
- open: boolean
- handleClose: () => void // TODO: what's the correct type?
- children?: ReactNode
-}) {
- return (
-
- )
-}
+import { Dropdown } from './Dropdown'
+import { NavButton } from './NavButton'
/** The menu items either appearing inside the dropdown or outside */
function FlexibleMenu({
diff --git a/client/src/navigation/Popup.tsx b/client/src/navigation/Popup.tsx
new file mode 100644
index 00000000..9c558d0b
--- /dev/null
+++ b/client/src/navigation/Popup.tsx
@@ -0,0 +1,22 @@
+import { ReactNode } from 'react'
+
+/** A popup which overlays the entire screen. */
+export function Popup({
+ open,
+ handleClose,
+ children,
+}: {
+ open: boolean
+ handleClose: () => void // TODO: what's the correct type?
+ children?: ReactNode
+}) {
+ return (
+
+ )
+}
diff --git a/client/src/settings/SettingsPopup.tsx b/client/src/settings/SettingsPopup.tsx
index 6a0f1451..c7600d91 100644
--- a/client/src/settings/SettingsPopup.tsx
+++ b/client/src/settings/SettingsPopup.tsx
@@ -4,7 +4,7 @@ import Switch from '@mui/material/Switch'
import { useAtom } from 'jotai/react'
import { useState } from 'react'
-import { Popup } from '../Navigation'
+import { Popup } from '../navigation/Popup'
import { shallowEqualSubset } from '../utils/shallowEqual'
import { applySettingsAtom, settingsAtom } from './settings-atoms'
import type { MobileValues, Theme } from './settings-types'
From 43dbf5cb0363e34e52c4ea7c26d9e3e5eda9b42a Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:36:28 +0100
Subject: [PATCH 13/17] less strict linting
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index f071cb28..8ace571c 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"build:client": "tsc -b && NODE_ENV=production vite build",
"production": "NODE_ENV=production node server/index.mjs",
"dev": "vite",
- "lint": "eslint --max-warnings=0 .",
+ "lint": "eslint .",
"format": "prettier --write .",
"preview": "vite preview",
"test:dev:bare": "wait-on \"http://localhost:8080\" \"http://localhost:3000\" && cypress run",
From 736bce8b359296dfb2d6da6d19caea23a973d722 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:42:50 +0100
Subject: [PATCH 14/17] fix imports
---
client/src/navigation/Navigation.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/client/src/navigation/Navigation.tsx b/client/src/navigation/Navigation.tsx
index 574fe476..66b72409 100644
--- a/client/src/navigation/Navigation.tsx
+++ b/client/src/navigation/Navigation.tsx
@@ -1,5 +1,5 @@
-import './css/Modal.css'
-import './css/Navigation.css'
+import '../css/Modal.css'
+import '../css/Navigation.css'
import { faArrowRotateRight, faCode, faInfoCircle } from '@fortawesome/free-solid-svg-icons'
import {
From ddde331645aa5f04ad245125fcfbdb466e091d79 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 18:43:17 +0100
Subject: [PATCH 15/17] less strict linting
---
client/src/navigation/Navigation.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/navigation/Navigation.tsx b/client/src/navigation/Navigation.tsx
index 66b72409..64e1eb0e 100644
--- a/client/src/navigation/Navigation.tsx
+++ b/client/src/navigation/Navigation.tsx
@@ -18,6 +18,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useAtom } from 'jotai'
import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react'
+import ZulipIcon from '../assets/zulip.svg'
import lean4webConfig from '../config/config'
import ImpressumPopup from '../Popups/Impressum'
import LoadUrlPopup from '../Popups/LoadUrl'
@@ -28,7 +29,6 @@ import { mobileAtom } from '../settings/settings-atoms'
import { SettingsPopup } from '../settings/SettingsPopup'
import { save } from '../utils/SaveToFile'
import { lookupUrl } from '../utils/UrlParsing'
-import ZulipIcon from './assets/zulip.svg'
import { Dropdown } from './Dropdown'
import { NavButton } from './NavButton'
From abb79bcc9b28dd929afd557d48a1ba228bc39991 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Sun, 28 Dec 2025 19:44:36 +0100
Subject: [PATCH 16/17] fix: restore useEffects as they were
---
client/src/App.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/client/src/App.tsx b/client/src/App.tsx
index ede11e3a..dbc3a859 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -82,7 +82,7 @@ function App() {
console.log(`[Lean4web] Setting project to ${project}`)
setProject(project)
- }, [setContent])
+ }, [])
// save the screen width in jotai
useEffect(() => {
@@ -233,7 +233,7 @@ function App() {
leanMonacoEditor.dispose()
_leanMonaco.dispose()
}
- }, [project, settings, options, infoviewRef, editorRef, code, mobile])
+ }, [project, settings, options, infoviewRef, editorRef])
// Load content from source URL.
// Once the editor is loaded, this reads the content of any provided `url=` in the URL and
@@ -327,7 +327,7 @@ function App() {
}
}
history.replaceState(undefined, undefined!, formatArgs(args))
- }, [editor, project, code, codeFromUrl, url, settings.compress])
+ }, [editor, project, code, codeFromUrl])
// Disable monaco context menu outside the editor
useEffect(() => {
From 17594927d216bf8a87913fc01967ccdc7099a976 Mon Sep 17 00:00:00 2001
From: Jon Eugster
Date: Mon, 29 Dec 2025 01:52:43 +0100
Subject: [PATCH 17/17] fix test
---
cypress/e2e/spec.cy.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cypress/e2e/spec.cy.ts b/cypress/e2e/spec.cy.ts
index 1e90f6f7..31b2ea19 100644
--- a/cypress/e2e/spec.cy.ts
+++ b/cypress/e2e/spec.cy.ts
@@ -146,8 +146,8 @@ describe("The Editor", () => {
"example (P: Prop) : P \\or \\not P := by apply?",
);
cy.contains("div.view-line", "apply?").should("exist");
- cy.wait(1000);
cy.get(".squiggly-info").should("exist");
+ cy.get(".codicon-gutter-lightbulb").should("be.visible");
cy.realPress([modBtn, "."]);
cy.contains(".action-widget", "Try this: exact Classical.em P").should(
"exist",