diff --git a/.planning/phases/04-marketplace-portal-navigation/04-RESEARCH.md b/.planning/phases/04-marketplace-portal-navigation/04-RESEARCH.md
new file mode 100644
index 0000000..7ffb45a
--- /dev/null
+++ b/.planning/phases/04-marketplace-portal-navigation/04-RESEARCH.md
@@ -0,0 +1,568 @@
+# Phase 4: Marketplace & Portal Navigation - Research
+
+**Researched:** 2026-06-22
+**Domain:** Frontend Marketplace UI, Sidebar Navigation Enhancement, Tenant Context Switching
+**Confidence:** HIGH
+
+## Summary
+
+Phase 4 builds a marketplace view for browsing and activating modules, enhances the sidebar with search/filter and individual module links, and adds a Super-Admin tenant context selector. The critical finding is that nearly all backend infrastructure already exists: the module registry API (GET /modules, GET /modules/active, POST activate/deactivate) is complete, the Prisma schema has Module and TenantModuleActivation models, and the TenantMiddleware already supports `x-tenant-id` header for Super-Admin tenant switching. The tenant list API (GET /tenants) is also available and restricted to SUPER_ADMIN role.
+
+This phase is primarily a frontend implementation phase. The backend needs only minor extensions: an endpoint to fetch modules with their activation status for a specific tenant (combined view for marketplace cards), and potentially a lightweight endpoint to get tenant list for the context selector (the existing GET /tenants already works). The frontend work involves creating the marketplace page with card grid, search, category filters, status tabs, activation/deactivation flows, a module detail page, and enhancing the existing sidebar with search functionality and active-state highlighting via `usePathname`.
+
+**Primary recommendation:** Leverage existing API endpoints and patterns. Focus frontend work on the marketplace page components (MarketplaceCard, search, filters, tenant selector) and sidebar enhancements (SidebarSearch, usePathname-based active state, individual module links). Use the existing admin/modules page pattern for activation toggle logic. All components are hand-rolled following established shadcn-compatible CSS variable conventions.
+
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+- **D-01:** Marketplace als Karten-Grid (wie VS Code Extensions / App Store). Jede Karte zeigt Icon, Name, Kurzbeschreibung, Kategorie-Badge, Aktivierungs-Status.
+- **D-02:** Suchleiste + Kategorie-Filter oben auf Marketplace-Seite. Live-Filter waehrend Tippen. Kategorie-Chips oder Dropdown.
+- **D-03:** Empty State: Freundliche Illustration + Text ("Noch keine Module verfuegbar").
+- **D-04:** Super-Admin bekommt leichtgewichtigen Mandanten-Kontext-Selector im Marketplace-Bereich (Dropdown zur Mandanten-Auswahl). Aktivierung/Deaktivierung erfolgt aus Perspektive des gewaehlten Mandanten. Kein volles Impersonation-System — nur Kontext-Wechsel fuer Modul-Management (volle Impersonation bleibt v2: FEAT-V2-04).
+- **D-05:** Regulaerer Admin aktiviert Module direkt fuer seinen eigenen Mandanten (kein Kontext-Wechsel noetig).
+
+### Claude's Discretion
+- Marketplace Filter/Status-Unterscheidung: Tab-Ansicht vs. Badge-System vs. Kombination — basierend auf UX Best Practices
+- Bestaetigungs-Dialog bei Aktivierung: Dialog vs. sofortige Aktion mit Undo-Toast — UX-Entscheidung
+- Visuelles Feedback nach Aktivierung: Toast + Badge-Update vs. Animation — passend zum existierenden Design
+- Sidebar Kategorien-Darstellung: Bestehendes Accordion beibehalten vs. flache Liste mit Gruppen-Header — basierend auf Modul-Anzahl und UX
+- Sidebar collapsed Modus: Modul-Icons anzeigen vs. nur Haupt-Navigation — basierend auf existierender Sidebar-Logik
+- Sidebar Suchfeld: Im Kategorien-Bereich vs. globale Header-Suche vs. beides — basierend auf PRTAL-05 und Komplexitaet
+- Modul-Detail-Ansicht Tiefe: Ausfuehrlich (App-Store-Stil) vs. kompakt — passend fuer v1 Modul-Anzahl
+- Marketplace-Detail vs. Modul-Nutzung Navigation: Separate Seiten vs. Tabs vs. anderes Pattern — basierend auf App Router Architektur
+
+### Deferred Ideas (OUT OF SCOPE)
+- **FEAT-V2-04: Volle Admin-Impersonation** — Komplettes "Als Mandant agieren"-Feature kommt in v2. Phase 4 baut nur leichtgewichtigen Kontext-Selector fuer Modul-Management.
+
+
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|------------------|
+| MRKT-01 | Uebersicht aller verfuegbaren Module mit Beschreibung und Kategorie | Marketplace page at `/marketplace` with card grid. GET /modules API exists. ModuleCard pattern reusable. UI-SPEC defines card anatomy with icon, name, description, category badge, status badge. |
+| MRKT-02 | Admin kann Module pro Mandant aktivieren und deaktivieren | POST /modules/:id/activate and /deactivate APIs exist. Admin/modules page has toggle pattern. Marketplace adds button-based activation on cards. TenantMiddleware supports x-tenant-id for Super-Admin context switch. |
+| MRKT-03 | Nur aktivierte Module erscheinen in der Seitenleiste des Mandanten | Sidebar already fetches GET /modules/active and groups by category. Need to add individual module links under each category and search/filter. |
+| MRKT-04 | Module sind in Kategorien gruppiert | Category data is part of Module model (category field). Sidebar already groups by category. Marketplace adds CategoryFilter chips. |
+| PRTAL-02 | Linke Seitenleiste zeigt Kategorien und aktivierte Module | Sidebar already shows categories and modules. Need to enhance with individual clickable module items under each category (currently links to category page, needs per-module links). |
+| PRTAL-03 | Ausgewaehltes Modul oeffnet sich im Hauptbereich (Mitte) | Module route `/modules/{category}/{slug}` exists with lazy-loaded component rendering in main area. Need active-state highlighting in sidebar via usePathname. |
+| PRTAL-05 | Module koennen in der Seitenleiste durchsucht und gefiltert werden | New SidebarSearch component needed between main nav and categories section. Client-side filtering of module names and category names. |
+
+
+
+## Architectural Responsibility Map
+
+| Capability | Primary Tier | Secondary Tier | Rationale |
+|------------|-------------|----------------|-----------|
+| Module catalog display | Frontend (Client) | API (Backend) | GET /modules already returns all modules. Client renders grid with filtering. All filtering is client-side (small dataset, all modules loaded). |
+| Module activation/deactivation | API (Backend) | Frontend (Client) | Business logic (tenant-module association, upsert) lives in backend service. Frontend sends POST and updates UI optimistically. |
+| Tenant context switching (Super-Admin) | API (Backend) | Frontend (Client) | TenantMiddleware reads x-tenant-id header. Frontend dropdown sets header on subsequent requests. |
+| Sidebar module navigation | Frontend (Client) | -- | Pure client-side: fetches active modules, groups by category, renders links, handles search/filter. |
+| Sidebar active state | Frontend (Client) | -- | usePathname comparison against link hrefs. No backend involvement. |
+| Module detail view | Frontend (Client) | API (Backend) | Frontend renders detail page at /marketplace/[slug]. Module data from same GET /modules response. |
+| i18n for marketplace copy | Frontend (Client) | -- | next-intl with DE/EN translation files. All strings from UI-SPEC copywriting contract. |
+
+## Standard Stack
+
+### Core (Already Installed -- No New Dependencies)
+
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| Next.js | 15.3.x | Frontend framework, App Router | Already installed, provides routing, dynamic imports, SSR [VERIFIED: apps/web/package.json] |
+| React | 19.x | UI library | Already installed [VERIFIED: apps/web/package.json] |
+| Zustand | 5.0.x | Client state management | Already used for sidebar-store, auth-store [VERIFIED: apps/web/src/lib/stores/] |
+| next-intl | 4.13.x | Internationalization | Already used for all UI strings [VERIFIED: apps/web/src/messages/] |
+| next-themes | 0.4.x | Theme switching | Already installed [VERIFIED: apps/web/package.json] |
+| Tailwind CSS | 4.x | Styling | Already configured with OKLCH tokens [VERIFIED: apps/web/src/app/globals.css] |
+| NestJS | 11.x | Backend API | Already running with module-registry controller [VERIFIED: apps/api/package.json] |
+| Prisma | 6.x | ORM | Schema has Module + TenantModuleActivation [VERIFIED: apps/api/prisma/schema.prisma] |
+
+### Supporting (No New Libraries Needed)
+
+This phase requires **zero new npm packages**. All functionality is achievable with existing dependencies:
+
+- **Search/filter:** Native string matching, `useState` for filter state
+- **Debounce:** Simple `setTimeout`/`clearTimeout` pattern (no lodash needed for one debounce)
+- **Toast notifications:** Hand-rolled toast component with CSS animations (no sonner/react-hot-toast needed -- project convention is hand-rolled components)
+- **Dialog:** Hand-rolled modal with focus trap (project convention, no @radix-ui/react-dialog)
+- **URL routing:** Next.js App Router dynamic routes (`/marketplace/[slug]`)
+- **Active state:** `usePathname()` from `next/navigation`
+
+### Alternatives Considered
+
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| Hand-rolled toast | sonner / react-hot-toast | Would introduce a new dependency. Project convention is hand-rolled components following shadcn CSS variable naming. Toast is simple enough to build. |
+| Hand-rolled dialog | @radix-ui/react-dialog | Same reasoning. Only one dialog needed (deactivation confirm). Focus trap can be done with a few lines of JS. |
+| Hand-rolled debounce | lodash.debounce | Adding lodash for a single 4-line debounce function is wasteful. |
+
+**Installation:**
+```bash
+# No new packages needed for this phase
+```
+
+## Package Legitimacy Audit
+
+> No new external packages are installed in this phase. All functionality uses existing project dependencies.
+
+| Package | Registry | Age | Downloads | Source Repo | Verdict | Disposition |
+|---------|----------|-----|-----------|-------------|---------|-------------|
+| (none) | -- | -- | -- | -- | -- | No new packages |
+
+**Packages removed due to [SLOP] verdict:** none
+**Packages flagged as suspicious [SUS]:** none
+
+## Architecture Patterns
+
+### System Architecture Diagram
+
+```
+User Browser
+ |
+ v
+[Next.js App Router - (portal) route group]
+ |
+ +-- /marketplace ---------> MarketplacePage
+ | | |-- MarketplaceSearch (debounced text input)
+ | | |-- StatusFilter (tabs: All/Active/Available)
+ | | |-- CategoryFilter (horizontal chips)
+ | | |-- TenantContextSelector (Super-Admin only)
+ | | |-- MarketplaceCard[] (grid)
+ | | |-- Activate/Deactivate button
+ | | |-- Link to /marketplace/[slug]
+ | |
+ | +-- /marketplace/[slug] -> ModuleDetailView
+ | |-- Back link
+ | |-- Full description, status, activation button
+ |
+ +-- Sidebar (enhanced)
+ | |-- Dashboard link
+ | |-- Marketplace link
+ | |-- SidebarSearch (filters categories+modules)
+ | |-- Categories accordion
+ | | |-- Category header (link to /modules/{cat})
+ | | |-- Module items (link to /modules/{cat}/{slug})
+ | |-- Admin section (role-gated)
+ | |-- Active state via usePathname()
+ |
+ +-- API calls (fetch with credentials: 'include')
+ |
+ v
+[NestJS API - existing endpoints]
+ |-- GET /modules -> all registered modules
+ |-- GET /modules/active -> tenant's active modules
+ |-- POST /modules/:id/activate -> activate for tenant
+ |-- POST /modules/:id/deactivate -> deactivate for tenant
+ |-- GET /tenants -> all tenants (SUPER_ADMIN)
+ |
+ +-- TenantMiddleware
+ |-- Reads x-tenant-id header for Super-Admin context switch
+ |-- Falls back to JWT tenantId for regular users
+```
+
+### Recommended Project Structure
+
+```
+apps/web/src/
+├── app/(portal)/
+│ ├── marketplace/
+│ │ ├── page.tsx # Marketplace grid page
+│ │ ├── [slug]/
+│ │ │ └── page.tsx # Module detail page
+│ │ └── components/
+│ │ ├── MarketplaceCard.tsx # Card with activation controls
+│ │ ├── MarketplaceSearch.tsx # Debounced search input
+│ │ ├── CategoryFilter.tsx # Category chip row
+│ │ ├── StatusFilter.tsx # Status tab bar
+│ │ ├── TenantContextSelector.tsx # Super-Admin tenant dropdown
+│ │ ├── ActivationDialog.tsx # Deactivation confirmation dialog
+│ │ └── Toast.tsx # Toast notification component
+│ └── ...existing routes...
+├── components/layout/
+│ ├── sidebar.tsx # MODIFIED: add SidebarSearch, usePathname, module links
+│ ├── sidebar-search.tsx # NEW: search input for sidebar
+│ └── ...existing layout components...
+├── lib/stores/
+│ └── marketplace-store.ts # NEW: Zustand store for marketplace state (optional)
+└── messages/
+ ├── de.json # MODIFIED: add marketplace translations
+ └── en.json # MODIFIED: add marketplace translations
+```
+
+### Pattern 1: Client-Side Filtering with Multiple Dimensions
+
+**What:** Marketplace loads all modules once, then filters client-side by search text, status tab, and category chip. Three filter dimensions compose together.
+
+**When to use:** Small dataset (modules are typically < 100), all data available from initial fetch, instant feedback needed.
+
+**Example:**
+```typescript
+// Source: Codebase convention — client-side state filtering
+// All three filters compose: search AND status AND category
+const filteredModules = useMemo(() => {
+ return allModules.filter((mod) => {
+ // Text search: name + description
+ const matchesSearch = !searchQuery ||
+ mod.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ getLocalizedDescription(mod.description).toLowerCase().includes(searchQuery.toLowerCase());
+
+ // Status filter: 'all' | 'active' | 'available'
+ const matchesStatus = statusFilter === 'all' ||
+ (statusFilter === 'active' && activationMap.has(mod.id)) ||
+ (statusFilter === 'available' && !activationMap.has(mod.id));
+
+ // Category filter: 'all' | specific category slug
+ const matchesCategory = categoryFilter === 'all' || mod.category === categoryFilter;
+
+ return matchesSearch && matchesStatus && matchesCategory;
+ });
+}, [allModules, searchQuery, statusFilter, categoryFilter, activationMap]);
+```
+
+### Pattern 2: Tenant Context Header for Super-Admin
+
+**What:** Super-Admin switches tenant context via dropdown. All subsequent API calls include `x-tenant-id` header. The existing TenantMiddleware already reads this header.
+
+**When to use:** When Super-Admin needs to manage modules for a specific tenant.
+
+**Example:**
+```typescript
+// Source: Existing pattern in apps/api/src/tenant/tenant.middleware.ts
+// Frontend sends x-tenant-id header for Super-Admin context switching
+const fetchModulesForTenant = async (tenantId: string) => {
+ const [allRes, activeRes] = await Promise.all([
+ fetch(`${API_URL}/modules`, { credentials: 'include' }),
+ fetch(`${API_URL}/modules/active`, {
+ credentials: 'include',
+ headers: tenantId ? { 'x-tenant-id': tenantId } : {},
+ }),
+ ]);
+ // ...process responses
+};
+```
+
+### Pattern 3: Sidebar Active State with usePathname
+
+**What:** Current page URL determines which sidebar item gets the active class. Uses `usePathname()` from `next/navigation`.
+
+**When to use:** Replace the hardcoded Dashboard active state with dynamic path matching.
+
+**Example:**
+```typescript
+// Source: Next.js App Router convention
+import { usePathname } from 'next/navigation';
+
+const pathname = usePathname();
+const isActive = (href: string) => {
+ if (href === '/') return pathname === '/';
+ return pathname.startsWith(href);
+};
+
+// Usage in sidebar link:
+
+```
+
+### Pattern 4: Hand-Rolled Toast System
+
+**What:** Lightweight toast notification system matching project convention (no external library). Uses a Zustand store or React context for state, CSS transitions for animations.
+
+**When to use:** Activation/deactivation feedback, error notifications.
+
+**Example:**
+```typescript
+// Toast store pattern — minimal Zustand approach
+import { create } from 'zustand';
+
+interface Toast {
+ id: string;
+ type: 'success' | 'error';
+ message: string;
+}
+
+interface ToastState {
+ toasts: Toast[];
+ addToast: (type: Toast['type'], message: string) => void;
+ removeToast: (id: string) => void;
+}
+
+export const useToastStore = create()((set) => ({
+ toasts: [],
+ addToast: (type, message) => {
+ const id = crypto.randomUUID();
+ set((s) => ({ toasts: [...s.toasts, { id, type, message }] }));
+ setTimeout(() => {
+ set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) }));
+ }, 4000);
+ },
+ removeToast: (id) => set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) })),
+}));
+```
+
+### Anti-Patterns to Avoid
+
+- **Fetching activation status per card individually:** All modules and their activation status should be fetched in parallel (one GET /modules + one GET /modules/active), then merged client-side. The admin/modules page already does this pattern with `Promise.all`. [VERIFIED: apps/web/src/app/(portal)/admin/modules/page.tsx]
+- **Storing marketplace filter state in URL search params:** For this MVP, local React state is sufficient. URL params add complexity (back/forward, sharing URLs with filters). Keep it simple.
+- **Building a full impersonation system:** CONTEXT.md explicitly defers this (FEAT-V2-04). The tenant context selector is ONLY a dropdown that changes which tenant's activation status is displayed/modified.
+- **Re-fetching sidebar modules on every navigation:** The sidebar should re-fetch active modules only after an activation/deactivation action, not on every route change. Use a callback or event pattern.
+- **Using `` tags instead of Next.js ``:** The current sidebar uses raw `` tags, which causes full page reloads. Phase 4 should migrate sidebar links to Next.js `` for client-side navigation.
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Module registry API | New CRUD endpoints | Existing GET /modules, GET /modules/active, POST activate/deactivate | Fully implemented in Phase 3, tested and working [VERIFIED: module-registry.controller.ts] |
+| Tenant context switching | Custom auth/session override | Existing x-tenant-id header in TenantMiddleware | Already supports Super-Admin context switching [VERIFIED: tenant.middleware.ts L33] |
+| Tenant list for selector | New API endpoint | Existing GET /tenants (SUPER_ADMIN only) | Returns all tenants with user count [VERIFIED: tenant.controller.ts] |
+| Module lazy loading | New import system | Existing MODULE_REGISTRY + loadModuleComponent | Whitelist-based dynamic imports [VERIFIED: lib/module-loader.ts] |
+| Localized descriptions | Custom i18n for module data | Existing pattern: `description[locale]` fallback chain | Module.description is JSON with locale keys [VERIFIED: ModuleCard.tsx L73] |
+| Design token system | New CSS variables | Existing OKLCH tokens in globals.css | Complete light/dark mode token set [VERIFIED: globals.css] |
+
+**Key insight:** This phase is 90% frontend work. The backend APIs, data models, middleware, and patterns all exist from Phase 2 and Phase 3. The only new code needed is the marketplace UI components, sidebar enhancements, and i18n translations.
+
+## Common Pitfalls
+
+### Pitfall 1: Sidebar Uses Raw `` Tags (Full Page Reloads)
+
+**What goes wrong:** The current sidebar.tsx uses raw HTML `` tags for all navigation links. This causes full-page reloads instead of client-side navigation.
+**Why it happens:** Phase 1 implemented the sidebar skeleton without Next.js navigation patterns.
+**How to avoid:** Replace all `` with Next.js `` in the sidebar. This is required for the usePathname-based active state to work correctly (no page reload means pathname persists in React state).
+**Warning signs:** Page flickers on sidebar clicks; loading spinner appears on every navigation.
+
+### Pitfall 2: Race Condition in Activation + Sidebar Refresh
+
+**What goes wrong:** After activating a module, the marketplace card updates optimistically but the sidebar re-fetch may return stale data if the activation hasn't fully propagated.
+**Why it happens:** POST activate returns the activation record, but GET /modules/active is a separate query.
+**How to avoid:** After successful POST activate/deactivate, wait for the response, then trigger sidebar re-fetch. The optimistic update on the marketplace card provides immediate feedback while sidebar refresh catches up. Consider a shared event bus or callback.
+**Warning signs:** Module shows as "Aktiviert" in marketplace but doesn't appear in sidebar.
+
+### Pitfall 3: Tenant Context Not Applied to All API Calls
+
+**What goes wrong:** Super-Admin changes tenant in the selector but only the module list updates; activation still targets the Super-Admin's own tenant.
+**Why it happens:** The x-tenant-id header must be included in EVERY API call that should respect the selected tenant context (GET /modules/active AND POST activate/deactivate).
+**How to avoid:** Create a wrapper function for API calls that automatically includes the x-tenant-id header based on the selected tenant. Store the selected tenant in React state, pass it through to all fetch calls.
+**Warning signs:** Super-Admin activates module for "Firma A" but it activates for their own tenant.
+
+### Pitfall 4: Missing Locale Handling for Module Descriptions
+
+**What goes wrong:** Module description shows as `[object Object]` or is blank.
+**Why it happens:** Module.description is a JSON field containing `{ "de": "...", "en": "..." }`. If rendered directly without locale extraction, it breaks.
+**How to avoid:** Follow existing ModuleCard pattern: extract `description[locale] || description.en || description.de || ''`. The locale can be detected from `document.documentElement.lang` (existing pattern).
+**Warning signs:** Cards show object notation or empty descriptions.
+
+### Pitfall 5: Categories Accordion State Reset on Re-render
+
+**What goes wrong:** Sidebar search filters modules, but when the search is cleared, all category accordions collapse to their default state.
+**Why it happens:** The categories accordion state (open/closed) is local component state that resets when the component re-renders with new data.
+**How to avoid:** Track which categories are expanded in stable state (set or map keyed by category name). When search filter changes, don't reset the accordion state.
+**Warning signs:** User expands a category, types in search, clears search, and the category is collapsed again.
+
+## Code Examples
+
+### Verified Pattern: Parallel Module + Activation Fetch
+
+```typescript
+// Source: apps/web/src/app/(portal)/admin/modules/page.tsx (existing pattern)
+// Fetch all modules and active modules in parallel, merge into activation map
+const [allRes, activeRes] = await Promise.all([
+ fetch(`${API_URL}/modules`, { credentials: 'include' }),
+ fetch(`${API_URL}/modules/active`, {
+ credentials: 'include',
+ headers: selectedTenantId ? { 'x-tenant-id': selectedTenantId } : {},
+ }),
+]);
+
+const allModules: Module[] = allRes.ok ? await allRes.json() : [];
+const activeModules: Module[] = activeRes.ok ? await activeRes.json() : [];
+
+// Build activation map: moduleId -> boolean
+const activationMap = new Map();
+for (const mod of activeModules) {
+ activationMap.set(mod.id, true);
+}
+```
+
+### Verified Pattern: Module Card with Localized Description
+
+```typescript
+// Source: apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx
+const locale = (typeof window !== 'undefined' && document.documentElement.lang) || 'de';
+const localizedDescription = typeof description === 'string'
+ ? description
+ : description[locale] || description.en || description.de || '';
+```
+
+### Verified Pattern: Toggle Activation API Call
+
+```typescript
+// Source: apps/web/src/app/(portal)/admin/modules/page.tsx
+const action = currentlyActive ? 'deactivate' : 'activate';
+const res = await fetch(`${API_URL}/modules/${moduleId}/${action}`, {
+ method: 'POST',
+ credentials: 'include',
+ headers: selectedTenantId ? { 'x-tenant-id': selectedTenantId } : {},
+});
+```
+
+### Verified Pattern: Tenant List Fetch (Super-Admin)
+
+```typescript
+// Source: apps/api/src/tenant/tenant.controller.ts — GET /tenants returns:
+// [{ id, name, slug, isActive, createdAt, userCount }]
+const fetchTenants = async (): Promise => {
+ const res = await fetch(`${API_URL}/tenants`, { credentials: 'include' });
+ if (!res.ok) return [];
+ return res.json();
+};
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| Sidebar uses `` tags | Should use Next.js `` | Phase 4 (now) | Enables client-side navigation, usePathname active state |
+| Sidebar Dashboard always "active" | Dynamic active state via usePathname | Phase 4 (now) | Correct visual feedback for current page |
+| Category-only links in sidebar | Per-module links under each category | Phase 4 (now) | Direct module navigation from sidebar |
+| Admin modules page for activation | Marketplace page with richer UX | Phase 4 (now) | Better discovery, search, filtering for end users |
+
+**Deprecated/outdated:**
+- Admin modules page remains available but marketplace becomes the primary discovery/activation interface for users
+- Raw `` tags in sidebar should be replaced with `` for performance
+
+## Assumptions Log
+
+> List all claims tagged `[ASSUMED]` in this research. The planner and discuss-phase use this
+> section to identify decisions that need user confirmation before execution.
+
+| # | Claim | Section | Risk if Wrong |
+|---|-------|---------|---------------|
+| A1 | Toast system can be implemented as a simple Zustand store + positioned div, no library needed | Architecture Patterns | Low -- toast is a simple UI pattern, worst case is slightly rougher animations than a library provides |
+| A2 | Deactivation confirmation dialog focus trap can be hand-rolled in ~10 lines | Architecture Patterns | Low -- if accessibility requirements demand more robust focus management, could add @radix-ui/react-dialog later |
+| A3 | All modules fit on one page without pagination | Architecture Patterns | Low -- v1 will have very few modules. If wrong, add client-side pagination later |
+
+**If this table is empty:** Most claims in this research were verified directly from the codebase -- minimal assumptions needed.
+
+## Open Questions
+
+1. **Sidebar Module Refresh Mechanism**
+ - What we know: Sidebar fetches `/modules/active` on mount. Admin modules page updates state locally after toggle.
+ - What's unclear: How to signal sidebar to re-fetch after marketplace activation. Options: (a) poll on interval, (b) event bus, (c) shared Zustand store that both marketplace and sidebar read, (d) callback passed via context.
+ - Recommendation: Use a shared Zustand store or a simple `useCallback` passed through props. Zustand is already the state management pattern in this project.
+
+2. **Marketplace Page vs. Admin Modules Page Overlap**
+ - What we know: Admin modules page (admin/modules) already allows activation toggle. Marketplace adds richer UX.
+ - What's unclear: Should admin modules page be kept as-is, deprecated, or redirect to marketplace?
+ - Recommendation: Keep both. Admin modules page is a quick admin-only toggle view. Marketplace is the user-facing discovery interface. They serve different use cases.
+
+## Environment Availability
+
+| Dependency | Required By | Available | Version | Fallback |
+|------------|------------|-----------|---------|----------|
+| Node.js | Next.js, NestJS | yes | 24.16.0 | -- |
+| pnpm | Package management | yes | 9.15.0 | -- |
+| Docker | Container runtime | yes | 29.5.3 | -- |
+| Docker Compose | Service orchestration | yes | 5.1.4 | -- |
+| Turborepo | Build orchestration | yes | 2.9.18 | -- |
+
+**Missing dependencies with no fallback:** none
+**Missing dependencies with fallback:** none
+
+## Validation Architecture
+
+### Test Framework
+
+| Property | Value |
+|----------|-------|
+| Framework | Vitest (specified in CLAUDE.md, not yet installed) |
+| Config file | none -- see Wave 0 |
+| Quick run command | `pnpm vitest run --reporter=verbose` |
+| Full suite command | `pnpm vitest run` |
+
+### Phase Requirements to Test Map
+
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| MRKT-01 | Marketplace grid renders all modules with descriptions and categories | unit | `pnpm vitest run apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.test.tsx -x` | -- Wave 0 |
+| MRKT-02 | Activation toggle calls correct API endpoint with tenant context | unit | `pnpm vitest run apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.test.tsx -x` | -- Wave 0 |
+| MRKT-03 | Sidebar shows only active modules | unit | `pnpm vitest run apps/web/src/components/layout/sidebar.test.tsx -x` | -- Wave 0 |
+| MRKT-04 | Modules grouped by category in grid and sidebar | unit | `pnpm vitest run apps/web/src/app/(portal)/marketplace/marketplace.test.tsx -x` | -- Wave 0 |
+| PRTAL-02 | Sidebar displays categories and activated module links | unit | `pnpm vitest run apps/web/src/components/layout/sidebar.test.tsx -x` | -- Wave 0 |
+| PRTAL-03 | Module opens in main content area, sidebar active state updates | integration | Manual verification -- requires full Next.js routing | -- Manual |
+| PRTAL-05 | Sidebar search filters modules by name | unit | `pnpm vitest run apps/web/src/components/layout/sidebar-search.test.tsx -x` | -- Wave 0 |
+
+### Sampling Rate
+
+- **Per task commit:** `pnpm vitest run --reporter=verbose` (if test infra exists)
+- **Per wave merge:** Full test suite
+- **Phase gate:** Full suite green + manual verification of navigation flows
+
+### Wave 0 Gaps
+
+- [ ] Vitest installation: `pnpm add -D vitest @testing-library/react @testing-library/jest-dom jsdom` in apps/web
+- [ ] `apps/web/vitest.config.ts` -- Vitest config for Next.js with jsdom environment
+- [ ] `apps/web/src/test/setup.ts` -- Test setup with @testing-library/jest-dom matchers
+- [ ] Test utilities for mocking fetch, Zustand stores, and next-intl
+
+## Security Domain
+
+### Applicable ASVS Categories
+
+| ASVS Category | Applies | Standard Control |
+|---------------|---------|-----------------|
+| V2 Authentication | yes (existing) | JWT auth via global JwtAuthGuard -- no changes needed |
+| V3 Session Management | no | No new session handling in this phase |
+| V4 Access Control | yes | RolesGuard on activate/deactivate endpoints (ADMIN/SUPER_ADMIN). TenantMiddleware for tenant isolation. x-tenant-id only respected for SUPER_ADMIN role. [VERIFIED: module-registry.controller.ts, tenant.middleware.ts] |
+| V5 Input Validation | yes | Module ID validated as UUID by Prisma parameterized queries. Search input is client-side only (no server-side SQL). Category filter uses module data, not user input for DB queries. |
+| V6 Cryptography | no | No cryptographic operations in this phase |
+
+### Known Threat Patterns for This Phase
+
+| Pattern | STRIDE | Standard Mitigation |
+|---------|--------|---------------------|
+| Tenant context spoofing via x-tenant-id header | Elevation of Privilege | TenantMiddleware checks `user.role === 'SUPER_ADMIN'` before reading header [VERIFIED: tenant.middleware.ts L33] |
+| Unauthorized module activation | Elevation of Privilege | RolesGuard requires ADMIN or SUPER_ADMIN [VERIFIED: module-registry.controller.ts] |
+| XSS via module description | Tampering | Module descriptions are rendered as text content (not innerHTML). React auto-escapes. |
+| CSRF on activation endpoints | Tampering | Cookie-based auth with `credentials: 'include'` + same-origin policy. POST endpoints require valid JWT. |
+
+## Sources
+
+### Primary (HIGH confidence)
+- `apps/api/src/module-registry/module-registry.controller.ts` -- Full module registry API with activate/deactivate
+- `apps/api/src/module-registry/module-registry.service.ts` -- Service with findAll, findActiveForTenant, activateForTenant, deactivateForTenant
+- `apps/api/src/tenant/tenant.middleware.ts` -- TenantMiddleware with x-tenant-id header support for SUPER_ADMIN
+- `apps/api/src/tenant/tenant.controller.ts` -- GET /tenants for Super-Admin tenant list
+- `apps/api/prisma/schema.prisma` -- Module + TenantModuleActivation models
+- `apps/web/src/components/layout/sidebar.tsx` -- Current sidebar implementation with category accordion
+- `apps/web/src/app/(portal)/admin/modules/page.tsx` -- Existing activation toggle pattern
+- `apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx` -- Module card pattern
+- `apps/web/src/lib/module-loader.ts` -- MODULE_REGISTRY and lazy loading
+- `apps/web/src/app/globals.css` -- OKLCH design tokens, layout variables
+- `apps/web/src/lib/stores/auth-store.ts` -- AuthUser with role and tenantId
+- `.planning/phases/04-marketplace-portal-navigation/04-UI-SPEC.md` -- UI design contract
+- `.planning/phases/04-marketplace-portal-navigation/04-CONTEXT.md` -- User decisions
+
+### Secondary (MEDIUM confidence)
+- `.planning/phases/01-foundation-portal-shell/01-CONTEXT.md` -- Phase 1 design decisions (sidebar, header, color scheme)
+- `.planning/phases/02-authentication-multi-tenancy/02-CONTEXT.md` -- Auth/RBAC decisions (SUPER_ADMIN, ADMIN, USER roles)
+- `.planning/phases/03-module-system-domaincheck/03-CONTEXT.md` -- Module system decisions (SDK, registry, categories)
+
+### Tertiary (LOW confidence)
+- None -- all critical findings verified from codebase
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH -- all libraries verified from package.json, no new dependencies needed
+- Architecture: HIGH -- all backend APIs verified from source code, patterns established in previous phases
+- Pitfalls: HIGH -- identified from direct codebase analysis (raw `` tags, missing usePathname, race conditions)
+
+**Research date:** 2026-06-22
+**Valid until:** 2026-07-22 (stable -- no external API changes, pure frontend work)