179 lines
15 KiB
Markdown
179 lines
15 KiB
Markdown
# 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 `<img>` 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*
|