diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-18 13:40:15 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-18 13:40:15 +0200 |
| commit | cf418a095b07c8127042390c748e721d1433879b (patch) | |
| tree | 6ede268b8354127764da4a97d726e1bc680bb1c4 /packages/meshbay-hub/src/meshbay_hub/static/platform.js | |
| parent | ae7099edefbff5cd6ac0e2329f0684b6caff0b73 (diff) | |
| download | meshbay-cf418a095b07c8127042390c748e721d1433879b.tar.gz | |
fix(client): downloads stream to disk, and two rough edges on first run
**Downloads were going through RAM.** `_openDownloadTarget` tries a granted
folder, then a service worker, then its floor: collect the whole file in the
page and hand the browser a blob. Both of the first two are absent in the
desktop application — `showDirectoryPicker` does not exist, and Chromium refuses
a service worker on a custom scheme — so every download under 512 MB took the
floor. A gigabyte of film meant a gigabyte of RAM, and the only visible symptom
was a Save As dialog at the *end* rather than the start, which is what the
operator noticed and asked about.
The main process now streams to disk: it honours "save automatically" with a
folder chosen once and no dialog, never overwrites (a colliding name gets a
suffix), awaits each write so the renderer cannot outrun the disk and queue the
file in memory anyway, and unlinks a cancelled download rather than leaving a
truncated file that looks complete to whoever opens it next. Settings now offers
the native folder picker instead of saying downloads are unsupported.
Measured in the running application: the file on disk grows 256 KB → 512 KB →
768 KB → 1 MB as the chunks arrive, and an aborted download leaves nothing
behind.
**A permanent scrollbar on sign-in.** `.layout` and `.page-center` each reserved
`100vh - 52px`, and `.page-center` sits inside `main`'s 24px vertical padding —
so the page overflowed by exactly 48px at every window size. Found by measuring
in the app rather than reading the stylesheet: `scrollHeight` 819 against a 771
viewport, then the bottom edge of every element. The centring page brings its
own padding, so main's is dropped for it and the duplicated arithmetic goes
rather than growing a third term. Now `scrollHeight == innerHeight`, no
overflowing elements.
**The first-run screen was unstyled.** It used a class name I invented
(`auth-page`) that appears nowhere in the stylesheet, so it had no card and the
button sat against the input. It now uses the same `page-center` + `login-card`
markup as sign-in, which is where the 12px gap comes from. The sign-in link in
the nav is hidden until a hub is chosen — it led to a page that could not work.
809 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/platform.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/platform.js | 27 |
1 files changed, 23 insertions, 4 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js index 994f558..372b666 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js @@ -176,13 +176,32 @@ export async function apiFetch(url, init) { * in `downloads.js`. Adding a native writer must not remove the three that * already work. */ -export async function nativeSave(suggestedName, size) { +export async function nativeSave(suggestedName, { auto = true } = {}) { if (!bridge || !bridge.saveFile) return null; - return bridge.saveFile(suggestedName, size); + const sink = await bridge.saveFile(suggestedName, { auto }); + if (!sink) return null; + // The shape every caller already expects from a download target: a `writable` + // with write/close/abort, and the name it was actually given on disk. + return { + name: sink.name, + writable: { + write: (bytes) => sink.write(bytes), + close: () => sink.close(), + abort: () => sink.abort(), + }, + }; } +/** Where downloads go on a desktop build. Null in a browser. */ +export const folder = { + available: Boolean(bridge && bridge.folder), + async choose() { return bridge && bridge.folder ? bridge.folder.choose() : null; }, + async get() { return bridge && bridge.folder ? bridge.folder.get() : null; }, + async forget() { return bridge && bridge.folder ? bridge.folder.forget() : false; }, +}; + export default { isNative, hubBase, capabilities, secrets, nativeSave, - apiFetch, device, bridgeMessage }; + apiFetch, device, bridgeMessage, folder }; // Also a global, because `transport.js` is loaded as a classic script — it // predates the module graph and exposes `MeshBayTransport` the same way. The @@ -191,5 +210,5 @@ export default { isNative, hubBase, capabilities, secrets, nativeSave, if (typeof window !== 'undefined') { window.MeshBayPlatform = { isNative, hubBase, capabilities, secrets, nativeSave, apiFetch, device, - bridgeMessage }; + bridgeMessage, folder }; } |