Files
agent-park/.planning/codebase/CONCERNS.md
T
2026-04-18 19:28:53 +08:00

15 KiB

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