Scope: Each app/package owns its own src/types/ (flat files; match existing names such as domain.ts, ui-chrome-theme.ts). No repo-root types/.
Zod-first domain: Define schemas in the owning module (schemas/, lib/validations/, etc.); export application types with z.infer<typeof Schema>. Re-export from src/types/ or src/types/index.ts when multiple features import the same shape. Prefer import type { Page } from '@/types' (CMS) or import type { Course } from '@/types/domain' (LMS) over importing validation modules from UI unless the file owns parsing.
Feature-local types: A types.ts beside a feature folder is allowed when only that feature uses them; promote to src/types/ when a second unrelated module imports them.
Component props: Every component exposes an explicit FooProps type (same file or adjacent). Move to src/types/ only when reused outside the component tree.
Packages: Cross-CMS/LMS wire shapes → @open-learning-hub/widget-wire-schemas; renderer adapters → widget-renderers; shared UI → @open-learning-hub/ui. Do not copy package types into app types/.
Database: Table shapes stay in src/db/types.ts (Kysely); do not duplicate row types in UI types/ unless widely needed.
Do not hand-duplicate Zod-inferred domain interfaces in types/; re-export or infer from the schema module.
Favor React Server Components (RSC) where possible.
Minimize 'use client' directives.
When using Radix asChild with next-intl Link, keep the composition in a client module if the primitive itself is "use client" (see packages/ui/AGENTS.md section "asChild across RSC boundary") to avoid Slot/cloneElement failures across the RSC boundary.
Performance optimizations:
add generateStaticParams: fetch all published course slugs only.
generateMetadata: set title/description/OG image from published courses only.
revalidate = 60 to all relevant pages.
Add ISR: Await params in dynamic routes (you already updated [slug] and nested [entrySlug]).
Wire up Portable Text rendering for content using @portabletext/react.
Use shadcn for the UI Components.
Static Generation Security:
Implement proper access control in both static and dynamic routes.
Server Components: Prefer over client components.
Props Interface: Always define prop types.
Component naming: PascalCase exports for React components (see 031-file-naming.mdc for kebab-case filenames).
File Organization: One component per file.
Image Optimization:
Use Next.js Image with proper sizing.
Next.js Image component used throughout.
Google profile images properly configured.
Code Splitting:
Dynamic imports for heavy components.
Authentication code loads only when needed.
Admin components lazy-loaded.
Database access via Kysely is server-only — never import it from a client component.
Input Validation: Validate user inputs (Zod schemas at every API and server-action boundary).
Data listing consistency: Follow 055-data-listing-patterns.mdc for table-first rendering, single-search default UX, URL-driven server filtering, and shared pagination.
Co-locate server actions with the route segment that owns them, using actions.ts or _actions.ts under the relevant src/app/ segment.
Shared cross-route helpers live in src/lib/ or feature-specific utility modules and are called by the co-located action; client components should not invoke shared persistence helpers directly.
Keep action inputs Zod-validated at the boundary and re-check authorization inside the action, even when the surrounding route already performed a guard check.