Skip to content

Feature/feature drafts - #4652

Open
xkello wants to merge 3 commits into
masterfrom
feature/feature-drafts
Open

Feature/feature drafts#4652
xkello wants to merge 3 commits into
masterfrom
feature/feature-drafts

Conversation

@xkello

@xkello xkello commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Description

Adds automatic recovery of in-progress feature edits. If the app closes unexpectedly while you're editing a feature - a crash, an incoming call, a dead battery - your unsaved geometry and attribute changes are no longer lost. On the next launch you're offered the chance to resume exactly where you left off, or discard the changes.

Fixes: #4585

What changed

  • New FeatureDraftStorage / FeatureDraftController (app/drafts/) persist an in-progress edit (geometry + attributes) as a JSON blob via QSettings, debounced by ~1s so it isn't written on every keystroke/vertex.
  • AttributeController and RecordingMapTool write a draft whenever attributes or geometry are edited, and clear it once the feature is saved, deleted, or the edit is cancelled.
  • Before a draft is offered, it's validated: not older than 10 days, the layer/schema still matches, and (for edits) the feature still exists.
  • A new notification, plus resume/discard drawers, let the user act on a pending draft.
  • New banners on the Layers list and Features list screens surface a draft on the affected layer, reusing the existing "active filters" banner styling.
  • ActiveProject exposes the draft controller to QML.

Behaviour

  • Editing a feature's attributes or geometry saves a draft in the background about a second after the last change.
  • If the app is closed and reopened with a pending draft, a notification appears: "You have unsaved changes. Tap here to open them."
  • Tapping it - or trying to start a new "Add"/"Edit" while a draft exists - opens a drawer offering to Resume or Discard.
  • Resuming puts you back exactly where you left off: mid geometry capture, or on the form with the in-progress attributes.
  • Saving, deleting, or cancelling an edit normally clears its draft; a draft is silently discarded instead of offered if its layer was removed, its schema changed, its feature no longer exists, or it's over 10 days old.
Screen_Recording_20260818-082140_One.UI.Home.mp4
Screen_Recording_20260818-082018_One.UI.Home.mp4
Screen_Recording_20260818-082039.mp4

TLDR @Withalion

Feature edits are now drafted automatically so a crash, call, or dead battery doesn't cost you your unsaved work - surfaced via a notification and resume/discard drawers.

@xkello
xkello requested a review from Withalion August 18, 2026 06:34
@github-actions

Copy link
Copy Markdown

Coverage Report for CI Build 32107333826

Coverage decreased (-0.5%) to 58.633%

Details

  • Coverage decreased (-0.5%) from the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • 531 coverage regressions across 8 files.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

531 previously-covered lines in 8 files lost coverage.

File Lines Losing Coverage Coverage
mm/app/maptools/recordingmaptool.cpp 224 67.12%
mm/app/attributes/attributecontroller.cpp 208 73.91%
mm/app/activeproject.cpp 63 70.43%
mm/app/notificationmodel.cpp 21 48.45%
mm/core/merginapi.cpp 7 74.79%
mm/app/maptools/recordingmaptool.h 4 50.0%
mm/app/notificationmodel.h 3 50.0%
mm/core/merginuserinfo.cpp 1 79.08%

Coverage Stats

Coverage Status
Relevant Lines: 15933
Covered Lines: 9342
Line Coverage: 58.63%
Coverage Strength: 96.94 hits per line

💛 - Coveralls

@github-actions

Copy link
Copy Markdown

📦 Build Artifacts Ready

OS Status Build Info Workflow run
macOS Build Build failed or not found. #7166
linux Build 📬 Mergin Maps 71921 x86_64 Expires: 16/11/2026 #7192
win64 Build 📬 Mergin Maps 63681 win64 Expires: 16/11/2026 #6368
Android Build 📬 Mergin Maps 847751 APK [arm64-v8a] Expires: 16/11/2026 #8477
📬 Mergin Maps 847751 APK [arm64-v8a] Google Play Store #8477
Android Build 📬 Mergin Maps 847711 APK [armeabi-v7a] Expires: 16/11/2026 #8477
📬 Mergin Maps 847711 APK [armeabi-v7a] Google Play Store #8477
iOS Build 📬 Build number: 26.08.941811 #9418

@Withalion Withalion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job, it looks very promising! There are some trivial things, a bit more refactoring is needed, but mainly unit tests.

UI/UX

  • It would be nice to navigate to the geometry you will be recording further after you open the draft
  • After user is done recording geometry and doesn't fill out anything in the form yet, application crashes. We should open the form right away and not the geometry recording
  • When a draft is available and an existing feature is clicked to edit the warning drawer opens and closes right away
  • When a draft is available and "add" button is clicked, the drawer opens correctly, but discarding draft doesn't start recording mode, but stays in "view" mode of map

Comment thread app/notificationmodel.h
ShowSwitchWorkspaceAction,
ShowSyncFailedDialog
ShowSyncFailedDialog,
OpenDraftAction

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
OpenDraftAction
ShowDraftAction

let's keep the naming convention

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add it also to gallery


secondaryButton.text: qsTr( "Do not discard" )

onPrimaryButtonClicked: {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onPrimaryButtonClicked: {
onPrimaryButtonClicked: () => {

close()
}

onSecondaryButtonClicked: {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onSecondaryButtonClicked: {
onSecondaryButtonClicked: () => {

signal discardClicked()

imageSource: __style.neutralMMSymbolImage
title: featureTitle !== ""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
title: featureTitle !== ""
title: featureTitle

setDraft( false );

if ( wkt.isEmpty() )
return QgsGeometry();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return QgsGeometry();
return {};

#ifndef FEATUREDRAFTSTORAGE_H
#define FEATUREDRAFTSTORAGE_H

#include <QString>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <QString>

#include <QSettings>
#include <QJsonDocument>

const QString FeatureDraftStorage::QSETTINGS_DRAFTS_GROUP_NAME = QStringLiteral( "featureDrafts" );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

declaring the literal should be enough here, you don't have to declare it in the class as well


if ( raw.isEmpty() )
{
return QJsonObject();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return QJsonObject();
return {};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as probably mentioned elsewhere the data of draft should be stored during runtime as a structure and this class should do the conversion between structure and QSettings friendly format. Other classes don't need to know how it's stored

@Withalion Withalion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job, it looks very promising! There are some trivial things, a bit more refactoring is needed, but mainly unit tests.

UI/UX

  • It would be nice to navigate to the geometry you will be recording further after you open the draft
  • After user is done recording geometry and doesn't fill out anything in the form yet, application crashes. We should open the form right away and not the geometry recording
  • When a draft is available and an existing feature is clicked to edit the warning drawer opens and closes right away
  • When a draft is available and "add" button is clicked, the drawer opens correctly, but discarding draft doesn't start recording mode, but stays in "view" mode of map

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.

Resume unsaved feature edits after unexpected app closure

2 participants