diff --git a/app/hooks/use-is-overflow.ts b/app/hooks/use-is-overflow.ts deleted file mode 100644 index 19519297fe..0000000000 --- a/app/hooks/use-is-overflow.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at https://mozilla.org/MPL/2.0/. - * - * Copyright Oxide Computer Company - */ -import throttle from 'lodash.throttle' -import { useLayoutEffect, useState, type MutableRefObject } from 'react' - -export const useIsOverflow = ( - ref: MutableRefObject, - dir: 'horizontal' | 'vertical', - callback?: (hasOverflow: boolean) => void -) => { - const [isOverflow, setIsOverflow] = useState() - const [scrollStart, setScrollStart] = useState(true) - const [scrollEnd, setScrollEnd] = useState(false) - - useLayoutEffect(() => { - if (!ref?.current) return - - const trigger = () => { - if (!ref?.current) return - - const el = ref.current - const hasOverflow = - dir === 'vertical' - ? el.scrollHeight > el.clientHeight - : el.scrollWidth > el.clientWidth - setIsOverflow(hasOverflow) - - if (callback) callback(hasOverflow) - } - - const handleScroll = throttle( - () => { - if (!ref?.current) return - - const el = ref.current - const [scrollAmount, overflowAmount] = - dir === 'vertical' - ? [el.scrollTop, el.scrollHeight - el.clientHeight] - : [el.scrollLeft, el.scrollWidth - el.clientWidth] - - setScrollStart(scrollAmount === 0) - setScrollEnd(scrollAmount >= overflowAmount && scrollEnd === false) - }, - 125, - { leading: true, trailing: true } - ) - - trigger() - - const { current } = ref - current.addEventListener('scroll', handleScroll) - window.addEventListener('resize', handleScroll) - return () => { - current.removeEventListener('scroll', handleScroll) - window.removeEventListener('resize', handleScroll) - } - }, [callback, ref, scrollStart, scrollEnd, dir]) - - return { - isOverflow, - scrollStart, - scrollEnd, - } -} diff --git a/app/pages/LoginPage.tsx b/app/pages/LoginPage.tsx index 3ca1741a73..1b4b883815 100644 --- a/app/pages/LoginPage.tsx +++ b/app/pages/LoginPage.tsx @@ -72,6 +72,7 @@ export default function LoginPage() { name="password" type="password" placeholder="Password" + autoComplete="current-password" required control={form.control} /> diff --git a/app/stores/toast.ts b/app/stores/toast.ts index 6bf3c4f5e8..455f9e5d5d 100644 --- a/app/stores/toast.ts +++ b/app/stores/toast.ts @@ -6,7 +6,6 @@ * Copyright Oxide Computer Company */ import { type ReactElement } from 'react' -import { v4 as uuid } from 'uuid' import { create } from 'zustand' import type { ToastProps } from '~/ui/lib/Toast' @@ -18,6 +17,10 @@ type Toast = { export const useToastStore = create<{ toasts: Toast[] }>(() => ({ toasts: [] })) +// A counter and not crypto.randomUUID(), which only exists in secure contexts. +// The console can be served over plain HTTP, e.g., from Nexus on a dev machine. +let nextId = 0 + /** * If argument is `ReactElement | string`, use it directly as `{ content }`. * Otherwise it's a config object. @@ -27,7 +30,9 @@ export function addToast(optionsOrContent: Toast['options'] | ReactElement | str typeof optionsOrContent === 'object' && 'content' in optionsOrContent ? optionsOrContent : { content: optionsOrContent } - useToastStore.setState(({ toasts }) => ({ toasts: [...toasts, { id: uuid(), options }] })) + useToastStore.setState(({ toasts }) => ({ + toasts: [...toasts, { id: String(nextId++), options }], + })) } export function removeToast(id: Toast['id']) { diff --git a/app/ui/lib/Table.tsx b/app/ui/lib/Table.tsx index ca27e061c6..1c5f2690cf 100644 --- a/app/ui/lib/Table.tsx +++ b/app/ui/lib/Table.tsx @@ -6,27 +6,15 @@ * Copyright Oxide Computer Company */ import cn from 'classnames' -import React, { useRef, type JSX, type ReactElement } from 'react' +import React, { type JSX, type ReactElement } from 'react' import SimpleBar from 'simplebar-react' -import { useIsOverflow } from '~/hooks/use-is-overflow' import { classed } from '~/util/classed' export type TableProps = JSX.IntrinsicElements['table'] export function Table({ className, ...props }: TableProps) { - const overflowRef = useRef(null) - const { isOverflow, scrollStart, scrollEnd } = useIsOverflow(overflowRef, 'horizontal') - return ( - + uuid()) +const mockRequestIds = Array.from({ length: 20 }, () => crypto.randomUUID()) // Use seeded random for consistent states across runs const rando = new Rando(0) @@ -94,7 +92,7 @@ function generateAuditLogEntry(index: number): Json { completedTime.setMilliseconds(rando.next() * 300 + completedTime.getMilliseconds()) // deterministic random durations return { - id: uuid(), + id: crypto.randomUUID(), auth_method: mockAuthMethod[index % mockAuthMethod.length], actor: { kind: 'silo_user', @@ -121,7 +119,7 @@ function generateAuditLogEntry(index: number): Json { export const auditLog: Json = [ // Recent successful operations { - id: uuid(), + id: crypto.randomUUID(), auth_method: 'session_cookie', actor: { kind: 'silo_user', @@ -137,7 +135,7 @@ export const auditLog: Json = [ source_ip: '192.168.1.100', }, { - id: uuid(), + id: crypto.randomUUID(), auth_method: 'access_token', actor: { kind: 'silo_user', @@ -155,7 +153,7 @@ export const auditLog: Json = [ }, // Failed operations { - id: uuid(), + id: crypto.randomUUID(), auth_method: 'session_cookie', actor: { kind: 'silo_user', @@ -177,7 +175,7 @@ export const auditLog: Json = [ source_ip: '172.16.0.25', }, { - id: uuid(), + id: crypto.randomUUID(), auth_method: null, actor: { kind: 'unauthenticated' }, result: { @@ -195,7 +193,7 @@ export const auditLog: Json = [ }, // More historical entries { - id: uuid(), + id: crypto.randomUUID(), auth_method: 'session_cookie', actor: { kind: 'silo_user', diff --git a/mock-api/msw/db.ts b/mock-api/msw/db.ts index 7529cb93ec..46ea982645 100644 --- a/mock-api/msw/db.ts +++ b/mock-api/msw/db.ts @@ -5,10 +5,7 @@ * * Copyright Oxide Computer Company */ -// note that isUuid checks for any kind of UUID. strictly speaking, we should -// only be checking for v4 import * as R from 'remeda' -import { validate as isUuid } from 'uuid' import type { ApiTypes as Api, IpPoolType, IpVersion } from '@oxide/api' import * as mock from '@oxide/api-mocks' @@ -20,6 +17,7 @@ import { commaSeries } from '~/util/str' import type { Json } from '../json-type' import { projects, type DbProject } from '../project' import { defaultSilo, siloSettings } from '../silo' +import { isUuid } from '../util' import { internalError, invalidRequest } from './util' export const notFoundErr = (msg: string) => { diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index 0c38ed297b..e916488d70 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -10,7 +10,6 @@ import { delay, HttpResponse } from 'msw' import * as R from 'remeda' import { lt as semverLessThan, rcompare as semverRCompare } from 'semver' import { match } from 'ts-pattern' -import { validate as isUuid, v4 as uuid } from 'uuid' import { DEFAULT_VPC_NAME, @@ -48,7 +47,7 @@ import { alertClasses, PROBE_ALERT_ID } from '../alert' import { defaultSilo, toIdp } from '../silo' import { SUPPORT_BUNDLE_SIZE } from '../support-bundle' import { timeseriesSchemas } from '../timeseries-schema' -import { getTimestamps } from '../util' +import { getTimestamps, isUuid } from '../util' import { defaultFirewallRules } from '../vpc' import { resendableAlerts, retryPendingDeliveries, validateSubscription } from './alert' import { @@ -120,7 +119,7 @@ export const handlers = makeHandlers({ errIfExists(db.projects, { name: body.name, silo_id: user.silo_id }, 'project') const newProject = { - id: uuid(), + id: crypto.randomUUID(), ...body, ...getTimestamps(), silo_id: user.silo_id, @@ -224,7 +223,7 @@ export const handlers = makeHandlers({ state: { state: 'detached' } as const, })) const newDisk: Json = { - id: uuid(), + id: crypto.randomUUID(), project_id: project.id, state, device_path: '/mnt/disk', @@ -306,7 +305,7 @@ export const handlers = makeHandlers({ if (body.snapshot_name) { const newSnapshot: Json = { - id: uuid(), + id: crypto.randomUUID(), name: body.snapshot_name, description: 'temporary snapshot for making an image', ...getTimestamps(), @@ -351,7 +350,7 @@ export const handlers = makeHandlers({ if (!member) throw notFoundErr(`subnet pool member for pool '${pool.id}'`) const newSubnet: Json = { - id: uuid(), + id: crypto.randomUUID(), project_id: project.id, instance_id: undefined, name: body.name, @@ -439,7 +438,7 @@ export const handlers = makeHandlers({ } const newFloatingIp: Json = { - id: uuid(), + id: crypto.randomUUID(), project_id: project.id, ip, ip_pool_id: pool.id, @@ -520,7 +519,7 @@ export const handlers = makeHandlers({ : 100 const newImage: Json = { - id: uuid(), + id: crypto.randomUUID(), project_id, size, block_size: 512, @@ -574,7 +573,7 @@ export const handlers = makeHandlers({ errIfExists(db.instances, { name: body.name, project_id: project.id }, 'instance') - const instanceId = uuid() + const instanceId = crypto.randomUUID() // https://github.com/oxidecomputer/omicron/blob/17e6fee/nexus/src/app/instance.rs#L2894-L2908 if (body.memory % GiB !== 0) throw 'Memory must be a multiple of 1 GiB' @@ -723,7 +722,7 @@ export const handlers = makeHandlers({ .with({ type: 'snapshot', read_only: true }, () => true) .otherwise(() => false) const newDisk: Json = { - id: uuid(), + id: crypto.randomUUID(), name, description, size, @@ -760,7 +759,7 @@ export const handlers = makeHandlers({ anySubnet ) { db.networkInterfaces.push({ - id: uuid(), + id: crypto.randomUUID(), description: 'The default network interface', instance_id: instanceId, primary: true, @@ -786,7 +785,7 @@ export const handlers = makeHandlers({ body.network_interfaces.params.forEach( ({ name, description, ip_config, subnet_name, vpc_name }, i) => { db.networkInterfaces.push({ - id: uuid(), + id: crypto.randomUUID(), name, description, instance_id: instanceId, @@ -1130,7 +1129,7 @@ export const handlers = makeHandlers({ const subnet = lookup.vpcSubnet({ ...query, vpc: vpc_name, subnet: subnet_name }) const newNic: Json = { - id: uuid(), + id: crypto.randomUUID(), // matches API logic: https://github.com/oxidecomputer/omicron/blob/ae22982/nexus/src/db/queries/network_interface.rs#L982-L1015 primary: nicsForInstance.length === 0, instance_id: instance.id, @@ -1402,7 +1401,7 @@ export const handlers = makeHandlers({ // address that will let us bypass that to test IPv6 handling const newRange: Json = { - id: uuid(), + id: crypto.randomUUID(), ip_pool_id: pool.id, range: body, time_created: new Date().toISOString(), @@ -1441,7 +1440,7 @@ export const handlers = makeHandlers({ errIfExists(db.ipPools, { name: body.name }, 'IP pool') const newPool: Json = { - id: uuid(), + id: crypto.randomUUID(), description: body.description, name: body.name, // It might not be possible to hit this fallback because the zod @@ -1539,7 +1538,7 @@ export const handlers = makeHandlers({ } const newSnapshot: Json = { - id: uuid(), + id: crypto.randomUUID(), ...body, ...getTimestamps(), state: 'ready', @@ -1585,9 +1584,9 @@ export const handlers = makeHandlers({ errIfExists(db.vpcs, { name: body.name, project_id: project.id }) const newVpc: Json = { - id: uuid(), + id: crypto.randomUUID(), project_id: project.id, - system_router_id: uuid(), + system_router_id: crypto.randomUUID(), ...body, // API is supposed to generate one if none provided. close enough ipv6_prefix: body.ipv6_prefix || 'fd2d:4569:88b2::/64', @@ -1597,7 +1596,7 @@ export const handlers = makeHandlers({ // Also create a default subnet const newSubnet: Json = { - id: uuid(), + id: crypto.randomUUID(), name: 'default', vpc_id: newVpc.id, ipv6_block: 'fd2d:4569:88b1::/64', @@ -1647,7 +1646,7 @@ export const handlers = makeHandlers({ const rules = (body.rules ?? []).map((rule) => ({ vpc_id: vpc.id, - id: uuid(), + id: crypto.randomUUID(), ...rule, ...getTimestamps(), })) @@ -1690,7 +1689,7 @@ export const handlers = makeHandlers({ errIfExists(db.vpcRouters, { vpc_id: vpc.id, name: body.name }) const newRouter: Json = { - id: uuid(), + id: crypto.randomUUID(), vpc_id: vpc.id, kind: 'custom', ...body, @@ -1730,7 +1729,7 @@ export const handlers = makeHandlers({ const vpcRouter = lookup.vpcRouter(query) errIfExists(db.vpcRouterRoutes, { vpc_router_id: vpcRouter.id, name: body.name }) const newRoute: Json = { - id: uuid(), + id: crypto.randomUUID(), vpc_router_id: vpcRouter.id, kind: 'custom', ...body, @@ -1778,7 +1777,7 @@ export const handlers = makeHandlers({ // TODO: Create a route for the subnet in the default router const newSubnet: Json = { - id: uuid(), + id: crypto.randomUUID(), vpc_id: vpc.id, name: body.name, description: body.description, @@ -1894,7 +1893,7 @@ export const handlers = makeHandlers({ errIfExists(db.sshKeys, { silo_user_id: user.id, name: body.name }) const newSshKey: Json = { - id: uuid(), + id: crypto.randomUUID(), silo_user_id: user.id, ...body, ...getTimestamps(), @@ -1946,7 +1945,7 @@ export const handlers = makeHandlers({ requireFleetViewer(cookies) errIfExists(db.silos, { name: body.name }) const newSilo: Json = { - id: uuid(), + id: crypto.randomUUID(), ...getTimestamps(), ...body, // discoverable was removed from SiloCreate, but it's still on the Silo @@ -2025,7 +2024,7 @@ export const handlers = makeHandlers({ // https://docs.rs/openssl/latest/openssl/rsa/struct.Rsa.html#method.private_key_from_der const provider: Json = { - id: uuid(), + id: crypto.randomUUID(), ...R.pick(body, [ 'name', 'acs_url', @@ -2091,7 +2090,7 @@ export const handlers = makeHandlers({ } const newBundle: Json = { - id: uuid(), + id: crypto.randomUUID(), reason_for_creation: 'Created by external API', state: 'collecting', time_created: new Date().toISOString(), @@ -2342,7 +2341,7 @@ export const handlers = makeHandlers({ errIfExists(db.antiAffinityGroups, { name: params.body.name, project_id: project.id }) const newAntiAffinityGroup: Json = { - id: uuid(), + id: crypto.randomUUID(), project_id: project.id, ...params.body, ...getTimestamps(), @@ -2539,7 +2538,7 @@ export const handlers = makeHandlers({ requireFleetAdminOrSiloAdmin(cookies, silo.id) const newToken: Json = { - id: uuid(), + id: crypto.randomUUID(), bearer_token: `oxide-scim-${randomHex(40)}`, time_created: new Date().toISOString(), } @@ -2587,7 +2586,7 @@ export const handlers = makeHandlers({ errIfExists(db.subnetPools, { name: body.name }, 'subnet pool') const newPool: Json = { - id: uuid(), + id: crypto.randomUUID(), description: body.description, name: body.name, ip_version: body.ip_version, @@ -2666,7 +2665,7 @@ export const handlers = makeHandlers({ } const newMember: Json = { - id: uuid(), + id: crypto.randomUUID(), subnet_pool_id: pool.id, subnet: body.subnet, min_prefix_length: minPL, @@ -2884,7 +2883,7 @@ export const handlers = makeHandlers({ // sentinel to let tests exercise the failure path const success = !receiver.kind.endpoint.includes('unreachable') const probe: Json = { - id: uuid(), + id: crypto.randomUUID(), // all probes reference the singleton probe alert, mirroring omicron alert_id: PROBE_ALERT_ID, alert_class: 'probe', @@ -2914,7 +2913,7 @@ export const handlers = makeHandlers({ const alerts = resendableAlerts(receiver) for (const d of alerts) { db.alertDeliveries.unshift({ - id: uuid(), + id: crypto.randomUUID(), alert_id: d.alert_id, alert_class: d.alert_class, receiver_id: receiver.id, @@ -2967,7 +2966,7 @@ export const handlers = makeHandlers({ } const now = new Date().toISOString() const newDelivery: Json = { - id: uuid(), + id: crypto.randomUUID(), alert_id: delivery.alert_id, alert_class: delivery.alert_class, receiver_id: receiver.id, @@ -2988,14 +2987,14 @@ export const handlers = makeHandlers({ const now = new Date().toISOString() const newReceiver: Json = { - id: uuid(), + id: crypto.randomUUID(), name: body.name, description: body.description, kind: { kind: 'webhook', endpoint: body.endpoint, // secret values are write-only; only IDs are stored - secrets: body.secrets.map(() => ({ id: uuid(), time_created: now })), + secrets: body.secrets.map(() => ({ id: crypto.randomUUID(), time_created: now })), }, subscriptions: body.subscriptions || [], ...getTimestamps(), @@ -3031,7 +3030,7 @@ export const handlers = makeHandlers({ requireFleetAdmin(cookies) const receiver = lookup.alertReceiver({ receiver: query.receiver }) const secret: Json = { - id: uuid(), + id: crypto.randomUUID(), time_created: new Date().toISOString(), } receiver.kind.secrets.push(secret) diff --git a/mock-api/snapshot.ts b/mock-api/snapshot.ts index e9758173d1..c6222c6411 100644 --- a/mock-api/snapshot.ts +++ b/mock-api/snapshot.ts @@ -6,8 +6,6 @@ * Copyright Oxide Computer Company */ -import { v4 as uuid } from 'uuid' - import type { Snapshot } from '@oxide/api' import { Rando } from '~/util/rando' @@ -37,7 +35,7 @@ function randomSnapshotState() { function generateSnapshot(index: number): Json { return { - id: uuid(), + id: crypto.randomUUID(), name: `disk-1-snapshot-${index + 8}`, description: '', project_id: project.id, diff --git a/mock-api/util.ts b/mock-api/util.ts index 41463378c8..fd1dea1e7c 100644 --- a/mock-api/util.ts +++ b/mock-api/util.ts @@ -13,3 +13,9 @@ export function getTimestamps() { const now = new Date().toISOString() return { time_created: now, time_modified: now } } + +// A name-or-ID param is an ID if `Uuid::parse_str` accepts it, which takes any +// UUID version, not only v4. +// https://github.com/oxidecomputer/omicron/blob/aa28c9c/common/src/api/external/mod.rs#L357-L367 +export const isUuid = (s: string) => + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s) diff --git a/mock-api/vpc.ts b/mock-api/vpc.ts index c9cfbebfd0..00e182e18d 100644 --- a/mock-api/vpc.ts +++ b/mock-api/vpc.ts @@ -6,8 +6,6 @@ * Copyright Oxide Computer Company */ -import { v4 as uuid } from 'uuid' - import type { RouterRoute, Vpc, VpcFirewallRule, VpcRouter, VpcSubnet } from '@oxide/api' import type { Json } from './json-type' @@ -268,7 +266,7 @@ export const vpcSubnets: Json = [ export function defaultFirewallRules(vpcId: string): Json { return [ { - id: uuid(), + id: crypto.randomUUID(), vpc_id: vpcId, name: 'allow-internal-inbound', status: 'enabled', @@ -284,7 +282,7 @@ export function defaultFirewallRules(vpcId: string): Json { ...getTimestamps(), }, { - id: uuid(), + id: crypto.randomUUID(), vpc_id: vpcId, name: 'allow-ssh', status: 'enabled', @@ -300,7 +298,7 @@ export function defaultFirewallRules(vpcId: string): Json { ...getTimestamps(), }, { - id: uuid(), + id: crypto.randomUUID(), vpc_id: vpcId, name: 'allow-icmp', status: 'enabled', @@ -325,7 +323,7 @@ export const firewallRules: Json = [ // second mock VPC in other project, meant to test display with lots of // targets and filters { - id: uuid(), + id: crypto.randomUUID(), name: 'lots-of-filters', status: 'enabled', direction: 'inbound', @@ -351,7 +349,7 @@ export const firewallRules: Json = [ vpc_id: vpc2.id, }, { - id: uuid(), + id: crypto.randomUUID(), name: 'lots-of-targets', status: 'enabled', direction: 'inbound', diff --git a/package-lock.json b/package-lock.json index 974f200e01..35692e11fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,6 @@ "classnames": "^2.5.1", "date-fns": "^3.6.0", "filesize": "^10.1.6", - "lodash.throttle": "^4.1.1", "match-sorter": "^6.3.4", "md5": "^2.3.0", "motion": "^11.16.2", @@ -58,7 +57,6 @@ "uplot": "^1.6.32", "uplot-react": "^1.2.4", "use-debounce": "^10.0.4", - "uuid": "^14.0.0", "zod": "^4.4.3", "zustand": "^5.0.3" }, @@ -69,14 +67,12 @@ "@playwright/test": "^1.61.0", "@react-router/dev": "^8.3.1", "@react-router/node": "^8.3.1", - "@types/lodash.throttle": "^4.1.9", "@types/md5": "^2.3.5", "@types/mousetrap": "^1.6.15", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "@types/react-is": "^19.2.0", "@types/semver": "^7.7.1", - "@types/uuid": "^10.0.0", "@vitejs/plugin-basic-ssl": "^2.3.0", "@vitejs/plugin-react": "^6.0.2", "@vitest/browser-playwright": "^5.0.3", @@ -3984,14 +3980,6 @@ "@types/lodash": "*" } }, - "node_modules/@types/lodash.throttle": { - "version": "4.1.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/md5": { "version": "2.3.5", "dev": true, @@ -4067,11 +4055,6 @@ "version": "3.0.3", "license": "MIT" }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript/typescript-aix-ppc64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", @@ -6927,10 +6910,6 @@ "version": "4.18.1", "license": "MIT" }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -9217,17 +9196,6 @@ "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "14.0.0", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, "node_modules/valibot": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.5.0.tgz", diff --git a/package.json b/package.json index fbd1c67096..ee8f0b51be 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "classnames": "^2.5.1", "date-fns": "^3.6.0", "filesize": "^10.1.6", - "lodash.throttle": "^4.1.1", "match-sorter": "^6.3.4", "md5": "^2.3.0", "motion": "^11.16.2", @@ -92,7 +91,6 @@ "uplot": "^1.6.32", "uplot-react": "^1.2.4", "use-debounce": "^10.0.4", - "uuid": "^14.0.0", "zod": "^4.4.3", "zustand": "^5.0.3" }, @@ -103,14 +101,12 @@ "@playwright/test": "^1.61.0", "@react-router/dev": "^8.3.1", "@react-router/node": "^8.3.1", - "@types/lodash.throttle": "^4.1.9", "@types/md5": "^2.3.5", "@types/mousetrap": "^1.6.15", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "@types/react-is": "^19.2.0", "@types/semver": "^7.7.1", - "@types/uuid": "^10.0.0", "@vitejs/plugin-basic-ssl": "^2.3.0", "@vitejs/plugin-react": "^6.0.2", "@vitest/browser-playwright": "^5.0.3", diff --git a/vitest.browser.config.ts b/vitest.browser.config.ts index 685119879e..ad76a0f989 100644 --- a/vitest.browser.config.ts +++ b/vitest.browser.config.ts @@ -40,7 +40,6 @@ export default defineConfig({ 'semver', 'uplot', 'uplot-react', - 'uuid', 'zod/v4', ], },