--- phase: 03-module-system-domaincheck plan: 03 subsystem: module-ui-loader tags: [lazy-loading, dynamic-imports, next-dynamic, category-page, module-cards] dependency_graph: requires: - module-registry-service - domaincheck-frontend-page provides: - module-loader-registry - category-page - expanded-module-view - api-client-utils affects: [sidebar-module-links, module-marketplace] tech_stack: added: [] patterns: [next-dynamic-ssr-false, slug-whitelist-registry, category-grid-layout] key_files: created: - apps/web/src/lib/module-loader.ts - apps/web/src/lib/api.ts - apps/web/src/app/(portal)/modules/[category]/page.tsx - apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx - apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx modified: - apps/web/src/messages/de.json - apps/web/src/messages/en.json key_decisions: - "MODULE_REGISTRY whitelist pattern: only slugs explicitly listed can trigger dynamic imports (T-03-09)" - "Client components for all pages to support next/dynamic with ssr:false and client-side API fetching" - "Inline SVG icons to avoid external icon library dependency while keeping ModuleCard self-contained" patterns_established: - "Module registry pattern: slug -> { component, cardComponent? } mapping for lazy loading" - "Category page pattern: [category] dynamic route with API fetch + filter + grid render" - "Expanded module view pattern: [moduleSlug] route with whitelist check + dynamic component render" requirements_completed: [MOD-04] duration: 6min completed: 2026-06-19 --- # Phase 03 Plan 03: Module UI Lazy Loading + Category Pages Summary **Lazy-loaded module UI rendering via next/dynamic with slug-whitelist registry, category grid pages, and expanded module views** ## Performance - **Duration:** 6 min - **Started:** 2026-06-19T11:32:50Z - **Completed:** 2026-06-19T11:38:50Z - **Tasks:** 2/2 - **Files created:** 5 - **Files modified:** 2 ## Accomplishments - MODULE_REGISTRY in module-loader.ts maps module slugs to dynamically imported components via next/dynamic with ssr:false, ensuring inactive modules are never bundled (MOD-04) - Category page at /modules/[category] fetches active modules from API, filters by category, and renders a responsive card grid (D-04) - ModuleCard component displays icon, name, localized description, and link to expanded view with hover effects (D-05a) - Expanded module view at /modules/[category]/[moduleSlug] loads the module component on demand via whitelist lookup (D-05b) - API client utilities (getActiveModules, getModuleBySlug) provide typed fetch wrappers for module endpoints - i18n keys added for "modules" namespace in both German and English ## Task Commits Each task was committed atomically: 1. **Task 1: Module Loader Utility + Category Page with Lazy Cards** - `a191628` (feat) 2. **Task 2: Expanded Module View with Dynamic Routing** - `de63b10` (feat) ## Files Created/Modified - `apps/web/src/lib/module-loader.ts` - MODULE_REGISTRY with loadModuleComponent and loadModuleCard exports - `apps/web/src/lib/api.ts` - getActiveModules and getModuleBySlug API client functions - `apps/web/src/app/(portal)/modules/[category]/page.tsx` - Category page with module grid and empty state - `apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx` - Module card with icon, description, and link - `apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx` - Expanded module view with lazy loading and not-found state - `apps/web/src/messages/de.json` - Added modules namespace (categoryTitle, openModule, noModules, notFound, etc.) - `apps/web/src/messages/en.json` - Added modules namespace (categoryTitle, openModule, noModules, notFound, etc.) ## Decisions Made - **Slug whitelist pattern:** MODULE_REGISTRY acts as an allowlist — only explicitly registered slugs can trigger dynamic imports. Arbitrary URL slugs get a not-found state, never an import attempt (T-03-09). - **Client components throughout:** All three pages use 'use client' to support next/dynamic with ssr:false and client-side fetch with credentials:include for auth cookie forwarding. - **Inline SVG icons:** ModuleCard uses inline SVGs rather than an icon library to avoid adding a dependency for a small number of icons. Icons are mapped by the module's icon property (e.g., 'Globe' for domaincheck). - **Category filtering:** The category page fetches all active modules and filters client-side by category. This keeps the API simple (single /modules/active endpoint) while supporting category-based views. ## Threat Mitigations Applied | Threat ID | Mitigation | Implementation | |-----------|------------|----------------| | T-03-09 | Slug whitelist via MODULE_REGISTRY | module-loader.ts: only registered slugs resolve to components; [moduleSlug]/page.tsx checks `slug in MODULE_REGISTRY` before loading | | T-03-10 | API filters by tenant active modules | [category]/page.tsx: fetches from /modules/active which is tenant-scoped server-side | | T-03-11 | Accepted | Module names/descriptions are non-sensitive catalog data | ## Deviations from Plan None - plan executed exactly as written. ## Verification Results - `pnpm --filter web build`: PASS - Route `/modules/[category]` generated: confirmed in build output (2.01 kB) - Route `/modules/[category]/[moduleSlug]` generated: confirmed in build output (1.95 kB) - Domaincheck module code at `/modules/domaincheck` (1.47 kB) is separate from category page, confirming code-splitting works - No stubs or placeholders found in created files ## Self-Check: PASSED All 5 created files verified on disk. Both task commits (a191628, de63b10) verified in git log.