diff --git a/.planning/phases/09-cert-manager-module/09-02-SUMMARY.md b/.planning/phases/09-cert-manager-module/09-02-SUMMARY.md new file mode 100644 index 0000000..67b02bc --- /dev/null +++ b/.planning/phases/09-cert-manager-module/09-02-SUMMARY.md @@ -0,0 +1,149 @@ +--- +phase: 09-cert-manager-module +plan: "02" +subsystem: frontend +tags: [cert-manager, frontend, i18n, components, shell] +requires: [] +provides: [cert-manager-page-shell, cert-manager-i18n, cert-manager-actions] +affects: [apps/web] +tech_stack: + added: [] + patterns: [useTranslations, vi.mock next-intl, client-component, conditional-render] +key_files: + created: + - apps/web/src/app/(portal)/modules/cert-manager/page.tsx + - apps/web/src/app/(portal)/modules/cert-manager/actions.ts + - apps/web/src/app/(portal)/modules/cert-manager/components/DropZone.tsx + - apps/web/src/app/(portal)/modules/cert-manager/components/PasswordField.tsx + - apps/web/src/app/(portal)/modules/cert-manager/components/InspectTab.tsx + - apps/web/src/app/(portal)/modules/cert-manager/components/SplitTab.tsx + - apps/web/src/app/(portal)/modules/cert-manager/components/MergeTab.tsx + - apps/web/src/app/(portal)/modules/cert-manager/components/ConvertTab.tsx + - apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx + modified: + - apps/web/src/messages/de.json + - apps/web/src/messages/en.json +decisions: + - "Used vi.mock('next-intl') test pattern per project convention instead of NextIntlClientProvider (matches VehicleTable.test.tsx, sidebar.test.tsx)" + - "PasswordField renders null when show=false — no DOM reflow, satisfies T-09-02 threat mitigation" + - "File/PEM mutual exclusion enforced in page state: selecting one clears the other" +metrics: + duration: "~6 minutes" + completed: "2026-07-01" + tasks_completed: 3 + files_created: 9 + files_modified: 2 +status: complete +--- + +# Phase 09 Plan 02: Cert-Manager Frontend Shell Summary + +**One-liner:** Cert-manager tab shell with DropZone, conditional PasswordField, shared input card, four tab stubs, downloadBase64/postForm client helpers, and full certManager i18n namespace in de + en. + +## Tasks Completed + +| Task | Name | Commit | Files | +|------|------|--------|-------| +| 1 | Add certManager i18n namespace (de + en) | 82a80e7 | de.json, en.json | +| 2 | Build page shell, components, client helpers | 42a41f7 | page.tsx, actions.ts, 6 components | +| 3 | Shell render tests (GREEN) | 2cb01f7 | cert-manager.test.tsx | + +## What Was Built + +### i18n Namespace (Task 1) +`certManager` namespace added to both `de.json` and `en.json` with identical key structure: +- `title`, `description` +- `tabs.{inspect,split,merge,convert}` +- `dropZone.{placeholder,formats}`, `paste.placeholder`, `password.label`, `or` +- `actions.{inspect,split,merge,convert,download,processing}` +- `emptyState.{inspect,inspectBody,split,splitBody,merge,mergeBody,convert,convertBody}` +- `error.{generic,wrongPassword,unknownFormat}` + +German copy matches UI-SPEC Copywriting Contract exactly. + +### Page Shell (Task 2) + +**page.tsx** — `'use client'` component with: +- State: `activeTab`, `file`, `pemText`, `password` +- Layout: `max-w-4xl mx-auto p-6 space-y-6` per UI-SPEC +- Shared input card with DropZone + OR divider + PEM textarea + conditional PasswordField +- Tab nav: 4 tabs with `border-b-2 border-primary` active indicator +- Renders active tab component with `{ file, pemText, password }` props +- File/PEM mutual exclusion enforced + +**actions.ts** — Client helpers: +- `API_URL` const (`NEXT_PUBLIC_API_URL || 'http://localhost:3001'`) +- `downloadBase64(filename, content, mimeType)` — atob → Uint8Array → Blob → URL.createObjectURL +- `postForm(endpoint, form)` — fetch with `credentials: 'include'`, throws on !ok + +**DropZone.tsx** — Drag-and-drop + click-to-browse, drag-over highlight (`border-primary bg-primary/5`), file display with clear button, resets `e.target.value` for re-selection. + +**PasswordField.tsx** — `input[type=password]` with inline SVG eye toggle; renders `null` when `show=false` (T-09-02). + +**Tab stubs** — InspectTab, SplitTab, MergeTab, ConvertTab render empty state strings only. + +### Tests (Task 3) +7 tests, all GREEN: +- Page renders title 'Zertifikat-Manager' and description +- All 4 tab labels rendered (Analysieren, Aufteilen, Zusammenfuehren, Konvertieren) +- Password field NOT in DOM on initial render (T-09-02 assertion) +- Inspect empty state on initial render +- Split / Merge / Convert empty states when respective tabs activated + +## Deviations from Plan + +### Auto-applied conventions + +**1. [Convention] vi.mock('next-intl') instead of NextIntlClientProvider** +- **Found during:** Task 3 +- **Issue:** Plan specified NextIntlClientProvider, but every existing test in the project mocks next-intl directly (sidebar.test.tsx, VehicleTable.test.tsx) +- **Fix:** Used `vi.mock('next-intl', ...)` pattern for consistency +- **Impact:** Tests are simpler and match project convention; no behavior difference + +**2. [Convention] node_modules symlink for worktree test execution** +- **Found during:** Task 3 +- **Issue:** Worktree has no node_modules; vitest not available +- **Fix:** Created temporary symlink `apps/web/node_modules -> main repo node_modules` to run tests; symlink is gitignored +- **Impact:** Tests pass correctly; symlink does not affect production + +## Threat Model Compliance + +| Threat ID | Status | +|-----------|--------| +| T-09-02 (password in state/URL) | Mitigated — PasswordField renders null when show=false; password never in URL or console | +| T-09-04 (unauthenticated fetch) | Mitigated — postForm uses credentials:'include'; ModuleGuard enforced server-side | +| T-09-05 (client accept filter) | Accepted — UX only, server validates | + +## Known Stubs + +| Component | Location | Reason | +|-----------|----------|--------| +| InspectTab | cert-manager/components/InspectTab.tsx | Empty state only — API wire-up in later plan | +| SplitTab | cert-manager/components/SplitTab.tsx | Empty state only — API wire-up in later plan | +| MergeTab | cert-manager/components/MergeTab.tsx | Empty state only — API wire-up in later plan | +| ConvertTab | cert-manager/components/ConvertTab.tsx | Empty state only — API wire-up in later plan | + +These stubs are intentional. Plan 02 explicitly scopes to the shell; later plans fill in each tab's API logic. The stubs do not prevent the plan goal (visible shell + navigation) from being achieved. + +## Verification Results + +| Check | Status | Notes | +|-------|--------|-------| +| `pnpm --filter @tessera/web test cert-manager --run` | PASS | 7/7 tests green | +| `tsc --noEmit` (cert-manager files) | PASS | No cert-manager type errors | +| I18N_OK node verify script | PASS | de.tabs keys correct, en.certManager.actions.download exists | +| `grep -q "max-w-4xl"` | PASS | Layout contract met | +| `grep -q "credentials: 'include'"` | PASS | T-09-04 mitigation present | +| `grep -q "URL.createObjectURL"` | PASS | downloadBase64 helper present | + +## Self-Check: PASSED + +- [x] apps/web/src/app/(portal)/modules/cert-manager/page.tsx — created +- [x] apps/web/src/app/(portal)/modules/cert-manager/actions.ts — created +- [x] apps/web/src/app/(portal)/modules/cert-manager/components/*.tsx (6 files) — created +- [x] apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx — created +- [x] apps/web/src/messages/de.json — certManager namespace added +- [x] apps/web/src/messages/en.json — certManager namespace added +- [x] Commit 82a80e7 exists (i18n) +- [x] Commit 42a41f7 exists (shell + components) +- [x] Commit 2cb01f7 exists (tests)