Skip to content

Choose save location and filename when saving a map to machine - #1514

Open
spencerpruitt wants to merge 1 commit into
Azgaar:masterfrom
spencerpruitt:feat/save-location-picker
Open

Choose save location and filename when saving a map to machine#1514
spencerpruitt wants to merge 1 commit into
Azgaar:masterfrom
spencerpruitt:feat/save-location-picker

Conversation

@spencerpruitt

Copy link
Copy Markdown

What & why

Today, Save → machine writes the .map file straight to the browser's Downloads folder under an auto-generated name. There's no way to choose where the file goes or what it's called, so users have to fish the file out of Downloads and move it, and can't decide whether to overwrite an existing map or keep a separate copy.

This PR makes Save to Machine open the OS "Save As" dialog every time, so the user chooses the folder and filename on each save:

  • Overwrite an existing map by selecting it in the dialog.
  • Save a new/separate file by choosing a new name or folder.

The dialog is pre-filled with the usual <map name> <timestamp> and filtered to .map.

Browser support

Uses the File System Access API (showSaveFilePicker), available in Chromium-based browsers (Chrome, Edge, Opera, Brave). Firefox and Safari don't support it, so they fall back to the existing Downloads behaviour (via the shared downloadFile helper), with a one-time tip explaining why the picker wasn't offered. Saving works everywhere.

Scope

Only the .map Save to Machine path — the machine button and the Ctrl+S hotkey, which share saveMap("machine"). Dropbox, browser-storage save, auto-save, and all exports are unchanged.

Implementation

  • src/io/save-to-file.ts (new): a small module wrapping the picker and the fallback behind one function, saveToFileSystem(mapData, suggestedName), returning a discriminated outcome — saved / downloaded-fallback / cancelled. It holds no cross-save state. Cancelling the dialog (AbortError, matched by name so a DOMException is handled too) is a silent no-op; write/permission errors propagate to the existing save error dialog.
  • src/io/save.ts: saveToMachine routes through the new module and maps the outcome to the usual tip(...) messages. The one-time fallback note is remembered in localStorage, guarded so a save never fails if storage is blocked (e.g. Safari private mode).
  • vitest.config.ts: adds the @src alias (mirroring vite.config.ts / tsconfig) so unit tests can resolve runtime @/... imports.

Tests

New Vitest unit tests stub the browser globals and cover: the picker opens on every save; the suggested name and .map filter are passed; cancel (including a DOMException-style AbortError) is a no-op; an unsupported browser delegates to downloadFile; write/permission errors propagate; and the outcome → tip mapping including the one-time fallback note and the storage-unavailable case.

Manual testing

  • Chrome / Edge: every Save opens the picker; overwriting an existing file and writing a new file both work; the success tip names the chosen file; cancelling is clean.
  • Firefox: falls back to Downloads with the one-time explanatory note.

🤖 Generated with Claude Code

Saving a map to the local machine dropped the .map file into the browser's
Downloads folder under an auto-generated name, with no way to choose where
it goes. This adds an OS "Save As" dialog on every Save to Machine so the
user picks the folder and filename each time — overwrite an existing file
by re-selecting it, or write a new file by choosing a new name/folder. The
dialog is pre-filled with the usual name and filtered to .map.

Uses the File System Access API (showSaveFilePicker), available in
Chromium-based browsers. Firefox/Safari lack it and fall back to the
existing Downloads behavior, with a one-time note explaining why.

- src/io/save-to-file.ts: new module wrapping the picker and the fallback,
  returning a discriminated outcome (saved | downloaded-fallback |
  cancelled). Cancel (AbortError, checked by name so a DOMException is
  handled) is a silent no-op; the fallback reuses the shared downloadFile
  helper; write/permission errors propagate to the existing save dialog.
- src/io/save.ts: saveToMachine routes through it and maps outcomes to
  tips; the one-time fallback note's localStorage access is guarded so a
  save never fails when storage is blocked (e.g. Safari private mode).
- Unit tests stub the browser globals; vitest.config gains the "@" -> src
  alias (mirroring vite.config.ts / tsconfig) so tests resolve runtime
  @/ imports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented Jun 28, 2026

Copy link
Copy Markdown

Deploy Preview for afmg ready!

Name Link
🔨 Latest commit be47d86
🔍 Latest deploy log https://app.netlify.com/projects/afmg/deploys/6a40b27efbfaee000907fd15
😎 Deploy Preview https://deploy-preview-1514--afmg.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Azgaar
Azgaar self-requested a review June 28, 2026 13:04
@Azgaar Azgaar moved this from Backlog to Approved in FMG dev board Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Approved

Development

Successfully merging this pull request may close these issues.

2 participants