From 22731e7978d824b949ca1787ed8768867fde5750 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 5 Mar 2026 17:30:11 +0100 Subject: [PATCH] fix: build speed, observer AI details, round tracker empty state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Disable typedRoutes and skip TS in build (run tsc separately) — build drops from ~9min to ~36s - Expand optimizePackageImports for sonner, date-fns, recharts, motion, zod - Docker: mount .next/cache as build cache for faster rebuilds - Observer filtering panel: fix AI reasoning extraction (nested under rule ID) and show confidence, quality score, spam risk, override reason - Round User Tracker: show empty state message instead of disappearing when selected round has no passed projects yet Co-Authored-By: Claude Opus 4.6 --- docker/Dockerfile | 4 +- next.config.ts | 15 ++++- .../dashboard/round-user-tracker.tsx | 31 ++++++--- .../observer/dashboard/filtering-panel.tsx | 65 ++++++++++++++++--- 4 files changed, 91 insertions(+), 24 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index af810d3..d57b620 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -23,9 +23,9 @@ COPY . . # Generate Prisma client RUN npx prisma generate -# Build Next.js +# Build Next.js — mount .next/cache as a Docker build cache for faster rebuilds ENV NEXT_TELEMETRY_DISABLED=1 -RUN npm run build +RUN --mount=type=cache,target=/app/.next/cache npm run build # Production image, copy all the files and run next FROM base AS runner diff --git a/next.config.ts b/next.config.ts index 7356c00..3475016 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,10 +2,21 @@ import type { NextConfig } from 'next' const nextConfig: NextConfig = { output: 'standalone', - typedRoutes: true, serverExternalPackages: ['@prisma/client', 'minio'], + typescript: { + // We run tsc --noEmit separately before each push + ignoreBuildErrors: true, + }, experimental: { - optimizePackageImports: ['lucide-react'], + optimizePackageImports: [ + 'lucide-react', + 'sonner', + 'date-fns', + 'recharts', + 'motion/react', + 'zod', + '@radix-ui/react-icons', + ], }, images: { remotePatterns: [ diff --git a/src/components/dashboard/round-user-tracker.tsx b/src/components/dashboard/round-user-tracker.tsx index c1526f0..677dad5 100644 --- a/src/components/dashboard/round-user-tracker.tsx +++ b/src/components/dashboard/round-user-tracker.tsx @@ -57,23 +57,21 @@ export function RoundUserTracker({ editionId }: RoundUserTrackerProps) { const { rounds, byCategory } = data const effectiveRoundId = data.selectedRoundId - // Don't render if no rounds or no data - if (!effectiveRoundId || rounds.length === 0) return null + // Don't render if no rounds at all + if (rounds.length === 0) return null const totalProjects = byCategory.reduce((sum, c) => sum + c.total, 0) const totalActivated = byCategory.reduce((sum, c) => sum + c.accountsSet, 0) const totalPending = byCategory.reduce((sum, c) => sum + c.accountsNotSet, 0) - if (totalProjects === 0) return null - - const selectedRound = rounds.find(r => r.id === effectiveRoundId) + const selectedRound = effectiveRoundId ? rounds.find(r => r.id === effectiveRoundId) : undefined const handleSendReminder = async (target: string, opts: { category?: 'STARTUP' | 'BUSINESS_CONCEPT' }) => { setSendingTarget(target) try { await sendReminders.mutateAsync({ editionId, - roundId: effectiveRoundId, + roundId: effectiveRoundId!, category: opts.category, }) } finally { @@ -89,13 +87,15 @@ export function RoundUserTracker({ editionId }: RoundUserTrackerProps) { Round User Tracker - - {totalActivated}/{totalProjects} activated - + {totalProjects > 0 && ( + + {totalActivated}/{totalProjects} activated + + )} {/* Round selector */} + {totalProjects === 0 ? ( +
+ +

+ No projects have passed {selectedRound?.name ?? 'this round'} yet +

+
+ ) : ( + <> {/* Subtitle showing round context */}

Projects that passed {selectedRound?.name ?? 'this round'} — account activation status @@ -192,6 +201,8 @@ export function RoundUserTracker({ editionId }: RoundUserTrackerProps) { )} + + )} ) diff --git a/src/components/observer/dashboard/filtering-panel.tsx b/src/components/observer/dashboard/filtering-panel.tsx index ccfd922..5b7f057 100644 --- a/src/components/observer/dashboard/filtering-panel.tsx +++ b/src/components/observer/dashboard/filtering-panel.tsx @@ -19,6 +19,29 @@ import { import { Filter, ChevronDown, ChevronUp, ChevronLeft, ChevronRight } from 'lucide-react' import { cn } from '@/lib/utils' +type AIScreeningData = { + meetsCriteria?: boolean + confidence?: number + reasoning?: string + qualityScore?: number + spamRisk?: boolean +} + +function parseAIData(json: unknown): AIScreeningData | null { + if (!json || typeof json !== 'object') return null + const obj = json as Record + // aiScreeningJson is nested under rule ID: { [ruleId]: { outcome, confidence, ... } } + if (!('outcome' in obj) && !('reasoning' in obj)) { + const keys = Object.keys(obj) + if (keys.length > 0) { + const inner = obj[keys[0]] + if (inner && typeof inner === 'object') return inner as AIScreeningData + } + return null + } + return obj as unknown as AIScreeningData +} + export function FilteringPanel({ roundId }: { roundId: string }) { const [outcomeFilter, setOutcomeFilter] = useState('ALL') const [page, setPage] = useState(1) @@ -199,17 +222,39 @@ export function FilteringPanel({ roundId }: { roundId: string }) { - {expandedId === r.id && ( -

-
- {(() => { - const screening = r.aiScreeningJson as Record | null - const reasoning = (screening?.reasoning ?? screening?.explanation ?? r.overrideReason ?? 'No details available') as string - return reasoning - })()} + {expandedId === r.id && (() => { + const ai = parseAIData(r.aiScreeningJson) + return ( +
+ {ai?.confidence != null && ( +
+ {ai.confidence != null && ( + + Confidence: {Math.round(ai.confidence * 100)}% + + )} + {ai.qualityScore != null && ( + + Quality: {ai.qualityScore}/10 + + )} + {ai.spamRisk && ( + Spam Risk + )} +
+ )} +
+ {ai?.reasoning || 'No AI reasoning available'} +
+ {r.overrideReason && ( +
+ Override: + {r.overrideReason} +
+ )}
-
- )} + ) + })()}
))}