diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 90bd5a8..cd0d182 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "26" - name: Install disposable PostgreSQL engine run: npm install --prefix backend/data/citation-validation --no-audit --no-fund --ignore-scripts @electric-sql/pglite@0.5.14 - name: Verify citation migration and transactions @@ -47,7 +47,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "26" cache: npm cache-dependency-path: "**/package-lock.json" - name: Install frontend deps @@ -58,7 +58,12 @@ jobs: run: npm run build e2e: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 15 + container: + # Keep this version aligned with @playwright/test in frontend/package-lock.json. + image: mcr.microsoft.com/playwright:v1.58.2-noble + options: --ipc=host needs: [backend, frontend] defaults: run: @@ -67,12 +72,14 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "26" cache: npm cache-dependency-path: "**/package-lock.json" - name: Install frontend deps + timeout-minutes: 5 run: npm ci - - name: Install Playwright browsers - run: npx playwright install --with-deps chromium + - name: Verify Playwright image version + run: node -e "if (require('@playwright/test/package.json').version !== '1.58.2') throw new Error('Update the CI Playwright image to match package-lock.json')" - name: Run E2E smoke tests + timeout-minutes: 8 run: npm run test:e2e diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 72482a6..1017add 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -30,7 +30,7 @@ "dompurify": "^3.4.17", "html-to-image": "^1.11.13", "jszip": "^3.10.1", - "katex": "^0.19.1", + "katex": "^0.18.10", "lowlight": "^3.3.0", "papaparse": "^5.5.3", "react": "^18.3.1", @@ -2393,14 +2393,12 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2411,7 +2409,6 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -3015,7 +3012,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/d3": { @@ -4270,9 +4266,9 @@ } }, "node_modules/katex": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.19.1.tgz", - "integrity": "sha512-jG8uKntjSEcm2EpQOh0XturpPuuC9r3Sq5HLHdsFdWZT27E672SF19QEi+of0/YeOy15lDgRhKvr5614uRrEfA==", + "version": "0.18.10", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.18.10.tgz", + "integrity": "sha512-/B6p9eY9DX7aHBfpkHdpirDTZ5QH9xTwL9y837PWuzuv41O5jFdqNQwVQEQsimY0/iVNkwBY4+4hMfhQ7d4Dbw==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" diff --git a/frontend/package.json b/frontend/package.json index a8b2aa1..3949c9e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,7 +34,7 @@ "dompurify": "^3.4.17", "html-to-image": "^1.11.13", "jszip": "^3.10.1", - "katex": "^0.19.1", + "katex": "^0.18.10", "lowlight": "^3.3.0", "papaparse": "^5.5.3", "react": "^18.3.1", diff --git a/frontend/playwright.config.js b/frontend/playwright.config.js index d6a1308..89a1df4 100644 --- a/frontend/playwright.config.js +++ b/frontend/playwright.config.js @@ -4,6 +4,7 @@ export default defineConfig({ testDir: './e2e', fullyParallel: false, workers: 1, + globalTimeout: process.env.CI ? 6 * 60 * 1000 : undefined, use: { baseURL: 'http://127.0.0.1:4173', trace: 'on-first-retry', diff --git a/frontend/src/components/ItemContextMenu.jsx b/frontend/src/components/ItemContextMenu.jsx index 6f39435..a4d50ad 100644 --- a/frontend/src/components/ItemContextMenu.jsx +++ b/frontend/src/components/ItemContextMenu.jsx @@ -1,4 +1,4 @@ -import { useLayoutEffect, useRef, useState } from 'react' +import { useEffect, useLayoutEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' const PANEL_CLASS = 'fixed z-[100] w-60 max-w-[calc(100vw-16px)] max-h-[calc(100vh-16px)] overflow-y-auto rounded-xl border border-slate-200 bg-white py-1 shadow-xl' @@ -16,7 +16,7 @@ function ActionButton({ action, onClose, onMouseEnter }) { } -function Submenu({ branch, onClose, onBack }) { +function Submenu({ branch, onClose, onBack, onMouseEnter }) { const ref = useRef(null) const [position, setPosition] = useState({ left: 0, top: 0 }) useLayoutEffect(() => { @@ -32,6 +32,7 @@ function Submenu({ branch, onClose, onBack }) { }, [branch]) return