diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7777069 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +# Minimal CI floor: lint + typecheck (npm run verify) and a production build. +# Purpose is to stop the repo silently rotting — not to run a test suite. +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - run: npm ci + + # Lint + typecheck. + - run: npm run verify + + # Production build. The build statically prerenders pages that construct a + # Supabase browser client, so it needs the two NEXT_PUBLIC_ values to be + # PRESENT (non-empty) — no live database or real secret is required. These + # are deliberately fake placeholders, safe to commit (the anon key is a + # public client value by design). + - run: npm run build + env: + NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co + NEXT_PUBLIC_SUPABASE_ANON_KEY: placeholder-anon-key-ci-build-only diff --git a/package.json b/package.json index 710aeaa..ec05aa3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "typecheck": "tsc --noEmit", + "verify": "npm run lint && npm run typecheck" }, "dependencies": { "@base-ui/react": "^1.3.0", diff --git a/src/app/project/[id]/surface/page.tsx b/src/app/project/[id]/surface/page.tsx index f2f0684..8c91f81 100644 --- a/src/app/project/[id]/surface/page.tsx +++ b/src/app/project/[id]/surface/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { use, useState, useEffect } from 'react' +import { use, useState } from 'react' import { useSurface, useUpsertSurface } from '@/hooks/useSurface' import { SURFACE_PRESETS, type SurfacePreset } from '@/lib/config/surface-presets' import { getTotalDimensions, getSeamPositionsFromPanels } from '@/lib/domain/surface' @@ -27,15 +27,18 @@ export default function SurfacePage({ params }: { params: Promise<{ id: string } const [dpiTarget, setDpiTarget] = useState(200) const [bleedMm, setBleedMm] = useState(3) - useEffect(() => { - if (existingSurface) { - setPanels(existingSurface.panels) - setDeadZones(existingSurface.dead_zones) - setDpiTarget(existingSurface.dpi_target) - setBleedMm(existingSurface.bleed_mm) - setSurfaceType(existingSurface.type) - } - }, [existingSurface]) + // Hydrate the form once the saved surface loads. Adjusting state during render + // against a remembered previous value (React "you might not need an effect") + // avoids the extra render pass an effect-driven setState would cause. + const [hydratedSurface, setHydratedSurface] = useState(existingSurface) + if (existingSurface && existingSurface !== hydratedSurface) { + setHydratedSurface(existingSurface) + setPanels(existingSurface.panels) + setDeadZones(existingSurface.dead_zones) + setDpiTarget(existingSurface.dpi_target) + setBleedMm(existingSurface.bleed_mm) + setSurfaceType(existingSurface.type) + } function applyPreset(preset: SurfacePreset) { setSelectedPreset(preset.id) diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx index 90dd61e..a63f033 100644 --- a/src/components/layout/AppShell.tsx +++ b/src/components/layout/AppShell.tsx @@ -32,10 +32,14 @@ export function AppShell({ children }: { children: ReactNode }) { return () => window.removeEventListener('scroll', onScroll) }, []) - // Close mobile menu on route change - useEffect(() => { + // Close the mobile menu on route change. Adjusting state during render with a + // remembered previous value (React "you might not need an effect") avoids the + // cascading re-render an effect-driven setState would trigger. + const [prevPathname, setPrevPathname] = useState(pathname) + if (pathname !== prevPathname) { + setPrevPathname(pathname) setMobileMenuOpen(false) - }, [pathname]) + } // Prevent body scroll when mobile menu is open useEffect(() => { diff --git a/tsconfig.json b/tsconfig.json index cf9c65d..c67d202 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,5 +30,5 @@ ".next/dev/types/**/*.ts", "**/*.mts" ], - "exclude": ["node_modules"] + "exclude": ["node_modules", "app", "_archive"] }