aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-15 15:48:36 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-15 15:48:36 +0200
commit4066c754a613deb965472853fe69727d68be593e (patch)
treef93b7d7129576f7406a2c97ac7399a8d7e606897 /packages/meshbay-hub/src/meshbay_hub/static/downloads.js
parent8528bce49bb637f4e9fb653fff148b7ea45cfdac (diff)
downloadmeshbay-4066c754a613deb965472853fe69727d68be593e.tar.gz
fix(downloads): automatic really is automatic, and a selection downloads all of it
Two bugs in what shipped last, and both were mine. Automatic mode still opened Save As, because with no folder granted the code fell through to the file picker — while the documentation said it would use the browser's own download folder. It does that now. Over 512 MB it still asks, since getting there means holding the file in memory and a tab will not survive a 40 GB blob; Settings is where to stop it asking again. Selecting two files downloaded one. They were started without awaiting, so each asked the browser for a save dialog at once, and a browser allows exactly one — the rest were rejected and the errors went nowhere. They are awaited one at a time now, which serializes the dialogs and not the transfers: each call returns as soon as its transfer is registered. Then the adjustments. The transfers widget offers Open on a finished download that went into a granted folder — the bytes go to a new tab, and that is the whole of what a page can do: no browser lets one start a desktop application or show a file manager, so the folder half of that request cannot be built and the guide says so. The Files toolbar was four controls of three different heights in a row. It is three groups now — what you can add, where you are, what you can do with what is here — on one baseline, with icons from the set and a gap between the dots and the word Actions. Chat comes first among the tabs and is the one you land on. The three Discover entries in the sidebar have icons. And a link in a chat message becomes a link: built as an element and never as markup, http and https only, so `javascript:` is not one message away from running here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/downloads.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/downloads.js29
1 files changed, 24 insertions, 5 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
index 9c6ced1..a71f289 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
@@ -133,11 +133,11 @@ export async function freeName(name, exists) {
}
/**
- * Where this download should be written.
+ * Where this download should be written, if a folder has been granted.
*
- * Returns a writable stream, or null meaning "there is nowhere to stream to —
- * collect it and hand the browser a blob". The caller opens the picker itself
- * in "ask" mode, because that one has to happen inside the click.
+ * Returns `{writable, name, open}` or null. Null does not mean failure: it
+ * means there is no granted folder, and the caller decides between handing the
+ * browser a blob and asking for a Save As.
*/
export async function openTarget(filename) {
if (!SUPPORTED || getMode() === 'ask') return null;
@@ -155,5 +155,24 @@ export async function openTarget(filename) {
};
const name = await freeName(filename, exists);
const handle = await dir.getFileHandle(name, { create: true });
- return { writable: await handle.createWritable(), name };
+ return {
+ writable: await handle.createWritable(),
+ name,
+ // Reading it back is the only way a page can "open" a file it wrote: hand
+ // the bytes to a tab and let the browser decide what to do with them. No
+ // web page can start a desktop application, or show a file manager.
+ open: async () => {
+ const file = await handle.getFile();
+ const url = URL.createObjectURL(file);
+ window.open(url, '_blank', 'noopener');
+ setTimeout(() => URL.revokeObjectURL(url), 60000);
+ },
+ };
}
+
+/**
+ * Below this, a download with no granted folder is collected in memory and
+ * handed to the browser, which saves it without asking. Above it that would
+ * mean holding gigabytes in a tab, so it is worth one Save As dialog instead.
+ */
+export const BLOB_LIMIT = 512 * 1024 * 1024;