diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md new file mode 100644 index 0000000..4e4d8ce --- /dev/null +++ b/.planning/codebase/ARCHITECTURE.md @@ -0,0 +1,232 @@ +# Architecture + +**Analysis Date:** 2026-04-18 + +## Pattern Overview + +**Overall:** Server-first Next.js App Router monolith with localized page routes, thin API handlers, Prisma-backed data access, and small client-side interaction islands. + +**Key Characteristics:** +- Use `src/app/layout.tsx` and `src/app/[locale]/layout.tsx` as the primary composition roots, with most route files implemented as async server components. +- Keep browser-only interaction in explicit client components such as `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/search/AISearchBar.tsx`, and `src/components/signals/SignalFeedClient.tsx`. +- Centralize most project and home-page reads in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`, even though these modules are named like React hooks. +- Validate external input with Zod schemas from `src/lib/validations.ts` before querying or mutating the database. +- Use Prisma as the only persistence client through `src/lib/prisma.ts`, with schema ownership in `prisma/schema.prisma`. + +## Layers + +**Routing and Layout Layer:** +- Purpose: Resolve locale-aware routes, shared layout chrome, SEO metadata, and global CSS. +- Location: `src/app/layout.tsx`, `src/app/[locale]/layout.tsx`, `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, `src/app/[locale]/projects/[id]/page.tsx`, `src/app/[locale]/signals/page.tsx`, `src/app/[locale]/about/page.tsx`, `src/app/sitemap.ts`, `src/app/robots.ts`, `src/middleware.ts`, `src/i18n/request.ts` +- Contains: App Router entry points, `generateMetadata`, `generateStaticParams`, `revalidate` exports, locale middleware, and SEO documents. +- Depends on: `next-intl`, server query modules in `src/hooks`, UI components in `src/components`, and shared helpers in `src/lib`. +- Used by: The Next.js runtime. + +**UI Composition Layer:** +- Purpose: Render reusable view fragments for pages and route-specific UI sections. +- Location: `src/components/home/*`, `src/components/layout/*`, `src/components/locale/*`, `src/components/project/*`, `src/components/search/*`, `src/components/signals/*` +- Contains: Mostly presentational components, plus a few server components that call translations or helpers directly, such as `src/components/project/ProjectList.tsx` and `src/components/project/ProjectSidebar.tsx`. +- Depends on: Translation APIs, route props, and typed data returned by `src/hooks/useProjects.ts` or `src/hooks/useHome.ts`. +- Used by: Route entry points under `src/app/[locale]`. + +**Client Interaction Layer:** +- Purpose: Own browser-only state, URL mutation, fetch loops, local storage, and interactive filters/search. +- Location: `src/app/VercelMetrics.tsx`, `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/layout/AnnouncementBar.tsx`, `src/components/locale/LocaleSwitcher.tsx`, `src/components/project/TagFilterPanel.tsx`, `src/components/search/AISearchBar.tsx`, `src/components/search/HomeSearchBar.tsx`, `src/components/search/AISearchResults.tsx`, `src/components/signals/SignalFeedClient.tsx` +- Contains: `useState`/`useEffect`-driven UI state, `window.history.replaceState`, `window.location.href`, `localStorage`, and client-side `fetch` calls to `/api/*`. +- Depends on: Page props, Next navigation APIs, and JSON APIs implemented under `src/app/api`. +- Used by: Server-rendered pages and layouts. + +**Query and Read Model Layer:** +- Purpose: Encapsulate database reads, filtering, retry logic, shape transformations, and cached read models for projects, tags, and home-page aggregates. +- Location: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts` +- Contains: Prisma queries, pagination logic, tag-category grouping, home-page aggregate builders, and result-type definitions such as `ProjectWithFlatTags`, `FilterTagCategoryGroup`, and `HomePageData`. +- Depends on: `src/lib/prisma.ts`, `src/lib/cache.ts`, `src/lib/tag-taxonomy.ts`, and Prisma types from `@prisma/client`. +- Used by: Page routes such as `src/app/[locale]/page.tsx` and list APIs such as `src/app/api/projects/route.ts`. + +**API and Mutation Layer:** +- Purpose: Expose JSON endpoints for list/detail reads, AI search proxying, signals feed reads, authenticated tag maintenance, project-tag resets, and signal ingestion. +- Location: `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`, `src/app/api/tags/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/maintenance/service.ts`, `src/app/api/tags/reset-projects/route.ts`, `src/app/api/webhook/signals/route.ts` +- Contains: `GET` and `POST` route handlers, request parsing, validation, response shaping, cache revalidation, and transaction-scoped maintenance logic. +- Depends on: `src/lib/validations.ts`, `src/lib/auth.ts`, `src/lib/prisma.ts`, `src/lib/signal-hotness.ts`, and the query layer in `src/hooks`. +- Used by: Client components, external webhook callers, and automation clients. + +**Domain Utility Layer:** +- Purpose: Hold cross-route domain logic that is not tied to a single route. +- Location: `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/prisma.ts`, `src/lib/signal-hotness.ts`, `src/lib/slug.ts`, `src/lib/tag-taxonomy.ts`, `src/lib/validations.ts`, `src/lib/github/badges.ts` +- Contains: API-key verification, cache fallback helpers, Prisma singleton setup, signal hotness scoring, slug generation, tag taxonomy inference, schema validation, and GitHub link parsing. +- Depends on: Standard library APIs, Prisma, and Zod. +- Used by: Both pages and API handlers. + +**Persistence Layer:** +- Purpose: Define and migrate the database model and seed baseline data. +- Location: `prisma/schema.prisma`, `prisma/migrations/*`, `prisma/seed.ts` +- Contains: PostgreSQL schema for `Project`, `Tag`, `ProjectTag`, `ExternalLink`, and `Signal`, plus migrations and seed data. +- Depends on: Prisma CLI and environment-provided `DATABASE_URL`. +- Used by: `src/lib/prisma.ts` at runtime and Prisma tooling during migrations/seed. + +## Data Flow + +**Localized Page Render:** + +1. `src/middleware.ts` forces a locale-prefixed pathname and excludes `/api`, `/_next`, and static assets from locale routing. +2. `src/app/[locale]/layout.tsx` validates the locale, calls `setRequestLocale(locale)`, loads messages through `next-intl`, and wraps the tree with `NextIntlClientProvider`. +3. Route pages such as `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, and `src/app/[locale]/projects/[id]/page.tsx` fetch data from `src/hooks/useProjects.ts` or `src/hooks/useHome.ts`. +4. Reusable server and client components render the data, with ISR enabled through `export const revalidate = 300` on the main content routes. + +**Traditional Projects Browse Flow:** + +1. `src/app/[locale]/projects/page.tsx` reads `searchParams`, normalizes filters, and calls `getProjects`, `getFixedProjectTypeFilters`, and `getTagCategoryGroups` from `src/hooks/useProjects.ts`. +2. `src/app/[locale]/projects/ProjectsPageClient.tsx` and `src/components/project/TagFilterPanel.tsx` manage filter-panel visibility and query-string updates in the browser. +3. `src/app/[locale]/projects/ProjectsResultsClient.tsx` fetches updated pages from `src/app/api/projects/route.ts` for client-side pagination and sorting. +4. `src/app/api/projects/route.ts` validates query parameters with Zod, then delegates to `getProjects`. + +**AI Search Flow:** + +1. `src/components/search/AISearchBar.tsx` toggles between traditional and AI mode and forwards the query back to the page-level handler. +2. `src/app/[locale]/projects/ProjectsResultsClient.tsx` requests `POST /api/search/ai`. +3. `src/app/api/search/ai/route.ts` validates the request with `ProjectQuerySchema`, forwards a GET request to the external N8N webhook URL from `N8N_AI_SEARCH_WEBHOOK`, then hydrates returned IDs by calling `getProjectsByIds` from `src/hooks/useProjects.ts`. +4. The route sorts and filters the hydrated projects again before returning JSON to the client. + +**Signals Feed Flow:** + +1. `src/app/[locale]/signals/page.tsx` renders shell content and mounts `src/components/signals/SignalFeedClient.tsx`. +2. `SignalFeedClient` holds search text, debounce state, source filter, sort mode, and pagination cursor in component state. +3. The client calls `GET /api/signals` on `src/app/api/signals/route.ts`. +4. `src/app/api/signals/route.ts` validates the query, queries Prisma, computes or reads hotness metadata via `src/lib/signal-hotness.ts`, and returns cursor-based pagination. + +**Signal Ingestion Flow:** + +1. External automation posts to `src/app/api/webhook/signals/route.ts`. +2. The handler validates the top-level payload and each item with `SignalWebhookPayloadSchema` and `SignalIngestionInputSchema` from `src/lib/validations.ts`. +3. `src/lib/auth.ts` verifies `apiKey` using `crypto.timingSafeEqual`. +4. The handler upserts signals in Prisma, computing fallback hotness when needed and downgrading if `hotScore`/`isHot` columns are absent. + +**Tag Maintenance Flow:** + +1. Clients call `POST /api/tags/maintenance` or `POST /api/tags/reset-projects`. +2. The route validates input, authenticates with `isValidApiKey`, and runs Prisma mutations. +3. `src/app/api/tags/maintenance/route.ts` delegates transactional merge/update logic to `src/app/api/tags/maintenance/service.ts`. +4. Successful mutations call `revalidatePath` for affected localized pages. + +## State Management + +**Server State:** +- Persistent application state lives in PostgreSQL, modeled in `prisma/schema.prisma` and accessed only through Prisma in `src/lib/prisma.ts`. +- Page-level read models are built in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`. + +**Cached Server State:** +- `unstable_cache` is used in `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, and `src/app/api/tags/route.ts`. +- `src/lib/cache.ts` provides `runWithCacheFallback` so cached fetchers can fall back to direct database reads when Incremental Cache is unavailable. + +**Client UI State:** +- Local component state, not a global store, drives interactivity. +- `src/app/[locale]/projects/ProjectsPageClient.tsx` stores filter-panel expansion. +- `src/app/[locale]/projects/ProjectsResultsClient.tsx` stores AI/traditional pagination, loading flags, and sort/limit state. +- `src/components/signals/SignalFeedClient.tsx` stores debounced search, source filters, sort mode, cursor, and loading state. +- `src/components/layout/AnnouncementBar.tsx` stores dismissal state in `localStorage`. + +**URL State:** +- Search, pagination, sort, AI mode, and taxonomy filters are encoded in the query string on `src/app/[locale]/projects/page.tsx`. +- The projects UI treats the URL as the source of truth and synchronizes local state to it through `router.push` and `window.history.replaceState`. + +**Translation State:** +- Locale selection is path-based and enforced by `src/middleware.ts`. +- Message bundles load from `src/messages/en.json` and `src/messages/zh.json` via `src/i18n/request.ts`. + +## Key Abstractions + +**Project Read Model:** +- Purpose: Present projects with flattened tags and related links, independent of Prisma join-table shape. +- Examples: `ProjectWithFlatTags` and `getProjects` in `src/hooks/useProjects.ts`, `getProjectBySlug` in `src/hooks/useProjects.ts` +- Pattern: Read-model transformation from Prisma includes to page/API-friendly objects. + +**Tag Taxonomy:** +- Purpose: Normalize and classify project tags into fixed categories used by filters and maintenance APIs. +- Examples: `src/lib/tag-taxonomy.ts`, `getTagCategoryGroups` in `src/hooks/useProjects.ts` +- Pattern: Central domain vocabulary with inference helpers shared across pages and mutations. + +**Signal View Model:** +- Purpose: Convert raw `Signal` rows, localized text fields, JSON sections, and hotness metadata into feed-ready objects. +- Examples: `toSignalView` and `parseSections` in `src/app/api/signals/route.ts` +- Pattern: API-specific presentation mapping on top of Prisma rows. + +**Maintenance Service:** +- Purpose: Keep complex tag merge/update rules out of the route handler. +- Examples: `executeTagMaintenance` and `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts` +- Pattern: Route delegates orchestration to a transaction-aware service module. + +**Auth Guard for Internal APIs:** +- Purpose: Reuse constant-time API-key validation across maintenance and webhook routes. +- Examples: `isValidApiKey` in `src/lib/auth.ts`, used by `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts` +- Pattern: Small shared guard function, not middleware-based auth. + +## Entry Points + +**Root App Shell:** +- Location: `src/app/layout.tsx` +- Triggers: Every page render. +- Responsibilities: Global HTML/body shell, global CSS import, and conditional Vercel analytics injection through `src/app/VercelMetrics.tsx`. + +**Localized App Shell:** +- Location: `src/app/[locale]/layout.tsx` +- Triggers: Every localized page render under `//*`. +- Responsibilities: Locale validation, translations, site navigation, announcement bar, footer, and `NextIntlClientProvider`. + +**Home Route:** +- Location: `src/app/[locale]/page.tsx` +- Triggers: `GET /zh` and `GET /en` +- Responsibilities: Render home hero, stats, rankings, tag insights, recent timeline, and featured projects. + +**Projects Route:** +- Location: `src/app/[locale]/projects/page.tsx` +- Triggers: `GET //projects` +- Responsibilities: Parse URL filters, render filter/search UI, fetch paginated project data, and mount the projects results client island. + +**Project Detail Route:** +- Location: `src/app/[locale]/projects/[id]/page.tsx` +- Triggers: `GET //projects/:slug` +- Responsibilities: Fetch a single project, derive related projects, render sidebar/details, and generate route metadata. + +**Signals Route:** +- Location: `src/app/[locale]/signals/page.tsx` +- Triggers: `GET //signals` +- Responsibilities: Render the signals feed shell and mount the client-side feed loader. + +**API Routes:** +- Location: `src/app/api/*` +- Triggers: Browser fetches and external webhook clients. +- Responsibilities: JSON read endpoints, AI search proxying, signals feed pagination, authenticated tag maintenance, project tag resets, and signal ingestion. + +**Locale Middleware:** +- Location: `src/middleware.ts` +- Triggers: All non-API, non-static requests. +- Responsibilities: Apply the `next-intl` locale prefix policy and route matching. + +## Error Handling + +**Strategy:** Validate early, return structured JSON for API errors, use `notFound()` for invalid page resources, and degrade some read paths to empty or fallback results instead of hard-failing the page. + +**Patterns:** +- Use Zod validation in `src/app/api/projects/route.ts`, `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts`. +- Use `notFound()` in `src/app/[locale]/layout.tsx`, `src/app/[locale]/projects/[id]/page.tsx`, and `src/app/[locale]/[...catchAll]/page.tsx` for route-level misses. +- Retry transient database errors in `src/hooks/useProjects.ts` via `withDbRetry`. +- Degrade to empty or zero-count results in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` when some DB reads fail. +- Wrap service-specific failures in `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts`. + +## Cross-Cutting Concerns + +**Logging:** Use `console.error` and `console.warn` directly in route handlers and server query modules, for example in `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts`, `src/app/api/webhook/signals/route.ts`, `src/hooks/useProjects.ts`, and `src/hooks/useHome.ts`. + +**Validation:** Use shared Zod schemas from `src/lib/validations.ts` for external request bodies and query parameters. + +**Authentication:** Protect internal mutation/webhook endpoints with API-key checks from `src/lib/auth.ts`. No session or user-account auth layer is present in the inspected files. + +**Internationalization:** Use `next-intl` with locale-prefixed routing through `src/middleware.ts`, request config in `src/i18n/request.ts`, and message bundles in `src/messages/*.json`. + +**Caching and Revalidation:** Use ISR-style route revalidation (`revalidate = 300`), `unstable_cache`, and `revalidatePath` after tag mutations. + +**Observability:** The only built-in runtime instrumentation found is Vercel analytics in `src/app/VercelMetrics.tsx`. No separate tracing or background job framework was detected in inspected files. + +--- + +*Architecture analysis: 2026-04-18* diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md new file mode 100644 index 0000000..a7b514a --- /dev/null +++ b/.planning/codebase/CONCERNS.md @@ -0,0 +1,178 @@ +# 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* diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md new file mode 100644 index 0000000..d2235f3 --- /dev/null +++ b/.planning/codebase/CONVENTIONS.md @@ -0,0 +1,148 @@ +# Coding Conventions + +**Analysis Date:** 2026-04-18 + +## Naming Patterns + +**Files:** +- Use Next.js route filenames in `src/app`: `page.tsx`, `layout.tsx`, `route.ts`, `not-found.tsx`, `robots.ts`, and `sitemap.ts` as seen in `src/app/[locale]/page.tsx`, `src/app/[locale]/layout.tsx`, `src/app/api/projects/route.ts`, and `src/app/[locale]/not-found.tsx`. +- Use `PascalCase.tsx` for reusable components in `src/components`, for example `src/components/project/ProjectCard.tsx`, `src/components/project/TagFilterPanel.tsx`, and `src/components/signals/SignalFeedClient.tsx`. +- Use lower-case utility filenames in `src/lib`, for example `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/signal-hotness.ts`, and `src/lib/validations.ts`. +- `src/hooks` is not limited to React hooks. `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` export server-side data access and aggregation functions, not hook APIs. Extend those files only when adding the same kind of server query layer. + +**Functions:** +- Use `camelCase` for functions and helpers: `isValidApiKey` in `src/lib/auth.ts`, `normalizeProjectSort` in `src/hooks/useProjects.ts`, `runWithCacheFallback` in `src/lib/cache.ts`, and `collectSelectedTagSlugs` in `src/app/api/tags/reset-projects/route.ts`. +- Use `PascalCase` only for React components and error classes: `ProjectCard` in `src/components/project/ProjectCard.tsx`, `ProjectsResultsClient` in `src/app/[locale]/projects/ProjectsResultsClient.tsx`, and `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts`. +- Use `GET` and `POST` named exports for route handlers in `src/app/api/**/route.ts`. + +**Variables:** +- Use `UPPER_SNAKE_CASE` for constants and configuration knobs, for example `DB_RETRY_DELAYS_MS` in `src/hooks/useProjects.ts`, `DEFAULT_RESET_CATEGORIES` in `src/app/api/tags/reset-projects/route.ts`, `TAGS_CACHE_REVALIDATE_SECONDS` in `src/app/api/tags/route.ts`, and `N8N_WEBHOOK_URL` in `src/app/api/search/ai/route.ts`. +- Use descriptive typed local variables for parsed or normalized input, such as `validatedQuery` in `src/app/api/projects/route.ts`, `normalizedTagSlugs` in `src/hooks/useProjects.ts`, and `validationResult` in `src/app/api/webhook/signals/route.ts`. + +**Types:** +- Prefer `type` aliases for data shapes and Prisma payloads, for example `ProjectWithFlatTags` in `src/hooks/useProjects.ts`, `HomePageData` in `src/hooks/useHome.ts`, and `ResetResultItem` in `src/app/api/tags/reset-projects/route.ts`. +- Use `interface` for component props, for example `ProjectCardProps` in `src/components/project/ProjectCard.tsx`, `ProjectsPageClientProps` in `src/app/[locale]/projects/ProjectsPageClient.tsx`, and `SignalFeedClientProps` in `src/components/signals/SignalFeedClient.tsx`. + +## Code Style + +**Formatting:** +- Prettier is configured in `.prettierrc.json` for 2-space indentation, semicolons, double quotes, trailing commas set to `es5`, and `printWidth` 100. +- The repository is not uniformly formatted to that config. Files such as `src/lib/validations.ts`, `src/hooks/useProjects.ts`, and `src/app/api/tags/maintenance/route.ts` match the configured double-quote and semicolon style, while `src/app/api/projects/route.ts`, `src/app/api/search/ai/route.ts`, `src/lib/auth.ts`, and `src/components/project/ProjectCard.tsx` use single quotes and omit semicolons. +- For new files, follow `.prettierrc.json`. When editing existing files, preserve the file-local style unless the whole file is reformatted. + +**Linting:** +- ESLint extends `next/core-web-vitals` and `prettier` in `.eslintrc.json`. +- `console.warn` and `console.error` are allowed; other `console` calls are warned by `no-console`. +- Quality checks on 2026-04-18: `pnpm lint` passed with `✔ No ESLint warnings or errors`. + +## Import Organization + +**Order:** +1. Framework and platform imports first, for example `next/server`, `next/cache`, `zod`, `@prisma/client`, or `react`. +2. Internal alias imports from `@/`, for example `@/lib/prisma`, `@/hooks/useProjects`, and `@/lib/validations`. +3. Relative imports last, for example `./service` in `src/app/api/tags/maintenance/route.ts`. + +**Path Aliases:** +- Use the `@/*` alias from `tsconfig.json` and `vitest.config.ts` for internal imports. +- Prefer `@/` over deep relative paths across `src`, for example `@/lib/prisma` in `src/app/api/tags/route.ts` and `@/lib/tag-taxonomy` in `src/hooks/useProjects.ts`. + +## Component and Module Design + +**React Components:** +- Default-export only route-level pages and layouts, for example `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, and `src/app/layout.tsx`. +- Use named exports for reusable components, for example `ProjectCard` in `src/components/project/ProjectCard.tsx`, `HomeRankings` in `src/components/home/HomeRankings.tsx`, and `AnnouncementBar` in `src/components/layout/AnnouncementBar.tsx`. +- Mark interactive components with `'use client'`, as seen in `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/components/project/TagFilterPanel.tsx`, and `src/components/signals/SignalFeedClient.tsx`. +- Keep server components async and free of client hooks, as seen in `src/app/[locale]/projects/[id]/page.tsx`, `src/app/[locale]/signals/page.tsx`, and `src/components/project/ProjectSidebar.tsx`. +- Type component props explicitly with a local `interface ...Props`. + +**Service and Query Modules:** +- Centralize Prisma-backed read logic in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`. Despite the directory name, these modules act as query services for routes and server components. +- Keep route handlers thin where possible and delegate business rules to local services for mutations. The clearest example is `src/app/api/tags/maintenance/route.ts` delegating to `src/app/api/tags/maintenance/service.ts`. +- Reuse shared utility modules in `src/lib` for cross-cutting concerns: `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/prisma.ts`, `src/lib/signal-hotness.ts`, `src/lib/slug.ts`, and `src/lib/tag-taxonomy.ts`. + +**Exports:** +- Prefer named exports across shared modules. No barrel files were detected under `src` on 2026-04-18. + +## Validation + +**Schema Placement:** +- Put broadly shared Zod schemas in `src/lib/validations.ts`. Examples include `ProjectInputSchema`, `SignalWebhookPayloadSchema`, `SignalQuerySchema`, and `TagMaintenanceRequestSchema`. +- Define route-local Zod schemas only when the contract is tightly coupled to a single endpoint, as in `ProjectsQuerySchema` in `src/app/api/projects/route.ts` and `N8NSearchResponseSchema` in `src/app/api/search/ai/route.ts`. + +**Validation Flow:** +- Use `.safeParse()` when the route needs to return a custom `400` payload without exceptions, as in `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts`. +- Use `.parse()` when the route already has a `ZodError` catch branch, as in `src/app/api/projects/route.ts`, `src/app/api/search/ai/route.ts`, and `src/app/api/signals/route.ts`. +- Use `z.coerce` for query-string number parsing in shared schemas, as in `SignalQuerySchema` and `ProjectQuerySchema` in `src/lib/validations.ts`. +- Add cross-field validation with `.superRefine()` for multi-item or relation rules, as in `TagMergeSchema` and `TagMaintenanceRequestSchema` in `src/lib/validations.ts`. + +## Data Access + +**Prisma:** +- Use the singleton Prisma client from `src/lib/prisma.ts`. +- Define Prisma payload types close to the query layer with `Prisma.*GetPayload`, as in `ProjectWithTagsAndLinks` and `TagWithProjectCount` in `src/hooks/useProjects.ts`. +- Prefer explicit `include` and `select` clauses to control payload shape, as seen throughout `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, `src/app/api/signals/route.ts`, and `src/app/api/tags/reset-projects/route.ts`. +- Use `prisma.$transaction(...)` for write paths that change multiple tables, as in `src/app/api/tags/maintenance/route.ts` and `src/app/api/tags/reset-projects/route.ts`. + +**Caching and Fallbacks:** +- Wrap cacheable server reads with `unstable_cache`, using stable key arrays and `revalidate` windows, as in `src/app/api/tags/route.ts`, `src/hooks/useProjects.ts`, and `src/hooks/useHome.ts`. +- Route cached reads through `runWithCacheFallback` from `src/lib/cache.ts` so execution can fall back when `unstable_cache` is unavailable. +- Cache fetcher functions keyed by input when the function signature varies, as in `topTagsCache` and `fixedProjectTypeFilterCache` in `src/hooks/useProjects.ts`. + +**Retry and Degrade Patterns:** +- Use retry wrappers for transient DB issues on read paths. `withDbRetry` and `isTransientDbError` in `src/hooks/useProjects.ts` are the current pattern. +- Degrade to safe defaults on non-critical homepage and filter data instead of failing the whole page, as in `safeQuery` in `src/hooks/useHome.ts` and `console.error` fallback branches in `src/hooks/useProjects.ts`. + +## Error Handling + +**Patterns:** +- Wrap route handlers in `try/catch` and return JSON error payloads through `NextResponse.json`, as in `src/app/api/projects/route.ts`, `src/app/api/tags/route.ts`, `src/app/api/search/ai/route.ts`, and `src/app/api/webhook/signals/route.ts`. +- Return `400` for schema and cursor validation failures, `401` for invalid API keys, `409` for tag conflicts inside service code, and `500` for unexpected errors. +- Use domain-specific error classes when mutation services need to communicate status and details back to routes. `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts` is the established pattern. +- Include machine-readable `success`, `error`, `details`, and sometimes `message` fields in JSON responses. The exact shape varies by route and is not fully normalized. + +**Logging:** +- Use `console.error` for failures and `console.warn` for degraded or summary logging, for example `src/hooks/useHome.ts`, `src/hooks/useProjects.ts`, `src/app/api/signals/route.ts`, and `src/app/api/webhook/signals/route.ts`. +- Error-path tests currently allow log output to stderr, as verified by `pnpm test` on 2026-04-18 from `src/app/api/tags/route.test.ts`. + +## Comments + +**When to Comment:** +- Comments are sparse and usually explain non-obvious intent, numbered route steps, or bilingual product context. +- English and Chinese comments coexist. Examples include the numbered route comments in `src/app/api/tags/maintenance/route.ts`, Chinese comments in `src/hooks/useProjects.ts`, and mixed bilingual commentary in `src/components/project/ProjectCard.tsx`. +- Prefer comments only where intent is not obvious from code. + +**JSDoc/TSDoc:** +- JSDoc is uncommon. The clearest example is the security-sensitive note on `isValidApiKey` in `src/lib/auth.ts`. + +## Function Design + +**Size:** +- Read/query modules tolerate large files with many helpers. `src/hooks/useProjects.ts` is 602 lines, `src/hooks/useHome.ts` is 250 lines, and `src/app/api/signals/route.ts` is 355 lines. +- Keep complex logic split into local helpers inside the same file before extracting a new module. Current examples include `parseSlugList` in `src/app/api/projects/route.ts`, `parseSections` in `src/app/api/signals/route.ts`, and `collectValidationErrorsForProjectItem` in `src/app/api/tags/reset-projects/route.ts`. + +**Parameters:** +- Prefer a single typed options object for query functions, as in `getProjects` in `src/hooks/useProjects.ts`. +- Use small helper functions for normalization of query and payload input, such as `normalizePositiveInteger` in `src/hooks/useProjects.ts` and `normalizeSlug` in `src/app/api/tags/reset-projects/route.ts`. + +**Return Values:** +- Return plain serializable objects from server query helpers when the result is meant for pages or APIs, as in `getProjects`, `getHomePageData`, and `getProjectsByIds`. +- Flatten Prisma relation shapes before returning to callers when the UI expects direct lists, as in `getProjects`, `getProjectBySlug`, and `getProjectsByIds` in `src/hooks/useProjects.ts`. + +## Environment and Configuration + +**Environment Files:** +- `.env`, `.env.local`, and `.env.example` are present at repository root. Use `.env.example` as the naming reference; do not commit real secrets. + +**Observed Variables:** +- `.env.example` defines `DATABASE_URL`, `WEBHOOK_API_KEY`, `N8N_AI_SEARCH_WEBHOOK`, `NEXT_INTL_DEFAULT_LOCALE`, and `NEXT_INTL_SUPPORTED_LOCALES`. +- Source code reads `WEBHOOK_API_KEY` in `src/lib/auth.ts`, `N8N_AI_SEARCH_WEBHOOK` in `src/app/api/search/ai/route.ts`, `NEXT_PUBLIC_SITE_URL` in `src/app/robots.ts` and `src/app/sitemap.ts`, `VERCEL_ENV` in `src/app/layout.tsx`, and `NODE_ENV` in `src/lib/prisma.ts`. +- `NEXT_INTL_DEFAULT_LOCALE` and `NEXT_INTL_SUPPORTED_LOCALES` appear in `.env.example` but were not detected in runtime code on 2026-04-18. Locale behavior is hard-coded in `src/middleware.ts` and `src/i18n/request.ts`. + +**Conventions:** +- Fail fast at module load only for truly required integration config. `src/app/api/search/ai/route.ts` throws immediately if `N8N_AI_SEARCH_WEBHOOK` is unset. +- Use safe fallbacks for public metadata values, as in `src/app/robots.ts` and `src/app/sitemap.ts` defaulting `NEXT_PUBLIC_SITE_URL` to `https://agentpark.ai`. +- Keep deployment and framework config in root files: `next.config.js`, `tailwind.config.ts`, `postcss.config.mjs`, `tsconfig.json`, `.eslintrc.json`, `.prettierrc.json`, and `vitest.config.ts`. + +--- + +*Convention analysis: 2026-04-18* diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md new file mode 100644 index 0000000..d4be04e --- /dev/null +++ b/.planning/codebase/INTEGRATIONS.md @@ -0,0 +1,125 @@ +# External Integrations + +**Analysis Date:** 2026-04-18 + +## APIs & External Services + +**Workflow Automation / Search:** +- n8n webhook - AI search requests are forwarded from `src/app/api/search/ai/route.ts` to the URL in `process.env.N8N_AI_SEARCH_WEBHOOK`. + - SDK/Client: Native `fetch` in `src/app/api/search/ai/route.ts` + - Auth: `N8N_AI_SEARCH_WEBHOOK` + - Evidence: outbound `GET` request is constructed in `src/app/api/search/ai/route.ts`; `.env.example` provides the webhook variable name. + +**Vercel Runtime Telemetry:** +- Vercel Analytics - Client analytics are mounted in `src/app/VercelMetrics.tsx` and only rendered when `process.env.VERCEL_ENV === "production"` in `src/app/layout.tsx`. + - SDK/Client: `@vercel/analytics` + - Auth: Managed by Vercel runtime; no repo-managed token detected +- Vercel Speed Insights - Frontend performance sampling is mounted beside analytics in `src/app/VercelMetrics.tsx`. + - SDK/Client: `@vercel/speed-insights` + - Auth: Managed by Vercel runtime; no repo-managed token detected + +**Static Asset Providers:** +- Google Fonts / Material Icons - CSS imports in `src/app/globals.css` load Inter, Space Mono, and Material Icons from `fonts.googleapis.com`. + - SDK/Client: CSS `@import` + - Auth: None +- Shields.io - GitHub badge images are generated in `src/lib/github/badges.ts` and rendered through `next/image` in `src/components/project/ProjectCard.tsx` and `src/components/project/GitHubTextStatsCard.tsx`. + - SDK/Client: URL construction only + - Auth: None + - Evidence: `img.shields.io` is explicitly whitelisted in `next.config.js`. + +**Content / Link Surfaces:** +- GitHub - The app stores GitHub repository links on projects and builds GitHub badge and deep-link URLs in `src/lib/github/badges.ts`. + - SDK/Client: None detected + - Auth: None detected + - Note: GitHub API calls are not detected in current code; integration is via stored URLs and Shields.io images. + +## Data Storage + +**Databases:** +- PostgreSQL + - Connection: `DATABASE_URL` + - Client: Prisma via `@prisma/client` in `src/lib/prisma.ts` + - Schema: `prisma/schema.prisma` + - Migrations: `prisma/migrations/*/migration.sql` + - Usage: Queried from `src/hooks/useProjects.ts`, `src/app/api/projects/route.ts`, `src/app/api/tags/route.ts`, `src/app/api/signals/route.ts`, and webhook-style route handlers. + +**File Storage:** +- Local filesystem only + - Evidence: No S3, Blob, Cloudinary, or similar storage SDK is declared in `package.json` or imported under `src/**/*`. + +**Caching:** +- Next.js data cache via `unstable_cache` + - Service: Built-in framework cache, not a separate external service + - Implementation: `src/app/api/tags/route.ts` and `src/hooks/useProjects.ts` +- External cache service: None detected + - Evidence: No Redis, Memcached, or similar client package is declared in `package.json`. + +## Authentication & Identity + +**Auth Provider:** +- Custom shared-secret authentication for machine-to-machine routes + - Implementation: `src/lib/auth.ts` compares a provided API key against `process.env.WEBHOOK_API_KEY` using `crypto.timingSafeEqual`. + - Used by: + - `src/app/api/webhook/signals/route.ts` + - `src/app/api/tags/maintenance/route.ts` + - `src/app/api/tags/reset-projects/route.ts` +- End-user authentication: Not detected + - Evidence: No NextAuth, Clerk, Auth.js, Supabase Auth, OAuth, or session middleware is present in `package.json` or `src/**/*`. + +## Monitoring & Observability + +**Error Tracking:** +- None detected + - Evidence: No Sentry, Bugsnag, Datadog, or Rollbar package is declared in `package.json`. + +**Logs:** +- Server logging uses `console.error` and `console.warn` in route handlers such as `src/app/api/search/ai/route.ts`, `src/app/api/webhook/signals/route.ts`, and `src/app/api/tags/route.ts`. +- Frontend telemetry uses Vercel Analytics and Speed Insights in `src/app/VercelMetrics.tsx`. + +## CI/CD & Deployment + +**Hosting:** +- Vercel + - Evidence: `vercel.json` sets `"framework": "nextjs"`, `buildCommand`, `installCommand`, and region `hkg1`. + +**CI Pipeline:** +- None detected in repo + - Evidence: No `.github/workflows/*`, GitLab CI file, CircleCI config, or other CI config file is present at repo root. + +## Environment Configuration + +**Required env vars:** +- `DATABASE_URL` - Required by Prisma datasource in `prisma/schema.prisma`. +- `WEBHOOK_API_KEY` - Required for authenticated webhook-style POST endpoints in `src/lib/auth.ts`. +- `N8N_AI_SEARCH_WEBHOOK` - Required by the outbound AI search proxy in `src/app/api/search/ai/route.ts`. +- `NEXT_PUBLIC_SITE_URL` - Optional but used to generate canonical URLs in `src/app/robots.ts` and `src/app/sitemap.ts`; code falls back to `https://agentpark.ai`. +- `VERCEL_ENV` - Read in `src/app/layout.tsx` to gate Vercel telemetry; expected when deployed on Vercel. +- `.env.example` also includes `NEXT_INTL_DEFAULT_LOCALE` and `NEXT_INTL_SUPPORTED_LOCALES`, but current locale middleware and request config rely on hard-coded values in `src/middleware.ts` and `src/i18n/request.ts`. + +**Secrets location:** +- Local development secrets: `.env.local` and `.env` files are present in repo root; contents were not read. +- Production secrets: Vercel environment variables are implied by `vercel.json` and `process.env.*` usage, but no separate secret manager config is committed. + +## Webhooks & Callbacks + +**Incoming:** +- `POST /api/webhook/signals` in `src/app/api/webhook/signals/route.ts` + - Purpose: Ingests batched signal payloads into PostgreSQL via Prisma. + - Auth: Shared secret in request body validated against `WEBHOOK_API_KEY`. +- `POST /api/tags/maintenance` in `src/app/api/tags/maintenance/route.ts` + - Purpose: Applies tag updates/merges and revalidates project pages. + - Auth: Shared secret in request body validated against `WEBHOOK_API_KEY`. +- `POST /api/tags/reset-projects` in `src/app/api/tags/reset-projects/route.ts` + - Purpose: Resets project tag assignments by category and optionally revalidates pages. + - Auth: Shared secret in request body validated against `WEBHOOK_API_KEY`. + +**Outgoing:** +- n8n AI search webhook + - Source: `src/app/api/search/ai/route.ts` + - Method: `GET` + - Target: URL from `N8N_AI_SEARCH_WEBHOOK` +- Search engine / badge asset requests from the browser are not hard-coded beyond standard page navigation, Google Fonts CSS, Material Icons CSS, and Shields.io image URLs. + +--- + +*Integration audit: 2026-04-18* diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md new file mode 100644 index 0000000..02ec826 --- /dev/null +++ b/.planning/codebase/STACK.md @@ -0,0 +1,97 @@ +# Technology Stack + +**Analysis Date:** 2026-04-18 + +## Languages + +**Primary:** +- TypeScript 5.x - Application code, API routes, hooks, Prisma access, and most tooling live in `src/**/*.ts`, `src/**/*.tsx`, `prisma/seed.ts`, `tailwind.config.ts`, and `vitest.config.ts`; the compiler is declared in `package.json`. + +**Secondary:** +- JavaScript - Next.js and PostCSS config live in `next.config.js` and `postcss.config.mjs`. +- CSS - Global styling and external font/icon imports live in `src/app/globals.css`. +- JSON - Locale message catalogs live in `src/messages/en.json` and `src/messages/zh.json`; repo config also uses `.eslintrc.json` and `.prettierrc.json`. +- Prisma schema DSL - Database schema and datasource definitions live in `prisma/schema.prisma`. + +## Runtime + +**Environment:** +- Node.js - Repo runtime is Node-based because scripts use `next`, `vitest`, `prisma`, and `ts-node` from `package.json`. +- Version pinning: Not detected in repo. No `.nvmrc`, `.node-version`, or `.tool-versions` file is present. +- Local tool version observed in this workspace: Node.js `v22.21.1`. + +**Package Manager:** +- `pnpm` - Project commands, lockfile, and Vercel install/build configuration use `pnpm` in `package.json`, `pnpm-lock.yaml`, and `vercel.json`. +- Local tool version observed in this workspace: `pnpm 10.27.0`. +- Lockfile: present in `pnpm-lock.yaml`. + +## Frameworks + +**Core:** +- Next.js `15.1.11` - App Router web framework for pages, layouts, metadata, and route handlers in `src/app/**/*`; version declared in `package.json`. +- React `19.0.0` and `react-dom` `19.0.0` - UI runtime for components in `src/components/**/*` and route segments in `src/app/**/*`; versions declared in `package.json`. +- `next-intl` `4.0.2` - Locale routing and message loading via `next.config.js`, `src/middleware.ts`, `src/i18n/request.ts`, and `src/app/[locale]/layout.tsx`. +- Prisma `6.1.0` / `@prisma/client` `6.1.0` - ORM and generated client used in `src/lib/prisma.ts`, `src/hooks/useProjects.ts`, `src/app/api/**/*`, `prisma/schema.prisma`, and `prisma/seed.ts`. +- Zod `3.24.1` - Request and payload validation in `src/lib/validations.ts` and multiple route handlers under `src/app/api/**/*`. + +**Testing:** +- Vitest `2.1.8` - Unit-style test runner configured in `vitest.config.ts` and used by files such as `src/lib/auth.test.ts` and `src/app/api/tags/route.test.ts`. + +**Build/Dev:** +- Tailwind CSS `3.4.17` - Utility-first styling configured in `tailwind.config.ts` and consumed by `src/app/globals.css` and component classes across `src/components/**/*`. +- PostCSS `8.x` with `autoprefixer` `10.4.20` - CSS processing configured in `postcss.config.mjs`. +- ESLint `9.x` with `eslint-config-next` `15.1.11` and `eslint-config-prettier` `9.1.0` - Linting configured in `.eslintrc.json`. +- Prettier `3.4.2` - Formatting configured in `.prettierrc.json`. +- `ts-node` `10.9.2` - TypeScript execution for seeding via the `prisma.seed` command in `package.json`. +- `tailwindcss-animate` `1.0.7` - Tailwind plugin loaded in `tailwind.config.ts`. + +## Key Dependencies + +**Critical:** +- `next` `15.1.11` - Main application framework; routes, layouts, metadata, and route handlers all depend on files under `src/app/**/*`. +- `react` `19.0.0` / `react-dom` `19.0.0` - Required by all React components in `src/components/**/*` and page/layout files in `src/app/**/*`. +- `@prisma/client` `6.1.0` - Database access layer instantiated in `src/lib/prisma.ts` and used heavily in `src/hooks/useProjects.ts`, `src/app/api/projects/route.ts`, `src/app/api/signals/route.ts`, and related files. +- `prisma` `6.1.0` - Schema and migration tool backing `prisma/schema.prisma` and `prisma/migrations/*/migration.sql`. +- `next-intl` `4.0.2` - Locale middleware and message loading depend on `src/middleware.ts`, `src/i18n/request.ts`, and `src/messages/*.json`. +- `zod` `3.24.1` - Input validation for search, tag maintenance, and webhook payloads in `src/app/api/search/ai/route.ts`, `src/app/api/tags/maintenance/route.ts`, and `src/app/api/webhook/signals/route.ts`. + +**Infrastructure:** +- `@vercel/analytics` `1.6.1` and `@vercel/speed-insights` `1.3.1` - Vercel client telemetry mounted in `src/app/VercelMetrics.tsx` and conditionally included in `src/app/layout.tsx`. +- `react-markdown` `10.1.0`, `remark-gfm` `4.0.1`, and `rehype-sanitize` `6.0.0` - Markdown rendering stack used by `src/components/project/MarkdownContent.tsx`. +- `lucide-react` `0.468.0` - Icon set optimized through `experimental.optimizePackageImports` in `next.config.js`. + +## Configuration + +**Environment:** +- Template env vars are documented in `.env.example`. +- Real runtime env files exist as `.env` and `.env.local`; contents were not read. +- Code-level env usage is limited to: + - `DATABASE_URL` in `prisma/schema.prisma` + - `WEBHOOK_API_KEY` in `src/lib/auth.ts` + - `N8N_AI_SEARCH_WEBHOOK` in `src/app/api/search/ai/route.ts` + - `NEXT_PUBLIC_SITE_URL` in `src/app/robots.ts` and `src/app/sitemap.ts` + - `VERCEL_ENV` in `src/app/layout.tsx` +- `.env.example` also declares `NEXT_INTL_DEFAULT_LOCALE` and `NEXT_INTL_SUPPORTED_LOCALES`, but locale handling in current code is hard-coded in `src/i18n/request.ts` and `src/middleware.ts`. + +**Build:** +- `next.config.js` wires `next-intl`, remote image hosts, and `optimizePackageImports`. +- `tsconfig.json` enables strict TypeScript, `noUncheckedIndexedAccess`, `noImplicitReturns`, and the `@/*` path alias. +- `tailwind.config.ts` and `postcss.config.mjs` define the styling pipeline. +- `vercel.json` defines deployment-time build/install commands, Next.js framework selection, region `hkg1`, and disables Git-triggered deployments. +- `package.json` scripts expose `dev`, `build`, `start`, `lint`, `test`, and Prisma seeding. + +## Platform Requirements + +**Development:** +- Node.js and `pnpm` are required to run `package.json` scripts. +- PostgreSQL is required because `prisma/schema.prisma` uses the `postgresql` provider and `DATABASE_URL`. +- Prisma client generation is required before production builds; `vercel.json` explicitly runs `pnpm prisma generate && pnpm build`. + +**Production:** +- Vercel is the explicit deployment target indicated by `vercel.json` and the Vercel-specific telemetry components in `src/app/VercelMetrics.tsx`. +- The app expects Vercel environment semantics for telemetry gating via `process.env.VERCEL_ENV` in `src/app/layout.tsx`. +- Server runtime storage is PostgreSQL via Prisma; no alternate production datastore is configured in repo. + +--- + +*Stack analysis: 2026-04-18* diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md new file mode 100644 index 0000000..3ea9ab3 --- /dev/null +++ b/.planning/codebase/STRUCTURE.md @@ -0,0 +1,224 @@ +# Codebase Structure + +**Analysis Date:** 2026-04-18 + +## Directory Layout + +```text +agent_park/ +├── prisma/ # Prisma schema, migrations, and seed script +├── src/app/ # Next.js App Router entry points, layouts, API routes, global assets +├── src/components/ # Reusable UI grouped by feature area +├── src/hooks/ # Server-side query/read-model modules +├── src/i18n/ # next-intl request configuration +├── src/lib/ # Shared utilities, validation, Prisma client, taxonomy logic +├── src/messages/ # Locale message JSON files +├── .eslintrc.json # ESLint rules +├── .prettierrc.json # Prettier rules +├── next.config.js # Next.js config with next-intl plugin +├── tailwind.config.ts # Tailwind theme and content scan config +├── tsconfig.json # TypeScript config and `@/*` path alias +├── vercel.json # Vercel build/deploy config +└── vitest.config.ts # Vitest config for `src/**/*.test.ts` +``` + +## Directory Purposes + +**`prisma/`:** +- Purpose: Own the database contract and seed path. +- Contains: `prisma/schema.prisma`, migration directories under `prisma/migrations/*`, and `prisma/seed.ts`. +- Key files: `prisma/schema.prisma`, `prisma/seed.ts` + +**`src/app/`:** +- Purpose: Hold all App Router entry points, route-local components, API handlers, and app-wide assets. +- Contains: `layout.tsx`, `page.tsx`, `route.ts`, route-local client components, `globals.css`, `robots.ts`, `sitemap.ts`, and `icon.svg`. +- Key files: `src/app/layout.tsx`, `src/app/[locale]/layout.tsx`, `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, `src/app/api/projects/route.ts`, `src/app/api/signals/route.ts` + +**`src/app/[locale]/`:** +- Purpose: Group locale-prefixed user-facing routes. +- Contains: localized pages such as `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, `src/app/[locale]/projects/[id]/page.tsx`, `src/app/[locale]/signals/page.tsx`, `src/app/[locale]/about/page.tsx`, and fallbacks like `src/app/[locale]/not-found.tsx`. +- Key files: `src/app/[locale]/layout.tsx`, `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx` + +**`src/app/api/`:** +- Purpose: Group JSON endpoints and webhook handlers by resource. +- Contains: route handlers and a route-local service module at `src/app/api/tags/maintenance/service.ts`. +- Key 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`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, `src/app/api/webhook/signals/route.ts` + +**`src/components/`:** +- Purpose: Store reusable UI by domain area, not by primitive type. +- Contains: feature folders `home`, `layout`, `locale`, `project`, `search`, and `signals`. +- Key files: `src/components/project/ProjectList.tsx`, `src/components/project/ProjectDetail.tsx`, `src/components/search/AISearchBar.tsx`, `src/components/signals/SignalFeedClient.tsx` + +**`src/hooks/`:** +- Purpose: Hold server-side data-fetching and read-model builders. +- Contains: `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`. +- Key files: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts` + +**`src/i18n/`:** +- Purpose: Configure `next-intl` request handling. +- Contains: `src/i18n/request.ts`. +- Key files: `src/i18n/request.ts` + +**`src/lib/`:** +- Purpose: Hold cross-cutting domain and infrastructure utilities. +- Contains: auth, cache helpers, Prisma client, GitHub link helpers, tag taxonomy, slug generation, hotness scoring, and validation schemas. +- Key files: `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/prisma.ts`, `src/lib/tag-taxonomy.ts`, `src/lib/signal-hotness.ts`, `src/lib/validations.ts`, `src/lib/github/badges.ts` + +**`src/messages/`:** +- Purpose: Store locale dictionaries loaded by `next-intl`. +- Contains: `src/messages/en.json` and `src/messages/zh.json`. +- Key files: `src/messages/en.json`, `src/messages/zh.json` + +## Key File Locations + +**Entry Points:** +- `src/app/layout.tsx`: Root HTML/body shell and conditional analytics. +- `src/app/[locale]/layout.tsx`: Locale-aware site shell, navigation, footer, and translation provider. +- `src/app/[locale]/page.tsx`: Localized home page. +- `src/app/[locale]/projects/page.tsx`: Projects search/browse page. +- `src/app/[locale]/projects/[id]/page.tsx`: Project detail page. +- `src/app/[locale]/signals/page.tsx`: Signals feed page shell. +- `src/app/[locale]/about/page.tsx`: About page. +- `src/middleware.ts`: Locale routing middleware. + +**API Endpoints:** +- `src/app/api/projects/route.ts`: Paginated projects list API. +- `src/app/api/projects/[slug]/route.ts`: Single-project JSON endpoint. +- `src/app/api/search/ai/route.ts`: AI search proxy and hydrator. +- `src/app/api/signals/route.ts`: Cursor-paginated signals feed API. +- `src/app/api/tags/route.ts`: Tags list API. +- `src/app/api/tags/maintenance/route.ts`: Authenticated tag update/merge API. +- `src/app/api/tags/reset-projects/route.ts`: Authenticated project-tag replacement API. +- `src/app/api/webhook/signals/route.ts`: Authenticated signal ingestion webhook. + +**Configuration:** +- `next.config.js`: Wraps Next config with the `next-intl` plugin and remote image rules. +- `tailwind.config.ts`: Tailwind content paths and theme extension. +- `tsconfig.json`: Strict TypeScript settings and the `@/*` alias. +- `vitest.config.ts`: Node test environment and `src/**/*.test.ts` inclusion. +- `vercel.json`: Build/install commands and region targeting. +- `.eslintrc.json`: Lint rules. +- `.prettierrc.json`: Formatting rules. +- `.env.example`: Template environment file. `.env` and `.env.local` are present in the repository root but were not inspected. + +**Core Logic:** +- `src/hooks/useProjects.ts`: Project list/detail queries, filter normalization, tag grouping, caching, and retry logic. +- `src/hooks/useHome.ts`: Home-page aggregate data builder. +- `src/lib/tag-taxonomy.ts`: Tag category metadata and inference rules. +- `src/lib/validations.ts`: Zod schemas for API inputs and domain payloads. +- `src/app/api/tags/maintenance/service.ts`: Transactional tag maintenance logic. +- `src/lib/signal-hotness.ts`: Signal hot-score computation and schema capability detection. + +**Testing:** +- `src/lib/auth.test.ts` +- `src/lib/validations.test.ts` +- `src/lib/validations.tag-maintenance.test.ts` +- `src/app/api/tags/route.test.ts` +- `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` +- `e2e/`: Not present in the inspected repository tree. + +## Naming Conventions + +**Files:** +- Use Next.js route conventions inside `src/app`, for example `page.tsx`, `layout.tsx`, `route.ts`, `not-found.tsx`, `robots.ts`, and `sitemap.ts`. +- Use `PascalCase.tsx` for reusable components, for example `src/components/project/ProjectCard.tsx`, `src/components/project/ProjectSidebar.tsx`, and `src/components/home/HomeOverviewStats.tsx`. +- Keep route-specific helper components adjacent to the route they support, for example `src/app/[locale]/projects/ProjectsPageClient.tsx` and `src/app/[locale]/projects/ProjectsResultsClient.tsx`. +- Use lower-case or kebab-case utility filenames in `src/lib`, for example `src/lib/prisma.ts`, `src/lib/cache.ts`, `src/lib/tag-taxonomy.ts`, and `src/lib/signal-hotness.ts`. +- Keep test files adjacent to the code they cover using `*.test.ts`, for example `src/app/api/tags/maintenance/service.test.ts`. + +**Directories:** +- Group reusable UI by product area under `src/components`, for example `src/components/project` and `src/components/search`. +- Group route files by URL shape under `src/app`, for example `src/app/[locale]/projects/[id]` and `src/app/api/projects/[slug]`. +- Keep infrastructure and domain utilities flat under `src/lib` instead of nesting many sublayers. The only nested utility folder detected is `src/lib/github/`. + +## Where to Add New Code + +**New User-Facing Page or Route Segment:** +- Primary code: add a new route under `src/app/[locale]/...` using Next conventions, for example `src/app/[locale]/new-section/page.tsx`. +- Shared shell changes: modify `src/app/[locale]/layout.tsx` only if the new route needs site-wide navigation or footer changes. +- Metadata/SEO for the route: colocate `generateMetadata` in the route file, following `src/app/[locale]/about/page.tsx` and `src/app/[locale]/signals/page.tsx`. + +**New API Endpoint:** +- Primary code: add `route.ts` under `src/app/api//`. +- Route-local helpers or service logic: colocate them next to the route, following `src/app/api/tags/maintenance/service.ts`. +- Validation: add or extend schemas in `src/lib/validations.ts` unless the validation is tightly route-local and one-off. + +**New Reusable UI Component:** +- Implementation: place it in the matching feature folder under `src/components/`. +- Examples: + - Project-related UI goes in `src/components/project/`. + - Search UI goes in `src/components/search/`. + - Home-page modules go in `src/components/home/`. +- If a component is only used by one route and owns that route’s browser state, colocate it under the route folder in `src/app/[locale]/...`, following `src/app/[locale]/projects/ProjectsPageClient.tsx`. + +**New Data Query or Read Model:** +- Shared project/home reads: extend `src/hooks/useProjects.ts` or `src/hooks/useHome.ts`. +- New domain-specific reads: add a new module under `src/hooks/` if the logic becomes large enough to stand alone. +- Use `src/lib/prisma.ts` for Prisma access instead of creating new Prisma clients. + +**New Shared Utility or Domain Rule:** +- Shared helpers: add to `src/lib/`. +- Taxonomy/tag rules: extend `src/lib/tag-taxonomy.ts`. +- Request validation: extend `src/lib/validations.ts`. +- Authentication helpers for internal APIs: extend `src/lib/auth.ts`. + +**New Locale Strings:** +- Add keys to both `src/messages/en.json` and `src/messages/zh.json`. +- Resolve them through `next-intl` in route or component code, following `src/app/[locale]/layout.tsx` and `src/components/project/ProjectList.tsx`. + +**New Database Model or Field:** +- Schema: update `prisma/schema.prisma`. +- Migration: create a new directory under `prisma/migrations/`. +- Seed updates: modify `prisma/seed.ts` only if the new data is required for local bootstrapping. + +**New Tests:** +- API and utility tests: colocate with the source file as `*.test.ts`. +- There is no current `e2e/` directory in the inspected tree, so introducing end-to-end tests will require creating that top-level directory explicitly. + +## Special Directories + +**`src/app/[locale]/projects/`:** +- Purpose: Contains one route page plus its route-local client islands. +- Generated: No +- Committed: Yes + +**`src/app/api/tags/maintenance/`:** +- Purpose: Contains a route handler, a service module, and colocated tests for the tag-maintenance feature. +- Generated: No +- Committed: Yes + +**`prisma/migrations/`:** +- Purpose: Stores schema migration history. +- Generated: Yes +- Committed: Yes + +**`.next/`:** +- Purpose: Next.js build output and development cache. +- Generated: Yes +- Committed: No + +**`.planning/codebase/`:** +- Purpose: Stores generated repository mapping documents such as this file. +- Generated: Yes +- Committed: Uncertain from inspected source files alone. + +## Placement Rules + +**Use `src/app` for route ownership:** +- Put code in `src/app` only when it directly maps to a URL, metadata document, or route-local UI state. + +**Use `src/components` for reuse across routes:** +- Promote a route-local component into `src/components` only when another route needs it or it becomes generic enough to stand alone. + +**Treat `src/hooks` as a server query layer, not browser hooks:** +- The existing `useProjects.ts` and `useHome.ts` modules are imported by server components and API handlers. Follow that pattern when adding read models there. + +**Keep Prisma out of most UI files:** +- UI code typically consumes data returned by `src/hooks/*` or JSON from `src/app/api/*`. +- Direct Prisma calls are concentrated in `src/hooks/*`, `src/app/api/*`, and `src/lib/prisma.ts`. + +--- + +*Structure analysis: 2026-04-18* diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md new file mode 100644 index 0000000..4cafb95 --- /dev/null +++ b/.planning/codebase/TESTING.md @@ -0,0 +1,267 @@ +# Testing Patterns + +**Analysis Date:** 2026-04-18 + +## Test Framework + +**Runner:** +- `vitest` via `vitest.config.ts`. +- `package.json` declares `vitest` in `devDependencies` and exposes `pnpm test`. +- Verified on 2026-04-18: `pnpm test` executed with Vitest `v2.1.9` and passed all current test files. + +**Assertion Library:** +- Vitest built-ins: `describe`, `it`, `expect`, `beforeEach`, and `vi`, as seen in `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`. + +**Config:** +- `vitest.config.ts` sets `environment: "node"`, aliases `@` to `./src`, disables watch mode, and includes only `src/**/*.test.ts`. +- No separate setup file, coverage config, browser environment, or integration test project was detected in `vitest.config.ts`. + +**Run Commands:** +```bash +pnpm test # Run all configured Vitest suites +pnpm lint # Run ESLint quality checks +pnpm build # Run production build, lint, and type validation +``` + +## Test File Organization + +**Location:** +- Tests are co-located beside the source they exercise. +- Library tests live next to utilities, for example `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, and `src/lib/validations.tag-maintenance.test.ts`. +- API tests live next to route or service modules, for example `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, `src/app/api/tags/maintenance/route.test.ts`, and `src/app/api/tags/maintenance/service.test.ts`. + +**Naming:** +- Use `*.test.ts`. No `*.spec.ts` files were detected under `src` on 2026-04-18. +- The include pattern in `vitest.config.ts` means `*.test.tsx` and files outside `src` are not picked up by default. + +**Structure:** +```text +src/ + lib/ + auth.ts + auth.test.ts + app/api/tags/ + route.ts + route.test.ts + maintenance/ + route.ts + route.test.ts + service.ts + service.test.ts +``` + +## Test Structure + +**Suite Organization:** +```typescript +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { NextRequest } from "next/server"; +import { POST } from "./route"; + +const { transactionMock, revalidatePathMock, txMock } = vi.hoisted(() => { + // create hoisted mocks here +}); + +vi.mock("@/lib/prisma", () => ({ prisma: { $transaction: transactionMock } })); +vi.mock("next/cache", () => ({ revalidatePath: revalidatePathMock })); + +function buildRequest(body: unknown): NextRequest { + return new NextRequest("http://localhost:3000/api/tags/maintenance", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); +} + +describe("POST /api/tags/maintenance", () => { + beforeEach(() => { + process.env.WEBHOOK_API_KEY = "k".repeat(32); + vi.clearAllMocks(); + }); + + it("returns 401 for wrong API key", async () => { + const response = await POST(buildRequest({ apiKey: "a".repeat(32), updates: [], merges: [] })); + expect(response.status).toBe(401); + }); +}); +``` + +**Patterns:** +- Use a top-level `describe(...)` per module or endpoint, with test names phrased as behavior statements. +- Reset spies and mock state in `beforeEach`, as in `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`. +- For API routes, call the exported `GET` or `POST` function directly and assert on both `response.status` and `await response.json()`. +- For pure helpers and schemas, call the function directly and assert return values or thrown errors, as in `src/lib/auth.test.ts` and `src/lib/validations.test.ts`. + +## Mocking + +**Framework:** +- Vitest mocking via `vi.mock`, `vi.fn`, `vi.hoisted`, `mockResolvedValue`, and `mockRejectedValue`. + +**Patterns:** +```typescript +const { findManyMock } = vi.hoisted(() => ({ + findManyMock: vi.fn(), +})); + +vi.mock("@/lib/prisma", () => ({ + prisma: { + tag: { + findMany: findManyMock, + }, + }, +})); + +findManyMock.mockResolvedValue([ + { + id: "tag-1", + name: "机器学习", + slug: "machine-learning", + _count: { projects: 4 }, + }, +]); +``` + +```typescript +vi.mock("next/cache", () => ({ + revalidatePath: revalidatePathMock, +})); +``` + +**What to Mock:** +- Mock Prisma client calls for route and service tests instead of hitting a real database. This is the established pattern in `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`. +- Mock Next.js side effects such as `revalidatePath` when testing mutation endpoints, as in `src/app/api/tags/reset-projects/route.test.ts` and `src/app/api/tags/maintenance/route.test.ts`. +- Set environment variables inline per suite when auth behavior depends on them, as in `process.env.WEBHOOK_API_KEY` usage in `src/app/api/tags/reset-projects/route.test.ts` and `src/app/api/tags/maintenance/route.test.ts`. + +**What NOT to Mock:** +- Do not mock pure validation or auth utilities when they can be tested directly. `src/lib/auth.test.ts` and `src/lib/validations.tag-maintenance.test.ts` exercise real implementation logic without mocks. +- There is no current pattern for browser, React component, or DOM mocking because no component tests are checked in. + +## Fixtures and Helpers + +**Test Data:** +```typescript +function buildValidPayload(apiKey: string) { + return { + apiKey, + projects: [ + { + projectSlug: "project-one", + selectedTagSlugsByCategory: { + FIXED_PROJECT_TYPE: ["agent-tooling"], + TECH_STACK: ["typescript"], + AI_PARADIGM: ["ai-agents"], + PRODUCT_FORM: ["web-application"], + DOMAIN_SCENARIO: ["code-dev"], + }, + }, + ], + }; +} +``` + +```typescript +const baseProjectInput = { + name: "Agent Park", + description: "A curated list of practical AI agent tools.", + tags: [{ name: "ai-agent" }], + links: [{ type: "GITHUB" as const, url: "https://github.com/example/repo" }], +}; +``` + +**Location:** +- Builders and fixtures are defined inline in each test file. No shared `test-utils`, factory module, or fixture directory was detected under `src` or repository root. +- Reuse small local helpers such as `buildRequest`, `buildValidPayload`, `createTxMock`, and `baseProjectInput` instead of creating cross-suite abstractions. + +## Coverage + +**Requirements:** +- No coverage thresholds or coverage command are configured in `package.json` or `vitest.config.ts`. +- No CI coverage upload or report configuration was detected. + +**View Coverage:** +```bash +Not configured +``` + +## Test Types + +**Unit Tests:** +- Pure utility tests cover security and schema logic in `src/lib/auth.test.ts`, `src/lib/validations.test.ts`, and `src/lib/validations.tag-maintenance.test.ts`. +- Service-level unit tests cover tag maintenance merge behavior in `src/app/api/tags/maintenance/service.test.ts`. + +**Route-Level Tests:** +- Route handler tests call Next.js App Router handlers directly with mocked dependencies in `src/app/api/tags/route.test.ts`, `src/app/api/tags/reset-projects/route.test.ts`, and `src/app/api/tags/maintenance/route.test.ts`. +- These are closer to isolated handler tests than full integration tests because Prisma and cache modules are mocked. + +**Integration Tests:** +- Not detected. No test currently exercises a real Prisma client, live database, or full Next.js server. + +**E2E Tests:** +- Not detected in the repository. No `e2e/` directory and no `playwright.config.*` file were found on 2026-04-18. +- Repository guidance mentions `pnpm test:e2e`, but `package.json` does not define that script. Treat E2E support as undocumented or not yet checked in. + +## Common Patterns + +**Async Testing:** +```typescript +const response = await POST(buildRequest(payload)); +const json = await response.json(); + +expect(response.status).toBe(200); +expect(json.success).toBe(true); +``` + +**Error Testing:** +```typescript +findManyMock.mockRejectedValue(new Error("db unavailable")); + +const response = await GET(); +const json = await response.json(); + +expect(response.status).toBe(500); +expect(json.error).toBe("Internal server error"); +expect(json.details).toContain("db unavailable"); +``` + +```typescript +await expect( + executeTagMaintenance(tx as Parameters[0], { + updates: [{ tagId: "missing-tag", nameEn: "Missing" }], + merges: [], + }) +).rejects.toMatchObject({ + status: 400, + message: "Validation error", +}); +``` + +## Current Gaps + +**Untested Server Modules:** +- No tests were detected for `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`, or `src/app/api/webhook/signals/route.ts`. +- No tests were detected for `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, `src/lib/cache.ts`, `src/lib/signal-hotness.ts`, `src/lib/tag-taxonomy.ts`, or `src/lib/github/badges.ts`. + +**Untested UI:** +- No component tests were detected for large client components such as `src/app/[locale]/projects/ProjectsResultsClient.tsx` (784 lines), `src/components/project/TagFilterPanel.tsx` (698 lines), or `src/components/signals/SignalFeedClient.tsx` (603 lines). +- No tests were detected for route pages under `src/app/[locale]`. + +**Test Infrastructure Gaps:** +- No shared test helpers, factories, or fixture libraries are present. +- No browser or DOM test environment is configured. +- No E2E harness or Playwright configuration is present. +- No CI workflow files were detected under `.github/`. + +## Verification Signals + +**Current Quality Checks:** +- `pnpm test` passed on 2026-04-18 with 7 test files and 24 tests passing. +- `pnpm lint` passed on 2026-04-18 with no warnings or errors. +- `pnpm build` passed on 2026-04-18. The build completed static generation and route analysis successfully. + +**Observed Test Output Notes:** +- `pnpm test` prints expected stderr from the error-path test in `src/app/api/tags/route.test.ts` because `src/app/api/tags/route.ts` logs with `console.error`. +- Vitest emitted a Vite deprecation notice about the CJS Node API during the run. This is a tooling signal, not a failing test. + +--- + +*Testing analysis: 2026-04-18*