docs(08-04): complete Link widget plan summary
- 3/3 tasks complete (RED test → GREEN implementation → full suite gate) - 88/88 web tests passing (18 test files, +7 link-widget tests) - web + api tsc --noEmit clean - LinkWidget: single-link D-06 via FavoriteLink backend reuse
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
---
|
||||
phase: 08-dashboard-widgets-vollimplementierung
|
||||
plan: "04"
|
||||
subsystem: dashboard-widgets
|
||||
tags: [link, widget, tdd, dashboard, persistence, i18n, icons, single-link]
|
||||
status: complete
|
||||
|
||||
dependency_graph:
|
||||
requires:
|
||||
- 08-01 (wireLinkWidget, WIDGET_CONSTRAINTS.link, i18n keys)
|
||||
- 08-03 (FavoriteLink backend, favorites-api.ts — shared via widgetId scoping)
|
||||
provides:
|
||||
- LinkWidget component (single-link, list/tile view, add/edit/delete, letter fallback)
|
||||
- wireLinkWidget() wired in page.tsx
|
||||
- i18n keys: link.* (de.json + en.json)
|
||||
affects:
|
||||
- apps/web/src/app/(portal)/page.tsx
|
||||
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- TDD Red/Green cycle (LinkWidget single-link behavioral contract)
|
||||
- Single-link enforcement via conditional rendering (add form hidden when link!=null)
|
||||
- Reuse of FavoriteLink backend (widgetId=instanceId, no new backend)
|
||||
- useEffect dep array excludes t() to prevent re-fetch loops (from Plan 03)
|
||||
- Letter fallback span always in DOM (img layered on top via absolute positioning)
|
||||
- target="_blank" rel="noreferrer" anchor pattern (T-08-12)
|
||||
|
||||
key_files:
|
||||
created:
|
||||
- apps/web/src/components/dashboard/widgets/link-widget.tsx
|
||||
- apps/web/src/components/dashboard/widgets/link-widget.test.tsx
|
||||
modified:
|
||||
- apps/web/src/app/(portal)/page.tsx (wireLinkWidget added)
|
||||
- apps/web/src/messages/de.json (link.* keys added)
|
||||
- apps/web/src/messages/en.json (link.* keys added)
|
||||
|
||||
decisions:
|
||||
- "Single-link enforcement: add form rendered only when link==null (conditional JSX), not via counter/limit logic — simpler and testable"
|
||||
- "renderIcon() helper extracted to avoid duplication between list+tile view (same icon logic, different size)"
|
||||
- "useEffect dep [instanceId] only — same rationale as Plan 03 (t() mock ref causes re-fetch loops)"
|
||||
- "letter-fallback testid: 'link-letter-fallback' (single link, no id suffix needed unlike favorites)"
|
||||
- "Worktree fast-forward: git merge main --ff-only before any task work (same pattern as Plan 03)"
|
||||
|
||||
metrics:
|
||||
duration: "~4 minutes"
|
||||
completed: "2026-07-01"
|
||||
tasks_completed: 3
|
||||
tests_added: 7
|
||||
files_created: 2
|
||||
files_modified: 3
|
||||
---
|
||||
|
||||
# Phase 08 Plan 04: Link Widget Summary
|
||||
|
||||
Single-Link-Widget (DASH-09 / D-06) via TDD Red/Green-Zyklus: ein kompaktes Widget das genau einen Link anzeigt, den FavoriteLink-Backend von Plan 03 (widgetId-Scoping) wiederverwendet, und zwischen Zeilen- und Kachelansicht umgeschaltet werden kann.
|
||||
|
||||
## What Was Built
|
||||
|
||||
- **LinkWidget** (`link-widget.tsx`): 'use client', useTranslations('widgets'). Laed beim Mount via fetchFavorites(instanceId) und nimmt den ersten Eintrag als den einzigen Link (Single-Link-Constraint D-06). Kein neues Backend — FavoriteLink-Tabelle und FavoritesModule aus Plan 03 werden via widgetId=instanceId isoliert genutzt.
|
||||
|
||||
- **Single-Link-Enforcement**: Das Hinzufuegen-Formular wird nur gerendert wenn `link == null` (nicht wenn ein Link bereits existiert). Das ist der "D-06: genau einen Link" Contract.
|
||||
|
||||
- **Ansichten (D-06)**: Standard-Listenansicht (`data-testid="link-list"`: Icon + Titel in einer Zeile) und Kachelansicht (`data-testid="link-tile"`: Icon oben, Titel darunter zentriert). Umschaltbar im Edit-Modus via viewMode-Toggle-Buttons, persistent via updateWidgetConfig().
|
||||
|
||||
- **CRUD im Edit-Modus**: Hinzufuegen-Formular (wenn kein Link vorhanden), Inline-Bearbeiten (Titel, URL, optionale Logo-URL), Loeschen-Button. Alle CRUD-Operationen rufen createFavorite/updateFavorite/deleteFavorite auf.
|
||||
|
||||
- **Icon-Display**: `<img src={iconUrl}>` mit onError-Hide + `<span data-testid="link-letter-fallback">` als Buchstaben-Fallback. Kein dangerouslySetInnerHTML (T-08-11). renderIcon()-Helper fuer list/tile.
|
||||
|
||||
- **Sicherheit**: Links mit `target="_blank" rel="noreferrer"` (T-08-12). Anchor-Klick im Edit-Modus via preventDefault() blockiert.
|
||||
|
||||
- **i18n**: `link.*`-Keys in de.json und en.json hinzugefuegt: loading, empty, addTitle, addUrl, addButton, listView, gridView, editButton, deleteButton, saveButton, cancelButton, error.
|
||||
|
||||
- **page.tsx Wiring**: `wireLinkWidget(LinkWidget)` und entsprechender Import hinzugefuegt.
|
||||
|
||||
## Tasks
|
||||
|
||||
| Task | Description | Status | Commit |
|
||||
|------|-------------|--------|--------|
|
||||
| 1 | RED: Failing tests fuer LinkWidget single-link contract | Complete | 4657e50 |
|
||||
| 2 | GREEN: LinkWidget implementation + i18n keys + page.tsx wiring | Complete | e9914f6 |
|
||||
| 3 | Full Phase-8 Suite + Type-Check Gate | Complete | (verification only) |
|
||||
|
||||
## Test Results
|
||||
|
||||
- **link-widget.test.tsx**: 7 Tests — fetchFavorites mit instanceId, Anchor target/_blank/rel=noreferrer, empty state, add-Formular, single-link enforcement, inline edit, view toggle (list/tile), letter fallback
|
||||
- **Full Web Suite**: 88/88 Tests bestehen (18 Test-Dateien; +7 neue gegenueber Plan 03's 81/81)
|
||||
- **Web TypeScript**: sauber (0 Fehler)
|
||||
- **API TypeScript**: sauber (0 Fehler)
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Worktree Fast-Forward (auto-fix, Rule 3)
|
||||
|
||||
**Found during:** Pre-task setup
|
||||
|
||||
**Issue:** Das Worktree-Branch `worktree-agent-a2d7feebc2349e393` war bei `88db54f` (Quick-Fix-Commit aus frueherem Task), nicht am erwarteten Basis-Commit `7b42a3a` (Main nach Wave 3). Phase-08 wave-1, wave-2 und wave-3 Artefakte (widget-registry, FavoritesModule, StopwatchWidget) fehlten im Worktree.
|
||||
|
||||
**Fix:** `git merge main --ff-only` — Fast-Forward auf `7b42a3a`. Keine Konflikte. Node-Modules-Symlinks auf main repo danach erstellt.
|
||||
|
||||
**Impact:** Keiner. Alle vorherigen Plan-Artefakte standen vollstaendig zur Verfuegung.
|
||||
|
||||
### Node-Modules-Symlinks (auto-fix, Rule 3)
|
||||
|
||||
**Found during:** Task 1 Verifikation (RED-Phase)
|
||||
|
||||
**Issue:** Worktree hatte keine node_modules (vitest: not found).
|
||||
|
||||
**Fix:** Symlinks von `MAIN_REPO/{,apps/web,apps/api,packages/shared}/node_modules` -> entsprechende Worktree-Verzeichnisse.
|
||||
|
||||
**Impact:** Keiner.
|
||||
|
||||
### dangerouslySetInnerHTML grep-count = 1 (kein Problem)
|
||||
|
||||
Das Vorkommen ist in einem **JSDoc-Kommentar** (Zeile 26) der erklaert WARUM dangerouslySetInnerHTML nicht verwendet wird. Kein tatsaechlicher JSX-Attribut-Einsatz. T-08-11 korrekt mitigiert.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
Keine Stubs. LinkWidget ist vollstaendig implementiert und mit dem FavoriteLink-Backend verbunden.
|
||||
|
||||
## Threat Surface Scan
|
||||
|
||||
Alle Threats aus dem Plan-Threat-Register wurden mitigiert. Keine neuen Sicherheitsoberflaechen eingefuehrt:
|
||||
|
||||
| Threat ID | Status |
|
||||
|-----------|--------|
|
||||
| T-08-10 (Spoofing via FavoritesService-Reuse) | Mitigiert — FavoritesService userId+widgetId-Scope von Plan 03 uebernommen (kein neuer Endpunkt) |
|
||||
| T-08-11 (XSS via icon render) | Mitigiert — iconUrl nur via `<img src>`; kein dangerouslySetInnerHTML |
|
||||
| T-08-12 (open redirect) | Mitigiert — Anchor mit target="_blank" rel="noreferrer" |
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| link-widget.tsx | FOUND |
|
||||
| link-widget.test.tsx | FOUND |
|
||||
| wireLinkWidget in page.tsx | FOUND |
|
||||
| link.* keys in de.json | FOUND |
|
||||
| link.* keys in en.json | FOUND |
|
||||
| Commit 4657e50 (RED) | FOUND |
|
||||
| Commit e9914f6 (GREEN) | FOUND |
|
||||
| Full suite 88/88 | PASSED |
|
||||
| Web TypeScript: 0 errors | PASSED |
|
||||
| API TypeScript: 0 errors | PASSED |
|
||||
Reference in New Issue
Block a user