0.37.4: fix folder upload doing nothing (set webkitdirectory reliably)

"Upload folder" silently did nothing: the directory-selection attribute
was set on the hidden <input> via a JSX spread
({...{webkitdirectory:"", directory:""}}), which React doesn't reliably
apply to the DOM — and if isAdmin resolves after first render, a one-shot
effect would miss the input mounting entirely. Without the attribute the
picker is a plain file picker where no folder can be selected, so the user
picks nothing and nothing happens.

- Set webkitdirectory/directory/mozdirectory imperatively through a
  callback ref, which runs whenever the input mounts. folderInput is now a
  MutableRefObject so the callback can populate it.
- Folder upload now shows the progress bar immediately on start (small
  files can finish before the browser emits any upload-progress event, so
  don't wait for the first one to render feedback).

Frontend-only; all 3 images rebuilt+pushed 0.37.4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
menzelj
2026-06-21 20:52:50 +00:00
co-authored by Claude Opus 4.8
parent a43e6b48f0
commit 98d756faf6
3 changed files with 29 additions and 7 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
"""Single source of truth for the StackPilot release version."""
APP_VERSION = "0.37.3"
APP_VERSION = "0.37.4"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "stackpilot-frontend",
"private": true,
"version": "0.37.3",
"version": "0.37.4",
"type": "module",
"scripts": {
"dev": "vite",
+27 -5
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
Folder,
@@ -92,7 +92,22 @@ export function Files() {
detail?: string;
} | null>(null);
const fileInput = useRef<HTMLInputElement>(null);
const folderInput = useRef<HTMLInputElement>(null);
const folderInput = useRef<HTMLInputElement | null>(null);
// Callback ref for the folder <input>. Enables directory selection
// imperatively: setting `webkitdirectory` via JSX is unreliable (React
// doesn't reliably apply the non-standard attribute), and without it the
// picker is a plain file picker where no folder can be chosen — so "Upload
// folder" silently does nothing. A callback ref also runs whenever the input
// mounts (e.g. once `isAdmin` resolves), which a one-shot effect would miss.
const folderInputRef = useCallback((el: HTMLInputElement | null) => {
folderInput.current = el;
if (el) {
el.setAttribute("webkitdirectory", "");
el.setAttribute("directory", "");
el.setAttribute("mozdirectory", "");
}
}, []);
const agents = useQuery({
queryKey: ["agents"],
@@ -155,6 +170,14 @@ export function Files() {
let ok = 0;
let failed = 0;
let doneBytes = 0; // bytes of fully-completed files
// Show the bar immediately — small files may finish before the browser
// emits any upload-progress event, so don't wait for the first one.
setProgress({
label: files[0]?.name ?? "",
pct: 0,
kind: "upload",
detail: `0 / ${total} files · ${formatBytes(0)} / ${formatBytes(totalBytes)}`,
});
for (let i = 0; i < files.length; i++) {
const f = files[i];
const rel = (f as File & { webkitRelativePath?: string }).webkitRelativePath || f.name;
@@ -315,13 +338,12 @@ export function Files() {
if (f) upload.mutate(f);
}}
/>
{/* webkitdirectory is set imperatively via the callback ref. */}
<input
ref={folderInput}
ref={folderInputRef}
type="file"
className="hidden"
multiple
// webkitdirectory/directory are non-standard but widely supported.
{...({ webkitdirectory: "", directory: "" } as Record<string, string>)}
onChange={(e) => {
const files = Array.from(e.target.files ?? []);
if (files.length) uploadFolder.mutate(files);