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

16 KiB

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 /<locale>/*.
  • 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 /<locale>/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 /<locale>/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 /<locale>/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