fix: Bug - Images and files in chat doesn't load

This commit is contained in:
2026-07-14 11:27:19 +02:00
parent 41ebaf2407
commit 20d7f22fd2
21 changed files with 631 additions and 38 deletions
+14
View File
@@ -25,6 +25,20 @@ Durable rules for AI agents working on this project. Read this file at session s
## Lessons
### Keep `NgOptimizedImage` off runtime blob and data URLs [angular] [images]
- **Trigger:** Angular template lint suggests replacing `[src]` with `ngSrc` for a user-uploaded image rendered from `blob:` or `data:`.
- **Rule:** Keep a plain `src` binding, document/disable `prefer-ngsrc`, and use native loading/decoding plus the app's own lifecycle controls; Angular throws `NG02952` for blob/data `ngSrc`.
- **Why:** `NgOptimizedImage` targets network/CDN images and cannot resize, preload, or safely manage renderer-created attachment blobs.
- **Example:** chat attachment thumbnails use `[src]="attachment.objectUrl" loading="lazy" decoding="async"`, never `[ngSrc]`.
### Read the exact Obsidian bug note before diagnosing a named ticket [workflow] [bugs]
- **Trigger:** The user names a `Bug - …` ticket, but the worktree already contains plausible changes or a similarly named resolved ticket.
- **Rule:** Resolve the exact note under `Log/Bugs/`, read every reported variant and reproduction step, and only then decide which code changes and status update belong to that ticket.
- **Why:** attachment reload-host changes looked related to “Images and files in chat doesn't load” but came from a separate resolved ticket and did not cover the reported channel-switch state regression.
- **Example:** read `/home/ludde/Nextcloud/Obsidian Vault/Log/Bugs/Bug - Images and files in chat doesn't load.md` before implementing or committing its fix.
### Run `npm run i18n:sync` after editing any `public/i18n/catalog/*.json` file [i18n] [testing]
- **Trigger:** Added new `call.errors.*` keys to `toju-app/public/i18n/catalog/call.json` and used them in code; the full test run failed in `app-i18n-catalog.rules.spec.ts` with "Missing i18n keys" even though the keys existed in the catalog file.
+9
View File
@@ -79,13 +79,18 @@ This area does **not** own:
- Receive strategy is decided once at request time by `canReceiveAttachment` (`attachment.logic.ts`): ≤ 10 MB assembles in memory everywhere; > 10 MB streams to disk on Electron/Capacitor, assembles in memory on the browser up to its 50 MB persist cap, and is rejected with a visible `fileTooLarge` error beyond that. `handleFileChunk` must accept whatever the request gate admitted — a stricter chunk-time size cap silently drops chunks and stalls the transfer.
- Visibility-based blob lifecycle on desktop: revoke `blob:` URLs when messages scroll off-screen if disk can rehydrate.
- Startup hydration is an availability boundary: file requests and host re-announcements wait for persisted metadata before inspecting local files. A host re-announces persisted files after reload and on peer connection even from non-chat routes, and can recover an original Electron source path by copying it into app data on demand before serving.
- Startup database hydration merges persisted metadata into the live runtime attachment map; it must preserve attachments announced during initialization and completed runtime state (`available`, progress, display URL) while filling missing local paths. Replacing the map can regress a completed download to Retry, spinner, or 100% after navigation.
- Starting a request updates the runtime version immediately so inline cards and galleries show pending/download state at zero bytes; exhausting all candidate peers surfaces `fileNotFound` instead of silently clearing the pending request. A repeat host announce re-queues guarded auto-download recovery for eligible media.
- Display-blob memory invariants (added 2026-07-14, RAM investigation):
- Inline hydration (`chat-message-item` effect) only runs for messages that are visible or within the `IntersectionObserver` root margin — gated by `attachment-hydration-visibility.rules.ts`. Off-screen rows never load blobs.
- Visibility observation uses the rendered message row (`componentHost.firstElementChild`), not the boxless Angular component host; otherwise returning to a channel leaves revoked attachments on permanent spinners.
- Disk-to-blob hydration is deduplicated per attachment and capped at two active tasks. Offscreen/destroy lifecycle cancellation is checked after every IPC read and before object-URL assignment, so rapid channel switches cannot accumulate stale full-file buffers or orphaned blobs; pinned fullscreen/gallery attachments are exempt.
- Disk-hydrated blobs are **not** duplicated into `AttachmentRuntimeStore.originalFiles`; peer requests are served from the disk path (`streamRequestedFile` prefers `resolveExistingPath`). `originalFiles` only holds uploads/downloads that have no disk copy yet.
- `revokeAttachmentDisplayBlob` also drops the `originalFiles` entry when `savedPath` exists, so revocation actually frees the bytes.
- Message rows always revoke their display blobs on destroy (pins are respected), not only when they were visible.
- Room switch sweeps display blobs of all other rooms (`releaseDisplayBlobsForInactiveRooms`, driven by `collectMessageIdsForInactiveRoomBlobRelease`). Messages with unknown room mapping are left alone.
- "Shared from your device" badge only when bytes are local to the viewing user.
- Blob-backed chat thumbnails use plain `src` with native lazy loading and async decoding. `NgOptimizedImage` is forbidden for these URLs because Angular rejects `blob:` inputs; fullscreen images remain eager.
---
@@ -121,6 +126,10 @@ This area does **not** own:
| Date | Change |
|------|--------|
| 2026-07-14 | Bounded display hydration to two deduplicated tasks, cancelled stale channel work before blob assignment, and added native lazy/async thumbnail hints |
| 2026-07-14 | Fixed channel-return hydration by observing the rendered message row instead of the boxless component host |
| 2026-07-14 | Made zero-byte requests visible, surfaced async peer-exhaustion failures, and retried eligible media when a host re-announces |
| 2026-07-14 | Preserved live attachment/download state when startup database hydration completes after realtime events |
| 2026-07-14 | Prevented reload-time `file-not-found` responses by waiting for metadata hydration, re-announcing hosts outside chat routes, and recovering persisted source paths on demand |
| 2026-07-14 | Blob-memory invariants: visibility-gated hydration, no `originalFiles` duplication for disk-backed blobs, revoke-on-destroy, inactive-room blob sweep |
| 2026-07-13 | Capacitor download/export to public `Documents` via `CapacitorAttachmentExportService` |