From 5fe82b24896fa56d1db77c47dd37c9fcfb1385bc Mon Sep 17 00:00:00 2001 From: soyalejolopez <88358406+soyalejolopez@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:34:48 -0500 Subject: [PATCH 1/5] Fix giscus like button not persisting first reaction (#1312) The report detail page embeds giscus for likes. On resources with no backing Discussion yet, the first reaction hit giscus bug #1312: giscus creates the discussion and adds the reaction server-side but never refetches, so the like visually reverts until a manual refresh. Fix the existing workaround so the remount reliably fires: - Event-driven remount: track whether a discussion existed at mount time and remount once when metadata reports it was just created by the user's reaction. - Remove the self-defeating hasDiscussion re-check from the blur-timer callback (it cancelled the very remount meant to persist the like). - Re-arm recovery when a speculative (blur-triggered) refetch finds no discussion, so a later genuine first reaction can still stick. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 61593e5a-6c5f-46eb-bb43-08ee66fce301 --- index.html | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 329a4f8..610de38 100644 --- a/index.html +++ b/index.html @@ -2334,7 +2334,13 @@

Browse all 54 scripts →

// fails to refetch, so the 👍 visually "reverts". We detect that the discussion // does not exist yet (via emit-metadata) and, once the user interacts, re-mount // giscus to pull the freshly-created discussion + reaction so it sticks. - const giscusState = { slug: null, hasDiscussion: false, refetched: false, pendingTimer: null }; + // existedAtMount: whether a backing discussion already existed the first time + // giscus reported metadata for the current slug (null = not yet known). It lets us + // tell a pre-existing discussion apart from one the user just created by reacting. + // speculativeRefetch: true when the current refetch was triggered by iframe focus + // (blur fallback) rather than an observed discussion creation, so we can re-arm if + // it turns out no discussion was actually created. + const giscusState = { slug: null, hasDiscussion: false, refetched: false, existedAtMount: null, speculativeRefetch: false, pendingTimer: null }; function giscusClearPending() { if (giscusState.pendingTimer) { clearTimeout(giscusState.pendingTimer); giscusState.pendingTimer = null; } @@ -2351,6 +2357,8 @@

Browse all 54 scripts →

} else { giscusState.hasDiscussion = false; giscusState.refetched = false; + giscusState.existedAtMount = null; + giscusState.speculativeRefetch = false; } const script = document.createElement('script'); script.src = "https://giscus.app/client.js"; @@ -2380,7 +2388,25 @@

Browse all 54 scripts →

const payload = event.data && event.data.giscus; if (!payload || !('discussion' in payload)) return; const d = payload.discussion; - giscusState.hasDiscussion = !!(d && d.id); + const exists = !!(d && d.id); + giscusState.hasDiscussion = exists; + // Record whether a discussion already existed the first time giscus reported in. + // Kept as a standalone `if` (not part of the chain below) so the re-arm branch can + // still run on the same message even when this is the first metadata we receive. + if (giscusState.existedAtMount === null) { + giscusState.existedAtMount = exists; + } + if (exists && giscusState.existedAtMount === false && !giscusState.refetched) { + // The discussion just came into existence — the user's first reaction created it + // (#1312). giscus won't refetch on its own, so remount once to make the 👍 stick. + giscusState.speculativeRefetch = false; + mountGiscus(giscusState.slug, document.documentElement.getAttribute('data-theme'), true); + } else if (!exists && giscusState.refetched && giscusState.speculativeRefetch) { + // A blur-triggered (speculative) refetch happened but there is still no discussion, + // so it was "wasted" (the iframe focus was a sign-in/comment, not a reaction). + // Re-arm recovery so a later genuine first reaction can still be refetched. + giscusState.refetched = false; + } }); // When the user clicks into the giscus iframe (e.g. to react) on a resource that @@ -2394,9 +2420,14 @@

Browse all 54 scripts →

if (!giscusState.slug || giscusState.hasDiscussion || giscusState.refetched || giscusState.pendingTimer) return; giscusState.pendingTimer = setTimeout(() => { giscusState.pendingTimer = null; - if (!giscusState.slug || giscusState.hasDiscussion || giscusState.refetched) return; + // Do NOT re-check hasDiscussion here: by now the reaction may have (just) created + // the discussion, which is exactly the case that needs a refetch. A remount is + // harmless (it only refetches current server state) and must fire so the 👍 sticks. + // Mark it speculative so we re-arm if it turns out no discussion was created. + if (!giscusState.slug || giscusState.refetched) return; + giscusState.speculativeRefetch = true; mountGiscus(giscusState.slug, document.documentElement.getAttribute('data-theme'), true); - }, 2500); + }, 3000); }, 0); }); From bb2b12273c04833c347ad9cc282c02a8257327d3 Mon Sep 17 00:00:00 2001 From: soyalejolopez <88358406+soyalejolopez@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:23:00 -0500 Subject: [PATCH 2/5] Fix giscus like not sticking on pre-existing empty (ghost) discussions Gate remount recovery on whether the discussion has reactions instead of whether it exists. A resource whose Discussion already exists with 0 reactions (a #1312 ghost created by an earlier reverted first-reaction) now arms the remount so the first reaction sticks, matching the brand-new discussion path. Active discussions (reactionCount > 0) are left untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 61593e5a-6c5f-46eb-bb43-08ee66fce301 --- index.html | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index 610de38..0902323 100644 --- a/index.html +++ b/index.html @@ -2340,7 +2340,7 @@

Browse all 54 scripts →

// speculativeRefetch: true when the current refetch was triggered by iframe focus // (blur fallback) rather than an observed discussion creation, so we can re-arm if // it turns out no discussion was actually created. - const giscusState = { slug: null, hasDiscussion: false, refetched: false, existedAtMount: null, speculativeRefetch: false, pendingTimer: null }; + const giscusState = { slug: null, hasDiscussion: false, hasReactions: false, refetched: false, existedAtMount: null, speculativeRefetch: false, pendingTimer: null }; function giscusClearPending() { if (giscusState.pendingTimer) { clearTimeout(giscusState.pendingTimer); giscusState.pendingTimer = null; } @@ -2356,6 +2356,7 @@

Browse all 54 scripts →

giscusState.refetched = true; } else { giscusState.hasDiscussion = false; + giscusState.hasReactions = false; giscusState.refetched = false; giscusState.existedAtMount = null; giscusState.speculativeRefetch = false; @@ -2389,7 +2390,12 @@

Browse all 54 scripts →

if (!payload || !('discussion' in payload)) return; const d = payload.discussion; const exists = !!(d && d.id); + const reactionCount = (d && typeof d.reactionCount === 'number') ? d.reactionCount : 0; giscusState.hasDiscussion = exists; + // A discussion with zero reactions is "empty" — either brand new, or a #1312 "ghost" + // (created by an earlier first-reaction that reverted). Both need remount recovery; + // only a discussion that already has reactions is treated as active and left alone. + giscusState.hasReactions = exists && reactionCount > 0; // Record whether a discussion already existed the first time giscus reported in. // Kept as a standalone `if` (not part of the chain below) so the re-arm branch can // still run on the same message even when this is the first metadata we receive. @@ -2401,23 +2407,23 @@

Browse all 54 scripts →

// (#1312). giscus won't refetch on its own, so remount once to make the 👍 stick. giscusState.speculativeRefetch = false; mountGiscus(giscusState.slug, document.documentElement.getAttribute('data-theme'), true); - } else if (!exists && giscusState.refetched && giscusState.speculativeRefetch) { - // A blur-triggered (speculative) refetch happened but there is still no discussion, + } else if (!giscusState.hasReactions && giscusState.refetched && giscusState.speculativeRefetch) { + // A blur-triggered (speculative) refetch happened but there are still no reactions, // so it was "wasted" (the iframe focus was a sign-in/comment, not a reaction). // Re-arm recovery so a later genuine first reaction can still be refetched. giscusState.refetched = false; } }); - // When the user clicks into the giscus iframe (e.g. to react) on a resource that - // has no discussion yet, schedule a single re-mount so the just-created discussion - // and its reaction are refetched instead of reverting. + // When the user clicks into the giscus iframe (e.g. to react) on a resource whose + // discussion has no reactions yet, schedule a single re-mount so the reaction is + // refetched instead of reverting. window.addEventListener('blur', () => { setTimeout(() => { const active = document.activeElement; const inGiscus = active && active.tagName === 'IFRAME' && active.classList.contains('giscus-frame'); if (!inGiscus) return; - if (!giscusState.slug || giscusState.hasDiscussion || giscusState.refetched || giscusState.pendingTimer) return; + if (!giscusState.slug || giscusState.hasReactions || giscusState.refetched || giscusState.pendingTimer) return; giscusState.pendingTimer = setTimeout(() => { giscusState.pendingTimer = null; // Do NOT re-check hasDiscussion here: by now the reaction may have (just) created From fd75c5d401752b4a3e7d26fc44d9936ce13b5bc9 Mon Sep 17 00:00:00 2001 From: soyalejolopez <88358406+soyalejolopez@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:23:00 -0500 Subject: [PATCH 3/5] Fix giscus like not sticking on pre-existing empty (ghost) discussions Gate remount recovery on whether the discussion has reactions instead of whether it exists. A resource whose Discussion already exists with 0 reactions (a #1312 ghost created by an earlier reverted first-reaction) now arms the remount so the first reaction sticks, matching the brand-new discussion path. Active discussions (reactionCount > 0) are left untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 61593e5a-6c5f-46eb-bb43-08ee66fce301 --- index.html | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index 6a29b0f..f6dcab1 100644 --- a/index.html +++ b/index.html @@ -2325,7 +2325,7 @@

Browse all 54 scripts →

// speculativeRefetch: true when the current refetch was triggered by iframe focus // (blur fallback) rather than an observed discussion creation, so we can re-arm if // it turns out no discussion was actually created. - const giscusState = { slug: null, hasDiscussion: false, refetched: false, existedAtMount: null, speculativeRefetch: false, pendingTimer: null }; + const giscusState = { slug: null, hasDiscussion: false, hasReactions: false, refetched: false, existedAtMount: null, speculativeRefetch: false, pendingTimer: null }; function giscusClearPending() { if (giscusState.pendingTimer) { clearTimeout(giscusState.pendingTimer); giscusState.pendingTimer = null; } @@ -2341,6 +2341,7 @@

Browse all 54 scripts →

giscusState.refetched = true; } else { giscusState.hasDiscussion = false; + giscusState.hasReactions = false; giscusState.refetched = false; giscusState.existedAtMount = null; giscusState.speculativeRefetch = false; @@ -2374,7 +2375,12 @@

Browse all 54 scripts →

if (!payload || !('discussion' in payload)) return; const d = payload.discussion; const exists = !!(d && d.id); + const reactionCount = (d && typeof d.reactionCount === 'number') ? d.reactionCount : 0; giscusState.hasDiscussion = exists; + // A discussion with zero reactions is "empty" — either brand new, or a #1312 "ghost" + // (created by an earlier first-reaction that reverted). Both need remount recovery; + // only a discussion that already has reactions is treated as active and left alone. + giscusState.hasReactions = exists && reactionCount > 0; // Record whether a discussion already existed the first time giscus reported in. // Kept as a standalone `if` (not part of the chain below) so the re-arm branch can // still run on the same message even when this is the first metadata we receive. @@ -2386,23 +2392,23 @@

Browse all 54 scripts →

// (#1312). giscus won't refetch on its own, so remount once to make the 👍 stick. giscusState.speculativeRefetch = false; mountGiscus(giscusState.slug, document.documentElement.getAttribute('data-theme'), true); - } else if (!exists && giscusState.refetched && giscusState.speculativeRefetch) { - // A blur-triggered (speculative) refetch happened but there is still no discussion, + } else if (!giscusState.hasReactions && giscusState.refetched && giscusState.speculativeRefetch) { + // A blur-triggered (speculative) refetch happened but there are still no reactions, // so it was "wasted" (the iframe focus was a sign-in/comment, not a reaction). // Re-arm recovery so a later genuine first reaction can still be refetched. giscusState.refetched = false; } }); - // When the user clicks into the giscus iframe (e.g. to react) on a resource that - // has no discussion yet, schedule a single re-mount so the just-created discussion - // and its reaction are refetched instead of reverting. + // When the user clicks into the giscus iframe (e.g. to react) on a resource whose + // discussion has no reactions yet, schedule a single re-mount so the reaction is + // refetched instead of reverting. window.addEventListener('blur', () => { setTimeout(() => { const active = document.activeElement; const inGiscus = active && active.tagName === 'IFRAME' && active.classList.contains('giscus-frame'); if (!inGiscus) return; - if (!giscusState.slug || giscusState.hasDiscussion || giscusState.refetched || giscusState.pendingTimer) return; + if (!giscusState.slug || giscusState.hasReactions || giscusState.refetched || giscusState.pendingTimer) return; giscusState.pendingTimer = setTimeout(() => { giscusState.pendingTimer = null; // Do NOT re-check hasDiscussion here: by now the reaction may have (just) created From d60425f9ff183925b5ef3b92530735125c9745ad Mon Sep 17 00:00:00 2001 From: soyalejolopez Date: Mon, 27 Jul 2026 14:21:53 -0500 Subject: [PATCH 4/5] Fix giscus like not persisting by removing reaction-destroying remount The per-resource giscus remount workaround for bug #1312 was itself destroying reactions. It remounted the iframe (container.innerHTML='') the instant a first reaction created the backing Discussion, at reactionCount 0, while the addReaction mutation was still in flight, aborting the commit before it landed. Every discussion created after the workaround shipped ended up with zero reactions; only a resource reacted to before it shipped kept its like. giscus persists reactions natively server-side, so remove the remount apparatus entirely: the emit-metadata listener, the blur speculative refetch, giscusClearPending, the data-emit-metadata attribute, and the isRefetch path. mountGiscus now just mounts. The residual #1312 in-session visual revert is cosmetic; the reaction is saved and correct on reload. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 61593e5a-6c5f-46eb-bb43-08ee66fce301 --- index.html | 104 ++++++++++------------------------------------------- 1 file changed, 18 insertions(+), 86 deletions(-) diff --git a/index.html b/index.html index f6dcab1..778e47f 100644 --- a/index.html +++ b/index.html @@ -1894,7 +1894,6 @@

Related resources

// Cleanup detail view state const giscusContainer = document.getElementById('giscusContainer'); if (giscusContainer) giscusContainer.innerHTML = ''; - giscusClearPending(); giscusState.slug = null; document.getElementById('catalogView').classList.remove('d-none'); @@ -2314,38 +2313,28 @@

Browse all 54 scripts →

parseTextBlock(source.slice(cursor)); } - // Tracks the giscus embed so we can work around giscus bug #1312: - // the FIRST reaction on a resource creates the backing Discussion but giscus - // fails to refetch, so the 👍 visually "reverts". We detect that the discussion - // does not exist yet (via emit-metadata) and, once the user interacts, re-mount - // giscus to pull the freshly-created discussion + reaction so it sticks. - // existedAtMount: whether a backing discussion already existed the first time - // giscus reported metadata for the current slug (null = not yet known). It lets us - // tell a pre-existing discussion apart from one the user just created by reacting. - // speculativeRefetch: true when the current refetch was triggered by iframe focus - // (blur fallback) rather than an observed discussion creation, so we can re-arm if - // it turns out no discussion was actually created. - const giscusState = { slug: null, hasDiscussion: false, hasReactions: false, refetched: false, existedAtMount: null, speculativeRefetch: false, pendingTimer: null }; - - function giscusClearPending() { - if (giscusState.pendingTimer) { clearTimeout(giscusState.pendingTimer); giscusState.pendingTimer = null; } - } - - function mountGiscus(slug, theme, isRefetch) { + // giscus like/reaction persistence — DELIBERATELY no remount workaround. + // + // History: giscus bug #1312 makes the FIRST reaction on a resource (the one that + // creates the backing Discussion) appear to visually "revert" in-session. Two earlier + // fixes tried to work around this by REMOUNTING the giscus iframe (container.innerHTML='') + // when emit-metadata reported the discussion existed. Server-side evidence proved those + // remounts were DESTROYING reactions: the remount fired while the addReaction mutation was + // still in flight and tore down the iframe before the commit landed, leaving discussions + // with zero reactions. giscus persists reactions natively server-side with no help from us + // (a resource reacted-to before any workaround shipped kept its 👍 perfectly). + // + // Any metadata-driven remount races giscus's own optimistic reactionCount update, so there + // is no safe signal to remount on. We therefore do nothing: reactions persist natively and + // show correctly on reload. Do NOT reintroduce a remount here — it reopens #1312-by-us and + // silently drops likes. + const giscusState = { slug: null }; + + function mountGiscus(slug, theme) { const container = document.getElementById('giscusContainer'); if (!container) return; container.innerHTML = ''; - giscusClearPending(); giscusState.slug = slug; - if (isRefetch) { - giscusState.refetched = true; - } else { - giscusState.hasDiscussion = false; - giscusState.hasReactions = false; - giscusState.refetched = false; - giscusState.existedAtMount = null; - giscusState.speculativeRefetch = false; - } const script = document.createElement('script'); script.src = "https://giscus.app/client.js"; script.setAttribute("data-repo", "microsoft/FastTrack"); @@ -2356,7 +2345,6 @@

Browse all 54 scripts →

script.setAttribute("data-term", slug); script.setAttribute("data-strict", "1"); script.setAttribute("data-reactions-enabled", "1"); - script.setAttribute("data-emit-metadata", "1"); script.setAttribute("data-input-position", "bottom"); script.setAttribute("data-theme", theme === "dark" ? "dark_dimmed" : "light"); script.setAttribute("data-lang", "en"); @@ -2366,62 +2354,6 @@

Browse all 54 scripts →

container.appendChild(script); } - // Learn whether a backing discussion exists for the open resource. giscus emits - // metadata on load and whenever the discussion changes; an empty id means "not - // created yet", so a reaction would hit the #1312 create-and-revert path. - window.addEventListener('message', (event) => { - if (event.origin !== 'https://giscus.app') return; - const payload = event.data && event.data.giscus; - if (!payload || !('discussion' in payload)) return; - const d = payload.discussion; - const exists = !!(d && d.id); - const reactionCount = (d && typeof d.reactionCount === 'number') ? d.reactionCount : 0; - giscusState.hasDiscussion = exists; - // A discussion with zero reactions is "empty" — either brand new, or a #1312 "ghost" - // (created by an earlier first-reaction that reverted). Both need remount recovery; - // only a discussion that already has reactions is treated as active and left alone. - giscusState.hasReactions = exists && reactionCount > 0; - // Record whether a discussion already existed the first time giscus reported in. - // Kept as a standalone `if` (not part of the chain below) so the re-arm branch can - // still run on the same message even when this is the first metadata we receive. - if (giscusState.existedAtMount === null) { - giscusState.existedAtMount = exists; - } - if (exists && giscusState.existedAtMount === false && !giscusState.refetched) { - // The discussion just came into existence — the user's first reaction created it - // (#1312). giscus won't refetch on its own, so remount once to make the 👍 stick. - giscusState.speculativeRefetch = false; - mountGiscus(giscusState.slug, document.documentElement.getAttribute('data-theme'), true); - } else if (!giscusState.hasReactions && giscusState.refetched && giscusState.speculativeRefetch) { - // A blur-triggered (speculative) refetch happened but there are still no reactions, - // so it was "wasted" (the iframe focus was a sign-in/comment, not a reaction). - // Re-arm recovery so a later genuine first reaction can still be refetched. - giscusState.refetched = false; - } - }); - - // When the user clicks into the giscus iframe (e.g. to react) on a resource whose - // discussion has no reactions yet, schedule a single re-mount so the reaction is - // refetched instead of reverting. - window.addEventListener('blur', () => { - setTimeout(() => { - const active = document.activeElement; - const inGiscus = active && active.tagName === 'IFRAME' && active.classList.contains('giscus-frame'); - if (!inGiscus) return; - if (!giscusState.slug || giscusState.hasReactions || giscusState.refetched || giscusState.pendingTimer) return; - giscusState.pendingTimer = setTimeout(() => { - giscusState.pendingTimer = null; - // Do NOT re-check hasDiscussion here: by now the reaction may have (just) created - // the discussion, which is exactly the case that needs a refetch. A remount is - // harmless (it only refetches current server state) and must fire so the 👍 sticks. - // Mark it speculative so we re-arm if it turns out no discussion was created. - if (!giscusState.slug || giscusState.refetched) return; - giscusState.speculativeRefetch = true; - mountGiscus(giscusState.slug, document.documentElement.getAttribute('data-theme'), true); - }, 3000); - }, 0); - }); - function renderDetailView(resource) { document.getElementById('catalogView').classList.add('d-none'); document.getElementById('detailView').classList.remove('d-none'); From aeb233c8994c4ebd6274e512c4e1f9085455a3a5 Mon Sep 17 00:00:00 2001 From: soyalejolopez Date: Mon, 27 Jul 2026 14:21:53 -0500 Subject: [PATCH 5/5] Fix giscus like not persisting by removing reaction-destroying remount The per-resource giscus remount workaround for bug #1312 was itself destroying reactions. It remounted the iframe (container.innerHTML='') the instant a first reaction created the backing Discussion, at reactionCount 0, while the addReaction mutation was still in flight, aborting the commit before it landed. Every discussion created after the workaround shipped ended up with zero reactions; only a resource reacted to before it shipped kept its like. giscus persists reactions natively server-side, so remove the remount apparatus entirely: the emit-metadata listener, the blur speculative refetch, giscusClearPending, the data-emit-metadata attribute, and the isRefetch path. mountGiscus now just mounts. The residual #1312 in-session visual revert is cosmetic; the reaction is saved and correct on reload. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 61593e5a-6c5f-46eb-bb43-08ee66fce301 --- index.html | 104 ++++++++++------------------------------------------- 1 file changed, 18 insertions(+), 86 deletions(-) diff --git a/index.html b/index.html index f6dcab1..778e47f 100644 --- a/index.html +++ b/index.html @@ -1894,7 +1894,6 @@

Related resources

// Cleanup detail view state const giscusContainer = document.getElementById('giscusContainer'); if (giscusContainer) giscusContainer.innerHTML = ''; - giscusClearPending(); giscusState.slug = null; document.getElementById('catalogView').classList.remove('d-none'); @@ -2314,38 +2313,28 @@

Browse all 54 scripts →

parseTextBlock(source.slice(cursor)); } - // Tracks the giscus embed so we can work around giscus bug #1312: - // the FIRST reaction on a resource creates the backing Discussion but giscus - // fails to refetch, so the 👍 visually "reverts". We detect that the discussion - // does not exist yet (via emit-metadata) and, once the user interacts, re-mount - // giscus to pull the freshly-created discussion + reaction so it sticks. - // existedAtMount: whether a backing discussion already existed the first time - // giscus reported metadata for the current slug (null = not yet known). It lets us - // tell a pre-existing discussion apart from one the user just created by reacting. - // speculativeRefetch: true when the current refetch was triggered by iframe focus - // (blur fallback) rather than an observed discussion creation, so we can re-arm if - // it turns out no discussion was actually created. - const giscusState = { slug: null, hasDiscussion: false, hasReactions: false, refetched: false, existedAtMount: null, speculativeRefetch: false, pendingTimer: null }; - - function giscusClearPending() { - if (giscusState.pendingTimer) { clearTimeout(giscusState.pendingTimer); giscusState.pendingTimer = null; } - } - - function mountGiscus(slug, theme, isRefetch) { + // giscus like/reaction persistence — DELIBERATELY no remount workaround. + // + // History: giscus bug #1312 makes the FIRST reaction on a resource (the one that + // creates the backing Discussion) appear to visually "revert" in-session. Two earlier + // fixes tried to work around this by REMOUNTING the giscus iframe (container.innerHTML='') + // when emit-metadata reported the discussion existed. Server-side evidence proved those + // remounts were DESTROYING reactions: the remount fired while the addReaction mutation was + // still in flight and tore down the iframe before the commit landed, leaving discussions + // with zero reactions. giscus persists reactions natively server-side with no help from us + // (a resource reacted-to before any workaround shipped kept its 👍 perfectly). + // + // Any metadata-driven remount races giscus's own optimistic reactionCount update, so there + // is no safe signal to remount on. We therefore do nothing: reactions persist natively and + // show correctly on reload. Do NOT reintroduce a remount here — it reopens #1312-by-us and + // silently drops likes. + const giscusState = { slug: null }; + + function mountGiscus(slug, theme) { const container = document.getElementById('giscusContainer'); if (!container) return; container.innerHTML = ''; - giscusClearPending(); giscusState.slug = slug; - if (isRefetch) { - giscusState.refetched = true; - } else { - giscusState.hasDiscussion = false; - giscusState.hasReactions = false; - giscusState.refetched = false; - giscusState.existedAtMount = null; - giscusState.speculativeRefetch = false; - } const script = document.createElement('script'); script.src = "https://giscus.app/client.js"; script.setAttribute("data-repo", "microsoft/FastTrack"); @@ -2356,7 +2345,6 @@

Browse all 54 scripts →

script.setAttribute("data-term", slug); script.setAttribute("data-strict", "1"); script.setAttribute("data-reactions-enabled", "1"); - script.setAttribute("data-emit-metadata", "1"); script.setAttribute("data-input-position", "bottom"); script.setAttribute("data-theme", theme === "dark" ? "dark_dimmed" : "light"); script.setAttribute("data-lang", "en"); @@ -2366,62 +2354,6 @@

Browse all 54 scripts →

container.appendChild(script); } - // Learn whether a backing discussion exists for the open resource. giscus emits - // metadata on load and whenever the discussion changes; an empty id means "not - // created yet", so a reaction would hit the #1312 create-and-revert path. - window.addEventListener('message', (event) => { - if (event.origin !== 'https://giscus.app') return; - const payload = event.data && event.data.giscus; - if (!payload || !('discussion' in payload)) return; - const d = payload.discussion; - const exists = !!(d && d.id); - const reactionCount = (d && typeof d.reactionCount === 'number') ? d.reactionCount : 0; - giscusState.hasDiscussion = exists; - // A discussion with zero reactions is "empty" — either brand new, or a #1312 "ghost" - // (created by an earlier first-reaction that reverted). Both need remount recovery; - // only a discussion that already has reactions is treated as active and left alone. - giscusState.hasReactions = exists && reactionCount > 0; - // Record whether a discussion already existed the first time giscus reported in. - // Kept as a standalone `if` (not part of the chain below) so the re-arm branch can - // still run on the same message even when this is the first metadata we receive. - if (giscusState.existedAtMount === null) { - giscusState.existedAtMount = exists; - } - if (exists && giscusState.existedAtMount === false && !giscusState.refetched) { - // The discussion just came into existence — the user's first reaction created it - // (#1312). giscus won't refetch on its own, so remount once to make the 👍 stick. - giscusState.speculativeRefetch = false; - mountGiscus(giscusState.slug, document.documentElement.getAttribute('data-theme'), true); - } else if (!giscusState.hasReactions && giscusState.refetched && giscusState.speculativeRefetch) { - // A blur-triggered (speculative) refetch happened but there are still no reactions, - // so it was "wasted" (the iframe focus was a sign-in/comment, not a reaction). - // Re-arm recovery so a later genuine first reaction can still be refetched. - giscusState.refetched = false; - } - }); - - // When the user clicks into the giscus iframe (e.g. to react) on a resource whose - // discussion has no reactions yet, schedule a single re-mount so the reaction is - // refetched instead of reverting. - window.addEventListener('blur', () => { - setTimeout(() => { - const active = document.activeElement; - const inGiscus = active && active.tagName === 'IFRAME' && active.classList.contains('giscus-frame'); - if (!inGiscus) return; - if (!giscusState.slug || giscusState.hasReactions || giscusState.refetched || giscusState.pendingTimer) return; - giscusState.pendingTimer = setTimeout(() => { - giscusState.pendingTimer = null; - // Do NOT re-check hasDiscussion here: by now the reaction may have (just) created - // the discussion, which is exactly the case that needs a refetch. A remount is - // harmless (it only refetches current server state) and must fire so the 👍 sticks. - // Mark it speculative so we re-arm if it turns out no discussion was created. - if (!giscusState.slug || giscusState.refetched) return; - giscusState.speculativeRefetch = true; - mountGiscus(giscusState.slug, document.documentElement.getAttribute('data-theme'), true); - }, 3000); - }, 0); - }); - function renderDetailView(resource) { document.getElementById('catalogView').classList.add('d-none'); document.getElementById('detailView').classList.remove('d-none');