diff --git a/TODO.md b/TODO.md
index 8130224..8d3300e 100644
--- a/TODO.md
+++ b/TODO.md
@@ -255,6 +255,16 @@
- backend debug broadcasts
## Recent Change Log
+- Date: `2026-03-16`
+- What changed:
+ - Bumped frontend asset version and added result-modal initialization guards to avoid click failures when browser cache serves mismatched HTML/JS.
+- Why it changed:
+ - The result modal stopped opening after the modal markup refactor, which is consistent with stale cached frontend assets or partially initialized modal DOM.
+- How it was verified:
+ - static code inspection of modal DOM/JS bindings
+- What is still risky or incomplete:
+ - Browser cache behavior itself was not fully reproduced here, so a hard refresh may still be needed in an already-open client session.
+
- Date: `2026-03-16`
- What changed:
- Envato preview extraction now also inspects `INITIAL_HYDRATION_DATA` when direct page meta / JSON-LD preview URLs are missing.
diff --git a/frontend/app.js b/frontend/app.js
index ec8d0a9..7d2217e 100644
--- a/frontend/app.js
+++ b/frontend/app.js
@@ -49,6 +49,20 @@ const resultModalMediaFrame = document.getElementById("resultModalMediaFrame");
const resultModalVideo = document.getElementById("resultModalVideo");
const resultModalThumbnail = document.getElementById("resultModalThumbnail");
const resultModalEmbedNotice = document.getElementById("resultModalEmbedNotice");
+const resultModalReady = Boolean(
+ resultModal &&
+ resultModalTitle &&
+ resultModalSource &&
+ resultModalSnippet &&
+ resultModalReason &&
+ resultModalOpenExternal &&
+ resultModalDownload &&
+ closeResultModal &&
+ resultModalMediaFrame &&
+ resultModalVideo &&
+ resultModalThumbnail &&
+ resultModalEmbedNotice,
+);
let pendingDownload = null;
let cropStart = 0;
@@ -349,6 +363,9 @@ function hideModal(element) {
}
function resetResultModalMedia() {
+ if (!resultModalReady) {
+ return;
+ }
resultModalVideo.pause();
detachVideoSource(resultModalVideo);
resultModalMediaFrame.style.aspectRatio = "";
@@ -420,6 +437,10 @@ async function prepareDirectDownload(targetUrl) {
}
function openResultModal(item) {
+ if (!resultModalReady) {
+ logEvent("result:modal:error", { message: "result modal is not fully initialized" });
+ return;
+ }
activeResultItem = item;
resultModalTitle.textContent = item.title || "Untitled";
resultModalSource.textContent = item.source || "";
@@ -444,6 +465,9 @@ function openResultModal(item) {
}
function closeResultViewer() {
+ if (!resultModalReady) {
+ return;
+ }
if (!resultModal.classList.contains("hidden")) {
logEvent("result:modal:close", { title: activeResultItem?.title || "" });
}
@@ -586,24 +610,27 @@ confirmDownload.addEventListener("click", async () => {
});
closePreviewModal.addEventListener("click", closeModal);
-closeResultModal.addEventListener("click", closeResultViewer);
-resultModal.addEventListener("click", (event) => {
- if (event.target === resultModal) {
- closeResultViewer();
- }
-});
-resultModalDownload.addEventListener("click", async () => {
- if (!activeResultItem?.link) {
- return;
- }
- try {
- closeResultViewer();
- await prepareDirectDownload(activeResultItem.link);
- } catch (error) {
- downloadResult.textContent = error.message;
- logEvent("download:preview:error", { message: error.message, data: error.data || null, source: activeResultItem?.source || "" });
- }
-});
+if (resultModalReady) {
+ closeResultModal.addEventListener("click", closeResultViewer);
+ resultModal.addEventListener("click", (event) => {
+ if (event.target === resultModal) {
+ closeResultViewer();
+ }
+ });
+ resultModalDownload.addEventListener("click", async () => {
+ if (!activeResultItem?.link) {
+ return;
+ }
+ const currentItem = activeResultItem;
+ try {
+ closeResultViewer();
+ await prepareDirectDownload(currentItem.link);
+ } catch (error) {
+ downloadResult.textContent = error.message;
+ logEvent("download:preview:error", { message: error.message, data: error.data || null, source: currentItem?.source || "" });
+ }
+ });
+}
previewModal.addEventListener("click", (event) => {
if (event.target === previewModal) {
closeModal();
@@ -663,16 +690,18 @@ previewThumbnail.addEventListener("load", () => {
previewMediaFrame.style.aspectRatio = `${previewThumbnail.naturalWidth} / ${previewThumbnail.naturalHeight}`;
}
});
-resultModalVideo.addEventListener("loadedmetadata", () => {
- if (resultModalVideo.videoWidth > 0 && resultModalVideo.videoHeight > 0) {
- resultModalMediaFrame.style.aspectRatio = `${resultModalVideo.videoWidth} / ${resultModalVideo.videoHeight}`;
- }
-});
-resultModalThumbnail.addEventListener("load", () => {
- if (!resultModalVideo.src && resultModalThumbnail.naturalWidth > 0 && resultModalThumbnail.naturalHeight > 0) {
- resultModalMediaFrame.style.aspectRatio = `${resultModalThumbnail.naturalWidth} / ${resultModalThumbnail.naturalHeight}`;
- }
-});
+if (resultModalReady) {
+ resultModalVideo.addEventListener("loadedmetadata", () => {
+ if (resultModalVideo.videoWidth > 0 && resultModalVideo.videoHeight > 0) {
+ resultModalMediaFrame.style.aspectRatio = `${resultModalVideo.videoWidth} / ${resultModalVideo.videoHeight}`;
+ }
+ });
+ resultModalThumbnail.addEventListener("load", () => {
+ if (!resultModalVideo.src && resultModalThumbnail.naturalWidth > 0 && resultModalThumbnail.naturalHeight > 0) {
+ resultModalMediaFrame.style.aspectRatio = `${resultModalThumbnail.naturalWidth} / ${resultModalThumbnail.naturalHeight}`;
+ }
+ });
+}
for (const button of platformToggles) {
button.addEventListener("click", () => {
const platform = button.dataset.platformToggle;
diff --git a/frontend/index.html b/frontend/index.html
index 527f7c0..f420118 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -206,6 +206,6 @@
-
+