From a40dd0de3e1f2f48b2141e02041b0e1f767631bb Mon Sep 17 00:00:00 2001 From: menzelj Date: Sun, 21 Jun 2026 21:20:15 +0000 Subject: [PATCH] 0.37.5: overwrite prompt for file upload + auto-reveal uploaded hidden files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reported "upload doesn't work, file never appears" was two things, both hit when uploading config files like .env: 1. Single-file upload used overwrite=false and dead-ended on "Already exists: .env — rename or remove the existing file first." with no way to replace the file. Now a conflict opens an Overwrite confirmation dialog (mirroring the copy/paste conflict flow) that retries with overwrite=true. 2. .env (and any dotfile) is hidden, so even a successful upload stayed invisible unless "Show hidden" was on. After an upload whose name/path has a dot-segment, "Show hidden" is now auto-enabled so the file shows. The single-file upload mutation now takes {file, overwrite}; folder upload (already overwrite=true) also auto-reveals hidden results. Frontend-only; all 3 images rebuilt+pushed 0.37.5. Co-Authored-By: Claude Opus 4.8 --- backend/version.py | 2 +- frontend/package.json | 2 +- frontend/src/pages/Files.tsx | 39 ++++++++++++++++++++++++++++++------ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/backend/version.py b/backend/version.py index 83dd98f..0dc6f6f 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,3 +1,3 @@ """Single source of truth for the StackPilot release version.""" -APP_VERSION = "0.37.4" +APP_VERSION = "0.37.5" diff --git a/frontend/package.json b/frontend/package.json index 25fd7e8..c5729eb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "stackpilot-frontend", "private": true, - "version": "0.37.4", + "version": "0.37.5", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/pages/Files.tsx b/frontend/src/pages/Files.tsx index 11a2b3a..ca0f831 100644 --- a/frontend/src/pages/Files.tsx +++ b/frontend/src/pages/Files.tsx @@ -82,6 +82,7 @@ export function Files() { const [deleting, setDeleting] = useState(null); const [newKind, setNewKind] = useState<"dir" | "file" | null>(null); const [clip, setClip] = useState(null); + const [uploadConflict, setUploadConflict] = useState(null); const [pasteConflict, setPasteConflict] = useState(false); const [progress, setProgress] = useState<{ label: string; @@ -131,10 +132,18 @@ export function Files() { const refresh = () => qc.invalidateQueries({ queryKey: ["files"] }); + // An uploaded dotfile (e.g. .env) is hidden by default — auto-reveal it so + // the user can actually see what they just uploaded. + const revealIfHidden = (...names: string[]) => { + if (!showHidden && names.some((n) => n.split("/").some((seg) => seg.startsWith(".")))) { + setShowHidden(true); + } + }; + const upload = useMutation({ - mutationFn: (file: File) => { + mutationFn: ({ file, overwrite }: { file: File; overwrite: boolean }) => { setProgress({ label: file.name, pct: 0, kind: "upload" }); - return filesApi.upload(path, file, false, "", host, (pct) => + return filesApi.upload(path, file, overwrite, "", host, (pct) => setProgress({ label: file.name, pct, @@ -145,12 +154,15 @@ export function Files() { }, onSuccess: (r) => { toast.success(`Uploaded ${r.name}`); + setUploadConflict(null); + revealIfHidden(r.name); refresh(); }, - onError: (e: unknown) => { + onError: (e: unknown, vars) => { const msg = apiErrorMessage(e); + // Offer to overwrite instead of dead-ending on "Already exists". if (msg.startsWith("Already exists")) { - toast.error(`${msg} — rename or remove the existing file first.`); + setUploadConflict(vars.file); } else { toast.error(msg); } @@ -201,9 +213,12 @@ export function Files() { } return { ok, failed }; }, - onSuccess: ({ ok, failed }) => { + onSuccess: ({ ok, failed }, files) => { if (failed) toast.warning(`Uploaded ${ok} file(s), ${failed} failed`); else toast.success(`Uploaded ${ok} file(s)`); + revealIfHidden( + ...files.map((f) => (f as File & { webkitRelativePath?: string }).webkitRelativePath || f.name), + ); refresh(); }, onError: (e) => toast.error(apiErrorMessage(e)), @@ -335,7 +350,7 @@ export function Files() { className="hidden" onChange={(e) => { const f = e.target.files?.[0]; - if (f) upload.mutate(f); + if (f) upload.mutate({ file: f, overwrite: false }); }} /> {/* webkitdirectory is set imperatively via the callback ref. */} @@ -610,6 +625,18 @@ export function Files() { onCancel={() => setPasteConflict(false)} /> )} + + {uploadConflict && ( + upload.mutate({ file: uploadConflict, overwrite: true })} + onCancel={() => setUploadConflict(null)} + /> + )} ); }