From 79ef02abc266c82a3fe1f6941ba84284723e3a5c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 02:50:51 +0000 Subject: [PATCH 1/7] chore(ci): import-cycle ratchet + mobile viewport regression suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two guardrails from the UI audit's Stage-1 'protect trust and stop regressions' list, each targeting a bug class that has repeatedly reached production: 1. Import-cycle ratchet (scripts/check-cycles.mjs + .cycles-baseline.json + CI job in the 'all' hard gate). Value-level import cycles typecheck and build cleanly but crash at cold runtime with TDZ errors — the #803/#804 prod-crash class. madge (config in .madgerc) skips 'import type' edges since those are erased at compile time; the 33 pre-existing runtime cycles are baselined and only NEW cycles fail, mirroring the lint-ratchet pattern. Verified: synthetic two-file cycle is detected, baseline run is green. 2. Mobile viewport regression suite (e2e/mobile-viewports.spec.ts + mobile-* Playwright projects + blocking step in playwright.yml). Functional checks at 320/390/430px on public + seeded baseball coach/player routes: no horizontal page pan, no partially-clipped header/nav controls (the #806 'Today pill' class), no fixed-element/ bottom-nav collisions (the #799 FAB class). Plain viewports rather than isMobile device emulation — mobile emulation zooms out on overflow, which defeats the geometry checks. Helpers validated against known-good and known-bad fixture pages (all three defect classes detected; clean page passes). playwright.config.ts also skips webServer when PLAYWRIGHT_BASE_URL points at an external deployment. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019WJ1Gzjj9MT3UsxhMoMzSV --- .cycles-baseline.json | 35 + .github/workflows/ci.yml | 26 + .github/workflows/playwright.yml | 15 + .madgerc | 8 + e2e/mobile-viewports.spec.ts | 169 ++++ package-lock.json | 1301 +++++++++++++++++++++++++++--- package.json | 6 +- playwright.config.ts | 64 +- scripts/check-cycles.mjs | 141 ++++ 9 files changed, 1649 insertions(+), 116 deletions(-) create mode 100644 .cycles-baseline.json create mode 100644 .madgerc create mode 100644 e2e/mobile-viewports.spec.ts create mode 100644 scripts/check-cycles.mjs diff --git a/.cycles-baseline.json b/.cycles-baseline.json new file mode 100644 index 000000000..6d022a2b8 --- /dev/null +++ b/.cycles-baseline.json @@ -0,0 +1,35 @@ +[ + "app/golf/actions/stats-data.ts > lib/golf/detailed-stats-admin-bridge.ts", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/CoverHero.tsx", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/EmptyIssue.tsx", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/GradeStampGrid.tsx", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/KPIContentsStrip.tsx", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/PlayerRowPlate.tsx", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/RecruitCard.tsx", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/SlashLine.tsx", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/StatLineStack.tsx", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/TearSheet.tsx", + "components/baseball/living-annual/index.ts > components/baseball/living-annual/molecules/index.ts > components/baseball/living-annual/molecules/ToolRailStack.tsx", + "components/baseball/stat-visuals/StatVisualsSection.tsx > components/baseball/stat-visuals/index.ts", + "components/fairway/cards-insight/themes/CauseRow.tsx > components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx > components/fairway/cards-insight/themes/index.ts", + "components/fairway/cards-insight/themes/ThemeCard.tsx > components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx > components/fairway/cards-insight/themes/index.ts", + "components/fairway/cards-insight/themes/ThemesPanel.tsx > components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx > components/fairway/cards-insight/themes/index.ts", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayBrief.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayEffectiveness.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayMyDevelopment.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayMyDevelopment.tsx > components/fairway/pages/coachhelm/CausalWhyPanel.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayMyDevelopment.tsx > components/fairway/pages/coachhelm/FocusAreaCard.tsx > components/fairway/pages/coachhelm/PracticeRxForInsight.tsx > components/fairway/pages/coachhelm/PracticeRxPanel.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayMyDevelopment.tsx > components/fairway/pages/coachhelm/GoalsSection.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayMyDevelopment.tsx > components/fairway/pages/coachhelm/GoalsSection.tsx > components/fairway/pages/coachhelm/FairwayGoalCard.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/FairwayPlayerCoachHelm.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/GenomeCompareView.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/GenomeDetailView.tsx", + "components/fairway/index.ts > components/fairway/pages/coachhelm/index.ts > components/fairway/pages/coachhelm/PlayersGridView.tsx", + "components/fairway/index.ts > components/fairway/pages/dashboard/index.ts > components/fairway/pages/dashboard/FairwayPlayerDashboard.tsx", + "components/fairway/index.ts > components/fairway/pages/dashboard/index.ts > components/fairway/pages/dashboard/FairwayPlayerDashboard.tsx > components/fairway/pages/dashboard/PlayerActionCenter.tsx", + "components/fairway/index.ts > components/fairway/pages/dashboard/index.ts > components/fairway/pages/dashboard/FairwayPlayerDashboard.tsx > components/fairway/pages/dashboard/PlayerActionCenter.tsx > components/fairway/pages/dashboard/player-dashboard-parts.tsx", + "components/fairway/index.ts > components/fairway/pages/dashboard/index.ts > components/fairway/pages/dashboard/FairwayPlayerDashboard.tsx > components/fairway/pages/dashboard/PlayerActionCenter.tsx > components/fairway/pages/hub/hub-parts.tsx", + "components/fairway/pages/coachhelm/FairwayBrief.tsx > components/fairway/pages/coachhelm/index.ts", + "components/golf/coachhelm/insights/DiagnosisPanel.tsx > components/golf/coachhelm/insights/EvidencePanel.tsx", + "lib/admin/data/incident-feed.ts > lib/admin/data/triage.ts" +] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0701ef133..5d2231ff7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -287,6 +287,31 @@ jobs: npm run ui:routes npm run routes:hygiene:p0p1 + import-cycles: + name: Import-cycle ratchet + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + persist-credentials: false + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + # Value-level import cycles typecheck and build cleanly but can crash at + # cold runtime (TDZ) — the #803/#804 prod-crash class. Baseline-aware: + # fails only on cycles not in .cycles-baseline.json. + - name: Check for new runtime import cycles + run: npm run check:cycles + all: name: all if: always() @@ -308,6 +333,7 @@ jobs: - next-build - route-hygiene - supabase + - import-cycles steps: - name: Fail if any required CI check failed if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ce42c609c..dc036e683 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -164,6 +164,21 @@ jobs: e2e/baseball-onboarding-smoke.spec.ts \ e2e/baseball-smoke.spec.ts + # Mobile viewport regression (blocking) — functional phone-width checks + # at 320/390/430px encoding the #799/#806 defect classes: horizontal + # page pan, clipped header/nav controls, fixed-element/bottom-nav + # collisions. See e2e/mobile-viewports.spec.ts. + - name: Run mobile viewport regression (blocking) + if: steps.baseball-auth-ready.outputs.ready == 'true' + run: | + npx playwright test \ + --project=mobile-public \ + --project=mobile-coach \ + --project=mobile-player + - name: Run mobile viewport regression (public routes only) + if: steps.baseball-auth-ready.outputs.ready != 'true' + run: npx playwright test --project=mobile-public + - name: Run Playwright tests env: PLAYWRIGHT_BASEBALL_SEEDED: ${{ env.SUPABASE_SERVICE_ROLE_KEY != '' && '1' || '' }} diff --git a/.madgerc b/.madgerc new file mode 100644 index 000000000..223b78979 --- /dev/null +++ b/.madgerc @@ -0,0 +1,8 @@ +{ + "fileExtensions": ["ts", "tsx"], + "tsConfig": "tsconfig.json", + "detectiveOptions": { + "ts": { "skipTypeImports": true }, + "tsx": { "skipTypeImports": true } + } +} diff --git a/e2e/mobile-viewports.spec.ts b/e2e/mobile-viewports.spec.ts new file mode 100644 index 000000000..d1d86bc30 --- /dev/null +++ b/e2e/mobile-viewports.spec.ts @@ -0,0 +1,169 @@ +import { test, expect, type Page } from '@playwright/test'; + +/** + * Mobile viewport regression suite. + * + * Encodes the phone-width defect classes that have repeatedly shipped and + * been fixed by hand (PR #799 FAB/bottom-nav collisions, PR #806 calendar + * "Today" pill clipped at 390px) as functional assertions at real device + * widths, per docs/MOBILE_DOCTRINE.md: + * + * 1. No horizontal page pan — the page never scrolls sideways. + * 2. No partially-clipped header/nav controls — a control that is visible + * but cut off by the viewport edge (the #806 class). Fully off-screen + * elements are ignored: closed drawers/sheets legitimately park at + * translateX(100%). + * 3. No fixed element overlapping the bottom navigation (the #799 class). + * + * Projects (see playwright.config.ts): `mobile-public` runs unauthenticated; + * `mobile-coach` / `mobile-player` reuse the CI-seeded baseball storageState + * from the `setup` project, same as the mandatory smoke suite. + */ + +const VIEWPORTS = [ + { width: 320, height: 568 }, + { width: 390, height: 844 }, + { width: 430, height: 932 }, +] as const; + +const PUBLIC_ROUTES = ['/', '/golf/login', '/baseball/login']; + +const COACH_ROUTES = [ + '/baseball/dashboard/command-center', + '/baseball/dashboard/calendar', + '/baseball/dashboard/roster', + '/baseball/dashboard/stats-center', + '/baseball/dashboard/settings', +]; + +const PLAYER_ROUTES = [ + '/baseball/player/today', + '/baseball/dashboard/calendar', + '/baseball/dashboard/roster', + '/baseball/dashboard/my-stats', +]; + +async function gotoSettled(page: Page, route: string): Promise { + // Reduced motion stabilizes geometry: entrance animations otherwise leave + // elements mid-transform when we measure. + await page.emulateMedia({ reducedMotion: 'reduce' }); + await page.goto(route, { waitUntil: 'domcontentloaded' }); + await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => {}); + await page.waitForTimeout(250); +} + +async function expectNoHorizontalPan(page: Page, route: string): Promise { + const { scrollWidth, innerWidth } = await page.evaluate(() => ({ + scrollWidth: document.documentElement.scrollWidth, + innerWidth: window.innerWidth, + })); + expect + .soft(scrollWidth, `${route}: page pans horizontally (scrollWidth ${scrollWidth} > viewport ${innerWidth})`) + .toBeLessThanOrEqual(innerWidth + 1); +} + +async function expectNoClippedControls(page: Page, route: string): Promise { + const clipped = await page.evaluate(() => { + const offenders: string[] = []; + const containers = Array.from( + document.querySelectorAll('header, nav, [role="navigation"], [role="toolbar"]'), + ); + const seen = new Set(); + for (const container of containers) { + for (const el of Array.from(container.querySelectorAll('a, button, [role="button"]'))) { + if (seen.has(el)) continue; + seen.add(el); + const r = el.getBoundingClientRect(); + if (r.width === 0 || r.height === 0) continue; + if (r.bottom < 0 || r.top > window.innerHeight) continue; + // Only flag PARTIAL clipping: visible in the viewport but cut off at + // an edge. Fully off-screen = intentional off-canvas UI, not a bug. + const partiallyVisible = r.left < window.innerWidth && r.right > 0; + const cutRight = r.right > window.innerWidth + 1 && r.left < window.innerWidth - 8; + const cutLeft = r.left < -1 && r.right > 8; + if (partiallyVisible && (cutRight || cutLeft)) { + const label = (el.textContent || el.getAttribute('aria-label') || el.tagName) + .trim() + .slice(0, 40); + offenders.push(`"${label}" [left ${Math.round(r.left)}, right ${Math.round(r.right)}] vs viewport ${window.innerWidth}`); + } + } + } + return offenders; + }); + expect.soft(clipped, `${route}: header/nav controls clipped at viewport edge`).toEqual([]); +} + +async function expectNoBottomNavCollision(page: Page, route: string): Promise { + const collisions = await page.evaluate(() => { + const fixedEls: Element[] = []; + for (const el of Array.from(document.body.querySelectorAll('*'))) { + const style = getComputedStyle(el); + if (style.position !== 'fixed') continue; + if (style.pointerEvents === 'none') continue; // toast portals, inset-0 wrappers + const r = el.getBoundingClientRect(); + if (r.width === 0 || r.height === 0) continue; + fixedEls.push(el); + } + const bottomNav = fixedEls.find((el) => { + const isNav = el.tagName === 'NAV' || el.getAttribute('role') === 'navigation' || el.querySelector('nav') !== null; + const r = el.getBoundingClientRect(); + return isNav && r.top > window.innerHeight - 140 && r.width > window.innerWidth * 0.6; + }); + if (!bottomNav) return []; + const navRect = bottomNav.getBoundingClientRect(); + const offenders: string[] = []; + for (const el of fixedEls) { + if (el === bottomNav || bottomNav.contains(el) || el.contains(bottomNav)) continue; + const r = el.getBoundingClientRect(); + const overlapX = Math.min(r.right, navRect.right) - Math.max(r.left, navRect.left); + const overlapY = Math.min(r.bottom, navRect.bottom) - Math.max(r.top, navRect.top); + if (overlapX > 8 && overlapY > 8) { + const label = (el.getAttribute('aria-label') || el.textContent || el.className.toString()) + .trim() + .slice(0, 40); + offenders.push(`"${label}" overlaps bottom nav by ${Math.round(overlapX)}×${Math.round(overlapY)}px`); + } + } + return offenders; + }); + expect.soft(collisions, `${route}: fixed element collides with bottom navigation`).toEqual([]); +} + +async function expectMobileFit(page: Page, route: string): Promise { + await gotoSettled(page, route); + await expectNoHorizontalPan(page, route); + await expectNoClippedControls(page, route); + await expectNoBottomNavCollision(page, route); +} + +for (const viewport of VIEWPORTS) { + test.describe(`public routes @ ${viewport.width}px`, { tag: '@public' }, () => { + test.use({ viewport }); + for (const route of PUBLIC_ROUTES) { + test(`${route} fits ${viewport.width}px`, async ({ page }) => { + await expectMobileFit(page, route); + }); + } + }); + + test.describe(`baseball coach routes @ ${viewport.width}px`, { tag: '@coach' }, () => { + test.use({ viewport }); + for (const route of COACH_ROUTES) { + test(`${route} fits ${viewport.width}px (coach)`, async ({ page }) => { + await expectMobileFit(page, route); + expect(page.url(), `expected ${route} to render without bouncing to /login`).not.toContain('/login'); + }); + } + }); + + test.describe(`baseball player routes @ ${viewport.width}px`, { tag: '@player' }, () => { + test.use({ viewport }); + for (const route of PLAYER_ROUTES) { + test(`${route} fits ${viewport.width}px (player)`, async ({ page }) => { + await expectMobileFit(page, route); + expect(page.url(), `expected ${route} to render without bouncing to /login`).not.toContain('/login'); + }); + } + }); +} diff --git a/package-lock.json b/package-lock.json index 32a257d1d..617671bd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,6 +111,7 @@ "fast-check": "^4.8.0", "jsdom": "^28.0.0", "knip": "^5.88.1", + "madge": "^8.0.0", "postcss-load-config": "^6.0.1", "postgres": "^3.4.9", "promptfoo": "^0.121.17", @@ -286,9 +287,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -303,9 +301,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -320,9 +315,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -337,9 +329,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -1806,18 +1795,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1846,12 +1835,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1934,13 +1923,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2447,6 +2436,20 @@ "integrity": "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==", "license": "MIT" }, + "node_modules/@dependents/detective-less": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.3.tgz", + "integrity": "sha512-v6oD9Ukp+N7V4n6p5I/+mM5fIohSfkrDSGlFm5w/pYmchvbk+sMIHsLxrFJ5Lnujewj1BzWL0K84d88lwZAMQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -2526,6 +2529,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -3013,6 +3017,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3035,6 +3040,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3057,6 +3063,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3073,6 +3080,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3089,6 +3097,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3105,6 +3114,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3121,6 +3131,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3137,6 +3148,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3153,6 +3165,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3169,6 +3182,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3185,6 +3199,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3201,6 +3216,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3217,6 +3233,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3239,6 +3256,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3261,6 +3279,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3283,6 +3302,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3305,6 +3325,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3327,6 +3348,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3349,6 +3371,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3371,6 +3394,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3393,6 +3417,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -3412,6 +3437,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3431,6 +3457,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -3450,6 +3477,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -5110,9 +5138,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5129,9 +5154,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5148,9 +5170,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5167,9 +5186,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8987,9 +9003,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9007,9 +9020,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -9027,9 +9037,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9047,9 +9054,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9067,9 +9071,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9087,9 +9088,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -10427,9 +10425,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -10444,9 +10439,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -10461,9 +10453,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -10478,9 +10467,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -11129,6 +11115,96 @@ "node": ">=8.6.0" } }, + "node_modules/@ts-graphviz/adapter": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ts-graphviz/adapter/-/adapter-2.0.6.tgz", + "integrity": "sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/ast": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@ts-graphviz/ast/-/ast-2.0.7.tgz", + "integrity": "sha512-e6+2qtNV99UT6DJSoLbHfkzfyqY84aIuoV8Xlb9+hZAjgpum8iVHprGeAMQ4rF6sKUAxrmY8rfF/vgAwoPc3gw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/common": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@ts-graphviz/common/-/common-2.1.5.tgz", + "integrity": "sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/core": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@ts-graphviz/core/-/core-2.0.7.tgz", + "integrity": "sha512-w071DSzP94YfN6XiWhOxnLpYT3uqtxJBDYdh6Jdjzt+Ce6DNspJsPQgpC7rbts/B8tEkq0LHoYuIF/O5Jh5rPg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/ast": "^2.0.7", + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", @@ -12411,6 +12487,80 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "dev": true, + "license": "MIT" + }, "node_modules/@workflow/serde": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@workflow/serde/-/serde-4.1.0.tgz", @@ -12697,6 +12847,13 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/app-module-path": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", + "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/apparatus": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/apparatus/-/apparatus-0.0.10.tgz", @@ -12892,6 +13049,16 @@ "node": ">=12" } }, + "node_modules/ast-module-types": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.2.tgz", + "integrity": "sha512-6KuK/7nZ/2Qh7sGuVEiwxjCxzTY2Pdb5mTo5z1e6/J8BA0tvjR7G8vQJKrQMTqwmnA3UPEyKIFX4YUS1DO1Hvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -13325,6 +13492,18 @@ "url": "https://bevry.me/fund" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -13476,6 +13655,31 @@ "node": ">=20.19.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -13938,6 +14142,16 @@ "node": ">=12" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -14781,6 +14995,16 @@ "node": ">=20" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -14819,6 +15043,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -14909,6 +15146,46 @@ "node": ">= 0.8" } }, + "node_modules/dependency-tree": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-11.5.0.tgz", + "integrity": "sha512-K9zBwKDZrot3RkxizugpVSdImxULAg4Ycp3+ydy2r561k96oiiw6nfsOR15fwNDQ5BF2UXe+2JFM/H5Xz4MGQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^1.1.0", + "commander": "^12.1.0", + "filing-cabinet": "^5.5.1", + "precinct": "^12.3.2", + "typescript": "^5.9.3" + }, + "bin": { + "dependency-tree": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dependency-tree/node_modules/@discoveryjs/json-ext": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-1.1.0.tgz", + "integrity": "sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/dependency-tree/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -14953,17 +15230,158 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, - "node_modules/devtools-protocol": { - "version": "0.0.1467305", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1467305.tgz", - "integrity": "sha512-LxwMLqBoPPGpMdRL4NkLFRNy3QLp6Uqa7GNp1v6JaBheop2QrB9Q7q0A/q/CYYP9sBfZdHOyszVx4gc9zyk7ow==", + "node_modules/detective-amd": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-6.1.0.tgz", + "integrity": "sha512-fmI6LGMvotqd49QaA3ZYw+q0aGp2yXmMjzIuY6fH9j9YFIXY/73yDhMwhX9cPbhWd+AH06NH1Di/LKOuCH0Ubg==", "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.1", + "escodegen": "^2.1.0", + "get-amd-module-type": "^6.0.2", + "node-source-walk": "^7.0.1" + }, + "bin": { + "detective-amd": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-cjs": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-6.1.1.tgz", + "integrity": "sha512-pSh7mkCKEtLlmANqLu3KDFS3NV8Hx41jy/JF1/gAWOgU+Uo5QTkeI1tWNP4dWGo4L0E9j18Ez9EPsTleautKqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.1", + "node-source-walk": "^7.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-es6": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-5.0.2.tgz", + "integrity": "sha512-+qHHGYhjupiVs4rnIpI9nZ5B130A4AmE35ZX1w33hb46vcZ7T3jfDbvmPw0FhWtMHn5BS5HHu7ZtnZ53bMcXZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-source-walk": "^7.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-postcss": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-8.0.4.tgz", + "integrity": "sha512-DZ7M/hWPZyr17ZUdoQ+TVXaPj70mYr4XXrAE+GeJbca44haCvZgb191L/jLJmFYewhxRJuBd4lUtNSu986TXag==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-url-superb": "^4.0.0", + "postcss-values-parser": "^6.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4.47" + } + }, + "node_modules/detective-sass": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-6.0.2.tgz", + "integrity": "sha512-i3xpXHDKS0qI2aFW4asQ7fqlPK00ndOVZELvQapFJCaF0VxYmsNWtd0AmvXbTLMk7bfO5VdIeorhY9KfmHVoVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-scss": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-5.0.2.tgz", + "integrity": "sha512-9JOEMZ8pDh3ShXmftq7hoQqqJsClaGgxo1hghfCeFlmKf5TC/Twtwb0PAaK8dXwpg9Z0uCmEYSrCxO+kel2eEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-stylus": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-5.0.1.tgz", + "integrity": "sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-typescript": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-14.1.2.tgz", + "integrity": "sha512-bIeEn0eVi/JRsE1YizBR2ilnMlWRAIBJJ6kXCKNFxEEWhUcEY3R6I3KYIAy48ieURbD1hcb3Ebvl8AqeoPMSzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "^8.58.2", + "ast-module-types": "^6.0.1", + "node-source-walk": "^7.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "typescript": "^5.4.4 || ^6.0.2" + } + }, + "node_modules/detective-vue2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/detective-vue2/-/detective-vue2-2.3.0.tgz", + "integrity": "sha512-3gwbZPqVTm9sL9XdZsgEJ7x4x99O853VVZHapQAiEkGuMJMpFPjHDrecSgfqnS5JW3FJfYXesLZGvUOibjn49g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dependents/detective-less": "^5.0.1", + "@vue/compiler-sfc": "^3.5.32", + "detective-es6": "^5.0.1", + "detective-sass": "^6.0.1", + "detective-scss": "^5.0.1", + "detective-stylus": "^5.0.1", + "detective-typescript": "^14.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "typescript": "^5.4.4 || ^6.0.2" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1467305", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1467305.tgz", + "integrity": "sha512-LxwMLqBoPPGpMdRL4NkLFRNy3QLp6Uqa7GNp1v6JaBheop2QrB9Q7q0A/q/CYYP9sBfZdHOyszVx4gc9zyk7ow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, "node_modules/disposable-email-domains": { @@ -15399,6 +15817,20 @@ } } }, + "node_modules/enhanced-resolve": { + "version": "5.24.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", + "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -15518,7 +15950,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -16528,6 +16959,42 @@ "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, + "node_modules/filing-cabinet": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-5.5.1.tgz", + "integrity": "sha512-PzLBTChlVPn6LnNxF0KWs+XqPziVh3Sfmz/3TXOymHxu6a9yhrDcQn7YwgpcRM6mqhR2WHVGPR8RU4fmcF1IVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-module-path": "^2.2.0", + "commander": "^12.1.0", + "enhanced-resolve": "^5.21.0", + "module-definition": "^6.0.2", + "module-lookup-amd": "^9.1.3", + "resolve": "^1.22.12", + "resolve-dependency-path": "^4.0.1", + "sass-lookup": "^6.1.2", + "stylus-lookup": "^6.1.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.9.3" + }, + "bin": { + "filing-cabinet": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/filing-cabinet/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -16947,6 +17414,20 @@ "node": ">=6.9.0" } }, + "node_modules/get-amd-module-type": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-6.0.2.tgz", + "integrity": "sha512-7zShVYAYtMnj9S65CfN+hvpBCByfuB1OY8xID01nZEzXTZbx4YyysAfi+nMl95JSR6odt4q8TCj2W63KAoyVLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.1", + "node-source-walk": "^7.0.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -17003,6 +17484,13 @@ "node": ">=6" } }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -17217,6 +17705,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "gonzales": "bin/gonzales.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/google-auth-library": { "version": "10.9.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", @@ -17763,8 +18267,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "optional": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", @@ -18625,6 +19128,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-set": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", @@ -18738,6 +19251,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-url-superb": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", + "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -19855,9 +20381,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -19879,9 +20402,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -19903,9 +20423,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -19927,9 +20444,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -20217,6 +20731,218 @@ "lz-string": "bin/bin.js" } }, + "node_modules/madge": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/madge/-/madge-8.0.0.tgz", + "integrity": "sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^7.2.0", + "commondir": "^1.0.1", + "debug": "^4.3.4", + "dependency-tree": "^11.0.0", + "ora": "^5.4.1", + "pluralize": "^8.0.0", + "pretty-ms": "^7.0.1", + "rc": "^1.2.8", + "stream-to-array": "^2.3.0", + "ts-graphviz": "^2.1.2", + "walkdir": "^0.4.1" + }, + "bin": { + "madge": "bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/pahen" + }, + "peerDependencies": { + "typescript": "^5.4.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/madge/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/madge/node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/madge/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/madge/node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/madge/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/madge/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/madge/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/madge/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/madge/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/madge/node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/madge/node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/madge/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/madge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -20593,12 +21319,57 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/module-definition": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.2.tgz", + "integrity": "sha512-SvAU3lB0+Yjbq55yHY3wkRZBOh+fhU1SnIF3IFbTewv6mtAh7yUT8ACHAJ2mGIJ7tCes2QuCL/cl6m0JSZ/ArA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.1", + "node-source-walk": "^7.0.1" + }, + "bin": { + "module-definition": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/module-details-from-path": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "license": "MIT" }, + "node_modules/module-lookup-amd": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-9.1.3.tgz", + "integrity": "sha512-Jc3XmOaR9FdfMJSK8+vyLgsCkzm8z2L0NS6vrlRWi12DjS7MY7TMNE7E1yj8yXx837xtMDbKSSgcdXnFlJ2YLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.1.0", + "requirejs": "^2.3.8", + "requirejs-config-file": "^4.0.0" + }, + "bin": { + "lookup-amd": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/module-lookup-amd/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/mongodb": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.2.0.tgz", @@ -21102,6 +21873,19 @@ "asn1": "^0.2.4" } }, + "node_modules/node-source-walk": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-7.0.2.tgz", + "integrity": "sha512-71kFFjYaSshDTA8/a2HiTYPLdASWjLJxUyJxGE+ffxU+KhxSBtM9kiLUX+R2yooFdSFKMFpi4n3PFtDy6qXv8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/node-sql-parser": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-5.4.0.tgz", @@ -22332,6 +23116,16 @@ "node": ">=10.4.0" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -22504,6 +23298,24 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/postcss-values-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz", + "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "color-name": "^1.1.4", + "is-url-superb": "^4.0.0", + "quote-unquote": "^1.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.2.9" + } + }, "node_modules/postgres": { "version": "3.4.9", "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.9.tgz", @@ -22609,6 +23421,46 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/precinct": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.3.2.tgz", + "integrity": "sha512-JbJevI1K80z8e/WIyDt/4vUN/4qcfBSKKqOjJA4mosPPPb7zODKRJQV7YN7apVWN3k58nZYm/vEsLgEGYmnxwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dependents/detective-less": "^5.0.3", + "commander": "^12.1.0", + "detective-amd": "^6.1.0", + "detective-cjs": "^6.1.1", + "detective-es6": "^5.0.2", + "detective-postcss": "^8.0.3", + "detective-sass": "^6.0.2", + "detective-scss": "^5.0.2", + "detective-stylus": "^5.0.1", + "detective-typescript": "^14.1.2", + "detective-vue2": "^2.3.0", + "module-definition": "^6.0.2", + "node-source-walk": "^7.0.2", + "postcss": "^8.5.14", + "typescript": "^5.9.3" + }, + "bin": { + "precinct": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/precinct/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -22919,9 +23771,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -23908,6 +24757,13 @@ "dev": true, "license": "MIT" }, + "node_modules/quote-unquote": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz", + "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", + "dev": true, + "license": "MIT" + }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -23944,6 +24800,39 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "19.2.7", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", @@ -24340,6 +25229,34 @@ "dev": true, "license": "ISC" }, + "node_modules/requirejs": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.8.tgz", + "integrity": "sha512-7/cTSLOdYkNBNJcDMWf+luFvMriVm7eYxp4BcFCsAX0wF421Vyce5SXP17c+Jd5otXKGNehIonFlyQXSowL6Mw==", + "dev": true, + "license": "MIT", + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/requirejs-config-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz", + "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esprima": "^4.0.0", + "stringify-object": "^3.2.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -24376,11 +25293,12 @@ } }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" @@ -24395,6 +25313,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-dependency-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-4.0.1.tgz", + "integrity": "sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -24817,6 +25745,33 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sass-lookup": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-6.1.2.tgz", + "integrity": "sha512-GjmndmKQBtlPil79RK72L7yc5kDXZPCQeH97bP8R8DcxtXQJO6vECExb3WP/m6+cxaV9h4ZxrSRvCkPG2v/VSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.1.0", + "enhanced-resolve": "^5.20.0" + }, + "bin": { + "sass-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sass-lookup/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/sax": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz", @@ -25594,6 +26549,16 @@ "node": ">=0.10.0" } }, + "node_modules/stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.1.0" + } + }, "node_modules/streamx": { "version": "2.26.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz", @@ -25703,6 +26668,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stringify-object/node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -25715,6 +26705,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/strip-final-newline": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", @@ -25807,6 +26807,32 @@ } } }, + "node_modules/stylus-lookup": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-6.1.2.tgz", + "integrity": "sha512-O+Q/SJ8s1X2aMLh4213fQ9X/bND9M3dhSsyTRe+O1OXPcewGLiYmAtKCrnP7FDvDBaXB2ZHPkCt3zi4cJXBlCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.1.0" + }, + "bin": { + "stylus-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylus-lookup/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -25974,6 +27000,20 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tar": { "version": "7.5.19", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", @@ -26326,12 +27366,53 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-graphviz": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-2.1.6.tgz", + "integrity": "sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/adapter": "^2.0.6", + "@ts-graphviz/ast": "^2.0.7", + "@ts-graphviz/common": "^2.1.5", + "@ts-graphviz/core": "^2.0.7" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -27605,6 +28686,26 @@ "node": "20 || >=22" } }, + "node_modules/walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/web-push": { "version": "3.6.7", "resolved": "https://registry.npmjs.org/web-push/-/web-push-3.6.7.tgz", diff --git a/package.json b/package.json index 8b1ba156e..1735c9ca0 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "check:env": "node scripts/check-required-env.mjs", "check:helm-bridge-env": "node scripts/check-helm-bridge-env.mjs", "check:ledger": "node scripts/check-migration-ledger.mjs", + "check:cycles": "node scripts/check-cycles.mjs", "check:types-drift": "bash scripts/check-types-drift.sh", "check:readiness-matrix": "tsx scripts/baseball/check-readiness-matrix.ts", "db:drift:check": "node scripts/db/check-supabase-drift.mjs", @@ -23,9 +24,9 @@ "lint:ratchet": "node scripts/lint-ratchet.mjs", "typecheck": "tsc --noEmit", "db:types": "npx supabase gen types typescript --project-id $SUPABASE_PROJECT_ID > src/lib/types/database.ts", - "db:types:check": "npm run db:types && git diff --exit-code src/lib/types/database.ts || (echo '❌ Types are out of date. Run npm run db:types and commit changes.' && exit 1)", + "db:types:check": "npm run db:types && git diff --exit-code src/lib/types/database.ts || (echo '\u274c Types are out of date. Run npm run db:types and commit changes.' && exit 1)", "docs:regen": "node scripts/regen-docs.mjs", - "docs:check": "npm run docs:regen && git diff --exit-code memory/glossary.md memory/projects/golfhelm.md || (echo '❌ Inventory docs are out of date. Run npm run docs:regen and commit changes.' && exit 1)", + "docs:check": "npm run docs:regen && git diff --exit-code memory/glossary.md memory/projects/golfhelm.md || (echo '\u274c Inventory docs are out of date. Run npm run docs:regen and commit changes.' && exit 1)", "knowledge:map": "node scripts/knowledge/map-changed-files.mjs", "knowledge:context": "node scripts/knowledge/generate-context-pack.mjs", "knowledge:check": "node scripts/knowledge/check.mjs", @@ -169,6 +170,7 @@ "fast-check": "^4.8.0", "jsdom": "^28.0.0", "knip": "^5.88.1", + "madge": "^8.0.0", "postcss-load-config": "^6.0.1", "postgres": "^3.4.9", "promptfoo": "^0.121.17", diff --git a/playwright.config.ts b/playwright.config.ts index c921c7bfb..018f690bd 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -48,7 +48,8 @@ export default defineConfig({ use: { ...devices['Desktop Chrome'] }, // baseball-smoke.spec.ts requires an authenticated storageState from // the `setup` project below — it must not also run anonymously here. - testIgnore: /baseball-smoke\.spec\.ts/, + // mobile-viewports.spec.ts runs only under the mobile-* projects. + testIgnore: /baseball-smoke\.spec\.ts|mobile-viewports\.spec\.ts/, }, // BaseballHelm mandatory smoke (#372) — durable per-role auth. `setup` @@ -82,6 +83,43 @@ export default defineConfig({ }, }, + // Mobile viewport regression suite (e2e/mobile-viewports.spec.ts) — + // functional phone-width checks (no horizontal pan, no clipped controls, + // no bottom-nav collisions) at 320/390/430px. The spec sets the exact + // viewport per describe block; these projects supply auth context. + { + name: 'mobile-public', + testMatch: /mobile-viewports\.spec\.ts/, + grep: /@public/, + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'mobile-coach', + testMatch: /mobile-viewports\.spec\.ts/, + grep: /@coach/, + dependencies: ['setup'], + use: { + // Plain viewport, NOT a mobile device descriptor: isMobile emulation + // zooms out on overflow (innerWidth grows with content), which defeats + // the horizontal-pan and clipping geometry checks. + ...devices['Desktop Chrome'], + storageState: 'playwright/.auth/baseball-coach.json', + }, + }, + { + name: 'mobile-player', + testMatch: /mobile-viewports\.spec\.ts/, + grep: /@player/, + dependencies: ['setup'], + use: { + // Plain viewport, NOT a mobile device descriptor: isMobile emulation + // zooms out on overflow (innerWidth grows with content), which defeats + // the horizontal-pan and clipping geometry checks. + ...devices['Desktop Chrome'], + storageState: 'playwright/.auth/baseball-player.json', + }, + }, + // Uncomment to test on other browsers // { // name: 'firefox', @@ -92,19 +130,17 @@ export default defineConfig({ // name: 'webkit', // use: { ...devices['Desktop Safari'] }, // }, - - /* Test against mobile viewports */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, ], - /* Run your local dev server before starting the tests */ - webServer: { - command: 'npm run dev', - url: 'http://localhost:3000', - reuseExistingServer: !process.env.CI, - timeout: 120 * 1000, - }, + /* Run your local dev server before starting the tests. Skipped when + * PLAYWRIGHT_BASE_URL points at an external deployment — there is nothing + * to boot locally in that case. */ + webServer: process.env.PLAYWRIGHT_BASE_URL + ? undefined + : { + command: 'npm run dev', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, + }, }); diff --git a/scripts/check-cycles.mjs b/scripts/check-cycles.mjs new file mode 100644 index 000000000..8afab4e71 --- /dev/null +++ b/scripts/check-cycles.mjs @@ -0,0 +1,141 @@ +#!/usr/bin/env node +/** + * check-cycles.mjs + * + * Runs `npx madge --circular --json src` (config in .madgerc skips + * `import type` edges — those are erased at compile time and cannot cause + * runtime TDZ crashes) and compares the RUNTIME import cycles it finds + * against .cycles-baseline.json. + * + * Why this exists: two production crashes (PR #803 golf CoachHelm, PR #804 + * baseball roster) came from value-level import cycles that typecheck and + * build cleanly but throw "Cannot access X before initialization" at cold + * runtime, depending on bundler eval order. This ratchet blocks NEW cycles + * while tolerating the pre-existing set until it is paid down. + * + * Exit codes: + * 0 — no new cycles (current set ⊆ baseline) + * 1 — at least one cycle not present in the baseline + * + * Flags: + * --update Rewrite .cycles-baseline.json from the current run and exit 0. + */ + +import { execFileSync } from 'node:child_process'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = resolve(__dirname, '..'); +const BASELINE_PATH = resolve(ROOT, '.cycles-baseline.json'); + +const UPDATE = process.argv.includes('--update'); + +// --------------------------------------------------------------------------- +// 1. Run madge and collect cycles +// --------------------------------------------------------------------------- +let madgeOutput; +try { + madgeOutput = execFileSync( + 'npx', + ['madge', '--circular', '--json', 'src'], + { cwd: ROOT, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'inherit'] } + ); +} catch (err) { + // madge exits 1 when cycles exist but still writes valid JSON to stdout. + madgeOutput = (err.stdout || '').trim(); + if (!madgeOutput.startsWith('[')) { + console.error('check-cycles: madge failed and did not produce JSON output.'); + console.error(err.message); + process.exit(1); + } +} + +/** @type {string[][]} */ +let rawCycles; +try { + rawCycles = JSON.parse(madgeOutput); +} catch (parseErr) { + console.error('check-cycles: could not parse madge JSON output:', parseErr.message); + process.exit(1); +} + +/** + * Canonicalize a cycle so the same loop always serializes identically: + * rotate the module list so the lexicographically smallest member comes + * first (a cycle has no inherent start), then join with ' > '. + * @param {string[]} cycle + */ +function canonicalize(cycle) { + if (cycle.length === 0) return ''; + let minIdx = 0; + for (let i = 1; i < cycle.length; i++) { + if (cycle[i] < cycle[minIdx]) minIdx = i; + } + return [...cycle.slice(minIdx), ...cycle.slice(0, minIdx)].join(' > '); +} + +const current = [...new Set(rawCycles.map(canonicalize))].sort(); + +// --------------------------------------------------------------------------- +// 2. --update: overwrite baseline and exit +// --------------------------------------------------------------------------- +if (UPDATE) { + writeFileSync(BASELINE_PATH, JSON.stringify(current, null, 2) + '\n', 'utf-8'); + console.log( + `check-cycles: baseline updated — ${current.length} runtime cycle${current.length !== 1 ? 's' : ''} locked in ${BASELINE_PATH}` + ); + process.exit(0); +} + +// --------------------------------------------------------------------------- +// 3. Load baseline and compare +// --------------------------------------------------------------------------- +/** @type {string[]} */ +let baseline; +try { + baseline = JSON.parse(readFileSync(BASELINE_PATH, 'utf-8')); +} catch { + console.error( + `check-cycles: baseline file not found at ${BASELINE_PATH}.\n` + + 'Run `npm run check:cycles -- --update` to create it.' + ); + process.exit(1); +} + +const baselineSet = new Set(baseline); +const newCycles = current.filter((c) => !baselineSet.has(c)); +const currentSet = new Set(current); +const resolved = baseline.filter((c) => !currentSet.has(c)); + +// --------------------------------------------------------------------------- +// 4. Report +// --------------------------------------------------------------------------- +if (newCycles.length > 0) { + console.error('check-cycles: NEW RUNTIME IMPORT CYCLE DETECTED\n'); + console.error( + 'Value-level import cycles can crash at cold runtime with\n' + + '"Cannot access X before initialization" even though typecheck and\n' + + 'build pass (see PRs #803/#804). Break the cycle — usually by moving\n' + + 'shared constants/helpers into a leaf module (e.g. roster-constants.ts)\n' + + 'or switching to `import type` where only types are needed.\n' + ); + for (const cycle of newCycles) { + console.error(` ${cycle}`); + } + console.error( + `\n ${newCycles.length} new cycle${newCycles.length !== 1 ? 's' : ''} (baseline ${baseline.length}, current ${current.length})` + ); + process.exit(1); +} + +if (resolved.length > 0) { + console.log( + `check-cycles: ${resolved.length} baseline cycle${resolved.length !== 1 ? 's' : ''} resolved (${baseline.length} → ${current.length}) — run \`npm run check:cycles -- --update\` to lock in the gains` + ); +} else { + console.log(`check-cycles: OK — ${current.length} known cycle${current.length !== 1 ? 's' : ''}, none new`); +} + +process.exit(0); From 5be64907792ca92c54aa28e42ea3f63c50d5a29b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 02:52:36 +0000 Subject: [PATCH 2/7] chore(ci): run public mobile viewport checks on PRs via pr-smoke MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The e2e job in playwright.yml (where the full mobile suite runs) only executes on main pushes and manual dispatch — PRs get pr-smoke. Add the public-route mobile project there as a blocking step so phone-width layout regressions are caught before merge, not after. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019WJ1Gzjj9MT3UsxhMoMzSV --- .github/workflows/pr-smoke.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/pr-smoke.yml b/.github/workflows/pr-smoke.yml index 9d959586c..2c0db430b 100644 --- a/.github/workflows/pr-smoke.yml +++ b/.github/workflows/pr-smoke.yml @@ -95,6 +95,13 @@ jobs: continue-on-error: true run: npx playwright test e2e/accessibility.spec.ts --project=chromium --reporter=line + # Public mobile viewport checks (320/390/430px): no horizontal pan, no + # clipped header/nav controls, no bottom-nav collisions. Blocking — these + # are pure layout-geometry assertions with no a11y-debt exemptions. + # Authenticated mobile coverage runs on main via playwright.yml. + - name: Run public mobile viewport checks + run: npx playwright test --project=mobile-public --reporter=line + - name: Upload Playwright report if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a From 32a9e72f2a46202e6b98a64125c0f14b7d90e6dd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 02:56:23 +0000 Subject: [PATCH 3/7] fix(ci): single dev-server lifecycle for mobile suite + fail-loud bottom-nav detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two Greptile findings: 1. The standalone mobile step booted a second Playwright webServer in the same job, risking port-3000 contention with the smoke run's server. The mobile projects now ride the mandatory-smoke invocation (one process, one server). The 'public routes only' fallback step was dead code — the job hard-fails earlier when baseball secrets are missing. 2. The bottom-nav collision check silently passed when it couldn't identify the nav. Detection now also accepts tab-bar-SHAPED fixed elements (bottom-pinned, near-full-width, 2+ interactive children), and authenticated dashboard routes assert the bottom nav exists at phone widths per MOBILE_DOCTRINE — so a missed nav fails loud instead of skipping the check. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019WJ1Gzjj9MT3UsxhMoMzSV --- .github/workflows/playwright.yml | 26 +++++++----------- e2e/mobile-viewports.spec.ts | 47 ++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index dc036e683..2cb114148 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -154,30 +154,24 @@ jobs: if: steps.baseball-auth-ready.outputs.ready == 'true' run: npm run seed:baseball:ci - - name: Run BaseballHelm mandatory smoke (blocking) + # One invocation = one dev-server lifecycle. The mobile viewport + # regression projects (blocking; e2e/mobile-viewports.spec.ts — the + # #799/#806 phone-width defect classes at 320/390/430px) ride the same + # Playwright process as the mandatory smoke instead of booting a second + # webServer that could hit port-3000 contention. + - name: Run BaseballHelm mandatory smoke + mobile viewport regression (blocking) if: steps.baseball-auth-ready.outputs.ready == 'true' run: | npx playwright test \ --project=chromium \ --project=baseball-coach \ --project=baseball-player \ - e2e/baseball-onboarding-smoke.spec.ts \ - e2e/baseball-smoke.spec.ts - - # Mobile viewport regression (blocking) — functional phone-width checks - # at 320/390/430px encoding the #799/#806 defect classes: horizontal - # page pan, clipped header/nav controls, fixed-element/bottom-nav - # collisions. See e2e/mobile-viewports.spec.ts. - - name: Run mobile viewport regression (blocking) - if: steps.baseball-auth-ready.outputs.ready == 'true' - run: | - npx playwright test \ --project=mobile-public \ --project=mobile-coach \ - --project=mobile-player - - name: Run mobile viewport regression (public routes only) - if: steps.baseball-auth-ready.outputs.ready != 'true' - run: npx playwright test --project=mobile-public + --project=mobile-player \ + e2e/baseball-onboarding-smoke.spec.ts \ + e2e/baseball-smoke.spec.ts \ + e2e/mobile-viewports.spec.ts - name: Run Playwright tests env: diff --git a/e2e/mobile-viewports.spec.ts b/e2e/mobile-viewports.spec.ts index d1d86bc30..1fa21066e 100644 --- a/e2e/mobile-viewports.spec.ts +++ b/e2e/mobile-viewports.spec.ts @@ -94,8 +94,12 @@ async function expectNoClippedControls(page: Page, route: string): Promise expect.soft(clipped, `${route}: header/nav controls clipped at viewport edge`).toEqual([]); } -async function expectNoBottomNavCollision(page: Page, route: string): Promise { - const collisions = await page.evaluate(() => { +async function expectNoBottomNavCollision( + page: Page, + route: string, + opts: { expectBottomNav?: boolean } = {}, +): Promise { + const result = await page.evaluate(() => { const fixedEls: Element[] = []; for (const el of Array.from(document.body.querySelectorAll('*'))) { const style = getComputedStyle(el); @@ -105,12 +109,21 @@ async function expectNoBottomNavCollision(page: Page, route: string): Promise { - const isNav = el.tagName === 'NAV' || el.getAttribute('role') === 'navigation' || el.querySelector('nav') !== null; const r = el.getBoundingClientRect(); - return isNav && r.top > window.innerHeight - 140 && r.width > window.innerWidth * 0.6; + const bottomPinned = r.top > window.innerHeight - 140 && r.bottom >= window.innerHeight - 40; + const wide = r.width > window.innerWidth * 0.6; + if (!bottomPinned || !wide) return false; + const semantic = + el.tagName === 'NAV' || el.getAttribute('role') === 'navigation' || el.querySelector('nav') !== null; + const tabBarShaped = el.querySelectorAll('a, button, [role="button"], [role="tab"]').length >= 2; + return semantic || tabBarShaped; }); - if (!bottomNav) return []; + if (!bottomNav) return { found: false, offenders: [] as string[] }; const navRect = bottomNav.getBoundingClientRect(); const offenders: string[] = []; for (const el of fixedEls) { @@ -125,16 +138,28 @@ async function expectNoBottomNavCollision(page: Page, route: string): Promise { +async function expectMobileFit( + page: Page, + route: string, + opts: { expectBottomNav?: boolean } = {}, +): Promise { await gotoSettled(page, route); await expectNoHorizontalPan(page, route); await expectNoClippedControls(page, route); - await expectNoBottomNavCollision(page, route); + await expectNoBottomNavCollision(page, route, opts); } for (const viewport of VIEWPORTS) { @@ -151,7 +176,7 @@ for (const viewport of VIEWPORTS) { test.use({ viewport }); for (const route of COACH_ROUTES) { test(`${route} fits ${viewport.width}px (coach)`, async ({ page }) => { - await expectMobileFit(page, route); + await expectMobileFit(page, route, { expectBottomNav: true }); expect(page.url(), `expected ${route} to render without bouncing to /login`).not.toContain('/login'); }); } @@ -161,7 +186,7 @@ for (const viewport of VIEWPORTS) { test.use({ viewport }); for (const route of PLAYER_ROUTES) { test(`${route} fits ${viewport.width}px (player)`, async ({ page }) => { - await expectMobileFit(page, route); + await expectMobileFit(page, route, { expectBottomNav: true }); expect(page.url(), `expected ${route} to render without bouncing to /login`).not.toContain('/login'); }); } From 915c110782bc0ebfcb899766437c17d0f46c7cfd Mon Sep 17 00:00:00 2001 From: Fable Integrator Date: Tue, 14 Jul 2026 21:04:13 -0400 Subject: [PATCH 4/7] fix(ci): madge API over CLI, skipped-file ratchet, shared bottom-nav detector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check-cycles.mjs now calls madge's Node API in-process instead of shelling out to `npx madge` — no unpinned-package fetch is possible, and we get res.warnings().skipped, which the CLI can never expose in --json mode (see bin/cli.js: it only prints --warning output when NOT combined with --json). Skipped files (e.g. this repo's permanent `server-only` bare-specifier, aliased by Next.js at build time without ever being an installed package) are now baseline-ratcheted the same way cycles are, so a genuinely new unresolved import fails closed instead of silently hiding a cycle, without permanently red-ing the gate on the known benign case. .cycles-baseline.json moves from a flat cycle array to { skipped, cycles } (same 33 cycles, verified byte-identical to the prior baseline). Baseline-file logging now happens before the write, not after. mobile-viewports.spec.ts: the bottom-nav detector (semantic nav/role OR tab-bar-shaped fixed wrapper) is now computed once and reused by both the clipped-controls scan and the collision scan, so a plain fixed bottom-nav wrapper with no