diff --git a/client/.prettierignore b/client/.prettierignore
new file mode 100644
index 0000000..308b3de
--- /dev/null
+++ b/client/.prettierignore
@@ -0,0 +1,5 @@
+dist/
+storybook-static/
+node_modules/
+*.css
+*.json
diff --git a/client/.prettierrc b/client/.prettierrc
new file mode 100644
index 0000000..1d9d0f6
--- /dev/null
+++ b/client/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "semi": true,
+ "singleQuote": true,
+ "trailingComma": "all",
+ "printWidth": 100,
+ "tabWidth": 2,
+ "arrowParens": "always"
+}
diff --git a/client/.storybook/main.ts b/client/.storybook/main.ts
index 03f32f9..931b608 100644
--- a/client/.storybook/main.ts
+++ b/client/.storybook/main.ts
@@ -1,26 +1,23 @@
import type { StorybookConfig } from '@storybook/react-vite';
-import { dirname } from "path"
+import { dirname } from 'path';
-import { fileURLToPath } from "url"
+import { fileURLToPath } from 'url';
/**
-* This function is used to resolve the absolute path of a package.
-* It is needed in projects that use Yarn PnP or are set up within a monorepo.
-*/
+ * This function is used to resolve the absolute path of a package.
+ * It is needed in projects that use Yarn PnP or are set up within a monorepo.
+ */
function getAbsolutePath(value: string) {
- return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)))
+ return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
}
const config: StorybookConfig = {
- "stories": [
- "./stories/**/*.mdx",
- "./stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"
- ],
- "addons": [
+ stories: ['./stories/**/*.mdx', './stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
+ addons: [
getAbsolutePath('@storybook/addon-vitest'),
getAbsolutePath('@storybook/addon-a11y'),
- getAbsolutePath('@storybook/addon-docs')
+ getAbsolutePath('@storybook/addon-docs'),
],
- "framework": getAbsolutePath('@storybook/react-vite')
+ framework: getAbsolutePath('@storybook/react-vite'),
};
-export default config;
\ No newline at end of file
+export default config;
diff --git a/client/.storybook/preview.tsx b/client/.storybook/preview.tsx
index 57d66d6..a1cc1ce 100644
--- a/client/.storybook/preview.tsx
+++ b/client/.storybook/preview.tsx
@@ -1,6 +1,6 @@
-import React from 'react'
-import type { Preview } from '@storybook/react-vite'
-import '../src/ui/tokens.css'
+import React from 'react';
+import type { Preview } from '@storybook/react-vite';
+import '../src/ui/tokens.css';
const preview: Preview = {
globalTypes: {
@@ -12,7 +12,7 @@ const preview: Preview = {
icon: 'circlehollow',
items: [
{ value: 'light', icon: 'sun', title: 'Light' },
- { value: 'dark', icon: 'moon', title: 'Dark' },
+ { value: 'dark', icon: 'moon', title: 'Dark' },
],
dynamicTitle: true,
},
@@ -20,13 +20,13 @@ const preview: Preview = {
},
decorators: [
(Story, context) => {
- const theme = (context.globals['theme'] as string) ?? 'light'
- document.documentElement.setAttribute('data-theme', theme)
+ const theme = (context.globals['theme'] as string) ?? 'light';
+ document.documentElement.setAttribute('data-theme', theme);
return (
- )
+ );
},
],
parameters: {
@@ -40,6 +40,6 @@ const preview: Preview = {
test: 'todo',
},
},
-}
+};
-export default preview
\ No newline at end of file
+export default preview;
diff --git a/client/.storybook/stories/Breadcrumbs.stories.tsx b/client/.storybook/stories/Breadcrumbs.stories.tsx
index cce2a08..a18a268 100644
--- a/client/.storybook/stories/Breadcrumbs.stories.tsx
+++ b/client/.storybook/stories/Breadcrumbs.stories.tsx
@@ -12,10 +12,7 @@ type Story = StoryObj;
export const Default: Story = {
args: {
- items: [
- { label: 'Scenarios', href: '#' },
- { label: 'Login flow' },
- ],
+ items: [{ label: 'Scenarios', href: '#' }, { label: 'Login flow' }],
},
};
diff --git a/client/.storybook/stories/Card.stories.tsx b/client/.storybook/stories/Card.stories.tsx
index 350f386..927d65c 100644
--- a/client/.storybook/stories/Card.stories.tsx
+++ b/client/.storybook/stories/Card.stories.tsx
@@ -30,16 +30,29 @@ export const Default: Story = {
export const ScenarioCard: Story = {
render: () => (
-
+
Login flow
- Passed
+
+ Passed
+
Last run 2 minutes ago · 4 steps
- Run
- Edit
+
+ Run
+
+
+ Edit
+
),
diff --git a/client/.storybook/stories/Colors.stories.tsx b/client/.storybook/stories/Colors.stories.tsx
index 7e36793..1b428c4 100644
--- a/client/.storybook/stories/Colors.stories.tsx
+++ b/client/.storybook/stories/Colors.stories.tsx
@@ -32,27 +32,51 @@ const surfaceColors = [
{ name: 'Primary fg', token: '--color-primary-fg', hex: '#2D3339' },
];
-function Swatch({ name, token, hex, role }: { name: string; token: string; hex: string; role?: string }) {
+function Swatch({
+ name,
+ token,
+ hex,
+ role,
+}: {
+ name: string;
+ token: string;
+ hex: string;
+ role?: string;
+}) {
return (
-
-
+
+
-
{name}
- {role &&
{role}
}
-
{hex}
-
{token}
+
+ {name}
+
+ {role && (
+
+ {role}
+
+ )}
+
+ {hex}
+
+
+ {token}
+
);
@@ -61,12 +85,20 @@ function Swatch({ name, token, hex, role }: { name: string; token: string; hex:
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
-
+
{title}
-
- {children}
-
+ {children}
);
}
@@ -74,18 +106,27 @@ function Section({ title, children }: { title: string; children: React.ReactNode
function ColorPalette() {
return (
-
Color Palette
+
+ Color Palette
+
- All colors are exposed as CSS custom properties on :root via tokens.css.
+ All colors are exposed as CSS custom properties on :root via{' '}
+ tokens.css.
- {palette.map(c => )}
+ {palette.map((c) => (
+
+ ))}
- {semanticColors.map(c => )}
+ {semanticColors.map((c) => (
+
+ ))}
- {surfaceColors.map(c => )}
+ {surfaceColors.map((c) => (
+
+ ))}
);
diff --git a/client/.storybook/stories/SidePanel.stories.tsx b/client/.storybook/stories/SidePanel.stories.tsx
index 0a03f52..ca62dba 100644
--- a/client/.storybook/stories/SidePanel.stories.tsx
+++ b/client/.storybook/stories/SidePanel.stories.tsx
@@ -5,7 +5,16 @@ const NAV_ITEMS = ['Dashboard', 'Scenarios', 'Environments', 'Reports', 'Setting
function NavList() {
return (
-
+
{NAV_ITEMS.map((item) => (
= {
(Story) => (
-
diff --git a/client/.storybook/stories/Table.stories.tsx b/client/.storybook/stories/Table.stories.tsx
index 3469eb5..d733e62 100644
--- a/client/.storybook/stories/Table.stories.tsx
+++ b/client/.storybook/stories/Table.stories.tsx
@@ -18,9 +18,9 @@ interface User {
}
const users: User[] = [
- { id: 1, name: 'Alice Müller', email: 'alice@example.com', status: 'active' },
- { id: 2, name: 'Bob Nguyen', email: 'bob@example.com', status: 'inactive' },
- { id: 3, name: 'Carol Santos', email: 'carol@example.com', status: 'active' },
+ { id: 1, name: 'Alice Müller', email: 'alice@example.com', status: 'active' },
+ { id: 2, name: 'Bob Nguyen', email: 'bob@example.com', status: 'inactive' },
+ { id: 3, name: 'Carol Santos', email: 'carol@example.com', status: 'active' },
];
export const Default: StoryObj
> = {
@@ -29,17 +29,15 @@ export const Default: StoryObj> = {
rowKey: (u) => u.id,
emptyMessage: 'No users found.',
columns: [
- { key: 'id', header: 'ID', render: (u) => u.id, width: 60 },
- { key: 'name', header: 'Name', render: (u) => u.name },
- { key: 'email', header: 'Email', render: (u) => u.email },
+ { key: 'id', header: 'ID', render: (u) => u.id, width: 60 },
+ { key: 'name', header: 'Name', render: (u) => u.name },
+ { key: 'email', header: 'Email', render: (u) => u.email },
{
key: 'status',
header: 'Status',
width: 110,
render: (u) => (
-
- {u.status}
-
+ {u.status}
),
},
],
diff --git a/client/.storybook/stories/Timestamp.stories.tsx b/client/.storybook/stories/Timestamp.stories.tsx
new file mode 100644
index 0000000..d2da94d
--- /dev/null
+++ b/client/.storybook/stories/Timestamp.stories.tsx
@@ -0,0 +1,21 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { Timestamp } from '../../src/ui/Timestamp/Timestamp';
+
+const meta: Meta = {
+ title: 'UI/Timestamp',
+ component: Timestamp,
+ parameters: { layout: 'centered' },
+ tags: ['autodocs'],
+};
+export default meta;
+type Story = StoryObj;
+
+const minutesAgo = (n: number) => new Date(Date.now() - n * 60_000).toISOString();
+const hoursAgo = (n: number) => new Date(Date.now() - n * 3_600_000).toISOString();
+const daysAgo = (n: number) => new Date(Date.now() - n * 86_400_000).toISOString();
+
+export const JustNow: Story = { args: { value: minutesAgo(1) } };
+export const MinutesAgo: Story = { args: { value: minutesAgo(15) } };
+export const HoursAgo: Story = { args: { value: hoursAgo(3) } };
+export const DaysAgo: Story = { args: { value: daysAgo(5) } };
+export const MonthsAgo: Story = { args: { value: daysAgo(90) } };
diff --git a/client/eslint.config.mjs b/client/eslint.config.mjs
new file mode 100644
index 0000000..b0d4b09
--- /dev/null
+++ b/client/eslint.config.mjs
@@ -0,0 +1,34 @@
+import js from '@eslint/js';
+import tseslint from 'typescript-eslint';
+import reactHooks from 'eslint-plugin-react-hooks';
+import reactRefresh from 'eslint-plugin-react-refresh';
+import prettier from 'eslint-plugin-prettier';
+import prettierConfig from 'eslint-config-prettier';
+
+export default tseslint.config(
+ { ignores: ['dist', 'storybook-static'] },
+
+ // Base JS rules
+ js.configs.recommended,
+
+ // TypeScript rules
+ ...tseslint.configs.recommended,
+
+ // React rules
+ {
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ prettier,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
+ 'prettier/prettier': 'warn',
+ '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
+ },
+ },
+
+ // Disable ESLint formatting rules that conflict with Prettier
+ prettierConfig,
+);
diff --git a/client/package.json b/client/package.json
index 4abab3d..c36d386 100644
--- a/client/package.json
+++ b/client/package.json
@@ -8,17 +8,23 @@
"build": "tsc -b && vite build",
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
- "build-storybook": "storybook build"
+ "build-storybook": "storybook build",
+ "test:storybook": "vitest run --project storybook",
+ "lint": "eslint src .storybook",
+ "lint:fix": "eslint src .storybook --fix",
+ "format": "prettier --write \"src/**/*.{ts,tsx}\" \".storybook/**/*.{ts,tsx}\""
},
"dependencies": {
"i18next": "^26.0.4",
"lucide-react": "^1.7.0",
+ "moment": "^2.30.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^17.0.2",
"react-router-dom": "^7.14.0"
},
"devDependencies": {
+ "@eslint/js": "^9.39.4",
"@storybook/addon-a11y": "^10.3.5",
"@storybook/addon-docs": "^10.3.5",
"@storybook/addon-vitest": "^10.3.5",
@@ -28,9 +34,16 @@
"@vitejs/plugin-react": "^4.4.1",
"@vitest/browser-playwright": "^4.1.3",
"@vitest/coverage-v8": "^4.1.3",
+ "eslint": "^10.2.0",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.5",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
"playwright": "^1.59.1",
+ "prettier": "^3.8.1",
"storybook": "^10.3.5",
"typescript": "~5.8.3",
+ "typescript-eslint": "^8.58.1",
"vite": "^6.3.1",
"vitest": "^4.1.3"
}
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 34b5ac2..2d63bd5 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -11,9 +11,9 @@ import type { LucideIcon } from 'lucide-react';
const NAV: { path: string; labelKey: string; Icon: LucideIcon }[] = [
{ path: '/environments', labelKey: 'nav.environments', Icon: Globe },
- { path: '/keys', labelKey: 'nav.keys', Icon: KeyRound },
- { path: '/sessions', labelKey: 'nav.sessions', Icon: Monitor },
- { path: '/scenarios', labelKey: 'nav.scenarios', Icon: ClipboardList },
+ { path: '/keys', labelKey: 'nav.keys', Icon: KeyRound },
+ { path: '/sessions', labelKey: 'nav.sessions', Icon: Monitor },
+ { path: '/scenarios', labelKey: 'nav.scenarios', Icon: ClipboardList },
];
export default function App() {
@@ -50,10 +50,10 @@ export default function App() {
} />
} />
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
diff --git a/client/src/hooks/useTheme.ts b/client/src/hooks/useTheme.ts
index 2dd08b4..e27ca7b 100644
--- a/client/src/hooks/useTheme.ts
+++ b/client/src/hooks/useTheme.ts
@@ -19,7 +19,7 @@ export function useTheme() {
}, [theme]);
function toggleTheme() {
- setTheme(t => (t === 'light' ? 'dark' : 'light'));
+ setTheme((t) => (t === 'light' ? 'dark' : 'light'));
}
return { theme, toggleTheme } as const;
diff --git a/client/src/i18n/index.ts b/client/src/i18n/index.ts
index 2e1ee67..79a43c1 100644
--- a/client/src/i18n/index.ts
+++ b/client/src/i18n/index.ts
@@ -2,13 +2,11 @@ import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './locales/en.json';
-i18n
- .use(initReactI18next)
- .init({
- resources: { en: { translation: en } },
- lng: 'en',
- fallbackLng: 'en',
- interpolation: { escapeValue: false },
- });
+i18n.use(initReactI18next).init({
+ resources: { en: { translation: en } },
+ lng: 'en',
+ fallbackLng: 'en',
+ interpolation: { escapeValue: false },
+});
export default i18n;
diff --git a/client/src/main.tsx b/client/src/main.tsx
index 3334ef8..8e2a7ec 100644
--- a/client/src/main.tsx
+++ b/client/src/main.tsx
@@ -1,9 +1,9 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import { HashRouter } from 'react-router-dom'
-import './i18n'
-import './ui/tokens.css'
-import App from './App'
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import { HashRouter } from 'react-router-dom';
+import './i18n';
+import './ui/tokens.css';
+import App from './App';
createRoot(document.getElementById('root')!).render(
@@ -11,4 +11,4 @@ createRoot(document.getElementById('root')!).render(
,
-)
+);
diff --git a/client/src/pages/EnvironmentsPage.tsx b/client/src/pages/EnvironmentsPage.tsx
index 3fa13b1..7c0681c 100644
--- a/client/src/pages/EnvironmentsPage.tsx
+++ b/client/src/pages/EnvironmentsPage.tsx
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { environments } from '../api';
import type { Environment } from '../api';
-import { Table, type TableColumn } from '../ui';
+import { Table, type TableColumn, Timestamp } from '../ui';
import styles from './Page.module.css';
export function EnvironmentsPage() {
@@ -12,8 +12,8 @@ export function EnvironmentsPage() {
const [error, setError] = useState(null);
const columns: TableColumn[] = [
- { key: 'id', header: t('environments.col_id'), render: (e) => e.id, width: 60 },
- { key: 'name', header: t('environments.col_name'), render: (e) => e.name },
+ { key: 'id', header: t('environments.col_id'), render: (e) => e.id, width: 60 },
+ { key: 'name', header: t('environments.col_name'), render: (e) => e.name },
{
key: 'urls',
header: t('environments.col_urls'),
@@ -26,14 +26,14 @@ export function EnvironmentsPage() {
{
key: 'updated',
header: t('environments.col_updated'),
- width: 120,
- render: (e) => new Date(e.updatedAt).toLocaleDateString(),
+ width: 140,
+ render: (e) => ,
},
];
useEffect(() => {
- setLoading(true);
- environments.list()
+ environments
+ .list()
.then((res) => setItems(res.data))
.catch((err: Error) => setError(err.message))
.finally(() => setLoading(false));
diff --git a/client/src/pages/KeysPage.tsx b/client/src/pages/KeysPage.tsx
index 8441022..ba06694 100644
--- a/client/src/pages/KeysPage.tsx
+++ b/client/src/pages/KeysPage.tsx
@@ -4,7 +4,9 @@ import { keys } from '../api';
import { Table, type TableColumn } from '../ui';
import styles from './Page.module.css';
-interface KeyRow { name: string }
+interface KeyRow {
+ name: string;
+}
export function KeysPage() {
const { t } = useTranslation();
@@ -17,8 +19,8 @@ export function KeysPage() {
];
useEffect(() => {
- setLoading(true);
- keys.list()
+ keys
+ .list()
.then((res) => setItems(res.keys.map((name) => ({ name }))))
.catch((err: Error) => setError(err.message))
.finally(() => setLoading(false));
diff --git a/client/src/pages/ScenariosPage.tsx b/client/src/pages/ScenariosPage.tsx
index 24f818c..a168e8e 100644
--- a/client/src/pages/ScenariosPage.tsx
+++ b/client/src/pages/ScenariosPage.tsx
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { scenarios } from '../api';
import type { Scenario } from '../api';
-import { Button, Table, type TableColumn } from '../ui';
+import { Button, Table, type TableColumn, Timestamp } from '../ui';
import styles from './Page.module.css';
export function ScenariosPage() {
@@ -12,14 +12,16 @@ export function ScenariosPage() {
const [error, setError] = useState(null);
const load = useCallback(() => {
- setLoading(true);
- scenarios.list()
+ scenarios
+ .list()
.then((res) => setItems(res.data))
.catch((err: Error) => setError(err.message))
.finally(() => setLoading(false));
}, []);
- useEffect(load, [load]);
+ useEffect(() => {
+ load();
+ }, [load]);
const handleRun = async (id: number) => {
await scenarios.run(id);
@@ -27,17 +29,18 @@ export function ScenariosPage() {
const handleDelete = async (id: number) => {
await scenarios.remove(id);
+ setLoading(true);
load();
};
const columns: TableColumn[] = [
- { key: 'id', header: t('scenarios.col_id'), render: (s) => s.id, width: 60 },
- { key: 'name', header: t('scenarios.col_name'), render: (s) => s.name },
+ { key: 'id', header: t('scenarios.col_id'), render: (s) => s.id, width: 60 },
+ { key: 'name', header: t('scenarios.col_name'), render: (s) => s.name },
{
key: 'updated',
header: t('scenarios.col_updated'),
- width: 120,
- render: (s) => new Date(s.updatedAt).toLocaleDateString(),
+ width: 140,
+ render: (s) => ,
},
{
key: 'actions',
@@ -46,8 +49,12 @@ export function ScenariosPage() {
align: 'right',
render: (s) => (
- handleRun(s.id)}>{t('scenarios.action_run')}
- handleDelete(s.id)}>{t('scenarios.action_delete')}
+ handleRun(s.id)}>
+ {t('scenarios.action_run')}
+
+ handleDelete(s.id)}>
+ {t('scenarios.action_delete')}
+
),
},
diff --git a/client/src/pages/SessionsPage.tsx b/client/src/pages/SessionsPage.tsx
index 11ac67a..d25a05b 100644
--- a/client/src/pages/SessionsPage.tsx
+++ b/client/src/pages/SessionsPage.tsx
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { sessions } from '../api';
import type { Session } from '../api';
-import { Badge, Button, Table, type TableColumn } from '../ui';
+import { Badge, Button, Table, type TableColumn, Timestamp } from '../ui';
import styles from './Page.module.css';
export function SessionsPage() {
@@ -12,38 +12,39 @@ export function SessionsPage() {
const [error, setError] = useState(null);
const load = useCallback(() => {
- setLoading(true);
- sessions.list()
+ sessions
+ .list()
.then((res) => setItems(res.data))
.catch((err: Error) => setError(err.message))
.finally(() => setLoading(false));
}, []);
- useEffect(load, [load]);
+ useEffect(() => {
+ load();
+ }, [load]);
const handleDelete = async (id: number) => {
await sessions.remove(id);
+ setLoading(true);
load();
};
const columns: TableColumn[] = [
- { key: 'id', header: t('sessions.col_id'), render: (s) => s.id, width: 60 },
- { key: 'name', header: t('sessions.col_name'), render: (s) => s.sessionName },
+ { key: 'id', header: t('sessions.col_id'), render: (s) => s.id, width: 60 },
+ { key: 'name', header: t('sessions.col_name'), render: (s) => s.sessionName },
{
key: 'status',
header: t('sessions.col_status'),
width: 100,
render: (s) => (
-
- {s.status}
-
+ {s.status}
),
},
{
key: 'lastUsed',
header: t('sessions.col_lastUsed'),
width: 160,
- render: (s) => s.lastUsedAt ? new Date(s.lastUsedAt).toLocaleString() : '—',
+ render: (s) => (s.lastUsedAt ? : '—'),
},
{
key: 'actions',
diff --git a/client/src/ui/Breadcrumbs/Breadcrumbs.tsx b/client/src/ui/Breadcrumbs/Breadcrumbs.tsx
index 83a77e9..e2d38d4 100644
--- a/client/src/ui/Breadcrumbs/Breadcrumbs.tsx
+++ b/client/src/ui/Breadcrumbs/Breadcrumbs.tsx
@@ -16,7 +16,13 @@ export interface BreadcrumbsProps {
export function Breadcrumbs({ items, separator, className }: BreadcrumbsProps) {
const sep = separator ?? (
-
+
);
@@ -28,11 +34,17 @@ export function Breadcrumbs({ items, separator, className }: BreadcrumbsProps) {
return (
{isLast ? (
- {item.label}
+
+ {item.label}
+
) : item.href ? (
- {item.label}
+
+ {item.label}
+
) : (
- {item.label}
+
+ {item.label}
+
)}
{!isLast && {sep} }
diff --git a/client/src/ui/Button/Button.tsx b/client/src/ui/Button/Button.tsx
index 62136ab..643388f 100644
--- a/client/src/ui/Button/Button.tsx
+++ b/client/src/ui/Button/Button.tsx
@@ -18,7 +18,9 @@ export function Button({
}: ButtonProps) {
return (
diff --git a/client/src/ui/Input/Input.tsx b/client/src/ui/Input/Input.tsx
index 4bd496e..c1ecdaa 100644
--- a/client/src/ui/Input/Input.tsx
+++ b/client/src/ui/Input/Input.tsx
@@ -18,7 +18,9 @@ export function Input({ label, hint, error, className, ...props }: InputProps) {
)}
@@ -27,7 +35,9 @@ export function Select({ label, hint, error, options, placeholder, className, ..
diff --git a/client/src/ui/SidePanel/SidePanel.tsx b/client/src/ui/SidePanel/SidePanel.tsx
index d87d7b9..1c13fa5 100644
--- a/client/src/ui/SidePanel/SidePanel.tsx
+++ b/client/src/ui/SidePanel/SidePanel.tsx
@@ -32,7 +32,9 @@ export function SidePanel({
return (
diff --git a/client/src/ui/Timestamp/Timestamp.module.css b/client/src/ui/Timestamp/Timestamp.module.css
new file mode 100644
index 0000000..562c0bf
--- /dev/null
+++ b/client/src/ui/Timestamp/Timestamp.module.css
@@ -0,0 +1,18 @@
+.timestamp {
+ display: inline-flex;
+ align-items: center;
+ font-family: var(--font-family);
+ font-size: var(--font-size-xs);
+ font-weight: 500;
+ padding: 2px var(--space-2);
+ border-radius: var(--radius-full);
+ white-space: nowrap;
+ line-height: 1.6;
+ background: var(--color-neutral-bg);
+ color: var(--color-neutral-fg);
+ cursor: default;
+}
+
+.timestamp:hover {
+ background: var(--color-bg-subtle);
+}
diff --git a/client/src/ui/Timestamp/Timestamp.tsx b/client/src/ui/Timestamp/Timestamp.tsx
new file mode 100644
index 0000000..6419ef6
--- /dev/null
+++ b/client/src/ui/Timestamp/Timestamp.tsx
@@ -0,0 +1,18 @@
+import moment from 'moment';
+import styles from './Timestamp.module.css';
+
+export interface TimestampProps {
+ value: string | number | Date;
+}
+
+export function Timestamp({ value }: TimestampProps) {
+ const m = moment(value);
+ const iso = m.toISOString();
+ const relative = m.fromNow();
+
+ return (
+
+ {relative}
+
+ );
+}
diff --git a/client/src/ui/index.ts b/client/src/ui/index.ts
index ddf1e2c..56165ba 100644
--- a/client/src/ui/index.ts
+++ b/client/src/ui/index.ts
@@ -21,5 +21,8 @@ export type { BreadcrumbsProps, BreadcrumbItem } from './Breadcrumbs/Breadcrumbs
export { ThemeSwitcher } from './ThemeSwitcher/ThemeSwitcher';
+export { Timestamp } from './Timestamp/Timestamp';
+export type { TimestampProps } from './Timestamp/Timestamp';
+
export { Table } from './Table/Table';
export type { TableProps, TableColumn } from './Table/Table';
diff --git a/package-lock.json b/package-lock.json
index 0f2a152..53321a5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,12 +18,14 @@
"dependencies": {
"i18next": "^26.0.4",
"lucide-react": "^1.7.0",
+ "moment": "^2.30.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^17.0.2",
"react-router-dom": "^7.14.0"
},
"devDependencies": {
+ "@eslint/js": "^9.39.4",
"@storybook/addon-a11y": "^10.3.5",
"@storybook/addon-docs": "^10.3.5",
"@storybook/addon-vitest": "^10.3.5",
@@ -33,13 +35,230 @@
"@vitejs/plugin-react": "^4.4.1",
"@vitest/browser-playwright": "^4.1.3",
"@vitest/coverage-v8": "^4.1.3",
+ "eslint": "^10.2.0",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.5",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
"playwright": "^1.59.1",
+ "prettier": "^3.8.1",
"storybook": "^10.3.5",
"typescript": "~5.8.3",
+ "typescript-eslint": "^8.58.1",
"vite": "^6.3.1",
"vitest": "^4.1.3"
}
},
+ "client/node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "client/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "client/node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "client/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "client/node_modules/eslint": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz",
+ "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.4",
+ "@eslint/config-helpers": "^0.5.4",
+ "@eslint/core": "^1.2.0",
+ "@eslint/plugin-kit": "^0.7.0",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^9.1.2",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.2.0",
+ "esquery": "^1.7.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "minimatch": "^10.2.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "client/node_modules/eslint-scope": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "client/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "client/node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "client/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "client/node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "client/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "client/node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"client/node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -1522,6 +1741,117 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.5",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
"node_modules/@eslint/object-schema": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
@@ -3830,65 +4160,6 @@
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/@testing-library/dom": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
- "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^5.0.1",
- "aria-query": "5.3.0",
- "dom-accessibility-api": "^0.5.9",
- "lz-string": "^1.5.0",
- "picocolors": "1.1.1",
- "pretty-format": "^27.0.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@testing-library/dom/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@testing-library/dom/node_modules/pretty-format": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
- "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "ansi-regex": "^5.0.1",
- "ansi-styles": "^5.0.0",
- "react-is": "^17.0.1"
- },
- "engines": {
- "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
- }
- },
- "node_modules/@testing-library/dom/node_modules/react-is": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
"node_modules/@testing-library/jest-dom": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
@@ -3963,14 +4234,6 @@
"@types/estree": "*"
}
},
- "node_modules/@types/aria-query": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
- "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -6684,14 +6947,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/dom-accessibility-api": {
- "version": "0.5.16",
- "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
- "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
"node_modules/dotenv": {
"version": "17.2.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
@@ -6968,30 +7223,34 @@
}
},
"node_modules/eslint": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz",
- "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==",
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.2",
- "@eslint/config-array": "^0.23.4",
- "@eslint/config-helpers": "^0.5.4",
- "@eslint/core": "^1.2.0",
- "@eslint/plugin-kit": "^0.7.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.2",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "9.39.4",
+ "@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
"ajv": "^6.14.0",
+ "chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^9.1.2",
- "eslint-visitor-keys": "^5.0.1",
- "espree": "^11.2.0",
- "esquery": "^1.7.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^8.0.0",
@@ -7001,7 +7260,8 @@
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "minimatch": "^10.2.4",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.5",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
@@ -7009,7 +7269,7 @@
"eslint": "bin/eslint.js"
},
"engines": {
- "node": "^20.19.0 || ^22.13.0 || >=24"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
@@ -7039,6 +7299,67 @@
"eslint": ">=7.0.0"
}
},
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.5.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
+ "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.1",
+ "synckit": "^0.11.12"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "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.5.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
+ "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": "^9 || ^10"
+ }
+ },
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -7066,12 +7387,83 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/eslint/node_modules/@eslint/config-array": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/eslint/node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/eslint/node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/eslint/node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/eslint/node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
"node_modules/eslint/node_modules/ajv": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -7083,35 +7475,13 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/eslint/node_modules/balanced-match": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
- "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/eslint/node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
"node_modules/eslint/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -7120,19 +7490,51 @@
}
},
"node_modules/eslint/node_modules/eslint-scope": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
- "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
- "@types/esrecurse": "^4.3.1",
- "@types/estree": "^1.0.8",
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
- "node": "^20.19.0 || ^22.13.0 || >=24"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -7144,6 +7546,7 @@
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"engines": {
"node": ">=4.0"
}
@@ -7154,6 +7557,7 @@
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
@@ -7170,7 +7574,8 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/eslint/node_modules/locate-path": {
"version": "6.0.0",
@@ -7178,6 +7583,7 @@
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"p-locate": "^5.0.0"
},
@@ -7188,28 +7594,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/eslint/node_modules/minimatch": {
- "version": "10.2.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
- "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "brace-expansion": "^5.0.5"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/eslint/node_modules/p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"p-limit": "^3.0.2"
},
@@ -7510,6 +7901,13 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -8023,6 +8421,20 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -8135,6 +8547,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/hono": {
"version": "4.12.12",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz",
@@ -9807,6 +10236,14 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -9858,17 +10295,6 @@
"node": ">=12"
}
},
- "node_modules/lz-string": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
- "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "bin": {
- "lz-string": "bin/bin.js"
- }
- },
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@@ -10097,6 +10523,15 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
+ "node_modules/moment": {
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/mrmime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@@ -10856,6 +11291,19 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
+ "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/pretty-format": {
"version": "30.3.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz",
@@ -13938,6 +14386,19 @@
"zod": "^3.25.28 || ^4"
}
},
+ "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"
+ }
+ },
"server": {
"name": "liquio-qa-bot",
"version": "1.2.0",
@@ -13986,6 +14447,216 @@
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.1"
}
+ },
+ "server/node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "server/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "server/node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "server/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "server/node_modules/eslint": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz",
+ "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.4",
+ "@eslint/config-helpers": "^0.5.4",
+ "@eslint/core": "^1.2.0",
+ "@eslint/plugin-kit": "^0.7.0",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^9.1.2",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.2.0",
+ "esquery": "^1.7.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "minimatch": "^10.2.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "server/node_modules/eslint-scope": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "server/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "server/node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "server/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "server/node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "server/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "server/node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}