From aad07184fd73af1d2d2af0f25bee523e03845c18 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Wed, 6 May 2026 22:02:50 +0200 Subject: [PATCH] fix(freshness): surface silent failures when pulling latest queryFileContent threw on octokit errors (stale SHA 404, expired token, network blip) and the rejection bubbled up unhandled through pullLatest and onBadgeClick, leaving the badge stuck on "Outdated" with no log or toast. Wrap the octokit call, log on failure, clear the cached SHA so the next click re-resolves it, and show an error toast. Also fix a dead `if (!user || !repo) { null }` that did nothing. --- src/components/StackedNote.vue | 32 ++++++++++++++++++------------ src/hooks/useNoteFreshness.hook.ts | 9 +++++++++ src/modules/repo/services/repo.ts | 29 +++++++++++++++------------ 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/components/StackedNote.vue b/src/components/StackedNote.vue index 230595c..5b180d2 100644 --- a/src/components/StackedNote.vue +++ b/src/components/StackedNote.vue @@ -26,6 +26,7 @@ import { useUserRepoStore } from "@/modules/repo/store/userRepo.store" import { encodeUTF8ToBase64 } from "@/utils/decodeBase64ToUTF8" import { getFileLanguage, isMarkdownPath } from "@/utils/fileLanguage" import { filenameToNoteTitle } from "@/utils/noteTitle" +import { errorMessage } from "@/utils/notif" const LinkedNotes = defineAsyncComponent( () => import("@/components/LinkedNotes.vue") @@ -241,21 +242,26 @@ const onConflictCancel = () => { } const onBadgeClick = async () => { - if (freshnessStatus.value !== "outdated") { - await checkFreshness() - return - } + try { + if (freshnessStatus.value !== "outdated") { + await checkFreshness() + return + } - const hasUnsavedEdits = rawContent.value !== initialRawContent.value - if (hasUnsavedEdits) { - conflictOpen.value = true - return - } + const hasUnsavedEdits = rawContent.value !== initialRawContent.value + if (hasUnsavedEdits) { + conflictOpen.value = true + return + } - const newRaw = await pullLatest() - if (newRaw !== null) { - rawContent.value = newRaw - initialRawContent.value = newRaw + const newRaw = await pullLatest() + if (newRaw !== null) { + rawContent.value = newRaw + initialRawContent.value = newRaw + } + } catch (error) { + console.error("freshness badge click failed", error) + errorMessage("❌ Couldn't pull latest from GitHub") } } diff --git a/src/hooks/useNoteFreshness.hook.ts b/src/hooks/useNoteFreshness.hook.ts index b0ce322..617ca8b 100644 --- a/src/hooks/useNoteFreshness.hook.ts +++ b/src/hooks/useNoteFreshness.hook.ts @@ -51,13 +51,22 @@ export const useNoteFreshness = ({ const pullLatest = async (): Promise => { if (!path.value) return null + const usedCachedSha = latestSha.value !== null const remoteSha = latestSha.value ?? (await fetchLatestSha(path.value)) if (!remoteSha) { + console.warn("pullLatest: could not resolve remote sha", { path: path.value }) status.value = "offline" return null } const fileContent = await queryFileContent(user, repo, remoteSha) if (!fileContent) { + console.warn("pullLatest: failed to fetch blob content", { + path: path.value, + remoteSha, + usedCachedSha + }) + // Cached SHA may be stale — clear so the next click re-resolves it. + if (usedCachedSha) latestSha.value = null status.value = "offline" return null } diff --git a/src/modules/repo/services/repo.ts b/src/modules/repo/services/repo.ts index 72fbfa5..7ba34c2 100644 --- a/src/modules/repo/services/repo.ts +++ b/src/modules/repo/services/repo.ts @@ -121,20 +121,23 @@ export const queryFileContent = async ( repo: string, sha: string ) => { - const octokit = await getOctokit() - if (!user || !repo) { - null + return null } - const file = await octokit.request( - "GET /repos/{owner}/{repo}/git/blobs/{file_sha}", - { - owner: user, - repo: repo, - file_sha: sha - } - ) - - return file?.data.content ?? null + try { + const octokit = await getOctokit() + const file = await octokit.request( + "GET /repos/{owner}/{repo}/git/blobs/{file_sha}", + { + owner: user, + repo: repo, + file_sha: sha + } + ) + return file?.data.content ?? null + } catch (error) { + console.warn("queryFileContent failed", { user, repo, sha, error }) + return null + } }