From 51ce477b07da239a96b00f1dbc871f347a7c5364 Mon Sep 17 00:00:00 2001 From: Snow Lee Date: Sun, 19 Jul 2026 09:48:17 -0700 Subject: [PATCH] =?UTF-8?q?feat(web):=20remove=20browser=20upload=20UI=20?= =?UTF-8?q?=E2=80=94=20content=20enters=20via=20local=20sync=20only=20(for?= =?UTF-8?q?=20now)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Topbar button, ⋯ menu entry, palette action, hidden file input, and the upload plumbing (upload.ts) are gone; the server upload API stays (devices and the store proxy depend on it). Spec reworked to seed via API and assert the affordance is absent. 44/44 green. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VbiaaVM2ACxeRi8ySG9ybc --- internal/webapp/frontend/e2e/browse.spec.ts | 19 ++-- internal/webapp/frontend/src/apps/Browser.tsx | 48 +--------- internal/webapp/frontend/src/upload.ts | 90 ------------------- .../webapp/static/assets/index-b5pl3IMV.js | 11 +++ .../webapp/static/assets/index-lt5n9mM7.js | 11 --- internal/webapp/static/index.html | 2 +- 6 files changed, 27 insertions(+), 154 deletions(-) delete mode 100644 internal/webapp/frontend/src/upload.ts create mode 100644 internal/webapp/static/assets/index-b5pl3IMV.js delete mode 100644 internal/webapp/static/assets/index-lt5n9mM7.js diff --git a/internal/webapp/frontend/e2e/browse.spec.ts b/internal/webapp/frontend/e2e/browse.spec.ts index 3fe7fd7..d6ef56d 100644 --- a/internal/webapp/frontend/e2e/browse.spec.ts +++ b/internal/webapp/frontend/e2e/browse.spec.ts @@ -113,17 +113,20 @@ test("share mints a public link that serves the file, revoke kills it", async ({ expect(gone.status()).toBe(404); }); -test("upload into the selected folder, then the file opens", async ({ page }) => { +test("no browser upload: content arrives via sync; the tree picks it up", async ({ page }) => { await login(page); const pid = await wikiId(page); await page.goto(`/${pid}/notes`); - await page.locator("#upload-btn").waitFor(); - await page.setInputFiles('input[type="file"]', { - name: "dropped.md", - mimeType: "text/markdown", - buffer: Buffer.from("# Dropped\n\nUploaded through the browser.\n"), - }); - await page.waitForURL(`/${pid}/notes/dropped.md`); + // The upload affordance is gone everywhere — content enters via local sync. + await expect(page.locator("#upload-btn")).toHaveCount(0); + await expect(page.locator('input[type="file"]')).toHaveCount(0); + // A file lands through the device/store path (simulated via the API)… + await page.request.put( + `/api/p/${pid}/upload/content?path=${encodeURIComponent("notes/dropped.md")}`, + { data: "# Dropped\n\nArrived through sync.\n" }, + ); + // …and the polling tree shows it; opening renders it. + await page.goto(`/${pid}/notes/dropped.md`); await expect(page.locator("#content h1")).toHaveText("Dropped"); await expect(page.locator('#tree .row[data-path="notes/dropped.md"]')).toBeVisible(); }); diff --git a/internal/webapp/frontend/src/apps/Browser.tsx b/internal/webapp/frontend/src/apps/Browser.tsx index 1d5adf6..2cbe787 100644 --- a/internal/webapp/frontend/src/apps/Browser.tsx +++ b/internal/webapp/frontend/src/apps/Browser.tsx @@ -10,7 +10,6 @@ import type { Project, ServerConfig } from "../api/types"; import { useHeat, useTree } from "../hooks/useBrowse"; import { urlForPath, urlForView, type Route } from "../router"; import { currentNavType, navigate, useLocationPath } from "../nav"; -import { uploadFile } from "../upload"; import { copyText } from "../util"; import { toast } from "../toast"; import { AppShell, Icon, Topbar, closeSidebarOnMobile } from "../components/shell"; @@ -138,17 +137,16 @@ export default function Browser(props: { /* ---- topbar state + actions ---- */ const [meta, setMeta] = useState(""); - const [uploadStatus, setUploadStatus] = useState(""); const [share, setShare] = useState<{ url: string; copied: boolean } | null>(null); const [moreOpen, setMoreOpen] = useState(false); const [paletteOpen, setPaletteOpen] = useState(false); - const uploadInput = useRef(null); const downloadRef = useRef(null); const panel = props.panel ?? null; const canShare = !panel && hub && !!project && isFile; const canHistory = !panel && hub && !!project; - const canUpload = !!config.upload?.enabled && (!hub || !!project); + // Browser upload is deliberately absent (for now): content enters through + // local sync only; the web app is a read/share/history surface. const canDownload = !panel && isFile; const canMore = !panel && (isFile || (hub && !!project && isDir)); const downloadURL = apiBase + "download?path=" + encodeURIComponent(path); @@ -175,32 +173,6 @@ export default function Browser(props: { openHistory(isDir ? path + "/" : path); }, [path, isDir, openHistory]); - const uploadNow = useCallback(() => uploadInput.current?.click(), []); - const onUploadPick = async () => { - const input = uploadInput.current!; - const file = input.files?.[0]; - input.value = ""; - if (!file) return; - // A selected folder receives the upload; a selected file means "next - // to it". - const dir = !path ? "" : isDir ? path : path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : ""; - const dest = dir ? dir + "/" + file.name : file.name; - try { - setUploadStatus(`Uploading ${dest}…`); - await uploadFile(apiBase, dest, file); - setUploadStatus(`Uploaded ${dest}`); - await qc.invalidateQueries({ queryKey: ["tree", apiBase] }); - openPath(dest); - } catch (err) { - setUploadStatus("Upload failed: " + (err as Error).message); - } - }; - - useEffect(() => { - // Any navigation clears a stale upload status from the meta slot. - setUploadStatus(""); - }, [routeKey]); - /* ---- ⌘K palette ---- */ useEffect(() => { const onKey = (e: KeyboardEvent) => { @@ -223,7 +195,6 @@ export default function Browser(props: { if (isFile) add("download", "Download: " + path, "action", () => downloadRef.current?.click()); } if (hub && project) add("hist", "History: whole project", "action", () => openHistory("")); - if (canUpload) add("upload", "Upload a file…", "action", uploadNow); if (hub) { for (const p of props.projects || []) { if (!project || p.id !== project.id) { @@ -237,7 +208,7 @@ export default function Browser(props: { for (const d of dirIndex.keys()) add("folder", d, "folder", () => openPath(d)); for (const f of flatFiles) add("doc", f.path, "file", () => openPath(f.path)); return items; - }, [hub, project, path, isFile, canUpload, config.auth?.enabled, dirIndex, flatFiles, props.projects, shareNow, historyNow, uploadNow, openHistory, openPath]); + }, [hub, project, path, isFile, config.auth?.enabled, dirIndex, flatFiles, props.projects, shareNow, historyNow, openHistory, openPath]); /* ---- "⋯ More" menu (secondary actions on narrow screens) ---- */ useEffect(() => { @@ -372,7 +343,7 @@ export default function Browser(props: { const topbar = ( )} - {canUpload && ( - - )} - {canDownload && ( Download @@ -420,11 +385,6 @@ export default function Browser(props: { History )} - {canUpload && ( - - )} {canDownload && (