From a33f4de3d45373ec28c2d917145926c7364b5b9a Mon Sep 17 00:00:00 2001 From: Glass Date: Thu, 4 Dec 2025 15:02:15 -0500 Subject: [PATCH 1/8] switch to oxlint/oxfmt and address some linting/type issues --- .pre-commit-config.yaml | 4 +- refl1d/webview/client/.oxfmtrc.json | 6 ++ refl1d/webview/client/.oxlintrc.json | 9 +++ refl1d/webview/client/eslint.config.js | 59 ------------------- refl1d/webview/client/package.json | 47 ++++++--------- refl1d/webview/client/prettier.config.js | 48 --------------- .../src/components/ProfileUncertaintyView.vue | 2 +- refl1d/webview/client/src/panels.ts | 12 ++-- refl1d/webview/client/src/types.d.ts | 6 ++ refl1d/webview/client/tsconfig.json | 2 - 10 files changed, 49 insertions(+), 146 deletions(-) create mode 100644 refl1d/webview/client/.oxfmtrc.json create mode 100644 refl1d/webview/client/.oxlintrc.json delete mode 100644 refl1d/webview/client/eslint.config.js delete mode 100644 refl1d/webview/client/prettier.config.js diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9585c194a..4535cb225 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: hooks: - id: eslint name: eslint - entry: bash -c "cd refl1d/webview/client && npm run lint" + entry: bash -c 'cd refl1d/webview/client && if command -v bun >/dev/null 2>&1; then bun run lint; elif command -v npm >/dev/null 2>&1; then npm run lint; else echo "Need bun or npm to run lint" >&2; exit 1; fi' language: system - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 @@ -16,7 +16,7 @@ repos: # - id: end-of-file-fixer # - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.7 + rev: v0.14.8 hooks: # - id: ruff # args: [--fix, --exit-non-zero-on-fix] diff --git a/refl1d/webview/client/.oxfmtrc.json b/refl1d/webview/client/.oxfmtrc.json new file mode 100644 index 000000000..e73c886af --- /dev/null +++ b/refl1d/webview/client/.oxfmtrc.json @@ -0,0 +1,6 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "__note__": "This is the configuration file for oxfmt. See oxc.rs/docs/guide/usage/formatter/config-file-reference.html", + "printWidth": 120, + "trailingComma": "es5" +} diff --git a/refl1d/webview/client/.oxlintrc.json b/refl1d/webview/client/.oxlintrc.json new file mode 100644 index 000000000..10dcc1176 --- /dev/null +++ b/refl1d/webview/client/.oxlintrc.json @@ -0,0 +1,9 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "extends": [ + "oxlint/recommended", + "oxlint/typescript", + "oxlint/vue" + ], + "files": ["src/**/*.{js,mjs,ts,tsx,vue}"] +} \ No newline at end of file diff --git a/refl1d/webview/client/eslint.config.js b/refl1d/webview/client/eslint.config.js deleted file mode 100644 index 5e5d8e795..000000000 --- a/refl1d/webview/client/eslint.config.js +++ /dev/null @@ -1,59 +0,0 @@ -import url from "url"; -import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; -import pluginVue from "eslint-plugin-vue"; -import { FlatCompat } from "@eslint/eslintrc"; -import js from "@eslint/js"; -import prettierConfig from "@vue/eslint-config-prettier"; -import vueTsEslintConfig from "@vue/eslint-config-typescript"; - -const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - // allConfig: js.configs.all, -}); - -export default [ - /** Extend recommended configs */ - ...compat.extends("plugin:vue/vue3-recommended", "plugin:vuejs-accessibility/recommended", "prettier"), - ...pluginVue.configs["flat/recommended"], - ...vueTsEslintConfig(), - eslintPluginPrettierRecommended, - prettierConfig, - /** Configuration */ - { - languageOptions: { - parserOptions: { - ecmaVersion: "latest", - sourceType: "script", - }, - }, - files: ["src/**/*.js", "src/**/*.mjs", "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], - /** Override rules */ - rules: { - "max-len": ["error", { code: 120 }], - "prefer-const": 0, - "@typescript-eslint/ban-ts-comment": ["error", { "ts-ignore": "allow-with-description" }], - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-expressions": [ - "error", - { allowShortCircuit: true, allowTernary: true }, // Temporary fix for indirect dependency @typescript-eslint <= 8.15.0 - ], - "prettier/prettier": [ - "warn", - {}, - { - usePrettierrc: true, - }, - ], - "vuejs-accessibility/label-has-for": [ - "error", - { - required: { - some: ["nesting", "id"], - }, - }, - ], - }, - }, -]; diff --git a/refl1d/webview/client/package.json b/refl1d/webview/client/package.json index 4c34c141a..d052d05a5 100644 --- a/refl1d/webview/client/package.json +++ b/refl1d/webview/client/package.json @@ -1,6 +1,6 @@ { "name": "refl1d-webview-client", - "version": "0.1.26", + "version": "0.1.27", "type": "module", "repository": { "type": "git", @@ -12,44 +12,35 @@ "build": "vite build --emptyOutDir -m development", "build_prod": "vite build --emptyOutDir -m production", "preview": "vite preview --port 4173", - "format": "prettier --write src", - "lint": "eslint src --fix", - "test:lint": "eslint src", + "format": "oxfmt src", + "lint": "oxlint src --fix", + "test:lint": "oxlint src", "test:types": "vue-tsc --noEmit --skipLibCheck -p tsconfig.json --composite false" }, "dependencies": { "@msgpack/msgpack": "^3.1.2", - "bootstrap": "^5.3.7", + "bootstrap": "^5.3.8", "date-fns": "^4.1.0", "json-difference": "^1.16.1", - "mpld3": "^0.5.10", - "plotly.js": "^3.0.1", + "mpld3": "^0.5.11", + "plotly.js": "^3.1.2", "socket.io-client": "^4.8.1", "uuid": "^11.1.0", - "vue": "^3.5.17", - "vue-json-pretty": "^2.5.0" + "vue": "^3.5.22", + "vue-json-pretty": "^2.6.0" }, "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "^4.4.1", "@tsconfig/node-lts": "^22.0.2", - "@types/node": "^24.5.2", - "@types/plotly.js": "^2.35.1", - "@types/uuid": "^10.0.0", - "@vitejs/plugin-vue": "^5.2.1", - "@vue/eslint-config-prettier": "10.1.0", - "@vue/eslint-config-typescript": "^14.2.0", - "@vue/tsconfig": "^0.7.0", - "eslint": "^9.18.0", - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-vue": "^9.32.0", - "eslint-plugin-vuejs-accessibility": "^2.4.1", - "prettier": "^3.4.2", - "prettier-plugin-css-order": "^2.1.2", - "prettier-plugin-jsdoc": "^1.3.2", - "typescript": "^5.7.3", - "vite": "^6.0.7", + "@types/node": "^24.9.2", + "@types/plotly.js": "^3.0.8", + "@types/uuid": "^11.0.0", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.8.1", + "oxlint": "^1.31.0", + "oxfmt": "^0.16.0", + "typescript": "^5.9.3", + "vite": "^7.1.12", "vite-svg-loader": "5.1.0", - "vue-tsc": "^2.2.0" + "vue-tsc": "^3.1.2" } } diff --git a/refl1d/webview/client/prettier.config.js b/refl1d/webview/client/prettier.config.js deleted file mode 100644 index f72006375..000000000 --- a/refl1d/webview/client/prettier.config.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @type {import("prettier").Config} - * @see https://prettier.io/docs/en/configuration.html - */ - -const config = { - plugins: [ - "@ianvs/prettier-plugin-sort-imports", - // "./node_modules/prettier-plugin-jsdoc/dist/index.js", - "prettier-plugin-css-order", - ], - importOrder: ["^vue", "^[a-zA-Z]", "^@[a-zA-Z]", "^@/", "^./", "^../"], - cssDeclarationSorterOrder: "smacss", - // jsdocCapitalizeDescription: false, - overrides: [ - { - files: "*.svg", - options: { - parser: "html", - }, - }, - ], - experimentalTernaries: true, - printWidth: 120, - // tabWidth: 4, - // useTabs: false, - // semi: true, - // singleQuote: false, - // jsxSingleQuote: false, - // quoteProps: "as-needed", - trailingComma: "es5", - // bracketSpacing: true, - // bracketSameLine: false, - // arrowParens: "always", - // rangeStart: 0, - // rangeEnd: Infinity, - // parser: "babel", - // requirePragma: false, - // insertPragma: false, - // proseWrap: "preserve", - // htmlWhitespaceSensitivity: "css", - // vueIndentScriptAndStyle: false, - // endOfLine: "lf", - // embeddedLanguageFormatting: "auto", - // singleAttributePerLine: true, -}; - -export default config; diff --git a/refl1d/webview/client/src/components/ProfileUncertaintyView.vue b/refl1d/webview/client/src/components/ProfileUncertaintyView.vue index 647bfee8a..2585afe89 100644 --- a/refl1d/webview/client/src/components/ProfileUncertaintyView.vue +++ b/refl1d/webview/client/src/components/ProfileUncertaintyView.vue @@ -67,7 +67,7 @@ function get_csv_data() { }); } const n = values[0].length; - const lines = new Array(n + 1); + const lines = Array.from({ length: n + 1 }); lines[0] = headers.join(","); for (let i = 0; i < n; i++) { lines[i + 1] = values.map((v) => v[i].toPrecision(6)).join(","); diff --git a/refl1d/webview/client/src/panels.ts b/refl1d/webview/client/src/panels.ts index 3ddc29847..f235234a0 100644 --- a/refl1d/webview/client/src/panels.ts +++ b/refl1d/webview/client/src/panels.ts @@ -1,9 +1,9 @@ import { shared_state } from "bumps-webview-client/src/app_state"; import { panels as bumps_panels, type Panel } from "bumps-webview-client/src/panels"; -import DataView from "./components/DataView.vue"; -import ModelView from "./components/ModelView.vue"; -import ProfileUncertaintyView from "./components/ProfileUncertaintyView.vue"; -import SimpleBuilder from "./components/SimpleBuilder.vue"; +import DataView from "@/components/DataView.vue"; +import ModelView from "@/components/ModelView.vue"; +import ProfileUncertaintyView from "@/components/ProfileUncertaintyView.vue"; +import SimpleBuilder from "@/components/SimpleBuilder.vue"; const refl1dPanels: Panel[] = [ { title: "Reflectivity", component: DataView }, @@ -34,7 +34,7 @@ function replace_panel( ) { const index = panels.findIndex((p) => p.title === replaced_title); const replacement_index = replacement_panels.findIndex((p) => p.title === replacement_title); - if (index >= 0 && replacement_index >= 0) { + if (index >= 0 && replacement_index >= 0 && replacement_panels[replacement_index]) { panels.splice(index, 1, replacement_panels[replacement_index]); } } @@ -46,7 +46,7 @@ function insert_panel(panels: Panel[], insertion_panels: Panel[], title: string, index = panels.length - 1; } const insertion_index = insertion_panels.findIndex((p) => p.title === title); - if (insertion_index >= 0) { + if (insertion_index >= 0 && insertion_panels[insertion_index]) { panels.splice(index + 1, 0, insertion_panels[insertion_index]); } } diff --git a/refl1d/webview/client/src/types.d.ts b/refl1d/webview/client/src/types.d.ts index d6f7d73b8..fbbe0f5a1 100644 --- a/refl1d/webview/client/src/types.d.ts +++ b/refl1d/webview/client/src/types.d.ts @@ -1 +1,7 @@ /// + +declare module "*.vue" { + import type { DefineComponent } from "vue"; + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/refl1d/webview/client/tsconfig.json b/refl1d/webview/client/tsconfig.json index 72286f396..8ce5babc3 100644 --- a/refl1d/webview/client/tsconfig.json +++ b/refl1d/webview/client/tsconfig.json @@ -8,7 +8,6 @@ ], "compilerOptions": { "noErrorTruncation": true, - // "composite": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"] @@ -19,6 +18,5 @@ "moduleResolution": "Bundler", "target": "ES6", "types": ["node"], - // "allowJs": true } } From c00da828c940aaf03631881b99f84ddc528dcfe2 Mon Sep 17 00:00:00 2001 From: Glass Date: Thu, 4 Dec 2025 15:02:53 -0500 Subject: [PATCH 2/8] better name for frontend lint precommit hook --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4535cb225..7c7659676 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,8 @@ repos: - repo: local hooks: - - id: eslint - name: eslint + - id: frontend-lint + name: frontend-lint entry: bash -c 'cd refl1d/webview/client && if command -v bun >/dev/null 2>&1; then bun run lint; elif command -v npm >/dev/null 2>&1; then npm run lint; else echo "Need bun or npm to run lint" >&2; exit 1; fi' language: system - repo: https://github.com/pre-commit/pre-commit-hooks @@ -24,4 +24,4 @@ repos: ci: autoupdate_schedule: monthly - skip: [eslint] + skip: [frontend-lint] From 1d455f5cfbd59d4ea62cb6104f751fcfaab93a1c Mon Sep 17 00:00:00 2001 From: Glass Date: Thu, 4 Dec 2025 15:08:11 -0500 Subject: [PATCH 3/8] fix resolve wasn't working --- refl1d/webview/client/vite.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/refl1d/webview/client/vite.config.js b/refl1d/webview/client/vite.config.js index d98ba1970..1ef13c118 100644 --- a/refl1d/webview/client/vite.config.js +++ b/refl1d/webview/client/vite.config.js @@ -1,4 +1,5 @@ import { defineConfig } from "vite"; +import { fileURLToPath, URL } from "node:url"; import svgLoader from "vite-svg-loader"; import vue from "@vitejs/plugin-vue"; @@ -14,4 +15,9 @@ export default defineConfig({ optimizeDeps: { include: ["plotly.js/lib/core", "plotly.js/lib/heatmap", "plotly.js/lib/bar", "json-difference"], }, + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)) + } + } }); From a64ba3d2b09077930de9b3da1626143e18493dee Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 5 Dec 2025 10:16:04 -0500 Subject: [PATCH 4/8] fix types on drag operation (dragged slab must exist) --- refl1d/webview/client/src/components/SimpleBuilder.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/refl1d/webview/client/src/components/SimpleBuilder.vue b/refl1d/webview/client/src/components/SimpleBuilder.vue index a148d698e..6efa14b3a 100644 --- a/refl1d/webview/client/src/components/SimpleBuilder.vue +++ b/refl1d/webview/client/src/components/SimpleBuilder.vue @@ -302,7 +302,8 @@ function dragOver(index: number, event: DragEvent) { function drop(index: number) { if (dragData.value !== null) { - const draggedDict = sortedLayers.value[dragData.value]; + // we know the dragged row exists, or the event couldn't have fired + const draggedDict = sortedLayers.value[dragData.value] as Slab; sortedLayers.value.splice(dragData.value, 1); sortedLayers.value.splice(index, 0, draggedDict); dragData.value = null; From 1aee94a9eab535a6bf226417b1a7550723dd04c8 Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 5 Dec 2025 10:17:37 -0500 Subject: [PATCH 5/8] handle potentially missing values from input --- .../webview/client/src/components/ProfileUncertaintyView.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refl1d/webview/client/src/components/ProfileUncertaintyView.vue b/refl1d/webview/client/src/components/ProfileUncertaintyView.vue index 2585afe89..4f6c4fc8b 100644 --- a/refl1d/webview/client/src/components/ProfileUncertaintyView.vue +++ b/refl1d/webview/client/src/components/ProfileUncertaintyView.vue @@ -66,11 +66,11 @@ function get_csv_data() { }); }); } - const n = values[0].length; + const n = values[0]?.length ?? 0; const lines = Array.from({ length: n + 1 }); lines[0] = headers.join(","); for (let i = 0; i < n; i++) { - lines[i + 1] = values.map((v) => v[i].toPrecision(6)).join(","); + lines[i + 1] = values.map((v) => v[i]?.toPrecision(6)).join(","); //keys.map(k => data[k][i].toPrecision(6)).join(','); } return "data:text/csv;charset=utf-8," + encodeURIComponent(lines.join("\n")); From a181431b8f929d0574f417d02efe5dfc4eb22e8b Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 5 Dec 2025 10:21:38 -0500 Subject: [PATCH 6/8] removing deprecated @types/uuid (types ship with the package now) --- refl1d/webview/client/package.json | 3 +-- refl1d/webview/client/src/components/ModelView.vue | 1 - refl1d/webview/client/src/components/ModelViewPlotly.vue | 1 - .../webview/client/src/components/ProfileUncertaintyView.vue | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/refl1d/webview/client/package.json b/refl1d/webview/client/package.json index d052d05a5..1603174bf 100644 --- a/refl1d/webview/client/package.json +++ b/refl1d/webview/client/package.json @@ -33,11 +33,10 @@ "@tsconfig/node-lts": "^22.0.2", "@types/node": "^24.9.2", "@types/plotly.js": "^3.0.8", - "@types/uuid": "^11.0.0", "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", - "oxlint": "^1.31.0", "oxfmt": "^0.16.0", + "oxlint": "^1.31.0", "typescript": "^5.9.3", "vite": "^7.1.12", "vite-svg-loader": "5.1.0", diff --git a/refl1d/webview/client/src/components/ModelView.vue b/refl1d/webview/client/src/components/ModelView.vue index 6c7bbcc27..72698db73 100644 --- a/refl1d/webview/client/src/components/ModelView.vue +++ b/refl1d/webview/client/src/components/ModelView.vue @@ -1,5 +1,4 @@ From 5b5be219d04934436e4a467286437fd3c988fb0c Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 5 Dec 2025 10:34:25 -0500 Subject: [PATCH 8/8] fix type and event listeners in profile view (was using old removed model_loaded topic instead of message) --- .../client/src/components/ModelViewPlotly.vue | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/refl1d/webview/client/src/components/ModelViewPlotly.vue b/refl1d/webview/client/src/components/ModelViewPlotly.vue index fb517a68c..ec802f50f 100644 --- a/refl1d/webview/client/src/components/ModelViewPlotly.vue +++ b/refl1d/webview/client/src/components/ModelViewPlotly.vue @@ -17,21 +17,38 @@ const props = defineProps<{ const { draw_requested } = setupDrawLoop("updated_parameters", props.socket, fetch_and_draw); -props.socket.on("model_loaded", async () => { +async function get_model_names() { const new_model_names = (await props.socket.asyncEmit("get_model_names")) as string[]; + if (new_model_names == null) { + return; + } model_names.value = new_model_names; -}); +} -onMounted(() => { - props.socket.asyncEmit("get_topic_messages", "model_loaded", (messages) => { - const new_model_names = messages?.[0]?.message?.model_names; - if (new_model_names != null) { - model_names.value = new_model_names; - } - }); +props.socket.on("model_loaded", get_model_names); + +onMounted(async () => { + await get_model_names(); }); -function generate_new_traces(profile_data) { +interface ProfileData { + step_profile: { + z: number[]; + rho: number[]; + irho: number[]; + rhoM?: number[]; + thetaM?: number[]; + }; + smooth_profile: { + z: number[]; + rho: number[]; + irho: number[]; + rhoM?: number[]; + thetaM?: number[]; + }; +} + +function generate_new_traces(profile_data: ProfileData) { const traces: (Plotly.Data & { x: number[]; y: number[] })[] = []; const { step_profile, smooth_profile } = profile_data; console.log(profile_data);