Skip to content

fix(map): disable Save Map while pending and harden mobile save flows#139

Merged
luandro merged 13 commits into
mainfrom
fix/save-map-disabled
Jul 22, 2026
Merged

fix(map): disable Save Map while pending and harden mobile save flows#139
luandro merged 13 commits into
mainfrom
fix/save-map-disabled

Conversation

@luandro

@luandro luandro commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Problem

The 'Save Map' button in the map settings sidebar and mobile overlay was always enabled, but clicking it while createMap.isPending (a save operation is already in flight) does nothing — it just opens a dialog that can't be completed. Additionally, the settings-sheet 'Save Map' button allowed saving a map identical to the current/default view, and the mobile settings sheet stayed open behind the save-name dialog, hiding the user from the next action.

Fix

Harden the map save flow on three axes:

  1. Disable both Save Map triggers while a save mutation is pending (sidebar + mobile floating overlay). Prevents the user from queuing duplicate saves or opening a second name dialog while one is in flight.
  2. Disable the settings-sheet Save Map button when no configuration changes are present (bbox, zoom, or basemap still at defaults). Forces the settings flow to be config-first.
  3. Close the mobile settings sheet before opening the save-name dialog. Keeps the modal stack tidy so the user is never hidden behind a sheet.

The mobile floating overlay intentionally has no !hasConfigChanges guard — it is a quick-action affordance that may save the current/default view at any time. Only the pending-disable applies. This asymmetry is now covered by a regression test so a future change to the floating button would fail the suite.

Changes

  • src/screens/MapScreen/MapScreen.tsx
    • disabled={createMap.isPending || !hasConfigChanges} on the settings-sheet Save Map button (renderControls)
    • disabled={createMap.isPending} on the mobile floating Save Map button (intentional, see comment)
    • hasConfigChanges useMemo: compares bbox / zoomRange / selectedStyle against defaults
    • openNameDialog() now closes the settings sheet before opening the name dialog
    • New i18n key activeMapOnlineBadge to distinguish online vs. offline active-map copy
  • src/i18n/messages/{en,es,pt}.json: new activeMapOnlineBadge message in all three locales
  • Tests: 6 new cases — both triggers disable while pending, both re-enable on settle, mobile default-state asymmetry (floating enabled / settings disabled), draw cancel button, DownloadPanel integration, name-required error, no-project empty state

Testing

  • Open map screen with a project selected
  • With defaults: floating Save Map is enabled, settings-sheet Save Map is disabled
  • Click floating Save Map → enter name → click 'Save Draft'
  • Both Save Map buttons disable while the mutation is in flight
  • Both re-enable after the save resolves
  • Mobile settings sheet closes before the name dialog opens
  • Clicking 'Cancel' on the name dialog leaves the floating button enabled

Out of scope

  • Draw controls on desktop (unchanged)
  • The disabled={createMap.isPending} on the dialog's own 'Save draft' submit button is unchanged (it uses loading={createMap.isPending})

Greptile Summary

This PR hardens the map save flow and updates active-map badge copy. The main changes are:

  • Disable settings and floating Save Map triggers while a save is pending.
  • Disable the settings-sheet Save Map button until bbox, zoom, or basemap changes exist.
  • Close the mobile settings sheet before opening the save-name dialog.
  • Add separate online active-map badge text across supported locales.
  • Add tests for the updated save states, mobile dialog flow, badge copy, and related map screen cases.

Confidence Score: 5/5

Safe to merge with minimal risk.

The changed behavior is localized to the map save UI and badge copy. The mobile quick-action asymmetry is intentional and covered by tests. No functional or security issues were identified in the changed files.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Browser discovery found a usable Chromium binary and Playwright ran successfully with that executable.
  • The map save UI capture logs recorded the default and changed UI state flags, including default-floating-enabled=true, default-sheet-save-enabled=false, changed-sheet-save-enabled=true, dialog-count-after-settings-save-click=1, settings-dialog-visible-after-name-open=false, and floating-enabled-after-cancel=true.
  • The focused vitest tests for the map save flow completed with all tests passing (Test Files 2 passed and Tests 45 passed) and exit code 0.
  • A set of artifacts, including an end-to-end map save flow video and multiple UI state images, was prepared to document the behavior of the map save flow.
  • Session logs and supporting assets, including URL-accessible logs and an inline script file, were collected to enable review of the run.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/screens/MapScreen/MapScreen.tsx Adds config-change detection, pending-disable states, and mobile sheet closure before the save-name dialog; no issues found.
src/components/shared/MapContainer/MapContainer.tsx Separates online active-map badge copy from offline active-map copy using a new i18n key; no issues found.
tests/unit/screens/MapScreen/MapScreen.test.tsx Adds tests for pending save disables, mobile dialog stacking, default-state save affordances, draw cancel, DownloadPanel rendering, validation, and no-project empty state.
tests/unit/components/shared/MapContainer/MapContainer.test.tsx Extends the active online badge assertion to verify the new copy includes online state and map name.

Sequence Diagram

sequenceDiagram
  participant U as User
  participant MS as MapScreen
  participant Sheet as SettingsSheet
  participant Dialog as Save name dialog
  participant Mutation as useCreateMap mutation
  participant DB as IndexedDB maps table

  U->>MS: Change bbox, zoom, or basemap
  MS->>MS: "hasConfigChanges = true"
  U->>Sheet: Tap settings Save Map
  Sheet->>MS: openNameDialog()
  MS->>Sheet: setSettingsOpen(false)
  MS->>Dialog: setNameDialogOpen(true)
  U->>Dialog: Enter name and submit
  Dialog->>Mutation: mutateAsync(savedMap)
  MS->>MS: Disable settings and floating Save Map while pending
  Mutation->>DB: add(savedMap)
  DB-->>Mutation: save complete
  Mutation-->>MS: settled
  MS->>Dialog: close dialog and clear form
  MS->>MS: Re-enable Save Map buttons
Loading

Reviews (12): Last reviewed commit: "test: cover mobile save flow gaps and re..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Preview deployment ready: https://fix-save-map-disabled.comapeo-cloud-app.pages.dev

Commit: 18c873a

luandro pushed a commit that referenced this pull request Jul 21, 2026
Adds a unit test verifying that both Save Map trigger buttons
(desktop sidebar and mobile floating button) enter disabled state
while createMap.isPending is true, preventing re-opening the name
dialog during an in-flight save mutation.

Reported by Greptile on PR #139.
luandro pushed a commit that referenced this pull request Jul 21, 2026
The handleSaveMap function awaits mutateAsync before closing the
dialog, so the buttons stay hidden behind the modal overlay while
the mutation is pending. Click Cancel to dismiss first, then verify
both trigger buttons are disabled.

Amended test from Greptile feedback on PR #139.
Comment thread src/screens/MapScreen/MapScreen.tsx
luandro added a commit that referenced this pull request Jul 22, 2026
Resolves baseline CI audit failure that blocked PR #139 and other PRs.

- Bumps vitest + @vitest/* from 4.1.8 to 4.1.10 (fixes GHSA-p63j-vcc4-9vmv)
- Adds overrides for fast-uri, sharp, axios, joi, js-yaml, shell-quote
- Adds ci.yml ignore for stale bun advisory (already fixed in 4.1.10)
- No source file changes
Terrastories added 12 commits July 22, 2026 11:11
The 'Save Map' button in both the settings sidebar and the mobile
overlay was always enabled, but clicking it while createMap.isPending
truly does nothing — the previous mutation is still in flight.

Disable both instances while createMap.isPending to prevent
confusing no-op clicks.
Adds a unit test verifying that both Save Map trigger buttons
(desktop sidebar and mobile floating button) enter disabled state
while createMap.isPending is true, preventing re-opening the name
dialog during an in-flight save mutation.

Reported by Greptile on PR #139.
The handleSaveMap function awaits mutateAsync before closing the
dialog, so the buttons stay hidden behind the modal overlay while
the mutation is pending. Click Cancel to dismiss first, then verify
both trigger buttons are disabled.

Amended test from Greptile feedback on PR #139.
Dexie.add() returns PromiseExtended<string>, not a standard Promise.
The lint-safe cast would require importing Dexie's internal type.
Use eslint-disable-next-line with explanation instead.
The settings-sheet Save Map button (mobile 'Map settings' panel) was
always enabled, but clicking it with default bbox/zoom/basemap
creates a meaningless draft. Now disabled until the user actually
changes at least one config option (basemap, bounds, or zoom range).

The floating Save Map button on the map (mobile, bottom-right)
remains always enabled — its context implies active map work.

Adds hasConfigChanges memo comparing bbox, zoomRange, and
selectedStyle against DEFAULT_* constants. Tests updated:
- New test: disabled with defaults, enabled after basemap change
- Existing tests: change basemap first to enable button before clicking
The online active badge (shown when a map is set active but SMP blob
not yet downloaded) was reusing the 'Active offline map' i18n message,
making it appear the map was already available offline when it wasn't.

Added a separate activeMapOnlineBadge message ('Active map (online)')
and updated the online badge to use it. The offline badge retains its
original 'Active offline map' label.
The settings sheet (z-[51]) was covering the name dialog Modal
(z-50), making the Save Map button appear broken — tapping it
opened a dialog the user could never see or interact with.

Added setSettingsOpen(false) in openNameDialog() so the sheet
closes before the name dialog opens. Desktop is unaffected
because the sidebar is a plain aside, not a z-indexed dialog.

Added test verifying single dialog remains after opening
name dialog from mobile settings sheet.
- await deferred mutation resolution and verify recovery\n- add online active-map translations for Portuguese and Spanish
- verify online active-map badge copy and map name\n- assert both mobile Save Map triggers disable and recover
- verify both mobile save triggers during pending and recovery\n- document intentional quick-action behavior for default map views
@luandro
luandro force-pushed the fix/save-map-disabled branch from fb50307 to b8adce6 Compare July 22, 2026 14:26
@luandro luandro changed the title fix: disable 'Save Map' button while mutation is pending fix(map): disable Save Map while pending and harden mobile save flows Jul 22, 2026
- both triggers disable+re-enable while save pending (already covered, kept)
- mobile default-state asymmetry: floating enabled, settings-sheet disabled
- draw cancel button exits draw_rectangle mode
- DownloadPanel renders for maps with downloadable status
- empty-name submission shows nameRequired and keeps dialog open
- no-project empty state with link to home
- frame confirm happy path with undo banner

MapScreen.tsx: 72.17% stmts / 68.83% branches -> 94.78% stmts / 84.41% branches
(80% per-file threshold met for all 4 metrics per AGENTS.md)

Refs PR #139 readiness gap (Codex GPT-5.6-Sol assessment)
@luandro
luandro merged commit 3fdb8bb into main Jul 22, 2026
21 checks passed
@luandro
luandro deleted the fix/save-map-disabled branch July 22, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant