Files
agent-park/.planning/codebase/CONCERNS.md
T
2026-04-20 18:59:15 +08:00

217 lines
20 KiB
Markdown

# Codebase Concerns
**Analysis Date:** 2026-04-20
## Tech Debt
**Oversized mixed-responsibility modules:**
- Issue: Database access, retry policy, cache fallback, query normalization, and response shaping are combined in the same modules instead of being split into smaller server-side layers.
- 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 unrelated concerns at once, so regressions in caching, pagination, or rendering are easy to introduce.
- Fix approach: Split server data access into `src/lib/` or `src/server/`, keep client state in client components only, and extract URL/query helpers plus cache wrappers into dedicated modules.
**Server-only data modules are mislabeled as 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 fallback behavior.
- Files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`
- Impact: The naming invites accidental client imports and hides the real server/client boundary during refactors.
- Fix approach: Move these modules to a server-oriented location and reserve `src/hooks/` for actual React hooks.
**n8n workflow state is documented, but executable exports are still missing:**
- Issue: The repository now carries rich n8n metadata and generated context, but `docs/integrations/n8n/exports/` contains only `.gitkeep` and every workflow entry currently records `Export file: not recorded`.
- Files: `docs/integrations/n8n/README.md`, `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/exports/.gitkeep`, `scripts/generate-n8n-context.mjs`
- Impact: The repo captures contracts and touchpoints, but not the actual workflow logic. Incident response, review, and reproducibility still depend on external n8n access.
- Fix approach: Commit workflow exports alongside `registry.json`, treat them as versioned artifacts, and keep `pnpm n8n:context` as a verification step instead of the primary source of truth.
**Repository instructions drift from the actual toolchain:**
- Issue: `AGENTS.md` documents `pnpm test:e2e` and an `e2e/` suite, but `package.json` has no `test:e2e` script and the repo contains no `playwright.config.*` or `e2e/` directory.
- Files: `AGENTS.md`, `package.json`
- Impact: Contributors can assume browser-level regression coverage exists when it does not.
- Fix approach: Either add the documented Playwright harness or remove the claim from `AGENTS.md` so verification expectations match reality.
## Known Bugs
**Related projects are picked from the newest three projects, not the actual related set:**
- Symptoms: The project detail page fetches `getProjects({ limit: 3 })` and then filters that tiny subset for shared tags.
- Files: `src/app/[locale]/projects/[id]/page.tsx`, `src/hooks/useProjects.ts`
- Trigger: Open a project whose genuinely related items are not among the newest three active projects.
- Workaround: None in code. The page simply shows fewer or zero related projects.
**AI search route crashes at module initialization when its env var is missing:**
- Symptoms: Importing the route throws before handling a request because `process.env.N8N_AI_SEARCH_WEBHOOK` is read at module scope and hard-fails.
- Files: `src/app/api/search/ai/route.ts`
- Trigger: Start the app, build, or run tests without `N8N_AI_SEARCH_WEBHOOK` configured.
- Workaround: Provide the env var in every environment that loads the route.
**Visible navigation and conversion entry points are placeholders:**
- Symptoms: Submit-project, footer resource/legal links, social links, and the newsletter form do not connect to real destinations or handlers. The mobile menu button also has no behavior.
- Files: `src/app/[locale]/layout.tsx`, `src/components/project/ProjectCard.tsx`
- Trigger: Click the submit CTA, footer links, social buttons, or submit the newsletter form on any localized page.
- Workaround: None in code. Users stay on the same page or submit to a no-op form target.
## Security Considerations
**Admin and ingestion endpoints depend on a single shared API key passed in request bodies:**
- Risk: The code checks only equality against `WEBHOOK_API_KEY`. There is no request signature, timestamp, nonce, replay protection, or application-side rate limiting.
- 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`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/workflows/07-signals-aggregation.md`, `docs/integrations/n8n/workflows/08-project-tag-reset.md`
- Current mitigation: `src/lib/auth.ts` uses `crypto.timingSafeEqual`, and the n8n docs call out the shared-secret problem explicitly.
- Recommendations: Move to HMAC-signed requests or provider-native webhook verification, reject stale timestamps, and apply rate limiting at the app or edge layer.
**AI search forwards user queries to n8n in a GET query string:**
- Risk: Search text and filters are serialized into the webhook URL, which is more likely to be logged by reverse proxies, platforms, and third-party tooling.
- Files: `src/app/api/search/ai/route.ts`, `docs/integrations/n8n/workflows/06-rag-project-search.md`
- Current mitigation: Not detected in code.
- Recommendations: Switch to `POST`, move request data into the body, and document the retention/logging expectations for the n8n side.
**Runtime secret handling spreads certificate material and credentials outside the repo boundary:**
- Risk: Database TLS assets are reconstructed from env vars onto disk at runtime and the client identity password is appended to the Prisma datasource URL. The n8n docs also confirm workflows that need app secrets or raw database access outside this repository.
- Files: `src/lib/prisma-url.ts`, `src/lib/prisma.ts`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/workflows/04-github-star-refresh.md`, `docs/integrations/n8n/workflows/05-project-description-vectorization.md`
- Current mitigation: `src/lib/prisma-url.ts` writes files with restrictive permissions and uses an environment-configurable certificate directory.
- Recommendations: Prefer mounted secrets over reconstructing certs in temp storage, keep passwords out of DSN strings where possible, scope database users per workflow, and document secret ownership/rotation outside the app.
**Raw markdown content can load remote images from arbitrary hosts:**
- Risk: Markdown rendering sanitizes HTML, but it still renders remote `<img>` URLs directly. If project content is not fully trusted, remote hosts can observe client IPs and referrers.
- 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 hosts, or disable markdown image rendering for untrusted content.
## Performance Bottlenecks
**Signal ingestion performs per-item existence checks and upserts sequentially:**
- Problem: Each signal performs validation, `findUnique`, and `upsert` inside a loop, with optional retry into a second upsert path when hotness columns are unavailable.
- Files: `src/app/api/webhook/signals/route.ts`
- Cause: The route is optimized for straightforward per-item error reporting, not for throughput.
- Improvement path: Bulk-load existing keys, batch inserts/updates, and move high-volume ingestion to a queue or transactional batch writer.
**Project and signal search rely on case-insensitive `contains` scans instead of search-specific indexes:**
- Problem: Search touches multiple text fields with `contains`, while `prisma/schema.prisma` indexes sorting/filter columns but no text-search structures.
- Files: `src/hooks/useProjects.ts`, `src/app/api/signals/route.ts`, `prisma/schema.prisma`
- Cause: Search is implemented as application-level substring matching over Prisma filters.
- Improvement path: Add PostgreSQL full-text or trigram indexes, or move search to a dedicated retrieval service.
**AI search does extra in-memory filtering and O(n²) result hydration after the webhook call:**
- Problem: `src/app/api/search/ai/route.ts` fetches candidate IDs from n8n, loads projects from the database, repeatedly calls `projects.find(...)` to restore ranking order, and then re-applies tag/domain/product-form filtering in memory.
- Files: `src/app/api/search/ai/route.ts`, `src/hooks/useProjects.ts`
- Cause: Responsibility is split awkwardly between n8n ranking and route-side post-processing.
- Improvement path: Use an ID-to-project map, keep filtering ownership on one side of the contract, and avoid loading more rows than the final page needs.
**Graceful-degradation fallbacks can return empty or zero-shaped data under DB failure:**
- Problem: Homepage and project metadata helpers catch database errors and degrade to empty arrays or zero counts.
- Files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`
- Cause: The code prefers availability over surfacing failures and uses the same modules for cached and uncached access.
- Improvement path: Separate failure-aware fetchers from UI fallback shaping, emit explicit telemetry, and avoid treating outage-shaped responses as normal product data.
## Fragile Areas
**Projects results UI maintains two overlapping state machines in one client component:**
- 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 keep separate pagination, sort, loading, error, and URL-sync state inside the same file. The component also mutates the browser URL with `window.history.replaceState`, which is easy to desynchronize from server-rendered state.
- Safe modification: Change only one search mode at a time, verify deep-linking after every edit, and extract shared query-state helpers 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`
**Direct database-writing n8n jobs bypass repository API routes and cache invalidation:**
- Files: `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/workflows/04-github-star-refresh.md`, `docs/integrations/n8n/workflows/05-project-description-vectorization.md`, `prisma/schema.prisma`, `src/hooks/useProjects.ts`, `src/app/api/search/ai/route.ts`
- Why fragile: The documented star-refresh and vectorization workflows write straight to Postgres instead of going through repository routes. That bypasses app-level validation, audit points, and any future route-based revalidation logic.
- Safe modification: Treat the Prisma schema and n8n registry as one shared contract, and change database columns/indexes only with coordinated workflow updates plus runtime verification.
- Test coverage: No contract or integration tests detected for these cross-system write paths
**The project ingestion loop is not self-contained in this repository:**
- Files: `docs/integrations/n8n/DATAFLOW.md`, `docs/integrations/n8n/CONTEXT.generated.md`, `docs/integrations/n8n/workflows/01-topic-discovery.md`, `docs/integrations/n8n/workflows/02-github-trending-discovery.md`, `docs/integrations/n8n/workflows/03-project-ingestion-multi-source.md`
- Why fragile: The docs explicitly depend on `/api/discovery/check-duplicates` and `/api/discovery/tasks*`, but there is no `src/app/api/discovery/` implementation in this repo. The repo depends on an upstream discovery service to stay operational.
- Safe modification: Treat discovery endpoints as an external contract, version their request/response shapes, and avoid assuming local end-to-end reproducibility for ingestion work.
- Test coverage: No in-repo tests can cover the full discovery-to-ingestion flow because the required service is absent here
**Bulk tag mutation endpoints couple validation, writes, and cache invalidation at the route layer:**
- 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: A single request can mutate many tags or projects and immediately trigger localized page revalidation. Business rules, transactional writes, and cache invalidation are tightly coupled.
- Safe modification: Preserve transaction boundaries, keep mutation rules covered with focused tests, and review every `revalidatePath` target before changing route semantics.
- Test coverage: Unit-style route and service tests exist, but there are no broader integration tests across Prisma writes, cache invalidation, and localized page rendering
## Scaling Limits
**Offset pagination on projects degrades with table size:**
- Current capacity: `src/hooks/useProjects.ts` uses `skip` and `take`, and `src/app/api/projects/route.ts` allows `limit` up to `100`.
- Limit: Deep pages require larger offset scans in PostgreSQL, especially when combined with tag joins and text filters.
- Scaling path: Move project listings to cursor pagination keyed by indexed sort fields or restrict deep paging.
**Signals pagination is ordered by mutable ranking fields:**
- Current capacity: `src/app/api/signals/route.ts` sorts hot feeds by `isHot`, `hotScore`, `engagement`, `publishedAt`, and `id`, but cursors by `id` only.
- Limit: As new signals arrive or hotness changes, clients can observe duplicates or skips between pages because the rank can move independently of the cursor key.
- Scaling path: Use a stable compound cursor that includes the sort fields or snapshot the ranking inputs for pagination windows.
**Bulk tag reset scales linearly in both writes and cache invalidations:**
- Current capacity: `src/app/api/tags/reset-projects/route.ts` can process many projects in one request and revalidate both locale list pages plus every updated detail page.
- Limit: Large batch operations increase request time and invalidation fan-out.
- Scaling path: Batch revalidation, shift large maintenance jobs to background execution, or use broader cache-tag invalidation when available.
## Dependencies at Risk
**External discovery services are mandatory but not versioned here:**
- Risk: Project discovery, dedupe, and task lifecycle all depend on endpoints that are documented but not implemented in this repo.
- Impact: Local development, replay, debugging, and disaster recovery are incomplete without a second system.
- Migration plan: Either bring `src/app/api/discovery/*` into the repo or maintain a separately versioned API contract with tests and operational ownership.
**AI search depends on an external n8n webhook contract with no local fallback:**
- Risk: `/api/search/ai` assumes the remote workflow exists, responds quickly, and preserves the `results[].id` plus `similarity` schema.
- Impact: AI search becomes a single external point of failure and contract drift breaks the feature immediately.
- Migration plan: Version the webhook contract in `docs/integrations/n8n/registry.json`, add contract tests around `src/app/api/search/ai/route.ts`, and return controlled degraded responses when the webhook is unavailable.
**Workflow metadata is committed, but workflow behavior still lives outside git:**
- Risk: The repo carries registry entries and generated context, but not the executable n8n exports.
- Impact: Reviewers can understand intent, but they still cannot reproduce or diff actual workflow logic from the repository alone.
- Migration plan: Start committing workflow exports to `docs/integrations/n8n/exports/` and reference them from `docs/integrations/n8n/registry.json`.
**`unstable_cache` behavior still depends on runtime capabilities:**
- Risk: The app uses `runWithCacheFallback` to catch environments where `unstable_cache` is unavailable.
- Impact: Cache behavior differs across local development, tests, and deployed runtimes, which complicates debugging and performance expectations.
- Migration plan: Centralize cache policy, document supported runtimes, and replace `unstable_cache` with stable APIs when the stack allows it.
## Missing Critical Features
**The repository does not contain a self-contained discovery ingestion loop:**
- Problem: The n8n docs depend on discovery task endpoints that do not exist under `src/app/api/`, so the repo cannot run its own project-intake workflow end to end.
- Blocks: End-to-end ingestion tests, local replay of failed discovery tasks, and full incident debugging from this repo alone.
**Browser-level regression coverage is missing:**
- Problem: The repo ships no `playwright.config.*`, no `e2e/` directory, and no `test:e2e` script even though repository guidance claims they exist.
- Blocks: Localized routing, multi-step filtering, AI search UX, and layout-level interaction regressions are not protected at the browser layer.
**User-facing submission and newsletter flows are still not implemented:**
- Problem: The visible submit-project and newsletter UI does not connect to backend handlers or third-party providers.
- Blocks: Users cannot submit projects, subscribe for updates, or rely on footer resource/legal/social destinations.
## Test Coverage Gaps
**Search and listing APIs remain 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, sorting, env-missing behavior, cursor correctness, and external-service error handling can break unnoticed.
- Priority: High
**Signal ingestion still has no route-level tests:**
- What's not tested: Validation, per-item error accounting, hotness 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
**n8n and discovery contracts have no automated verification in this repo:**
- What's not tested: That `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/CONTEXT.generated.md`, and route expectations stay aligned with live n8n workflows and the external discovery service.
- Files: `docs/integrations/n8n/registry.json`, `docs/integrations/n8n/CONTEXT.generated.md`, `scripts/generate-n8n-context.mjs`, `src/app/api/search/ai/route.ts`, `src/app/api/tags/reset-projects/route.ts`, `src/app/api/webhook/signals/route.ts`
- Risk: Cross-system contract drift is detected late, usually only after production failures.
- Priority: High
**Large client components and shared layout have no regression tests:**
- What's not tested: Filter toggling, URL synchronization, AI/traditional search mode switching, signal feed paging, and layout-level placeholder interactions
- 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 branchy.
- Priority: High
**Current tests focus on tag admin flows and low-level helpers, not main user journeys:**
- What's not tested: Most database-backed pages and operational boundaries outside tag maintenance, auth comparison, validation schemas, and Prisma URL construction
- Files: `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, `src/app/api/tags/maintenance/route.test.ts`, `src/app/api/tags/maintenance/service.test.ts`, `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, `src/lib/validations.tag-maintenance.test.ts`, `src/lib/prisma-url.test.ts`
- Risk: The suite provides confidence for admin mutation helpers but not for the main product surfaces or external integration edges.
- Priority: Medium
---
*Concerns audit: 2026-04-20*