From d8a59467a0a07f303af8af200e9b7e4a35b2b042 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Sun, 3 May 2026 23:32:37 +0200 Subject: [PATCH] refactor(github-content): expose conflict info and add latest-sha lookup updateFile/createFile now return { sha, conflict } so 409/422 from GitHub can drive a UI flow instead of being swallowed as a generic save error. Also adds fetchLatestSha(path) for cheap freshness checks against HEAD. --- src/hooks/useCheckboxCommit.hook.ts | 2 +- src/hooks/useGitHubContent.hook.ts | 38 ++++++++++++++++++++++++----- src/views/FleetingNotes.vue | 2 +- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/hooks/useCheckboxCommit.hook.ts b/src/hooks/useCheckboxCommit.hook.ts index d69d140..b4f5cea 100644 --- a/src/hooks/useCheckboxCommit.hook.ts +++ b/src/hooks/useCheckboxCommit.hook.ts @@ -74,7 +74,7 @@ export const useCheckboxCommit = ({ isCommitting.value = true - const newSha = await updateFile({ + const { sha: newSha } = await updateFile({ content: pendingContent.value, path: pathValue, sha: currentSha.value diff --git a/src/hooks/useGitHubContent.hook.ts b/src/hooks/useGitHubContent.hook.ts index 68500bc..cf6d9e6 100644 --- a/src/hooks/useGitHubContent.hook.ts +++ b/src/hooks/useGitHubContent.hook.ts @@ -2,6 +2,8 @@ import { getOctokit } from "@/modules/repo/services/octo" import { encodeUTF8ToBase64 } from "@/utils/decodeBase64ToUTF8" import { confirmMessage, errorMessage } from "@/utils/notif" +const isConflictStatus = (status: number) => status === 409 || status === 422 + export const useGitHubContent = ({ user, repo @@ -9,6 +11,21 @@ export const useGitHubContent = ({ user: string repo: string }) => { + const fetchLatestSha = async (path: string): Promise => { + try { + const octokit = await getOctokit() + const response = await octokit.request( + "GET /repos/{owner}/{repo}/contents/{path}", + { owner: user, repo, path } + ) + const data = response?.data + if (Array.isArray(data) || !data) return null + return "sha" in data ? data.sha : null + } catch { + return null + } + } + const putFile = async ({ content, path, @@ -17,7 +34,7 @@ export const useGitHubContent = ({ content: string path: string sha?: string - }) => { + }): Promise<{ sha: string | null; conflict: boolean }> => { try { const octokit = await getOctokit() @@ -35,18 +52,27 @@ export const useGitHubContent = ({ confirmMessage("✅ Note saved") - return response?.data.content?.sha ?? null + return { sha: response?.data.content?.sha ?? null, conflict: false } } catch (error) { + const status = (error as { status?: number })?.status + if (status && isConflictStatus(status)) { + errorMessage("⚠ Conflict: this note changed on GitHub") + console.warn(error) + return { sha: null, conflict: true } + } errorMessage("❌ Note could not be saved") console.warn(error) + return { sha: null, conflict: false } } - - return null } return { - updateFile: async (props: { content: string; path: string; sha: string }) => - putFile(props), + fetchLatestSha, + updateFile: async (props: { + content: string + path: string + sha: string + }) => putFile(props), createFile: async (props: { content: string; path: string }) => putFile(props) } diff --git a/src/views/FleetingNotes.vue b/src/views/FleetingNotes.vue index 284b291..853fe13 100644 --- a/src/views/FleetingNotes.vue +++ b/src/views/FleetingNotes.vue @@ -82,7 +82,7 @@ watch(mode, async (newMode) => { newContent.value }` - const newSha = await createFile({ + const { sha: newSha } = await createFile({ content, path: newContentPath })