Files
tessera-ctl/.planning/phases/03-module-system-domaincheck/03-03-SUMMARY.md
T
schalli fbd7d44e88 docs(03-03): complete module UI lazy loading plan summary
- Document module loader registry, category pages, and expanded module views
- Record threat mitigations T-03-09, T-03-10, T-03-11
- Self-check passed: all files and commits verified
2026-06-19 13:39:58 +02:00

111 lines
5.6 KiB
Markdown

---
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.