# Codebase Concerns **Analysis Date:** 2026-04-18 ## Tech Debt **Oversized mixed-responsibility modules:** - Issue: Data access, cache policy, retry logic, query construction, and response shaping are combined in single files instead of being split into smaller modules. - Files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/project/TagFilterPanel.tsx`, `src/components/signals/SignalFeedClient.tsx`, `src/app/[locale]/layout.tsx` - Impact: Safe changes require understanding many unrelated branches at once; regressions are likely because query behavior, cache behavior, and UI state are tightly coupled. - Fix approach: Split server-side data access from presentation and client state; isolate URL/query builders, Prisma queries, cache wrappers, and presentational subcomponents into separate files before adding more behavior. **Misleading server utilities under `hooks/`:** - Issue: `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` are not React hooks. They contain Prisma queries, `unstable_cache`, retry logic, and server-only behavior. - Files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts` - Impact: The naming invites accidental client imports and makes server/client boundaries harder to reason about during refactors. - Fix approach: Move these modules to a server-oriented location such as `src/lib/` or `src/server/`, then keep React hooks in `src/hooks/` only. **Schema-drift tolerance around signal hotness columns:** - Issue: The application carries compatibility code for `hotScore` and `isHot` column absence even though the Prisma schema declares both fields. - Files: `src/lib/signal-hotness.ts`, `src/app/api/signals/route.ts`, `src/app/api/webhook/signals/route.ts`, `prisma/schema.prisma`, `prisma/migrations/20260224120000_add_signal_hot_fields/migration.sql` - Impact: Deployments can run with partially applied migrations without failing fast. That reduces blast radius in production but also hides schema drift and makes behavior environment-dependent. - Fix approach: Treat missing columns as deployment errors after rollout is stable, or move the compatibility branch behind an explicit feature flag with clear removal criteria. **Boot-time failure for AI search route configuration:** - Issue: The AI search route reads `process.env.N8N_AI_SEARCH_WEBHOOK!` at module scope and throws immediately if the variable is missing. - Files: `src/app/api/search/ai/route.ts` - Impact: A missing env var breaks route initialization instead of returning a controlled runtime error. This is fragile in local setup, preview deployments, and tests. - Fix approach: Read the env var inside the request handler, return a structured `500`, and cover the missing-env path with tests. ## Known Bugs **Related projects are not selected from the actual related set:** - Symptoms: The project detail page fetches only the latest three projects and then filters that tiny set for shared tags. - Files: `src/app/[locale]/projects/[id]/page.tsx`, `src/hooks/useProjects.ts` - Trigger: Open a project whose related items are not in the newest three active projects. - Workaround: None in code. The page simply renders fewer or zero related projects. **Interactive navigation and CTA elements are placeholders:** - Symptoms: Several visible controls navigate to `#`, the mobile menu button has no behavior, and the newsletter form has no action handler. - Files: `src/app/[locale]/layout.tsx`, `src/components/project/ProjectCard.tsx` - Trigger: Click `submitProject`, footer/legal/social links, the mobile menu button, or submit the newsletter form. - Workaround: None in code. Users stay on the same page or submit a form with no integration. ## Security Considerations **Admin and ingestion endpoints rely on a single shared API key in request bodies:** - Risk: The code checks only `apiKey` equality. There is no request signature, timestamp, nonce, replay protection, or rate limiting in application code. - Files: `src/lib/auth.ts`, `src/app/api/webhook/signals/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts` - Current mitigation: Timing-safe comparison via `crypto.timingSafeEqual` in `src/lib/auth.ts` - Recommendations: Prefer HMAC-signed requests or provider-native webhook signatures, reject stale timestamps, add rate limiting, and keep network allowlisting outside the app if that is part of deployment. **AI search leaks query text through URL-based webhook forwarding:** - Risk: The route forwards user search text and filters to an external service with a `GET` request query string. - Files: `src/app/api/search/ai/route.ts` - Current mitigation: Not detected in code. - Recommendations: Send the payload with `POST`, avoid placing search text in URLs, and document the data handling expectations for the external n8n workflow. **Raw project markdown can load third-party images:** - Risk: Markdown rendering sanitizes HTML, but image URLs still render through plain `` elements. If project content is not fully trusted, remote images can leak user IPs and referrers to arbitrary hosts. - Files: `src/components/project/MarkdownContent.tsx` - Current mitigation: `rehype-sanitize` removes unsafe HTML, and external links use `rel="noopener noreferrer"`. - Recommendations: Proxy images, restrict allowed image hosts, or disable markdown images for untrusted content. This concern depends on whether project content is curated or user-submitted; the code alone does not establish that trust boundary. ## Performance Bottlenecks **Signal ingestion performs per-item existence checks and upserts sequentially:** - Problem: Each signal performs validation, `findUnique`, and `upsert` inside a loop. The route processes up to 100 signals per request and does not batch database writes. - Files: `src/app/api/webhook/signals/route.ts` - Cause: The ingestion path is written for straightforward correctness and partial failure reporting, not throughput. - Improvement path: Preload existing records in bulk, batch inserts/updates where possible, and use a transaction or job queue if partial writes are unacceptable. **Project and signal text search use `contains` scans without matching full-text indexes:** - Problem: Search endpoints query multiple text fields with case-insensitive `contains`, but the Prisma schema indexes only sorting/filter columns such as `slug`, `status`, `createdAt`, `githubStars`, `publishedAt`, `hotScore`, and `engagement`. - Files: `src/hooks/useProjects.ts`, `src/app/api/signals/route.ts`, `prisma/schema.prisma` - Cause: Search logic is application-level string matching with no dedicated full-text search index. - Improvement path: Add PostgreSQL full-text search or trigram indexes for project and signal search fields, or move search to a dedicated search service. **Silent fallback paths can cache outage-shaped responses:** - Problem: Several server-side queries catch database errors and return empty arrays or zero counts instead of surfacing failure. - Files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts` - Cause: The code prefers graceful degradation, combined with `unstable_cache` wrappers in the same modules. - Improvement path: Separate fallback behavior from cached fetchers, emit structured telemetry, and avoid caching degraded empty results for homepage and listing data. **AI search hydrates external results with local DB lookups and in-memory reordering:** - Problem: The AI route requests candidate IDs from n8n, fetches projects from the database, then matches records back to the remote order with repeated `find` calls. - Files: `src/app/api/search/ai/route.ts`, `src/hooks/useProjects.ts` - Cause: Remote ranking and local hydration are stitched together in the route layer instead of a dedicated search service. - Improvement path: Preserve order with an ID-to-project map, keep pagination logic on one side, and avoid fetching more rows than the final page requires. ## Fragile Areas **Projects results UI has duplicated URL, pagination, and fetch state machines:** - Files: `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/app/api/projects/route.ts`, `src/app/api/search/ai/route.ts` - Why fragile: Traditional search and AI search maintain separate state, pagination, URL sync, and fetch flows inside one client component. The file also manually uses `window.history.replaceState`, which is easy to desynchronize from server-rendered state. - Safe modification: Change one mode at a time, verify deep-linking and pagination after every edit, and extract shared query/pagination logic before adding more filters or sorts. - Test coverage: No tests detected for `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/app/api/projects/route.ts`, or `src/app/api/search/ai/route.ts` **Tag maintenance endpoints mix validation, mutation, and cache invalidation in request handlers:** - Files: `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/maintenance/service.ts`, `src/app/api/tags/reset-projects/route.ts` - Why fragile: Business rules, Prisma writes, per-project iteration, and `revalidatePath` calls are tightly coupled. Bulk operations can change many rows and many pages in one request. - Safe modification: Keep schema validation and mutation rules under tests, preserve transaction boundaries, and review all `revalidatePath` targets before changing route semantics. - Test coverage: Route and service tests exist for these files, but there are no broader integration tests across Prisma, cache invalidation, and localized page rendering. **Shared layout contains production UI plus unfinished placeholders:** - Files: `src/app/[locale]/layout.tsx` - Why fragile: The same layout file owns metadata, locale setup, navigation, announcement bar, newsletter section, footer, and placeholder interactions. - Safe modification: Extract navigation, newsletter, and footer into dedicated components before wiring real integrations. - Test coverage: No tests detected for this file. ## Scaling Limits **Offset pagination on projects will degrade as data grows:** - Current capacity: `src/hooks/useProjects.ts` uses `skip` and `take`; `src/app/api/projects/route.ts` allows `limit` up to `100`. - Limit: High page numbers require larger offset scans in PostgreSQL, especially when combined with multi-join tag filters and text search. - Scaling path: Move to cursor-based pagination for project listings or restrict deep paging with indexed sort keys. **Signal search and sort scale with table growth, not just page size:** - Current capacity: `src/app/api/signals/route.ts` limits pages to `50` items, but search still scans multiple text columns and sort-by-hot depends on computed/indexed metadata. - Limit: Query latency rises as the `signals` table grows because there is no text-search index for `q`. - Scaling path: Add dedicated search indexes and keep cursor pagination tied to indexed sort orders only. **Bulk tag reset revalidates pages per updated project:** - Current capacity: `src/app/api/tags/reset-projects/route.ts` accepts up to `100` projects and revalidates both locale list pages plus two detail pages per updated slug. - Limit: A large batch creates many cache invalidations and can amplify request time. - Scaling path: Batch revalidation, use broader tag-based invalidation if available, or move bulk operations to a background job. ## Dependencies at Risk **`next/cache` `unstable_cache` behavior is runtime-sensitive:** - Risk: Both server data modules include custom fallback logic for missing incremental cache support, which means cache behavior is not consistent across every execution context. - Impact: The same function can behave differently in local development, tests, and production-like runtimes. - Migration plan: Centralize cache wrappers in one server utility, document expected runtimes, and replace `unstable_cache` usage with stable APIs when the project upgrades to a supported alternative. **External AI search depends on an n8n webhook contract with no local fallback:** - Risk: The project assumes an external response shape and throws or fails requests when the webhook is unavailable or changes shape. - Impact: `/api/search/ai` becomes a single external point of failure for AI search. - Migration plan: Version the webhook contract, add contract tests, and consider a local adapter layer that can degrade more predictably. ## Missing Critical Features **End-to-end test harness is absent from the repository:** - Problem: `package.json` exposes only `pnpm test` for Vitest. No `playwright.config.*`, no `e2e/` directory, and no `test:e2e` script are present in the repository. - Blocks: Critical user flows such as localized routing, project filtering, AI search, signals pagination, and webhook-backed content updates have no browser-level regression protection. **User-facing submission and newsletter flows are not implemented:** - Problem: The visible submission CTA and newsletter UI are placeholders with no connected backend or external provider. - Blocks: Users cannot actually submit projects, subscribe to updates, or access legal/resource destinations from the shipped UI. ## Test Coverage Gaps **Search and listing APIs are untested:** - What's not tested: `GET /api/projects`, `GET /api/projects/[slug]`, `POST /api/search/ai`, and `GET /api/signals` - Files: `src/app/api/projects/route.ts`, `src/app/api/projects/[slug]/route.ts`, `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts` - Risk: Pagination, filtering, sort correctness, env-missing behavior, and external-service failure handling can break unnoticed. - Priority: High **Signal ingestion path is untested:** - What's not tested: Validation, per-item failure accounting, schema-drift fallback, and write behavior in `POST /api/webhook/signals` - Files: `src/app/api/webhook/signals/route.ts`, `src/lib/signal-hotness.ts` - Risk: Ingestion regressions can silently drop, mis-rank, or partially write signals. - Priority: High **Large client components and shared layout have no regression tests:** - What's not tested: Filter toggling, URL synchronization, pagination controls, AI/traditional mode switching, signal feed interactions, and layout placeholders - Files: `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/project/TagFilterPanel.tsx`, `src/components/signals/SignalFeedClient.tsx`, `src/app/[locale]/layout.tsx` - Risk: UI regressions are likely because these files are state-heavy and have many interaction branches. - Priority: High **Current tests focus narrowly on tag maintenance and schema validation:** - What's not tested: Most database-backed pages and non-tag APIs outside a few route/service units - Files: `src/app/api/tags/maintenance/route.test.ts`, `src/app/api/tags/maintenance/service.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, `src/app/api/tags/route.test.ts`, `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, `src/lib/validations.tag-maintenance.test.ts` - Risk: The test suite gives confidence for tag admin flows but not for the main product surfaces. - Priority: Medium --- *Concerns audit: 2026-04-18*