0.37.5: overwrite prompt for file upload + auto-reveal uploaded hidden files
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
98d756faf6
commit
a40dd0de3e
+1
-1
@@ -1,3 +1,3 @@
|
||||
"""Single source of truth for the StackPilot release version."""
|
||||
|
||||
APP_VERSION = "0.37.4"
|
||||
APP_VERSION = "0.37.5"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "stackpilot-frontend",
|
||||
"private": true,
|
||||
"version": "0.37.4",
|
||||
"version": "0.37.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -82,6 +82,7 @@ export function Files() {
|
||||
const [deleting, setDeleting] = useState<HostPathEntry | null>(null);
|
||||
const [newKind, setNewKind] = useState<"dir" | "file" | null>(null);
|
||||
const [clip, setClip] = useState<Clipboard | null>(null);
|
||||
const [uploadConflict, setUploadConflict] = useState<File | null>(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 && (
|
||||
<ConfirmDialog
|
||||
title={`“${uploadConflict.name}” already exists here`}
|
||||
message={`Overwrite the existing file at ${path}?`}
|
||||
confirmLabel="Overwrite"
|
||||
danger
|
||||
busy={upload.isPending}
|
||||
onConfirm={() => upload.mutate({ file: uploadConflict, overwrite: true })}
|
||||
onCancel={() => setUploadConflict(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user