diff --git a/e2e/src/web/specs/asset-viewer/detail-panel.e2e-spec.ts b/e2e/src/web/specs/asset-viewer/detail-panel.e2e-spec.ts
index 072b48908e..2f90e4e3d8 100644
--- a/e2e/src/web/specs/asset-viewer/detail-panel.e2e-spec.ts
+++ b/e2e/src/web/specs/asset-viewer/detail-panel.e2e-spec.ts
@@ -1,16 +1,23 @@
 import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk';
 import { expect, test } from '@playwright/test';
+import type { Socket } from 'socket.io-client';
 import { utils } from 'src/utils';
 
 test.describe('Detail Panel', () => {
   let admin: LoginResponseDto;
   let asset: AssetMediaResponseDto;
+  let websocket: Socket;
 
   test.beforeAll(async () => {
     utils.initSdk();
     await utils.resetDatabase();
     admin = await utils.adminSetup();
     asset = await utils.createAsset(admin.accessToken);
+    websocket = await utils.connectWebsocket(admin.accessToken);
+  });
+
+  test.afterAll(() => {
+    utils.disconnectWebsocket(websocket);
   });
 
   test('can be opened for shared links', async ({ page }) => {
@@ -57,4 +64,23 @@ test.describe('Detail Panel', () => {
     await expect(textarea).toBeVisible();
     await expect(textarea).not.toBeDisabled();
   });
+
+  test('description changes are visible after reopening', async ({ context, page }) => {
+    await utils.setAuthCookies(context, admin.accessToken);
+    await page.goto(`/photos/${asset.id}`);
+    await page.waitForSelector('#immich-asset-viewer');
+
+    await page.getByRole('button', { name: 'Info' }).click();
+    const textarea = page.getByRole('textbox', { name: 'Add a description' });
+    await textarea.fill('new description');
+    await expect(textarea).toHaveValue('new description');
+
+    await page.getByRole('button', { name: 'Info' }).click();
+    await expect(textarea).not.toBeVisible();
+    await page.getByRole('button', { name: 'Info' }).click();
+    await expect(textarea).toBeVisible();
+
+    await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id });
+    await expect(textarea).toHaveValue('new description');
+  });
 });
diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte
index 91238bb9e7..2148ff7dda 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte
@@ -83,7 +83,7 @@
   let isLiked: ActivityResponseDto | null = null;
   let numberOfComments: number;
   let fullscreenElement: Element;
-  let unsubscribe: () => void;
+  let unsubscribes: (() => void)[] = [];
   let zoomToggle = () => void 0;
   let copyImage: () => Promise<void>;
 
@@ -172,6 +172,12 @@
     }
   };
 
+  const onAssetUpdate = (assetUpdate: AssetResponseDto) => {
+    if (assetUpdate.id === asset.id) {
+      asset = assetUpdate;
+    }
+  };
+
   $: {
     if (isShared && asset.id) {
       handlePromiseError(getFavorite());
@@ -180,11 +186,11 @@
   }
 
   onMount(async () => {
-    unsubscribe = websocketEvents.on('on_upload_success', (assetUpdate) => {
-      if (assetUpdate.id === asset.id) {
-        asset = assetUpdate;
-      }
-    });
+    unsubscribes.push(
+      websocketEvents.on('on_upload_success', onAssetUpdate),
+      websocketEvents.on('on_asset_update', onAssetUpdate),
+    );
+
     await navigate({ targetRoute: 'current', assetId: asset.id });
     slideshowStateUnsubscribe = slideshowState.subscribe((value) => {
       if (value === SlideshowState.PlaySlideshow) {
@@ -225,7 +231,10 @@
     if (shuffleSlideshowUnsubscribe) {
       shuffleSlideshowUnsubscribe();
     }
-    unsubscribe?.();
+
+    for (const unsubscribe of unsubscribes) {
+      unsubscribe();
+    }
   });
 
   $: {
diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte
index 268de61f04..2dd5ff1a4d 100644
--- a/web/src/lib/components/asset-viewer/detail-panel.svelte
+++ b/web/src/lib/components/asset-viewer/detail-panel.svelte
@@ -7,7 +7,6 @@
   import { locale } from '$lib/stores/preferences.store';
   import { featureFlags } from '$lib/stores/server-config.store';
   import { user } from '$lib/stores/user.store';
-  import { websocketEvents } from '$lib/stores/websocket';
   import { getAssetThumbnailUrl, getPeopleThumbnailUrl, handlePromiseError, isSharedLink } from '$lib/utils';
   import { delay, isFlipped } from '$lib/utils/asset-utils';
   import {
@@ -30,7 +29,7 @@
     mdiAccountOff,
   } from '@mdi/js';
   import { DateTime } from 'luxon';
-  import { createEventDispatcher, onMount } from 'svelte';
+  import { createEventDispatcher } from 'svelte';
   import { slide } from 'svelte/transition';
   import { getByteUnitString } from '$lib/utils/byte-units';
   import { handleError } from '$lib/utils/handle-error';
@@ -99,14 +98,6 @@
 
   $: unassignedFaces = asset.unassignedFaces || [];
 
-  onMount(() => {
-    return websocketEvents.on('on_asset_update', (assetUpdate) => {
-      if (assetUpdate.id === asset.id) {
-        asset = assetUpdate;
-      }
-    });
-  });
-
   const dispatch = createEventDispatcher<{
     close: void;
   }>();