From 892b422b9f688f373a5684f2d359434dcbc4e6c9 Mon Sep 17 00:00:00 2001 From: Kevin Williams Date: Tue, 3 Nov 2026 21:20:41 +0000 Subject: [PATCH 1/6] perf(ui): Type the consumer plugin's view models without zod Nothing parsed with the Zod schemas; adapter.ts builds every value and only the inferred types were used. Declaring them as plain types keeps zod (~360KB unminified) out of the chunk every page loads. --- ui/consumer/src/schema.ts | 196 ++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 104 deletions(-) diff --git a/ui/consumer/src/schema.ts b/ui/consumer/src/schema.ts index 2ac37b73..f3e5ca80 100644 --- a/ui/consumer/src/schema.ts +++ b/ui/consumer/src/schema.ts @@ -1,11 +1,13 @@ /** - * Zod schemas for the compute plugin's two resources — pure data shapes, - * ported near-verbatim from cloud-portal PR #1315's - * `app/resources/workloads/workload.schema.ts` and - * `app/resources/instances/instance.schema.ts`. Combined into one file here + * View-model types for the compute plugin's two resources — pure data shapes, + * ported from cloud-portal PR #1315's `app/resources/workloads/workload.schema.ts` + * and `app/resources/instances/instance.schema.ts`. Combined into one file here * since the plugin only exposes list/detail read views for both resources. + * + * PR #1315 declared these as Zod schemas, but nothing here ever parses with + * them — `adapter.ts` builds every value — so they are plain types. That keeps + * zod out of the bundle every page loads. */ -import { z } from 'zod'; // ── Workload ───────────────────────────────────────────────────────────── @@ -16,69 +18,63 @@ import { z } from 'zod'; */ export type WorkloadHealth = 'Available' | 'Degraded' | 'Deploying' | 'Unavailable' | 'Unknown'; -const WORKLOAD_HEALTH = ['Available', 'Degraded', 'Deploying', 'Unavailable', 'Unknown'] as const; +type ConditionStatus = 'True' | 'False' | 'Unknown'; -const workloadConditionSchema = z.object({ - type: z.string(), - status: z.enum(['True', 'False', 'Unknown']), - reason: z.string().optional(), - message: z.string().optional(), - lastTransitionTime: z.string().optional(), - observedGeneration: z.number().optional(), -}); +interface WorkloadCondition { + type: string; + status: ConditionStatus; + reason?: string; + message?: string; + lastTransitionTime?: string; + observedGeneration?: number; +} -export const workloadPlacementRegionSchema = z.object({ - name: z.string(), - locations: z.array(z.string()).default([]), +export interface WorkloadPlacementRegion { + name: string; + locations: string[]; /** Topology selector the placement resolves through, when it does not name locations. */ - locationSelector: z.string().optional(), - readyReplicas: z.number(), - desiredReplicas: z.number(), - health: z.enum(WORKLOAD_HEALTH), -}); - -export type WorkloadPlacementRegion = z.infer; + locationSelector?: string; + readyReplicas: number; + desiredReplicas: number; + health: WorkloadHealth; +} -export const workloadResourceSchema = z.object({ - uid: z.string(), - name: z.string(), - namespace: z.string().optional(), - resourceVersion: z.string().optional(), - createdAt: z.coerce.date(), +export interface Workload { + uid: string; + name: string; + namespace?: string; + resourceVersion?: string; + createdAt: Date; /** Latest status condition transition, when present — used for “Updated … ago”. */ - updatedAt: z.coerce.date().optional(), - image: z.string().optional(), - health: z.enum(WORKLOAD_HEALTH), + updatedAt?: Date; + image?: string; + health: WorkloadHealth; /** Programmed replicas (latest template applied) — not the same as ready. */ - currentReplicas: z.number(), + currentReplicas: number; /** Ready-to-serve replicas — prefer this for health counts. */ - readyReplicas: z.number(), - desiredReplicas: z.number(), + readyReplicas: number; + desiredReplicas: number; /** Placement names (legacy list consumers). */ - placements: z.array(z.string()), + placements: string[]; /** Spec + status joined for region rows on the home cards. */ - placementRegions: z.array(workloadPlacementRegionSchema).default([]), - conditions: z.array(workloadConditionSchema).default([]), + placementRegions: WorkloadPlacementRegion[]; + conditions: WorkloadCondition[]; // Detail/overview fields - runtimeType: z.string().optional(), - tags: z.array(z.string()).default([]), - ports: z.array(z.string()).default([]), - locations: z.array(z.string()).default([]), - resources: z.string().optional(), - replicasPerRegion: z.number().optional(), + runtimeType?: string; + tags: string[]; + ports: string[]; + locations: string[]; + resources?: string; + replicasPerRegion?: number; /** Networks the instance template attaches to (`networkInterfaces[].network.name`). */ - networks: z.array(z.string()).default([]), + networks: string[]; /** Set once `metadata.deletionTimestamp` is — the finalizer is tearing the workload down. */ - deleting: z.boolean().default(false), -}); - -export type Workload = z.infer; - -export const workloadListSchema = z.object({ - items: z.array(workloadResourceSchema), -}); + deleting: boolean; +} -export type WorkloadList = z.infer; +export interface WorkloadList { + items: Workload[]; +} /** * Maps a workload health value to a `Badge` `type` prop understood by @@ -105,73 +101,65 @@ export function workloadHealthToBadgeType( export type InstanceStatusValue = 'Available' | 'Pending' | 'Failed' | 'Unknown'; -const instanceConditionSchema = z.object({ - type: z.string(), - status: z.enum(['True', 'False', 'Unknown']), - reason: z.string().optional(), - message: z.string().optional(), - lastTransitionTime: z.string().optional(), -}); - -export type InstanceCondition = z.infer; +export interface InstanceCondition { + type: string; + status: ConditionStatus; + reason?: string; + message?: string; + lastTransitionTime?: string; +} -export const instanceNetworkInterfaceSchema = z.object({ +export interface InstanceNetworkInterface { /** Spec interface name, for example "eth0". */ - name: z.string(), + name: string; /** VPC network the interface attaches to (spec `network.name`). */ - network: z.string().optional(), + network?: string; /** `status.networkInterfaces[].networkInterfaceRef.name`; the `network` label on traffic series. */ - networkInterfaceRef: z.string().optional(), -}); - -export type InstanceNetworkInterface = z.infer; + networkInterfaceRef?: string; +} -export const instanceResourceSchema = z.object({ - uid: z.string(), - name: z.string(), - namespace: z.string().optional(), - createdAt: z.coerce.date(), - workloadName: z.string().optional(), - workloadUid: z.string().optional(), - location: z.string().optional(), - placement: z.string().optional(), - instanceType: z.string().optional(), +export interface Instance { + uid: string; + name: string; + namespace?: string; + createdAt: Date; + workloadName?: string; + workloadUid?: string; + location?: string; + placement?: string; + instanceType?: string; /** Allocated CPU — from requests, or resolved from instanceType catalog (e.g. "1"). */ - cpu: z.string().optional(), + cpu?: string; /** Allocated memory — from requests, or resolved from instanceType catalog (e.g. "2Gi"). */ - memory: z.string().optional(), - image: z.string().optional(), - ports: z.array(z.string()).default([]), + memory?: string; + image?: string; + ports: string[]; /** Sandbox container names, in spec order. Empty for virtual machines. */ - containers: z.array(z.string()).default([]), + containers: string[]; /** RuntimeClass the instance names; unset selects the catalog default. */ - runtimeClass: z.string().optional(), - status: z.enum(['Available', 'Pending', 'Failed', 'Unknown']), - externalIP: z.string().optional(), - internalIP: z.string().optional(), + runtimeClass?: string; + status: InstanceStatusValue; + externalIP?: string; + internalIP?: string; /** Host-route in-network IPs Envoy may dial (`upstream_host`), v4 and v6. */ - internalIPs: z.array(z.string()).default([]), + internalIPs: string[]; /** * Every in-network address the guest answers on, v4 and v6, for display. * Unlike `internalIPs` it keeps a delegated IPv6 block, by its base address. */ - addresses: z.array(z.string()).default([]), + addresses: string[]; /** Every externally reachable address, v4 and v6. */ - externalIPs: z.array(z.string()).default([]), + externalIPs: string[]; /** Private DNS names allocated in the instance's networks (`status.dns[].hostnames`). */ - hostnames: z.array(z.string()).default([]), + hostnames: string[]; /** Interfaces in spec order, each joined to its status entry by name. */ - networkInterfaces: z.array(instanceNetworkInterfaceSchema).default([]), - conditions: z.array(instanceConditionSchema).default([]), -}); - -export type Instance = z.infer; - -export const instanceListSchema = z.object({ - items: z.array(instanceResourceSchema), -}); + networkInterfaces: InstanceNetworkInterface[]; + conditions: InstanceCondition[]; +} -export type InstanceList = z.infer; +export interface InstanceList { + items: Instance[]; +} /** * Maps an instance status value to a `Badge` `type` prop understood by From 9eba94c5636da35de4bbb94b74066fd77e05de53 Mon Sep 17 00:00:00 2001 From: Kevin Williams Date: Tue, 3 Nov 2026 21:20:41 +0000 Subject: [PATCH 2/6] chore(ui): Align consumer plugin dependencies with cloud-portal Pin datum-ui (3.6.0) and react-query (5.102.8) to the versions cloud-portal resolves: the host's copies are what run, so the local ones should type-check against the same APIs. Take patch and minor updates elsewhere, keep motion below 14 (datum-ui's peer range), and drop zod, which nothing imports now. --- ui/consumer/package-lock.json | 424 ++++++++++++++++++++++------------ ui/consumer/package.json | 29 ++- 2 files changed, 288 insertions(+), 165 deletions(-) diff --git a/ui/consumer/package-lock.json b/ui/consumer/package-lock.json index fd461f25..865d4dd8 100644 --- a/ui/consumer/package-lock.json +++ b/ui/consumer/package-lock.json @@ -9,39 +9,89 @@ "version": "0.1.0", "dependencies": { "@datum-cloud/activity-ui": "0.7.1", - "@datum-cloud/datum-ui": "^3.4.1", - "@tanstack/react-query": "5.101.4", - "@tanstack/react-table": "^9.1.2", - "@tanstack/react-virtual": "^3", + "@datum-cloud/datum-ui": "3.6.0", + "@tanstack/react-query": "5.102.8", + "@tanstack/react-table": "^9.3.1", + "@tanstack/react-virtual": "^3.14.26", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", - "lucide-react": "^1.21.0", - "motion": "^13", - "nuqs": "^2.10.0", + "lucide-react": "^1.67.0", + "motion": "^13.5.1", + "nuqs": "^2.10.2", "react": "19.3.0", - "react-day-picker": "^10", + "react-day-picker": "^10.0.2", "react-dom": "19.3.0", "react-router": "8.3.1", "recharts": "^3.10.1", - "sonner": "^2.0.7", - "zod": "^4.4.3" + "sonner": "^2.0.8" }, "devDependencies": { - "@module-federation/vite": "^1.16.16", - "@types/node": "^26.6.3", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", + "@module-federation/vite": "^1.23.9", + "@types/node": "^26.6.6", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^5.2.0", "typescript": "^7.0.0", "vite": "^7.3.5", - "vitest": "^5.0.5" + "vitest": "^5.1.0" }, "engines": { "node": ">=26" } }, + "node_modules/@ai-sdk/gateway": { + "version": "4.0.135", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.135.tgz", + "integrity": "sha512-gnMpZP42WIhy24fpnbbtV7SBqEhYY8ZSMsfgoNRh5r41D1HwnJTm1k7d4uYSIpiaEb5J8IPTHVSzwgWmGoSFEA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@ai-sdk/provider": "4.0.35", + "@ai-sdk/provider-utils": "5.0.74", + "@vercel/oidc": "3.2.0" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "4.0.35", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.35.tgz", + "integrity": "sha512-yBz6fmLS8MSCIzy7NiHEUAq56ZLD4ZA1GR6bpXBxgbwUacP1La7LuwAr/xy9jSzthem4F8Xu2OrhvU/JrE9fYA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "5.0.74", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.74.tgz", + "integrity": "sha512-42R/bzs520oy3PMj8FI1KGegjLEjaUsNcaEltb6yxN0KnmhihWvlgMb19pEF8CKKqv6Bq6+qMVji8mtyrMubSQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@ai-sdk/provider": "4.0.35", + "@standard-schema/spec": "^1.1.0", + "@workflow/serde": "4.1.0", + "eventsource-parser": "^3.0.8", + "undici": "^7.29.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "6.0.7", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.7.tgz", @@ -556,11 +606,12 @@ } }, "node_modules/@datum-cloud/datum-ui": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@datum-cloud/datum-ui/-/datum-ui-3.4.1.tgz", - "integrity": "sha512-MDfZSonZ9d6b6RMmGS8qgATcKV23MJGD3Efw7yJwlB2jeQc9KnCNesiP74GWshXA3wuyLPeLUN9QHeCSOP0cNQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@datum-cloud/datum-ui/-/datum-ui-3.6.0.tgz", + "integrity": "sha512-SWA6m7k1UXsu8JZEjFGo0K32zfaoNFozYtLlrx7emNSP1TRBIOLkFVtRSewIhWKVPPMIdgI2o5zE2EuF+F0MOg==", "license": "MIT", "dependencies": { + "@datum-cloud/datum-ui-assistant-core": "^0.1.0", "@radix-ui/react-avatar": "^1.2.6", "@radix-ui/react-checkbox": "^1.3.11", "@radix-ui/react-collapsible": "^1.1.20", @@ -747,6 +798,15 @@ } } }, + "node_modules/@datum-cloud/datum-ui-assistant-core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@datum-cloud/datum-ui-assistant-core/-/datum-ui-assistant-core-0.1.0.tgz", + "integrity": "sha512-k/d17fVwYDt/DswVuWOCWo6hsAUJ/Y1hLeambRb3PckUqXnU+RdXJpYi62gNqHLpUZdU7rmmsVOohxyhFoxX0w==", + "license": "MIT", + "peerDependencies": { + "ai": ">=7 <8" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -1295,19 +1355,19 @@ } }, "node_modules/@module-federation/dts-plugin": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-2.9.2.tgz", - "integrity": "sha512-J84cRrewtUFnFdMeD/91mHtW+Toh3mAWvGnhsFAc9oEbGwqfzJEfgPJri65xdtzWcNhs1d6FImPMTvnjzOnYvA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-2.9.3.tgz", + "integrity": "sha512-U5H8D36rLuCTS7ZPEAfvqIp6b5OZms3PComHPzFQcg12xlUr31PPi1gd3FbjKVFFvlqyDpEiDZ355fKi9ba5zA==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "2.9.2", - "@module-federation/managers": "2.9.2", - "@module-federation/sdk": "2.9.2", - "@module-federation/third-party-dts-extractor": "2.9.2", + "@module-federation/error-codes": "2.9.3", + "@module-federation/managers": "2.9.3", + "@module-federation/sdk": "2.9.3", + "@module-federation/third-party-dts-extractor": "2.9.3", "adm-zip": "^0.6.1", "isomorphic-ws": "5.0.0", - "undici": "7.29.1", + "undici": "7.30.1", "ws": "8.21.3" }, "peerDependencies": { @@ -1321,69 +1381,69 @@ } }, "node_modules/@module-federation/error-codes": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-2.9.2.tgz", - "integrity": "sha512-MTpKubqsJSBgnuw+2Pli1kHL4INeHD5cGnHL84oRANwQAWK7b6lN3ZZTmL3FL6lYn65ImineqZNqGWBv5GQVLQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-2.9.3.tgz", + "integrity": "sha512-sXWo6XHOFKC1Dcu9DcSA1okZjtIz2y3uM+NJrAoSpeZ9QSPXielkMWT5PtG7MqGE3OQ+/HDawiLK53IOi3NHcQ==", "dev": true, "license": "MIT" }, "node_modules/@module-federation/managers": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-2.9.2.tgz", - "integrity": "sha512-VtSKLkT2EA1wqCMFXNiklC+XF+5bZPYY3sUQ9LCFHqKS7v7NOJDrwMV6Qtw3fxH4Y6Pe6P28MiU8D9dlzknCKQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-2.9.3.tgz", + "integrity": "sha512-C9yyp/edzCYs7+yvvQv2cBRd9A5/n9RPkGhn6KMBbchMuKWyn5qbC6pbl70TAkFh3wHRqcyUCIHrMf+Fek0u4A==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/sdk": "2.9.2" + "@module-federation/sdk": "2.9.3" } }, "node_modules/@module-federation/runtime": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-2.9.2.tgz", - "integrity": "sha512-1lPZVy7TkMTTBMkkjgQt7jA+9FAx226yQqEz+X0lFAIbpxIWfpzNQLD+7IydEe83ZhDJpK3cv/9bn3jjQBZeqQ==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-2.9.3.tgz", + "integrity": "sha512-VCJV1VzKk+jvt+1CcTrslbl3LAKScHFpPxKSviafpMIqeHDWIDEY7ZEbVVJl/GuUjA36R7j0QhOPEjihPcz9/A==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "2.9.2", - "@module-federation/runtime-core": "2.9.2", - "@module-federation/sdk": "2.9.2" + "@module-federation/error-codes": "2.9.3", + "@module-federation/runtime-core": "2.9.3", + "@module-federation/sdk": "2.9.3" } }, "node_modules/@module-federation/runtime-core": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-2.9.2.tgz", - "integrity": "sha512-S9roTDqLtAWaaDBuug69ZnXXGEx0pUq/yG06J759bjRgfAg2P9+dWEv1q7rB2eMIp7HH4L6s3ColDRYdL6tJ9w==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-2.9.3.tgz", + "integrity": "sha512-E6AjfIWFgZOEATxf+GzBIg54Ejp7QFNTxtVzwnnhEEu01dErBo7qLjJvEPXGiN5sJTNf2a4HIW/wZTM23skouQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "2.9.2", - "@module-federation/sdk": "2.9.2" + "@module-federation/error-codes": "2.9.3", + "@module-federation/sdk": "2.9.3" } }, "node_modules/@module-federation/sdk": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-2.9.2.tgz", - "integrity": "sha512-ILtBFPuYpVcRA/Qay1lJ2hMtZI8fDE/v1n7xiN17e5qx5rSBR3FaamTsmRpN1hIt+gVnoa76CzOP/Y73d8hHNg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-2.9.3.tgz", + "integrity": "sha512-cT5ko/JDzGrKe8N+MFPgwb07G8fzmpnZbKZjGjXvT9phagQpiOfV6WQZhu1j+HtO152Frv1vjv/1Hhg2wMUL8g==", "dev": true, "license": "MIT" }, "node_modules/@module-federation/third-party-dts-extractor": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-2.9.2.tgz", - "integrity": "sha512-FKJVGx+/jIe16RfmwoyIDVMPAttIFJv6AJCLkKf05IRNvI3RJFqQ1tLP1YjcfGuumCbYuWGa4QKnMla7YjVpqg==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-2.9.3.tgz", + "integrity": "sha512-jUWFWa5l7j2jb3JU9bfGHZ5luv6XAogMsvJ/yG59AZmhpliPozkyxYRjnbtik85IipZF4Cb06PW9qv5qRRf6OQ==", "dev": true, "license": "MIT" }, "node_modules/@module-federation/vite": { - "version": "1.23.7", - "resolved": "https://registry.npmjs.org/@module-federation/vite/-/vite-1.23.7.tgz", - "integrity": "sha512-b60aujpV0uleW7uOqtx+jXxZk1cdydidb7ixPof/PwaL0r17mQCA1V11p48vyiP+FB94azOZRNu+LiflPpAy8w==", + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/@module-federation/vite/-/vite-1.23.9.tgz", + "integrity": "sha512-fNAIE6z2fe/+QW2znXoJvviYXZPcPau5qqXfQ1VfV/BkdJVlyxSlOCPz5GvRiwcpa4oC/n9LlbuXNoDOajDgDQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/dts-plugin": "2.9.2", - "@module-federation/runtime": "2.9.2", - "@module-federation/sdk": "2.9.2" + "@module-federation/dts-plugin": "2.9.3", + "@module-federation/runtime": "2.9.3", + "@module-federation/sdk": "2.9.3" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -2834,9 +2894,9 @@ "license": "MIT" }, "node_modules/@tanstack/query-core": { - "version": "5.101.4", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", - "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.102.8.tgz", + "integrity": "sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==", "license": "MIT", "funding": { "type": "github", @@ -2844,12 +2904,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.101.4", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", - "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.102.8.tgz", + "integrity": "sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.101.4" + "@tanstack/query-core": "5.102.8" }, "funding": { "type": "github", @@ -2859,51 +2919,51 @@ "react": "^18 || ^19" } }, - "node_modules/@tanstack/react-store": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.11.1.tgz", - "integrity": "sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==", + "node_modules/@tanstack/react-table": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-9.3.1.tgz", + "integrity": "sha512-E6Ya5TnY6EaGyZYIUqig/29Lr/gJSAubQkLJFqiWkUl5nRCr0Xr9hvJot+NiRTtG+TzGxHTiqUW3bnLtQxlHug==", "license": "MIT", "dependencies": { - "@tanstack/store": "0.11.1", - "use-sync-external-store": "^1.6.0" + "@tanstack/react-store": "^0.11.2", + "@tanstack/table-core": "9.3.1" + }, + "engines": { + "node": ">=20" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "react": ">=18" } }, - "node_modules/@tanstack/react-table": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-9.2.4.tgz", - "integrity": "sha512-Rzp1Q4e0/nIgEjmISYR5HeEgLTNtrG+C7NFZf/AbCxPO5hg+zC3yNGwcoECigUk8SFzzvhXbd2rFdtP2ZiGrfA==", + "node_modules/@tanstack/react-table/node_modules/@tanstack/react-store": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.11.2.tgz", + "integrity": "sha512-oj5O5DmiMWE+ih+SioX2ayC1kbK7d3fpLx0B75fPJr9nY3zU+hoW010yr64qXsY/9fyiLVE2bn5mGNV3owm8RQ==", "license": "MIT", "dependencies": { - "@tanstack/react-store": "^0.11.1", - "@tanstack/table-core": "9.2.4" - }, - "engines": { - "node": ">=20" + "@tanstack/store": "0.11.2", + "use-sync-external-store": "^1.6.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": ">=18" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@tanstack/react-virtual": { - "version": "3.14.10", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.10.tgz", - "integrity": "sha512-SRyoUbdFMRHuYXMijV5H4ZarQWpXkj3iANq8OFre+pybeVap8ZJjZ3Nz9bVjx4d8PfobVUQUdKyyyHYk3E+djw==", + "version": "3.14.26", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.26.tgz", + "integrity": "sha512-HUDlfsrYH6ko5xH56L62Mcl/uzT+AGVNMHFuDNqDJMdZWc1AQa2oeVKNUvZ/RWBZQ4TYtyQerFrNvLnp39h65A==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.17.8" + "@tanstack/virtual-core": "3.19.11" }, "funding": { "type": "github", @@ -2915,9 +2975,9 @@ } }, "node_modules/@tanstack/store": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.11.1.tgz", - "integrity": "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.11.2.tgz", + "integrity": "sha512-sJ4mjol8uQsHV0gOJzzjwXfh2Fwm+Sz0+8deqiTm4jGbMdjzNSW+xZCFm0kUa870uhd8yi+DpKnZb5Kc4apa0Q==", "license": "MIT", "funding": { "type": "github", @@ -2925,12 +2985,12 @@ } }, "node_modules/@tanstack/table-core": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-9.2.4.tgz", - "integrity": "sha512-GwdDyGGr6UXAtubF14yAwcXvdaogqfsQgIk89Suebjxob/Rjq2xvfmXsjDF1rQmKmhHsJm9TOY7myxv3i6geJw==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-9.3.1.tgz", + "integrity": "sha512-zQvlxSNi2qGM3L4jh85Z6hqMfdIR5qOfD9KHOU32nuKFJGN0RlJ7UvV1lsPb1fPVhpFYI0VP3jJGxLdLDpTKjQ==", "license": "MIT", "dependencies": { - "@tanstack/store": "^0.11.1" + "@tanstack/store": "^0.11.2" }, "engines": { "node": ">=20" @@ -2941,9 +3001,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.17.8", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.8.tgz", - "integrity": "sha512-BfEvehNpOT75r5Ksc5xW6NZuXujTfb7nlSEyVu4XHG3gdxNg1KqXruWbDewXOUaUYIo4oRbSfkjIajz4MAT8tA==", + "version": "3.19.11", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.19.11.tgz", + "integrity": "sha512-/5iHb0RHBvj8Di59FDs4ESNm45jls33aZAi4zccVXZjc/6ZvwQP6nzEYeT6weXjTf9E6/vrp2JhYWRyO+ZP13A==", "license": "MIT", "funding": { "type": "github", @@ -3084,9 +3144,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", - "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", + "version": "26.6.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.6.tgz", + "integrity": "sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==", "dev": true, "license": "MIT", "dependencies": { @@ -3094,9 +3154,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3104,13 +3164,13 @@ } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", "devOptional": true, "license": "MIT", "peerDependencies": { - "@types/react": "^19.2.0" + "@types/react": "^19.3.0" } }, "node_modules/@types/trusted-types": { @@ -3466,6 +3526,16 @@ "node": ">=16.20.0" } }, + "node_modules/@vercel/oidc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz", + "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">= 20" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", @@ -3488,14 +3558,14 @@ } }, "node_modules/@vitest/mocker": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.5.tgz", - "integrity": "sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.1.0.tgz", + "integrity": "sha512-5umB9/jlWcqVS6fsrFmv5Gdnpu6wAUio/lEOwAmvB7v+lFo/IW2B9FhAD0p19GCULTgMzLCdUmuCDCnAzWgppQ==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.31", - "@vitest/spy": "5.0.5", + "@vitest/spy": "5.1.0", "estree-walker": "^3.0.3", "magic-string": "^1.4.3" }, @@ -3516,15 +3586,22 @@ } }, "node_modules/@vitest/spy": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.5.tgz", - "integrity": "sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.1.0.tgz", + "integrity": "sha512-2DPO5OMHWKrBngC+fCmTQqemNF0gidRhp3lxbBmRhW+M1XJrfgPpqNqWVevXtn2f2ufD5ZaR1B6B641aAlcX5A==", "dev": true, "license": "MIT", "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@workflow/serde": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@workflow/serde/-/serde-4.1.0.tgz", + "integrity": "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/@xterm/addon-fit": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", @@ -3550,6 +3627,24 @@ "node": ">=14.0" } }, + "node_modules/ai": { + "version": "7.0.162", + "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.162.tgz", + "integrity": "sha512-NPzL6/GmjjPwhzM1kGP9O60TfJ3uXV4Z4A/ztljxcJFAmRAFAjgk5mAHaikk3sE8NQo4NbRCiS+DgHinE2d0sQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@ai-sdk/gateway": "4.0.135", + "@ai-sdk/provider": "4.0.35", + "@ai-sdk/provider-utils": "5.0.74" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -4423,6 +4518,16 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -4452,13 +4557,13 @@ } }, "node_modules/framer-motion": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.4.6.tgz", - "integrity": "sha512-TwKWt2CC8rjhARzwV00TZIh04ipumSAqyKM/JZmS+urdncSHg6wTF/mfj/Fd6Oi2W01TZSWsiA3uxXDrXZtKyw==", + "version": "13.5.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.5.1.tgz", + "integrity": "sha512-HeWOaeHD3kyTSIEMT6+nw7fBn4saIfxxabZyAITbycAl4xr1kzGJKIvGt/OWghSlvIA9qTnZ1zTGelHAe6S8DA==", "license": "MIT", "dependencies": { - "motion-dom": "^13.4.5", - "motion-utils": "^13.3.0", + "motion-dom": "13.5.1", + "motion-utils": "13.5.0", "tslib": "^2.4.0" }, "peerDependencies": { @@ -4646,6 +4751,13 @@ "node": ">=6" } }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)", + "peer": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -4670,12 +4782,18 @@ } }, "node_modules/lucide-react": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.26.0.tgz", - "integrity": "sha512-raglYVR2+VkMfJL158krjVmE+rV5ST2lzA/KQm1FRSjMHT4MnWaegHxoVEpmc2So3nOEhp9oGejJwAPX8MoAjg==", + "version": "1.67.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.67.0.tgz", + "integrity": "sha512-z7kd21YNm/Gw3E7mTnq5dTuvV+yc+OEwA//oDENSG1SbZ/NVgKdjxALJFNdGoC0Ji7HaJR2Dha9ev7VUPxy9CQ==", "license": "ISC", "peerDependencies": { + "@types/react": "*", "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/magic-string": { @@ -4702,12 +4820,12 @@ "peer": true }, "node_modules/motion": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/motion/-/motion-13.4.6.tgz", - "integrity": "sha512-zDXi6lIdyhda6wZlTsv9QgT2ed6UExAfRegtl/akQmZb5iaeLlArXr9Gmo/hx3iTx2y4R7tmdb9k9koFQmV+NQ==", + "version": "13.5.1", + "resolved": "https://registry.npmjs.org/motion/-/motion-13.5.1.tgz", + "integrity": "sha512-eV9SUem6KnzEa9+eT+dVBeDlhy5TTdn09YWR1WhUsKoZCJPw/n4TcYhjbxq2Gy5PTwDr//4H3r5vxkcZnNnO1w==", "license": "MIT", "dependencies": { - "framer-motion": "^13.4.6", + "framer-motion": "13.5.1", "tslib": "^2.4.0" }, "peerDependencies": { @@ -4724,18 +4842,18 @@ } }, "node_modules/motion-dom": { - "version": "13.4.5", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-13.4.5.tgz", - "integrity": "sha512-p63p2Doq+US/O5wADauIPUVx0/eONHMLPzsudBM9XMWRuBQLXogdxH0vwIA8WYQZw864pyzoV723xoDqakwWTg==", + "version": "13.5.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-13.5.1.tgz", + "integrity": "sha512-/7CVCD20Mz1KobktorpdgvowV0V41J7UxqE+qWS3GkSJqrRr5ft+2RloOYtEKv+wvBp3JaSFBclhxt4ja8ovHg==", "license": "MIT", "dependencies": { - "motion-utils": "^13.3.0" + "motion-utils": "13.5.0" } }, "node_modules/motion-utils": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-13.3.0.tgz", - "integrity": "sha512-sgSschQp7EseHInIlR7hBbMuvet3RA0bs28KPZAXJcGKGdxHGvh1ogpYDilY3bOMtl73EqPNmp75sAKHYPU5sg==", + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-13.5.0.tgz", + "integrity": "sha512-Hqe/sqjIKliPZg8sVjWAkdySDPJNIeKH+euVkDOG8ri3DUUU7ZjndmHsGqEPz2iHpiyVaV5I22iguCMHU2he+Q==", "license": "MIT" }, "node_modules/ms": { @@ -4775,9 +4893,9 @@ } }, "node_modules/nuqs": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-2.10.1.tgz", - "integrity": "sha512-7lPZrPJVOsD0VvfQSKtodYBBPGPLsLzkFU8ueYIap9yMMJvSw6UC12p8luA5NW7aKe7AbPHKxcxb3OluH6iAJA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-2.10.2.tgz", + "integrity": "sha512-HaLMCr9H2wywEnTu3j+QW98EteDT3Kfmx3SJJ6JfcvCvkFi/g/fKd/oCXa5DF0x1s1l4zcFtzsq9bflj6RcM6A==", "license": "MIT", "dependencies": { "@standard-schema/spec": "1.1.0" @@ -4905,9 +5023,9 @@ } }, "node_modules/react-day-picker": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-10.0.1.tgz", - "integrity": "sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-10.0.2.tgz", + "integrity": "sha512-QetiKtZWE9Uaw/pM/SYNv6WPYOTRnSJ2puL0GgwkmUFMIwiY0dtjH1W4B7DOVXVNQM0ZOClGpH/heOfBSzWeiA==", "license": "MIT", "dependencies": { "@date-fns/tz": "^1.4.1", @@ -5206,13 +5324,19 @@ } }, "node_modules/sonner": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", - "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.8.tgz", + "integrity": "sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==", "license": "MIT", "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/source-map-js": { @@ -5393,7 +5517,6 @@ "version": "7.30.1", "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", - "dev": true, "license": "MIT", "engines": { "node": ">=20.18.1" @@ -5587,14 +5710,14 @@ } }, "node_modules/vitest": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.5.tgz", - "integrity": "sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.1.0.tgz", + "integrity": "sha512-MzmNFnrwa8v8lF1bLxw3UJ4EREwVOVb75VqJZmUDRjJ6EluK/x9th7qTbaeDk41WxE1GxWqmTXcdsfobfH5sOA==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/mocker": "5.0.5", + "@vitest/mocker": "5.1.0", "chai": "^6.3.0", "es-module-lexer": "^2.3.2", "expect-type": "^1.4.0", @@ -5620,12 +5743,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "5.0.5", - "@vitest/browser-preview": "5.0.5", + "@vitest/browser-playwright": "5.1.0", + "@vitest/browser-preview": "5.1.0", "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", - "@vitest/coverage-istanbul": "5.0.5", - "@vitest/coverage-v8": "5.0.5", - "@vitest/ui": "5.0.5", + "@vitest/coverage-istanbul": "5.1.0", + "@vitest/coverage-v8": "5.1.0", + "@vitest/ui": "5.1.0", "happy-dom": "*", "jsdom": "*", "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" @@ -5775,6 +5898,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/ui/consumer/package.json b/ui/consumer/package.json index 01f2d04b..b7f1831f 100644 --- a/ui/consumer/package.json +++ b/ui/consumer/package.json @@ -13,34 +13,33 @@ }, "dependencies": { "@datum-cloud/activity-ui": "0.7.1", - "@datum-cloud/datum-ui": "^3.4.1", - "@tanstack/react-query": "5.101.4", - "@tanstack/react-table": "^9.1.2", - "@tanstack/react-virtual": "^3", + "@datum-cloud/datum-ui": "3.6.0", + "@tanstack/react-query": "5.102.8", + "@tanstack/react-table": "^9.3.1", + "@tanstack/react-virtual": "^3.14.26", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", - "lucide-react": "^1.21.0", - "motion": "^13", - "nuqs": "^2.10.0", + "lucide-react": "^1.67.0", + "motion": "^13.5.1", + "nuqs": "^2.10.2", "react": "19.3.0", - "react-day-picker": "^10", + "react-day-picker": "^10.0.2", "react-dom": "19.3.0", "react-router": "8.3.1", "recharts": "^3.10.1", - "sonner": "^2.0.7", - "zod": "^4.4.3" + "sonner": "^2.0.8" }, "devDependencies": { - "@module-federation/vite": "^1.16.16", - "@types/node": "^26.6.3", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", + "@module-federation/vite": "^1.23.9", + "@types/node": "^26.6.6", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^5.2.0", "typescript": "^7.0.0", "vite": "^7.3.5", - "vitest": "^5.0.5" + "vitest": "^5.1.0" }, "engines": { "node": ">=26" From 6c422a72c44f5a50ae9972fffaae08b3ce6b8433 Mon Sep 17 00:00:00 2001 From: Kevin Williams Date: Tue, 3 Nov 2026 21:20:41 +0000 Subject: [PATCH 3/6] perf(ui): Minify the consumer plugin bundle The portal downloads and parses every page's chunks on first visit. Minifying cuts the bundle from ~6.0MB to ~2.5MB (~1.3MB to ~0.75MB gzipped); source maps keep it readable in devtools. --- ui/consumer/vite.config.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/consumer/vite.config.ts b/ui/consumer/vite.config.ts index 322dceb7..326eed82 100644 --- a/ui/consumer/vite.config.ts +++ b/ui/consumer/vite.config.ts @@ -98,8 +98,11 @@ export default defineConfig({ }, build: { target: 'esnext', - // Keep the plugin readable when inspecting the built bundle. - minify: false, + // Minified: the portal downloads and parses every page's chunks on first + // visit, and minifying cuts the bundle from ~6.0MB to ~2.5MB (~1.3MB to + // ~0.8MB gzipped). Source maps keep it readable in devtools; the browser + // fetches them only when devtools is open. + sourcemap: true, }, experimental: { renderBuiltUrl(_filename, { hostType }) { From 58b1439fc949cae4c0d64150f395f5607d7b7015 Mon Sep 17 00:00:00 2001 From: Kevin Williams Date: Tue, 3 Nov 2026 21:20:41 +0000 Subject: [PATCH 4/6] perf(ui): Answer conditional requests in the consumer plugin server The portal's asset proxy serves remoteEntry.js and the manifests as no-cache and forwards the browser's validators, but serve.ts sent none, so every page load downloaded them again. Send a weak ETag and Last-Modified and answer a match with 304. --- ui/consumer/serve.ts | 36 ++++++++++++++++++++++++++++++++++-- ui/consumer/tsconfig.json | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/ui/consumer/serve.ts b/ui/consumer/serve.ts index e207dd17..50031279 100644 --- a/ui/consumer/serve.ts +++ b/ui/consumer/serve.ts @@ -6,8 +6,9 @@ * by the Deployment. * * Node runs this file directly by stripping its types. Content types match - * what Bun.file reported, which this server replaced; it is the same server - * as network-services-operator's consumer plugin. + * what Bun.file reported, which this server replaced. It started as the same + * server as network-services-operator's consumer plugin and adds conditional + * requests (ETag / 304). */ import { createReadStream, readFileSync } from "node:fs"; import { realpath, stat } from "node:fs/promises"; @@ -52,6 +53,22 @@ function notFound(res: ServerResponse): void { res.end("Not Found"); } +/** RFC 9110 §13.2.2: If-None-Match wins; If-Modified-Since applies only without it. */ +function isNotModified(req: IncomingMessage, etag: string, mtime: Date): boolean { + const ifNoneMatch = req.headers["if-none-match"]; + if (ifNoneMatch !== undefined) { + const opaque = (tag: string) => tag.trim().replace(/^W\//, ""); + return ifNoneMatch.split(",").some((tag) => tag.trim() === "*" || opaque(tag) === opaque(etag)); + } + const ifModifiedSince = req.headers["if-modified-since"]; + if (ifModifiedSince === undefined) { + return false; + } + const since = Date.parse(ifModifiedSince); + // HTTP dates have one-second resolution. + return !Number.isNaN(since) && Math.floor(mtime.getTime() / 1000) * 1000 <= since; +} + async function handle(req: IncomingMessage, res: ServerResponse): Promise { const { pathname } = new URL(req.url ?? "/", "https://localhost"); const filePath = resolve(join(DIST_DIR, pathname)); @@ -72,10 +89,25 @@ async function handle(req: IncomingMessage, res: ServerResponse): Promise return; } + // The portal's asset proxy serves fixed-name entry points (remoteEntry.js, + // the manifests) as `no-cache` and forwards the browser's validators, so a + // validator here lets every page load revalidate them with a 304 instead of + // downloading them again. Every replica runs the same image, so size and + // mtime agree across replicas. + const etag = `W/"${info.size.toString(16)}-${Math.trunc(info.mtimeMs).toString(16)}"`; + const lastModified = info.mtime.toUTCString(); + const validators = { etag, "last-modified": lastModified }; + if ((req.method === "GET" || req.method === "HEAD") && isNotModified(req, etag, info.mtime)) { + res.writeHead(304, validators); + res.end(); + return; + } + res.writeHead(200, { "content-type": MIME_TYPES[extname(realFilePath).toLowerCase()] ?? "application/octet-stream", "content-length": info.size, + ...validators, }); if (req.method === "HEAD") { res.end(); diff --git a/ui/consumer/tsconfig.json b/ui/consumer/tsconfig.json index 57d88d9d..13ed94e2 100644 --- a/ui/consumer/tsconfig.json +++ b/ui/consumer/tsconfig.json @@ -14,6 +14,6 @@ "isolatedModules": true, "types": ["vite/client"] }, - "include": ["src", "vite.config.ts"], + "include": ["src", "vite.config.ts", "serve.ts"], "exclude": ["src/**/*.test.ts"] } From 35e82941628e0b66b9189469c25eedb389c41c3b Mon Sep 17 00:00:00 2001 From: Kevin Williams Date: Tue, 3 Nov 2026 21:20:41 +0000 Subject: [PATCH 5/6] docs(ui): Bring the consumer plugin README up to date Describe the datum-ui subpaths shared from the host, the rule for pinning host-provided packages, serving and caching, and the current file layout. --- ui/consumer/README.md | 50 ++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/ui/consumer/README.md b/ui/consumer/README.md index d62e89cb..72c448b5 100644 --- a/ui/consumer/README.md +++ b/ui/consumer/README.md @@ -77,7 +77,8 @@ parts that depended on portal internals: | `runDetailLoader`/`runListLoader` server-side RBAC gate | Client-side catch of a non-ok/403 `fetch()` response → inline restricted state | | `paths.config.ts` + `getPathWithParams` | `useParams()` / `useLocation()` / `useNavigate()` from the shared `react-router` singleton | -`workload.schema.ts`/`instance.schema.ts` (combined into `src/schema.ts`), +`workload.schema.ts`/`instance.schema.ts` (combined into `src/schema.ts`, as +plain types rather than Zod schemas), `workload.adapter.ts`/`instance.adapter.ts` (combined into `src/adapter.ts`), `cli-section.tsx`, and `compute.helper.ts` (as `src/lib/format.ts`) ported over near-verbatim — they were already free of portal internals (aside from @@ -202,8 +203,15 @@ Three things stay in lockstep, same as the sample plugin: The host provides `react`, `react-dom`, `react-router`, and `@tanstack/react-query` as shared singletons (host's exact instances — see -`vite.config.ts`). This plugin bundles its own copy of `@datum-cloud/datum-ui` -(not shared as a singleton in v1), so its CSS ships with the bundle. +`vite.config.ts`). It also shares a curated set of `@datum-cloud/datum-ui` +subpaths (cloud-portal's `federation-host.ts`, `DATUM_UI_SHARED`); the ones +listed in this plugin's `shared` config resolve to the host's copy, and any +other datum-ui subpath the plugin imports is bundled into the remote. + +Keep the plugin's versions of these packages at the host's resolved versions +(cloud-portal's `package-lock.json`), not npm's latest: the host's copy is +what runs, so a newer local version only lets the plugin type-check against +APIs the host doesn't have. ## Network interface traffic @@ -224,29 +232,31 @@ values of `network` on the receive metric. If no value matches the instance's interfaces, the page keeps showing "Not collected yet". The charts appear when series exist for the instance's interfaces. +## Serving + +`vite build` minifies and emits source maps; the browser fetches a map only +when devtools is open. `serve.ts` sends a weak `ETag` and `Last-Modified` and +answers conditional requests with `304`. The portal's asset proxy serves +`remoteEntry.js` and the manifests as `no-cache`, so every page load +revalidates them; hashed chunks under `assets/` are cached for a year. + ## File layout ``` ui/consumer/ vite.config.ts MF container "workload.compute.datumapis.com" + serve.ts static HTTPS server for dist/ (the image's entrypoint) public/plugin-manifest.json src/ - lib/api.ts fetch wrappers + useQuery hooks (workloads/instances/HTTPProxy) - lib/prometheus.ts queryapi metric queries + useQuery hooks - lib/prometheus-format.ts Prometheus results → chart series / card values - lib/o11y-logs.ts Loki query_range + ALB LogQL + schema.ts view-model types for Workload / Instance + adapter.ts raw K8s JSON → view models + pages/ exposed pages and the tabs they nest + cards/ project-home column + components/ shared UI (tables, topology, charts, shell, dialogs) + lib/api.ts fetch wrappers + useQuery hooks (workloads/instances/HTTPProxy) + lib/prometheus*.ts queryapi metric queries and result formatting lib/metrics-queries.ts PromQL builders + instance identity discovery - lib/network-interface-metrics.ts probe for network interface metric labels - lib/format.ts formatUptime / splitSlashValue (ported as-is) - schema.ts Zod schemas for Workload / Instance - adapter.ts raw K8s JSON → schema mappers - components/cli-section.tsx CommandBlock / SectionCard / CliBanner - components/states.tsx Loading / Error / Restricted state components - pages/workload-list.tsx - pages/workload-detail.tsx - pages/instance-detail.tsx - pages/instance-overview.tsx - pages/instance-logs.tsx - pages/instance-metrics.tsx - main.tsx standalone preview harness only + lib/o11y-logs.ts Loki query_range + ALB LogQL + lib/console-client/ shell Web Worker and wasm client adapter + main.tsx standalone preview harness only ``` From 1f61abbac4a31b4189175eb88d139770d2f9e15b Mon Sep 17 00:00:00 2001 From: Kevin Williams Date: Tue, 3 Nov 2026 21:20:41 +0000 Subject: [PATCH 6/6] ci: Typecheck and test the portal plugins publish.yaml only builds the plugin images, so neither plugin's typecheck nor the consumer's unit tests ever ran in CI. --- .github/workflows/ui-plugins.yml | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/ui-plugins.yml diff --git a/.github/workflows/ui-plugins.yml b/.github/workflows/ui-plugins.yml new file mode 100644 index 00000000..29d40a90 --- /dev/null +++ b/.github/workflows/ui-plugins.yml @@ -0,0 +1,63 @@ +name: UI Plugins + +# Typechecks and unit-tests the portal plugins under ui/. Each is its own npm +# project with its own lockfile, so this runs per plugin. Their images are +# built by publish.yaml, which never ran either check. +# +# No Go toolchain: the consumer plugin's shell wasm client is optional at build +# time and the tests don't load it. + +on: + push: + branches: [main] + paths: + - 'ui/**' + - '.github/workflows/ui-plugins.yml' + pull_request: + paths: + - 'ui/**' + - '.github/workflows/ui-plugins.yml' + +concurrency: + group: ui-plugins-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + check: + name: ${{ matrix.plugin }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - plugin: consumer + test: true + # ui/provider has no tests yet. + - plugin: provider + test: false + defaults: + run: + working-directory: ui/${{ matrix.plugin }} + steps: + - name: Clone the code + uses: actions/checkout@v7 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version-file: ui/${{ matrix.plugin }}/.nvmrc + cache: npm + cache-dependency-path: ui/${{ matrix.plugin }}/package-lock.json + + - name: Install dependencies + run: npm ci --ignore-scripts --no-audit --no-fund + + - name: Typecheck + run: npm run typecheck + + - name: Unit tests + if: matrix.test + run: npm test