From e72814abd97756c9c46238461e17bc9f2a821cef Mon Sep 17 00:00:00 2001 From: Schalli Date: Tue, 29 Sep 2026 15:48:39 +0200 Subject: [PATCH] docs(quick-260929-lh3): Favoriten-Symbole, Browser-Pruefung Co-Authored-By: Claude Opus 5.5 (1M context) --- .planning/STATE.md | 1 + .../260929-lh3-PLAN.md | 67 +++++++++++++++ .../260929-lh3-SUMMARY.md | 82 +++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 .planning/quick/260929-lh3-favoriten-eigenes-symbol-wirkt-nicht/260929-lh3-PLAN.md create mode 100644 .planning/quick/260929-lh3-favoriten-eigenes-symbol-wirkt-nicht/260929-lh3-SUMMARY.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 5f23fad..528272c 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -482,6 +482,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests. | 260929-dmx | **Widget-Raster horizontal feiner + Kalender schmaler.** COLS lg 48/md 40/sm 24/xs 16/xxs 4, GRID_VERSION 3 (v2->v3 nur x/w/minW/maxW x2), alle minW/defaultW x2, Kalender minW 8 (~250 px). Browser: Anordnung pixelgleich, Kalender bis 252 px, Schritt 33 px. Auch: Hover-Anheben der Widgets entfernt (acd3c7a, Nutzerwunsch). | 2026-09-29 | 97744b5,9c9e142,46ebb4e | [260929-dmx-widget-raster-horizontal-feiner-48-spalt](./quick/260929-dmx-widget-raster-horizontal-feiner-48-spalt/) | | 260929-dzu | **Eigene Module fuer jeden Benutzer (persoenlich).** `CustomModule.ownerUserId` (null = gemeinsam), RLS-Muster SearchProvider, Einstellungen > Eigene Module (nur eigene), Verwaltung nur gemeinsame; Browser: Sichtbarkeit/Rechte wie verlangt. Nebenbei ohne eigenen Quick: Zentrierung entfernt (bc4c011), Desktop neue Fenster -> System-Browser (76a9234, Windows-VM bestaetigt), Single-Instance auf VM bestaetigt. | 2026-09-29 | c703d87,ee97b4e,8f41bd2 | [260929-dzu-eigene-module-fuer-jeden-benutzer-persoe](./quick/260929-dzu-eigene-module-fuer-jeden-benutzer-persoe/) | | 260929-if2 | **Erinnerungen-Widget (Reminder).** Modell `Reminder` + RLS, API /reminders (anlegen/listen/bearbeiten/loeschen/erledigt/snooze, 409/404-Regeln), E-Mail-Scheduler alle 30 s mit Claim-once + max. 3 Versuche, globaler ReminderNotifier (Browser-Notification, Desktop via Tauri-Notification mit Laufzeit-Capability nur fuer die Server-Origin, Pattern escaped + vorab geprueft). Verifier human_needed (Windows-Toast offen); Browser dunkel bestanden inkl. echter Mail ueber MailHog. api 1570, web 1069, cargo 57. Nebenbei: eigene Module ohne Kopfzeile (cd1f8f6), Update-Klick prueft frisch (41d00a3). | 2026-09-29 | 325c5dd,709b41a,6879c75 | [260929-if2-reminder-widget-mit-benachrichtigung](./quick/260929-if2-reminder-widget-mit-benachrichtigung/) | +| 260929-lh3 | **Favoriten: eigene Symbol-Adresse wirkt.** Neue iconUrl ersetzt Upload + bumpt iconVersion; iconUrl wird auch gespeichert, wenn nur der Browser sie laden kann (kein 422 mehr, nur Formpruefung); Kachel: Proxy -> iconUrl direkt -> origin/favicon -> Buchstabe; Discovery liest auch aus Nicht-2xx-Seiten (docuvita 400). | 2026-09-29 | 7188c5b,b15c746,0e72ad4 | [260929-lh3-favoriten-eigenes-symbol-wirkt-nicht](./quick/260929-lh3-favoriten-eigenes-symbol-wirkt-nicht/) | ## Deferred Items diff --git a/.planning/quick/260929-lh3-favoriten-eigenes-symbol-wirkt-nicht/260929-lh3-PLAN.md b/.planning/quick/260929-lh3-favoriten-eigenes-symbol-wirkt-nicht/260929-lh3-PLAN.md new file mode 100644 index 0000000..12d486d --- /dev/null +++ b/.planning/quick/260929-lh3-favoriten-eigenes-symbol-wirkt-nicht/260929-lh3-PLAN.md @@ -0,0 +1,67 @@ +--- +quick_id: 260929-lh3 +type: quick +wave: 1 +autonomous: true +--- + +# Quick 260929-lh3: Favoriten — eigene Symbol-Adresse wirkt nicht + +## User reports (29.09.2026, alpha 8c644de) + +1. Favorite with URL https://docuvita.ctl.local/server/services/web/ shows a black circle with a white "V" + instead of the page's favicon (visible in the browser tab). +2. Setting an explicit icon URL ("Symbol-Adresse", field `iconUrl`) to + https://nextcloud.com/c/uploads/2025/10/Nextcloud_01-standard-logo.png on a favorite does not change the shown icon. + +## Measured facts (orchestrator, from inside the alpha api container) + +- docuvita.ctl.local resolves (172.16.0.46). Server-side GET of the page returns **400** but the HTML contains + ``. + Server-side GET of that icon returns **404 text/html** (also with a Chrome User-Agent). Root /favicon.ico → 404. + → docuvita refuses the files to the server; the browser can load them (user sees the icon in the tab). +- Discovery (`apps/api/src/favorites/icon-discovery.service.ts` `discoverFavoriteIconUrl`) ignores non-2xx HTML + (`fetchHtml` returns null) → falls back to `{origin}/favicon.ico`; proxy fails → browser direct + `{origin}/favicon.ico` shows the "V" (a real icon served to browsers at the root). +- Explicit `iconUrl` that the server cannot fetch → API answers 422 `iconUrlUnreachable` (quick 260923-lrr), + so the user cannot set the docuvita icon URL at all. + +## Task 1: Explicit icon URL change must show immediately (bug 2) + +- files: apps/api/src/favorites/*, apps/web/src/components/dashboard/widgets/favorites-widget.tsx, apps/web/src/lib/favorites-api.ts (+ tests) +- action: Reproduce locally (admin/admin123, favorites widget): set/change `iconUrl` to a reachable PNG (e.g. the Nextcloud URL, + and a second different one). Find why the tile keeps the old image — likely the icon proxy URL + (`/favorites/:id/icon?...`) does not change when `iconUrl` changes (browser/HTTP cache, Cache-Control on the proxy + response, `iconVersion` only bumped on upload, or the server returns a cached/discovered icon instead of the explicit one). + Fix at the root: the explicit `iconUrl` wins over discovery, and any change of `iconUrl` changes the image URL + (e.g. cache-buster from `iconVersion` bumped on every iconUrl change, or a hash of iconUrl). Add regression tests + (API: PATCH iconUrl bumps version / proxy serves new bytes; web: tile src changes when iconUrl changes). +- verify: api + web tests for favorites green. +- done: commit `fix(favorites): geaenderte Symbol-Adresse wird sofort angezeigt`. + +## Task 2: Accept icon URLs the server cannot fetch; browser loads them directly (bug 1) + +- action: + - API: an explicit `iconUrl` that is a valid http/https URL is stored even if the server cannot fetch it + (no more 422 for "unreachable"; keep validation of scheme/length and keep rejecting non-image responses only + when the server DID get a response with a non-image content type — decide and document). Keep SSRF guard for + server-side fetches unchanged. + - Web tile: chain for an explicit `iconUrl`: proxy image → on error the browser loads `iconUrl` directly + (`` with referrerPolicy="no-referrer", only http/https) → letter fallback. Existing chain for discovered icons unchanged. + - Discovery improvement (small, safe): if the page answers non-2xx but returns HTML with a ``, + still use that icon URL (so docuvita-like servers yield `/webclient/.../favicon.ico`, which the browser can then load directly). + - Remove/adjust the now-unused `iconUrlUnreachable` error text (de/en) if no longer reachable. +- verify: api + web favorites tests green; type-check/lint green; biome web ≤ 55, api ≤ 82. +- done: commit `fix(favorites): Symbol-Adresse auch speichern, wenn nur der Browser sie laden kann`. + +## Task 3: CHANGELOG + rebuild + +- CHANGELOG `## Unveröffentlicht` → `### Behoben`: two plain-German bullets (Sie-Form) for both fixes. +- `docker compose up -d --build web api`. +- Commit `docs(changelog): Favoriten-Symbole`. + +## Constraints + +- Commit locally only, NEVER git push. Commits end with `Co-Authored-By: Claude Opus 5.5 (1M context) `. +- Commit with explicit paths only. +- Browser check is done by the orchestrator. diff --git a/.planning/quick/260929-lh3-favoriten-eigenes-symbol-wirkt-nicht/260929-lh3-SUMMARY.md b/.planning/quick/260929-lh3-favoriten-eigenes-symbol-wirkt-nicht/260929-lh3-SUMMARY.md new file mode 100644 index 0000000..f1f3009 --- /dev/null +++ b/.planning/quick/260929-lh3-favoriten-eigenes-symbol-wirkt-nicht/260929-lh3-SUMMARY.md @@ -0,0 +1,82 @@ +--- +quick_id: 260929-lh3 +phase: quick +plan: 260929-lh3 +subsystem: favorites +tags: [favorites, icons, icon-discovery, browser-fallback] +status: complete +commits: 3 +plan_head_before: 8c644de5dad56a0394a14a00180a125919565246 +plan_head_after: 0e72ad45f8833cb93ae9a8c0afa1f6d4e948e3e0 +actuals: + tasks: 3 + commits: 3 +key-files: + modified: + - apps/api/src/favorites/favorites.service.ts + - apps/api/src/favorites/icon-discovery.service.ts + - apps/api/src/favorites/dto/create-favorite.dto.ts + - apps/api/src/favorites/dto/update-favorite.dto.ts + - apps/web/src/components/dashboard/widgets/favorites-widget.tsx + - apps/web/src/lib/favorites-api.ts + - apps/web/src/messages/de.json + - apps/web/src/messages/en.json + - CHANGELOG.md +--- + +# Quick 260929-lh3: Favoriten-Symbol-Adresse Summary + +Explicit icon URLs are now stored even when the server cannot fetch them, the tile loads them directly in the browser, discovery uses `` from non-2xx HTML pages, and a newly entered icon URL replaces a previously uploaded icon. + +## Commits + +- 7188c5b `fix(favorites): geaenderte Symbol-Adresse wird sofort angezeigt` (Task 1) +- b15c746 `fix(favorites): Symbol-Adresse auch speichern, wenn nur der Browser sie laden kann` (Task 2) +- 0e72ad4 `docs(changelog): Favoriten-Symbole` (Task 3) + +## Root cause, bug 2 ("Symbol-Adresse wirkt nicht") + +Measured, not assumed: + +- Local reproduction (API with curl, and the real widget in headless Chromium via CDP): changing `iconUrl` on a favorite WITHOUT an uploaded icon works. `iconVersion` is bumped, the tile is remounted, the `` src changes (`?v=1` -> `?v=2`), and the bytes change (naturalWidth 626 -> 48). The proxy, `Cache-Control` and Next rewrite are not the cause. +- The alpha database (read-only psql) shows the save path works there too: favorite "Medon" holds the Nextcloud URL with `iconVersion = 1`. No favorite on alpha has `uploadedIconMime` set, and the alpha web image contains the `?v=` code. +- The real defect found in the code: `getIconBytes` always serves the uploaded file when `uploadedIconMime` is set, and `update()` never cleared it. A newly entered `iconUrl` was therefore saved but invisible for any favorite with an uploaded icon (the form even said "Ein hochgeladenes Symbol hat Vorrang"). Fixed: a new, different, non-empty `iconUrl` now clears `uploadedIconMime`, removes the file, and bumps `iconVersion`. An unchanged `iconUrl` (the form resends it on every save) leaves the upload alone. +- Caveat for the orchestrator: for the exact alpha "Medon" case (no upload) I could not reproduce a stale display; the alpha row and local browser behavior are both correct. The browser check on alpha (https, NPM, basic auth) remains the only place this can still show. If it still fails there with the new build, capture the network request of `/api-proxy/favorites//icon?v=1` in the alpha browser. + +## Root cause, bug 1 (black circle with "V") + +`fetchHtml` dropped every non-2xx response, so docuvita (answers the server with 400 but ships ``) fell back to `{origin}/favicon.ico`; the proxy failed and the browser showed the root favicon (the "V"). And an explicit `iconUrl` was rejected by the 422 fetch probe because docuvita returns 404 HTML to the server. + +## Changes + +- API: `assertIconUrlLoadable` (422) replaced by `assertIconUrlWellFormed` (http/https, <= 2048 chars, else 400); DTO `@MaxLength(2048)` on both DTOs. Decision, documented in code: even a response the server DID receive with a non-image type does not reject, because "server gets no image" does not mean "browser gets none". SSRF guard for server-side fetches is unchanged. +- Discovery: `fetchWithRedirectGuard` got `allowErrorStatus` (used only by the HTML search; `fetchIconBytes` stays strict). On a non-2xx page only `` counts, not `og:image`. +- Web tile: proxy -> `iconUrl` direct (`referrerPolicy="no-referrer"`, http/https only) -> `{origin}/favicon.ico` (skipped when identical) -> letter. Existing chain for discovered icons behaves as before. +- Removed the `iconUrlUnreachable` reason, 422 handling and de/en texts; adjusted the upload hint (a newly entered address replaces an upload). +- CHANGELOG: two plain-German bullets under Unveröffentlicht / Behoben. +- Rebuilt `web` and `api` (`docker compose up -d --build`, healthy). Smoke test against the rebuilt API: URL the server cannot fetch is saved (proxy answers 502, tile falls back to browser), `javascript:` is rejected with 400. Test favorite deleted afterwards. + +## Verification + +- API favorites specs: 99 tests green. Web favorites widget, favorites-api and messages tests: 47 green. `tsc --noEmit` clean for web and api. +- Biome: web 55 warnings, api 82 warnings (at the limits, not above). + +## Deviations from Plan + +- [Rule 2 - Missing validation] Plan said "keep validation of scheme/length", but none existed for `iconUrl`; added form validation (service + DTO length) so the SSRF-relevant direct browser load never receives non-http(s) schemes. +- Task 1 needed no web change: the existing test "Speichern mit neuer Logo-Adresse ... ?v=1" already covers the src change; API regression tests were added. +- Commits were made on `main` as instructed (no worktree). + +## Known Stubs + +None. + +## Self-Check: PASSED + +Commits 7188c5b, b15c746, 0e72ad4 exist; SUMMARY location correct; PLAN/SUMMARY/STATE not committed. + +## Browser-Pruefung (Orchestrator, 29.09.) + +- Favorit „Claude“: iconUrl -> Nextcloud-PNG (PATCH 200), nach Neuladen Proxy-Bild ?v=5 mit 626 px Breite geladen. +- iconUrl -> docuvita-Brand-Icon (vom Dev-Host nicht aufloesbar): PATCH 200 (frueher 422), Kachel faellt sauber zurueck. Im Firmennetz laedt der Browser direkt. +- Zurueckgesetzt auf das urspruengliche Symbol.