From c6b516bd9373361f41da90baa914e7dae448c100 Mon Sep 17 00:00:00 2001 From: Kris McGinnes Date: Wed, 14 Oct 2026 15:59:00 -0500 Subject: [PATCH 1/3] Raise the Chrome and Edge floor to 124 for AbortSignal.timeout --- docs/agents/browser-floor.md | 25 +++++++++++++++++++ packages/graph-explorer/.browserslistrc | 7 +++--- packages/graph-explorer/buildTarget.test.ts | 4 +-- .../SearchSidebar/ShowRawResponseDialog.tsx | 1 + packages/graph-explorer/src/utils/fileData.ts | 1 + 5 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 docs/agents/browser-floor.md diff --git a/docs/agents/browser-floor.md b/docs/agents/browser-floor.md new file mode 100644 index 0000000000..10e858dcc7 --- /dev/null +++ b/docs/agents/browser-floor.md @@ -0,0 +1,25 @@ +# Browser Floor + +Graph Explorer supports the browsers that are Baseline Widely available. `packages/graph-explorer/.browserslistrc` pins their minimum versions. Raise them on purpose, never silently through a dependency bump. + +Three layers enforce the floor. Each one catches what the others miss: + +- **Build**: `vite.config.ts` derives `build.target` from `.browserslistrc` through `buildTarget.ts`. Vite lowers JS syntax to that target, but it never polyfills a missing API. CSS follows the same floor because Vite's Lightning CSS minify compiles for `build.cssTarget`, which defaults to `build.target`. Tailwind's own optimize pass is off because its browser targets are fixed. +- **Types**: browser packages set `lib: ES2023`, so `tsc` rejects ES2024 and later built-ins, including instance methods such as Iterator helpers and `Set.prototype.union`. +- **Lint**: in `.oxlintrc.json`, production code in `packages/graph-explorer/src` and `packages/shared/src` runs `baseline-js/use-baseline` with `available: "widely"`, along with a few `es-x` syntax rules. Tests are excluded because they run in Node. + +## What the lint covers + +- `baseline-js` flags static built-ins (`Error.isError`, `Promise.try`) and Web APIs that aren't Widely available. Its data comes bundled with the plugin version, so upgrading the plugin can move what it allows. +- The `es-x` rules flag syntax the other two layers let through: `using` declarations, RegExp duplicate named groups and modifiers, import attributes, and JSON modules. +- `ignoreFeatures` skips two features: + - `html-wrapper-methods`, which flags any `.bold` or `.anchor` property. + - `top-level-await`, which web-features lists as Safari 27 even though the floor's Safari already runs it and Vite builds it for the floor. + +## What nothing catches + +- Instance members on DOM types, such as ARIA reflection properties, `inert`, and `element.checkVisibility()`. Under oxlint, `baseline-js` has no type information, and `lib`'s `DOM` isn't versioned. +- Instance methods on built-ins that `lib: ES2023` allows but the floor doesn't. The same missing type information hides them from the lint. +- ES2023 features outside the floor, such as symbols as `WeakMap` keys (Firefox 146). + +Check these by hand in review. A justified `oxlint-disable-next-line baseline-js/use-baseline -- ` is fine when the code feature-detects the API, when the lint misreads a feature that is Widely available, or when the gap is one browser version and the feature reaches Widely available within weeks. diff --git a/packages/graph-explorer/.browserslistrc b/packages/graph-explorer/.browserslistrc index 18bc64a049..a654da0800 100644 --- a/packages/graph-explorer/.browserslistrc +++ b/packages/graph-explorer/.browserslistrc @@ -1,7 +1,8 @@ -# Baseline Widely available on 2026-10-04, per baseline-browser-mapping. +# Baseline Widely available on 2026-10-04, per baseline-browser-mapping, with +# Chrome and Edge at 124 for AbortSignal.timeout (Widely available 2026-10-18). # vite.config.ts derives build.target from this list. -chrome >= 123 -edge >= 123 +chrome >= 124 +edge >= 124 firefox >= 124 safari >= 17.4 ios_saf >= 17.4 diff --git a/packages/graph-explorer/buildTarget.test.ts b/packages/graph-explorer/buildTarget.test.ts index 0b13b620a5..3a4ea71263 100644 --- a/packages/graph-explorer/buildTarget.test.ts +++ b/packages/graph-explorer/buildTarget.test.ts @@ -35,8 +35,8 @@ describe("toBuildTarget", () => { describe("browserslistBuildTarget", () => { it("builds for the browsers in .browserslistrc", () => { expect(browserslistBuildTarget()).toStrictEqual([ - "chrome123", - "edge123", + "chrome124", + "edge124", "firefox124", "ios17.4", "safari17.4", diff --git a/packages/graph-explorer/src/modules/SearchSidebar/ShowRawResponseDialog.tsx b/packages/graph-explorer/src/modules/SearchSidebar/ShowRawResponseDialog.tsx index 490f4b205d..438ef9dc72 100644 --- a/packages/graph-explorer/src/modules/SearchSidebar/ShowRawResponseDialog.tsx +++ b/packages/graph-explorer/src/modules/SearchSidebar/ShowRawResponseDialog.tsx @@ -72,6 +72,7 @@ function CopyToClipboardButton({ const copyToClipboard = async () => { try { + // oxlint-disable-next-line baseline-js/use-baseline -- writeText is Widely available; the feature also covers Clipboard.write await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); diff --git a/packages/graph-explorer/src/utils/fileData.ts b/packages/graph-explorer/src/utils/fileData.ts index 27779b984a..827b05dd86 100644 --- a/packages/graph-explorer/src/utils/fileData.ts +++ b/packages/graph-explorer/src/utils/fileData.ts @@ -37,6 +37,7 @@ export async function saveFile( return; } + // oxlint-disable-next-line baseline-js/use-baseline -- feature-detected above, falls back to saveAs const fileHandle = await window.showSaveFilePicker({ suggestedName: defaultFileName, types: [ From 83206745138e727faba13e6a1f0308944f2d4ffc Mon Sep 17 00:00:00 2001 From: Kris McGinnes Date: Wed, 14 Oct 2026 15:59:05 -0500 Subject: [PATCH 2/3] Compile CSS for the browser floor instead of Tailwind's fixed targets --- packages/graph-explorer/vite.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/graph-explorer/vite.config.ts b/packages/graph-explorer/vite.config.ts index c63f54334f..7964a47c49 100644 --- a/packages/graph-explorer/vite.config.ts +++ b/packages/graph-explorer/vite.config.ts @@ -43,7 +43,9 @@ export default defineConfig(({ mode }) => { target: browserslistBuildTarget(), }, plugins: lazyPlugins(() => [ - tailwindcss(), + // Tailwind's own Lightning CSS pass has fixed browser targets. Vite's CSS + // minify compiles for build.cssTarget, which follows .browserslistrc. + tailwindcss({ optimize: false }), react(), babel({ presets: [reactCompilerPreset()], From 8b57d86aa46a1928f83c1de7ed20398951dcd6ce Mon Sep 17 00:00:00 2001 From: Kris McGinnes Date: Wed, 14 Oct 2026 15:59:08 -0500 Subject: [PATCH 3/3] Lint production code against the Baseline Widely available floor --- AGENTS.md | 1 + REVIEW.md | 2 +- docs/agents/browser-floor.md | 8 +- docs/agents/testing.md | 1 - package.json | 2 + .../src/connector/fetchDatabaseRequest.ts | 1 + .../src/utils/createErrorDetails.test.ts | 50 ----- pnpm-lock.yaml | 200 ++++++++++++++---- vite.config.ts | 30 +++ 9 files changed, 199 insertions(+), 96 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 76bb2eeedc..1c3290c7de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,6 +58,7 @@ Read the relevant doc before working in that area: - `docs/agents/testing.md` — Vitest patterns, DbState, factories, backward-compat - `docs/agents/connectors.md` — Gremlin/openCypher/SPARQL query templates - `docs/agents/schema.md` — schema storage, discovery, Jotai atoms +- `docs/agents/browser-floor.md` — supported browsers and how build, types, and lint enforce them - `docs/agents/issue-tracker.md` — GitHub issue and PR conventions - `docs/agents/documentation.md` — writing user-facing docs (READMEs, guides, docs site) - `docs/agents/product.md` — product overview, supported databases, architecture diff --git a/REVIEW.md b/REVIEW.md index 74f82925bf..620c8b5364 100644 --- a/REVIEW.md +++ b/REVIEW.md @@ -12,7 +12,7 @@ Reserve Important for the cases below. Everything else is Nit at most. That incl - Values that reach a query outside a Query Fragment, or any other break of the rules in `docs/agents/connectors.md`. - A limit that doesn't bound the scan. - A change to one connector, or to shared query code, that changes behavior in the other query languages without saying why. -- **Runtime APIs newer than the browser floor in `packages/graph-explorer/.browserslistrc`,** such as `Error.isError` or Iterator helpers. The browser packages set `lib: ES2023` (graph-explorer adds `ES2024.String`, which is inside the floor), so `tsc` catches the rest of ES2024 and later APIs, but not ES2023 APIs that shipped after the floor, or DOM APIs, which `lib` doesn't version. +- **Runtime APIs newer than the browser floor in `packages/graph-explorer/.browserslistrc`,** such as `Error.isError` or Iterator helpers. `tsc` and oxlint catch most of these. Check by hand for what they miss, listed in `docs/agents/browser-floor.md`: DOM instance members, and built-in features that `lib` allows but the floor doesn't. - **Production code that imports a `devDependency`.** - **Work that grows with graph or schema size:** one query or subscription per item, quadratic loops, or copying passes over large collections. - **Tests that can't fail.** Name a plausible regression of the behavior the test's name promises, and prove the verdict with a mutation: inject that regression and run the test. A test that stays green is the finding, and the fix is to tighten it until the mutation turns it red. Recommend deletion only when no regression of that behavior could reach the test. Flag these: diff --git a/docs/agents/browser-floor.md b/docs/agents/browser-floor.md index 10e858dcc7..b60a945ef4 100644 --- a/docs/agents/browser-floor.md +++ b/docs/agents/browser-floor.md @@ -5,8 +5,8 @@ Graph Explorer supports the browsers that are Baseline Widely available. `packag Three layers enforce the floor. Each one catches what the others miss: - **Build**: `vite.config.ts` derives `build.target` from `.browserslistrc` through `buildTarget.ts`. Vite lowers JS syntax to that target, but it never polyfills a missing API. CSS follows the same floor because Vite's Lightning CSS minify compiles for `build.cssTarget`, which defaults to `build.target`. Tailwind's own optimize pass is off because its browser targets are fixed. -- **Types**: browser packages set `lib: ES2023`, so `tsc` rejects ES2024 and later built-ins, including instance methods such as Iterator helpers and `Set.prototype.union`. -- **Lint**: in `.oxlintrc.json`, production code in `packages/graph-explorer/src` and `packages/shared/src` runs `baseline-js/use-baseline` with `available: "widely"`, along with a few `es-x` syntax rules. Tests are excluded because they run in Node. +- **Types**: browser packages set `lib: ES2023`, so `tsc` rejects ES2024 and later built-ins, including instance methods such as Iterator helpers and `Set.prototype.union`. graph-explorer also adds `ES2024.String`, whose `isWellFormed` and `toWellFormed` are inside the floor. +- **Lint**: in the root `vite.config.ts` `lint` block, production code in `packages/graph-explorer/src` and `packages/shared/src` runs `baseline-js/use-baseline` with `available: "widely"`, along with a few `es-x` syntax rules. Tests are excluded because they run in Node. ## What the lint covers @@ -19,7 +19,7 @@ Three layers enforce the floor. Each one catches what the others miss: ## What nothing catches - Instance members on DOM types, such as ARIA reflection properties, `inert`, and `element.checkVisibility()`. Under oxlint, `baseline-js` has no type information, and `lib`'s `DOM` isn't versioned. -- Instance methods on built-ins that `lib: ES2023` allows but the floor doesn't. The same missing type information hides them from the lint. +- Instance methods on built-ins that `lib` allows but the floor doesn't. The same missing type information hides them from the lint. - ES2023 features outside the floor, such as symbols as `WeakMap` keys (Firefox 146). -Check these by hand in review. A justified `oxlint-disable-next-line baseline-js/use-baseline -- ` is fine when the code feature-detects the API, when the lint misreads a feature that is Widely available, or when the gap is one browser version and the feature reaches Widely available within weeks. +Check these by hand in review. A justified `oxlint-disable-next-line baseline-js/use-baseline -- ` is fine when the code feature-detects the API, when the lint misreads a feature that is Widely available, or when `.browserslistrc` already covers a feature that the plugin's bundled data doesn't yet count as Widely available. diff --git a/docs/agents/testing.md b/docs/agents/testing.md index 3d49a394d7..671c5f3358 100644 --- a/docs/agents/testing.md +++ b/docs/agents/testing.md @@ -82,7 +82,6 @@ Commands are in AGENTS.md. - **`vi.doMock` + dynamic `import()`**: call `vi.resetModules()` in the test's own `beforeEach` (not global — it's expensive). See any test that swaps a module impl between cases. - **Production behavior**: tests run `DEV=true`/`PROD=false`; override per-test with `vi.stubEnv("PROD", true)`. - **jsdom/happy-dom layout**: jsdom and happy-dom never lay out elements, so `offsetHeight`/`offsetWidth` are always `0`. A component that measures its own size (e.g. a virtualizer deciding which rows are visible) will render as empty, and the failure looks like a component bug rather than an environment limitation. Call `mockVirtualizedLayout` in a `beforeEach` — it mocks `offsetHeight`/`offsetWidth` to read the element's own inline style, falling back to a fixed size, so real measurements are distinguishable from unmeasured ones. -- **Runtime features newer than the browser floor**: to cover the path taken by a browser that lacks a global or method, delete the property reflectively and restore it in a `finally` — `Object.getOwnPropertyDescriptor` plus `Reflect.deleteProperty`. Don't write the typed reference (`Error.isError`), which stops compiling as soon as `lib` excludes the feature. See `withoutErrorIsError` in `src/utils/createErrorDetails.test.ts`. - **Jotai mount effects**: `store.get` reads an atom without mounting it, so `onMount` never runs. When the behavior under test depends on mounting, such as a subscription, cross-tab sync, or a listener, mount the atom first with `store.sub(atom, () => {})`. See the `subscribe` helper in `src/core/StateProvider/sessionScopedStorage.test.ts`. - **Errors**: assert the full error, not just that one was thrown. `expect(() => fn()).toThrow(new FooError(a, b))` — or `await expect(fn()).rejects.toThrow(new FooError(a, b))` for a rejected promise — deep-compares every property, so a wrong field fails the test. Prefer this over `toThrow(FooError)` (type only) or `toThrow("message")` (message only), which pass even when the code built the error with the wrong data. No need to catch the error and assert fields separately — the instance form already covers them. diff --git a/package.json b/package.json index 29230996e3..63bbbd0bf6 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "dependencies": {}, "devDependencies": { "@tanstack/eslint-plugin-query": "5.104.2", + "eslint-plugin-baseline-js": "0.7.4", + "eslint-plugin-es-x": "10.0.1", "eslint-plugin-react-hooks": "7.1.1", "fast-check": "^4.10.2", "typescript": "^7.0.2", diff --git a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts index d5c8c24d20..35cbd6cc7b 100644 --- a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts +++ b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts @@ -137,6 +137,7 @@ function createFetchTimeout( return null; } + // oxlint-disable-next-line baseline-js/use-baseline -- inside the floor at Chrome 124; the plugin's bundled data predates its 2026-10-18 Widely date return { timeoutMs, signal: AbortSignal.timeout(timeoutMs) }; } diff --git a/packages/graph-explorer/src/utils/createErrorDetails.test.ts b/packages/graph-explorer/src/utils/createErrorDetails.test.ts index eec69ba2d1..d7e738762b 100644 --- a/packages/graph-explorer/src/utils/createErrorDetails.test.ts +++ b/packages/graph-explorer/src/utils/createErrorDetails.test.ts @@ -437,37 +437,6 @@ describe("createErrorDetails", () => { }); }); - describe("browsers without Error.isError", () => { - it("returns name and message from a standard Error", () => { - const error = new Error("something broke"); - expect( - withoutErrorIsError(() => createErrorDetails(error)), - ).toStrictEqual({ - name: "Error", - message: "something broke", - }); - }); - - it("includes the nested cause chain as data", () => { - const root = new Error("ECONNREFUSED"); - const error = new Error("request failed", { cause: root }); - const details = withoutErrorIsError(() => createErrorDetails(error)); - expect(details.data).toBe( - JSON.stringify({ name: root.name, message: root.message }, null, 2), - ); - }); - - it("returns name and message from a DOMException", () => { - const error = new DOMException("The operation was aborted", "AbortError"); - expect( - withoutErrorIsError(() => createErrorDetails(error)), - ).toStrictEqual({ - name: "AbortError", - message: "The operation was aborted", - }); - }); - }); - describe("non-Error values", () => { it("returns 'Unknown Error' with JSON for a plain object", () => { const error = { code: "ECONNREFUSED" }; @@ -522,25 +491,6 @@ describe("createErrorDetails", () => { }); }); -/** - * Runs `fn` with `Error.isError` absent, as on browsers that do not implement - * it. - * - * Reflection keeps the property name out of the type system, so this stays - * compilable under a `lib` that predates `Error.isError`. - */ -function withoutErrorIsError(fn: () => T): T { - const descriptor = Object.getOwnPropertyDescriptor(Error, "isError"); - Reflect.deleteProperty(Error, "isError"); - try { - return fn(); - } finally { - if (descriptor) { - Object.defineProperty(Error, "isError", descriptor); - } - } -} - /** Stands in for a query value failure type that `createErrorDetails` has no specific branch for. */ class DetailedQueryValueError extends QueryValueError { readonly details = { someContext: "value" }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fe6bd436f..6ddf2b654e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,10 +191,16 @@ importers: devDependencies: '@tanstack/eslint-plugin-query': specifier: 5.104.2 - version: 5.104.2(eslint@10.2.1(jiti@2.7.0))(typescript@7.0.2) + version: 5.104.2(eslint@10.12.0(jiti@2.7.0))(typescript@7.0.2) + eslint-plugin-baseline-js: + specifier: 0.7.4 + version: 0.7.4(eslint@10.12.0(jiti@2.7.0)) + eslint-plugin-es-x: + specifier: 10.0.1 + version: 10.0.1(eslint@10.12.0(jiti@2.7.0)) eslint-plugin-react-hooks: specifier: 7.1.1 - version: 7.1.1(eslint@10.2.1(jiti@2.7.0)) + version: 7.1.1(eslint@10.12.0(jiti@2.7.0)) fast-check: specifier: ^4.10.2 version: 4.10.2 @@ -709,6 +715,12 @@ packages: resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true + '@cacheable/memory@2.2.0': + resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==} + + '@cacheable/utils@2.5.0': + resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==} + '@csstools/color-helpers@6.1.2': resolution: {integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==} engines: {node: '>=20.19.0'} @@ -915,8 +927,8 @@ packages: resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.5.5': - resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': @@ -1081,6 +1093,15 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@keyv/bigmap@1.3.1': + resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.6.0 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + '@monaco-editor/loader@1.7.0': resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} @@ -3100,6 +3121,9 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cacheable@2.5.0: + resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -3394,6 +3418,24 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-plugin-baseline-js@0.7.4: + resolution: {integrity: sha512-WUl7oMZoGGMjeVXRzid8kNyehc2+YrgFnAbIPWhk3B6Kr39JGjCEMdUlkbQzxQ/eyD19SdXY1YyabRKs5YbQGw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + eslint: '>=9.29.0 <11' + + eslint-plugin-es-x@10.0.1: + resolution: {integrity: sha512-HfShsMg5LKr9Cpz8VTBOLlrExvnHiXG0hf+nZT3MfsskWbttUSC/9jpO5Y//nyZ9Aik8sRvInf2Pe3kSUcQhYQ==} + engines: {node: ^22.23.0 || ^24.18.0 || >=26.4.0} + peerDependencies: + eslint: '>=10.6.0' + + eslint-plugin-es-x@9.7.0: + resolution: {integrity: sha512-BT0pUw0toxtrHulYycGiUTL++qC11opyPYMuGUR7AgmuQ/N1YN1xLupF8rGxlaGqEqkqeedJuacCxxAcYgGkvg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + eslint: '>=9.29.0' + eslint-plugin-react-hooks@7.1.1: resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} engines: {node: '>=18'} @@ -3404,6 +3446,12 @@ packages: resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint-type-tracer@0.5.2: + resolution: {integrity: sha512-YiJmgk6OD1suPB4UC3M/PB+oxahf145jQQ5gFXFk9F9Y7puWwnqGYC2BtTB+FoHqdOohImeTt0rNk0lhhECNaQ==} + engines: {node: ^18 || >=20} + peerDependencies: + eslint: '>=8' + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3412,8 +3460,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.2.1: - resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} + eslint@10.12.0: + resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -3496,9 +3544,8 @@ packages: fflate@0.8.3: resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} + file-entry-cache@11.1.5: + resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==} file-saver@2.0.5: resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} @@ -3511,9 +3558,8 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} + flat-cache@6.1.23: + resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==} flat@6.0.1: resolution: {integrity: sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==} @@ -3608,6 +3654,10 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hashery@1.5.1: + resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} + engines: {node: '>=20'} + hasown@2.0.4: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} @@ -3621,6 +3671,12 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + html-encoding-sniffer@7.0.0: resolution: {integrity: sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==} engines: {node: ^22.13.0 || >=24.0.0} @@ -3715,9 +3771,6 @@ packages: engines: {node: '>=6'} hasBin: true - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -3729,8 +3782,8 @@ packages: engines: {node: '>=6'} hasBin: true - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} klayjs@0.4.1: resolution: {integrity: sha512-WUNxuO7O79TEkxCj6OIaK5TJBkaWaR/IKNTakgV9PwDn+mrr63MLHed34AcE2yTaDntgO6l0zGFIzhcoTeroTA==} @@ -4202,6 +4255,10 @@ packages: pure-rand@8.4.2: resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} + qified@0.10.1: + resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} + engines: {node: '>=20'} + qs@6.16.0: resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} @@ -5146,6 +5203,18 @@ snapshots: dependencies: css-tree: 3.2.1 + '@cacheable/memory@2.2.0': + dependencies: + '@cacheable/utils': 2.5.0 + '@keyv/bigmap': 1.3.1(keyv@5.6.0) + hookified: 1.15.1 + keyv: 5.6.0 + + '@cacheable/utils@2.5.0': + dependencies: + hashery: 1.5.1 + keyv: 5.6.0 + '@csstools/color-helpers@6.1.2': {} '@csstools/css-calc@3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': @@ -5248,9 +5317,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.2.1(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(jiti@2.7.0))': dependencies: - eslint: 10.2.1(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -5263,7 +5332,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.5.5': + '@eslint/config-helpers@0.7.0': dependencies: '@eslint/core': 1.2.1 @@ -5360,6 +5429,14 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.6.0 + '@keyv/bigmap@1.3.1(keyv@5.6.0)': + dependencies: + hashery: 1.5.1 + hookified: 1.15.1 + keyv: 5.6.0 + + '@keyv/serialize@1.1.1': {} + '@monaco-editor/loader@1.7.0': dependencies: state-local: 1.0.7 @@ -6469,10 +6546,10 @@ snapshots: tailwindcss: 4.3.3 vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@24.19.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.42.0)(tsx@4.21.0)(typescript@7.0.2)(yaml@2.9.0)' - '@tanstack/eslint-plugin-query@5.104.2(eslint@10.2.1(jiti@2.7.0))(typescript@7.0.2)': + '@tanstack/eslint-plugin-query@5.104.2(eslint@10.12.0(jiti@2.7.0))(typescript@7.0.2)': dependencies: - '@typescript-eslint/utils': 8.71.0(eslint@10.2.1(jiti@2.7.0))(typescript@7.0.2) - eslint: 10.2.1(jiti@2.7.0) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(jiti@2.7.0))(typescript@7.0.2) + eslint: 10.12.0(jiti@2.7.0) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -6687,13 +6764,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.71.0(eslint@10.2.1(jiti@2.7.0))(typescript@7.0.2)': + '@typescript-eslint/utils@8.71.0(eslint@10.12.0(jiti@2.7.0))(typescript@7.0.2)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.2.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.71.0 '@typescript-eslint/types': 8.71.0 '@typescript-eslint/typescript-estree': 8.71.0(typescript@7.0.2) - eslint: 10.2.1(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0) typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -7113,6 +7190,14 @@ snapshots: bytes@3.1.2: {} + cacheable@2.5.0: + dependencies: + '@cacheable/memory': 2.2.0 + '@cacheable/utils': 2.5.0 + hookified: 1.15.1 + keyv: 5.6.0 + qified: 0.10.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -7383,11 +7468,30 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@7.1.1(eslint@10.2.1(jiti@2.7.0)): + eslint-plugin-baseline-js@0.7.4(eslint@10.12.0(jiti@2.7.0)): + dependencies: + eslint: 10.12.0(jiti@2.7.0) + eslint-plugin-es-x: 9.7.0(eslint@10.12.0(jiti@2.7.0)) + + eslint-plugin-es-x@10.0.1(eslint@10.12.0(jiti@2.7.0)): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + eslint: 10.12.0(jiti@2.7.0) + eslint-type-tracer: 0.5.2(eslint@10.12.0(jiti@2.7.0)) + + eslint-plugin-es-x@9.7.0(eslint@10.12.0(jiti@2.7.0)): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + eslint: 10.12.0(jiti@2.7.0) + eslint-type-tracer: 0.5.2(eslint@10.12.0(jiti@2.7.0)) + + eslint-plugin-react-hooks@7.1.1(eslint@10.12.0(jiti@2.7.0)): dependencies: '@babel/core': 7.29.7 '@babel/parser': 7.29.8 - eslint: 10.2.1(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.6.7 zod-validation-error: 4.0.2(zod@4.6.7) @@ -7401,16 +7505,21 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-type-tracer@0.5.2(eslint@10.12.0(jiti@2.7.0)): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) + eslint: 10.12.0(jiti@2.7.0) + eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@5.0.1: {} - eslint@10.2.1(jiti@2.7.0): + eslint@10.12.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.2.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 - '@eslint/config-helpers': 0.5.5 + '@eslint/config-helpers': 0.7.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.3 '@humanfs/node': 0.16.8 @@ -7427,7 +7536,7 @@ snapshots: esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 + file-entry-cache: 11.1.5 find-up: 5.0.0 glob-parent: 6.0.2 ignore: 5.3.2 @@ -7528,9 +7637,9 @@ snapshots: fflate@0.8.3: {} - file-entry-cache@8.0.0: + file-entry-cache@11.1.5: dependencies: - flat-cache: 4.0.1 + flat-cache: 6.1.23 file-saver@2.0.5: {} @@ -7550,10 +7659,11 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@4.0.1: + flat-cache@6.1.23: dependencies: + cacheable: 2.5.0 flatted: 3.4.4 - keyv: 4.5.4 + hookified: 1.15.1 flat@6.0.1: {} @@ -7655,6 +7765,10 @@ snapshots: dependencies: has-symbols: 1.1.0 + hashery@1.5.1: + dependencies: + hookified: 1.15.1 + hasown@2.0.4: dependencies: function-bind: 1.1.2 @@ -7667,6 +7781,10 @@ snapshots: dependencies: hermes-estree: 0.25.1 + hookified@1.15.1: {} + + hookified@2.2.0: {} + html-encoding-sniffer@7.0.0(@noble/hashes@1.8.0): dependencies: '@exodus/bytes': 1.16.0(@noble/hashes@1.8.0) @@ -7751,17 +7869,15 @@ snapshots: jsesc@3.1.0: {} - json-buffer@3.0.1: {} - json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} - keyv@4.5.4: + keyv@5.6.0: dependencies: - json-buffer: 3.0.1 + '@keyv/serialize': 1.1.1 klayjs@0.4.1: {} @@ -8173,6 +8289,10 @@ snapshots: pure-rand@8.4.2: {} + qified@0.10.1: + dependencies: + hookified: 2.2.0 + qs@6.16.0: dependencies: es-define-property: 1.0.1 diff --git a/vite.config.ts b/vite.config.ts index 0eb39aad97..1e29cb1418 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -206,6 +206,36 @@ export default defineConfig({ node: true, }, }, + { + files: [ + "packages/graph-explorer/src/**/*.{ts,tsx}", + "packages/shared/src/**/*.ts", + ], + excludeFiles: [ + "**/*.test.{ts,tsx}", + "**/testing/**", + "**/testUtils/**", + ], + jsPlugins: ["eslint-plugin-baseline-js", "eslint-plugin-es-x"], + rules: { + "baseline-js/use-baseline": [ + "error", + { + available: "widely", + includeWebApis: { preset: "auto" }, + includeJsBuiltins: { preset: "auto" }, + ignoreFeatures: ["html-wrapper-methods", "top-level-await"], + }, + ], + "es-x/no-using-declarations": "error", + "es-x/no-regexp-duplicate-named-capturing-groups": "error", + "es-x/no-regexp-modifiers": "error", + "es-x/no-import-attributes": "error", + "es-x/no-json-modules": "error", + "es-x/no-dynamic-import-options": "error", + "es-x/no-trailing-dynamic-import-commas": "error", + }, + }, { files: ["packages/graph-explorer/src/**/*.{ts,tsx}"], excludeFiles: ["packages/graph-explorer/src/connections/**"],