Add "sign allows cycling on sidewalk, both directions" option to cycleway quest/overlay

Adds the option "None, but sign allows cycling on sidewalk, both directions" to the available road-side cycleway options.

![photo_2024-10-13_19-59-21](https://github.com/user-attachments/assets/ccbd4558-b324-45ce-a31f-30c3aadfaef6)

Fixes #5888

Cycling on sidewalk (in both directions) answer is missing
Thanks for adding back the differentiation of cycling on the sidewalk being allowed vs. a shared use sidewalk. (not sure when that happend, but it was missing a while)

However there's an option missing:

Cycling on the sidewalk is allowed in both directions - on none side. Meaning: Riding on the sidewalk is not a one way.

The equivalent of "shared use path" however has such an option:

![Screenshot_2024-09-10-07-22-48-133-edit_de westnordost streetcomplete](https://github.com/user-attachments/assets/dfc086ac-6c16-40e1-acc1-3984345d0e29)

Btw: Wouldn't shared use sidewalk not be less confusing here?

**Use case**
We got sidewalks which are marked from both directions for usage with bikes:

![Screenshot_2024-09-10-07-29-08-179-edit_com google android apps maps](https://github.com/user-attachments/assets/323fc4bf-1838-432d-a30d-fabd3ab2f333)

**Proposed Solution**
Just another button. Not sure about the tagging thought.

"Btw: Wouldn't shared use sidewalk not be less confusing here?"

No. Sidewalk is a term specifically referring to a path next to a road. There are plenty of unsegregated cycle tracks that do not follow the route of a road and are thus not "sidewalk" in any sense of the meaning of the word.
> "Btw: Wouldn't shared use sidewalk not be less confusing here?"
>
> No. Sidewalk is a term specifically referring to a path next to a road. There are plenty of unsegregated cycle tracks that do not follow the route of a road and are thus not "sidewalk" in any sense of the meaning of the word.

I think you're missing the point here. The quest is regarding infrastructure along a road. So sidewalk is always the appropriate term here.
> there's an option missing:
>
> Cycling on the sidewalk is allowed in both directions - on [one] side

I had this same problem yesterday. Not sure which I ended up picking anymore, also because the effect seems virtually identical: you can cycle on the same path as pedestrians can walk on, either because a sign says it's ok or because a sign says it's a shared path. I'm also not sure how to differentiate between a path where it says "cycling ok" but means it only for one direction, and one where the same sign means both directions.

> We got sidewalks which are marked from both directions for usage with bikes:

The picture doesn't show anything that indicates both directions though? Since there is no oneway variant of it, it stands to reason that it means that, but honestly I wouldn't know

>
> > We got sidewalks which are marked from both directions for usage with bikes:
>
> The picture doesn't show anything that indicates both directions though? Since there is no oneway variant of it, it stands to reason that it means that, but honestly I wouldn't know

Well, that's on the left side of the road. If the sign is facing you, it means you're allowed to ride in this direction.

I the other direction there's the same sign, just facing the other way.

🎶 Now this looks like a job for me...🎶

I also thought about that when implementing that change, but thought it would clutter up the UI too much for the limited times I thought it would apply. If @westnordost is okay with it, I can implement that addition.

We should keep in mind that we still need to think about if theese options should be only enabled in certain countries, following my post on the forum that got out of hand a bit. :D
Cool, yeah, I think it should be added (at the very bottom). I can imagine that it is a bit common in rural areas where there is just one path on one side of a rural road for both cyclists and pedestrians which however is not broad enough to legally force cyclists to use it.
For tagging:
My intuitive way to tag it would be
`sidewalk:<side>:bicycle=yes`
`sidewalk:<side>:oneway:bicycle=no`
I guess it would be weird to have `cycleway:<side>:oneway=no` when there is `cycleway:<side>=no`, so your suggestion sounds good.

./gradlew :app:testDebugUnitTest --tests "de.westnordost.streetcomplete.osm.cycleway.CyclewayCreatorKtTest" --tests "de.westnordost.streetcomplete.osm.cycleway.CyclewayParserKtTest"

diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt
index 44b6cd2c1af..a56eaba6727 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt
@@ -64,7 +64,7 @@ data class CyclewayAndDirection(val cycleway: Cycleway, val direction: Direction
 fun CyclewayAndDirection.isSelectable(countryInfo: CountryInfo): Boolean =
     cycleway.isSelectable(countryInfo) &&
     // only allow dual track, dual lanes and "dual" sidewalk (not dual pictograms or something)
-    (direction != BOTH || cycleway in listOf(TRACK, UNSPECIFIED_LANE, EXCLUSIVE_LANE, SIDEWALK_EXPLICIT))
+    (direction != BOTH || cycleway in listOf(TRACK, UNSPECIFIED_LANE, EXCLUSIVE_LANE, SIDEWALK_EXPLICIT, SIDEWALK_OK))

 @Serializable
 enum class Direction {
@@ -195,7 +195,8 @@ fun getSelectableCycleways(
     val dualCycleways = listOf(
         CyclewayAndDirection(if (countryInfo.hasAdvisoryCycleLane) EXCLUSIVE_LANE else UNSPECIFIED_LANE, BOTH),
         CyclewayAndDirection(TRACK, BOTH),
-        CyclewayAndDirection(SIDEWALK_EXPLICIT, BOTH)
+        CyclewayAndDirection(SIDEWALK_EXPLICIT, BOTH),
+        CyclewayAndDirection(SIDEWALK_OK, BOTH)
     )

     // no need to distinguish between advisory and exclusive lane where the concept of exclusive
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt
index fda4de15278..31fb4e680a4 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt
@@ -181,7 +181,11 @@ private fun CyclewayAndDirection.applyTo(tags: Tags, isRight: Boolean, isLeftHan
         val isDefaultDirection = defaultDirection == direction
         val isInContraflowOfOneway = isInContraflowOfOneway(tags, direction)
         if (!isDefaultDirection || isInContraflowOfOneway || tags.containsKey("$cyclewayKey:oneway")) {
-            tags["$cyclewayKey:oneway"] = direction.onewayValue
+            if (cycleway == SIDEWALK_OK) {
+                tags["sidewalk:$side:oneway:bicycle"] = direction.onewayValue
+            } else {
+                tags["$cyclewayKey:oneway"] = direction.onewayValue
+            }
         }
     }

@@ -195,11 +199,14 @@ private fun CyclewayAndDirection.applyTo(tags: Tags, isRight: Boolean, isLeftHan
     if (!touchedSegregatedValue) {
         tags.remove("$cyclewayKey:segregated")
     }
-    // no matter what option was chosen, if it's not SIDEWAY_OK, remove that cycling is signed and ok on sidewalk if it's there
+    // no matter what option was chosen, if it's not SIDEWALK_OK, remove that cycling is signed and ok on sidewalk if it's there
     if (cycleway != SIDEWALK_OK && tags["sidewalk:$side:bicycle"] == "yes" && tags["sidewalk:$side:bicycle:signed"] == "yes") {
         tags.remove("sidewalk:$side:bicycle")
         tags.remove("sidewalk:$side:bicycle:signed")
     }
+    if (direction != BOTH && tags["sidewalk:$side:oneway:bicycle"] == "no") {
+        tags.remove("sidewalk:$side:oneway:bicycle")
+    }
     // if anything that is not SIDEWALK_EXPLICIT is chosen, remove sidewalk:<side>:bicycle=designated if it's there
     if (cycleway != SIDEWALK_EXPLICIT && tags["sidewalk:$side:bicycle"] == "designated") {
         tags.remove("sidewalk:$side:bicycle")
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt
index 6ede17cf6d2..16670a08887 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt
@@ -110,6 +110,7 @@ private fun Cycleway.getDualTrafficIconResId(countryInfo: CountryInfo): Int = wh
             R.drawable.ic_cycleway_track_dual
         }
     SIDEWALK_EXPLICIT ->                   R.drawable.ic_cycleway_sidewalk_explicit_dual
+    SIDEWALK_OK ->                         R.drawable.ic_cycleway_sidewalk_ok_both
     else ->                                0
 }

@@ -183,6 +184,12 @@ private fun CyclewayAndDirection.getTitleResId(isContraflowInOneway: Boolean): I
     BUSWAY ->            R.string.quest_cycleway_value_bus_lane
     SEPARATE ->          R.string.quest_cycleway_value_separate
     SHOULDER ->          R.string.quest_cycleway_value_shoulder
-    SIDEWALK_OK ->       R.string.quest_cycleway_value_sidewalk_ok
+    SIDEWALK_OK ->       {
+        if (direction == BOTH) {
+            R.string.quest_cycleway_value_sidewalk_ok_dual
+        } else {
+            R.string.quest_cycleway_value_sidewalk_ok
+        }
+    }
     else -> 0
 }
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt
index 67701662d99..32f1655f903 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt
@@ -154,11 +154,20 @@ private fun parseDirectionForSide(
 ): Direction {
     val sideVal = if (isRight) ":right" else ":left"
     val cyclewayKey = "cycleway$sideVal"
+    val sidewalkKey = "sidewalk$sideVal"
     val explicitDirection = when (tags["$cyclewayKey:oneway"]) {
         "yes" -> Direction.FORWARD
         "-1" ->  Direction.BACKWARD
         "no" ->  Direction.BOTH
-        else ->  null
+        else ->  when (tags["$sidewalkKey:oneway:bicycle"]) {
+            "yes" -> Direction.FORWARD
+            "-1" ->  Direction.BACKWARD
+            "no" ->  when (tags["$sidewalkKey:bicycle:signed"]) {
+                "yes" -> Direction.BOTH
+                else -> null
+            }
+            else -> null
+        }
     }
     return explicitDirection ?: Direction.getDefault(isRight, isLeftHandTraffic)
 }
@@ -188,6 +197,7 @@ private fun expandRelevantSidesTags(tags: Map<String, String>): Map<String, Stri
     result.expandSidesTags("cycleway", "segregated", true)
     result.expandSidesTags("sidewalk", "bicycle", true)
     result.expandSidesTags("sidewalk", "bicycle:signed", true)
+    result.expandSidesTags("sidewalk", "oneway:bicycle", true)
     return result
 }

diff --git a/app/src/main/res/drawable/ic_cycleway_sidewalk_ok_both.xml b/app/src/main/res/drawable/ic_cycleway_sidewalk_ok_both.xml
new file mode 100644
index 00000000000..ef7c48f5a01
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cycleway_sidewalk_ok_both.xml
@@ -0,0 +1,80 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="256dp"
+    android:height="256dp"
+    android:viewportWidth="256"
+    android:viewportHeight="256">
+  <path
+      android:pathData="M128,0.01l72,0l0,256l-72,0z"
+      android:fillColor="#aaa"/>
+  <path
+      android:pathData="m192,231.99l8,0m-8,-24l8,0m-8,-24l8,0m0,-24l-8,0m0,-24l8,0m-8,-24l8,0m-8,-24l8,0m-8,-24l8,0m-8,-24l8,0m-8,-24l8,0m-72,216l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-48l16,0m-16,-24l16,0m-16,-48l16,0m-16,24l16,0m-16,72l16,0m16,120l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-48l16,0m-16,-24l16,0m-16,-48l16,0m-16,24l16,0m-16,72l16,0m-32,132l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-48l16,0m-16,-24l16,0m-16,-48l16,0m-16,24l16,0m-16,72l16,0m32,156l0,-256m-16,0l0,256m-16,0l0,-256m-16,0l0,256m32,-228.58l16,0m-16,24l16,0m-16,192l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-72l16,0"
+      android:strokeWidth="4"
+      android:fillColor="#00000000"
+      android:strokeColor="#999"/>
+  <path
+      android:pathData="M120,0.01l8,0l0,256l-8,0z"
+      android:fillColor="#999"/>
+  <path
+      android:pathData="M200,0.01l8,0l0,256l-8,0z"
+      android:fillColor="#999"/>
+  <path
+      android:pathData="M112,0.01l8,0l0,256l-8,0z"
+      android:fillColor="#666"/>
+  <path
+      android:pathData="m168.11,152.45c-2.87,0 -5.19,2.32 -5.19,5.19 0,2.87 2.33,5.19 5.19,5.19 2.87,0 5.19,-2.32 5.19,-5.19 0,-2.87 -2.32,-5.19 -5.19,-5.19zM162.68,164.72c-5.25,1.96 -9.3,5.3 -9.35,10.86 0,1.42 1.15,2.57 2.57,2.57 1.42,0 2.57,-1.15 2.57,-2.57 0.04,-1.42 0.64,-2.62 1.52,-3.59 0.03,1.26 0.23,2.68 0.66,4.26 -0.27,0.45 -0.38,1.12 -0.27,2.1 -0.91,5.23 -6.67,8.63 -10.39,10.32 -1.3,0.58 -1.89,2.1 -1.31,3.4 0.58,1.3 2.1,1.89 3.4,1.31 4.93,-2.57 11.2,-6.71 13,-12.15 4.1,1.91 7.93,6.2 8.62,10.16 0.2,1.41 1.51,2.38 2.91,2.18 1.41,-0.2 2.39,-1.5 2.18,-2.91 -1.07,-6.35 -5.45,-11.05 -10.93,-13.81 -1.58,-0.68 -1.71,-4.38 -1.62,-5.71 4.8,2.73 9.72,3.94 14.59,1.55 1.27,-0.64 1.79,-2.18 1.15,-3.45 -0.64,-1.27 -2.18,-1.79 -3.45,-1.15 -5.72,2.83 -9.29,-1.5 -12.3,-3.26 -1.08,-0.75 -2.51,-0.76 -3.58,-0.1z"
+      android:strokeWidth="3.78"
+      android:fillColor="#ffffff"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="m153.73,200.93l22.41,0a4.8,4.8 0,0 1,4.8 4.79l0,20.41a4.8,4.8 0,0 1,-4.8 4.79l-22.41,0a4.8,4.8 0,0 1,-4.8 -4.79l0,-20.41a4.8,4.8 0,0 1,4.8 -4.79z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#ffffff"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m162.06,215.14a4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64 4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64zM177.24,215.14a4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64 4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64zM159.99,207.99 L165.88,215.11l6.73,0l-4.63,-7.12L160,207.99m5.89,7.12 l2.11,-8.8m0,0 l2.05,-0.01m-12.57,8.8 l3.51,-10.39l3.36,0"
+      android:strokeLineJoin="round"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m164.94,224.93c0,1.5 -0.99,3 -3,3 -2.01,0 -3.03,-1.47 -3,-3 0.03,-1.52 0.95,-3 3,-3 2.05,0 3,1.5 3,3zM163.44,224.93c0,-0.73 -0.5,-1.5 -1.5,-1.5 -1.01,0 -1.5,0.74 -1.5,1.5 0,0.76 0.5,1.5 1.5,1.5 1,0 1.5,-0.77 1.5,-1.5z"
+      android:strokeWidth="0.5"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m171.43,227.94l-1.64,0l-1.85,-2.5 -0.5,0.5l0,2l-1.5,0l0,-6l1.5,0l0,2.5l2.5,-2.5l1.65,0l-2.65,2.6z"
+      android:strokeWidth="0.5"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m161.83,97.48c2.87,0 5.19,-2.32 5.19,-5.19 0,-2.87 -2.33,-5.19 -5.19,-5.19 -2.87,0 -5.19,2.32 -5.19,5.19 0,2.87 2.32,5.19 5.19,5.19zM167.26,85.21c5.25,-1.96 9.3,-5.3 9.35,-10.86 0,-1.42 -1.15,-2.57 -2.57,-2.57 -1.42,0 -2.57,1.15 -2.57,2.57 -0.04,1.42 -0.64,2.62 -1.52,3.59 -0.03,-1.26 -0.23,-2.68 -0.66,-4.26 0.27,-0.45 0.38,-1.12 0.27,-2.1 0.91,-5.23 6.67,-8.63 10.39,-10.32 1.3,-0.58 1.89,-2.1 1.31,-3.4 -0.58,-1.3 -2.1,-1.89 -3.4,-1.31 -4.93,2.57 -11.2,6.71 -13,12.15 -4.1,-1.91 -7.93,-6.2 -8.62,-10.16 -0.2,-1.41 -1.51,-2.38 -2.91,-2.18 -1.41,0.2 -2.39,1.5 -2.18,2.91 1.07,6.35 5.45,11.05 10.93,13.81 1.58,0.68 1.71,4.38 1.62,5.71 -4.8,-2.73 -9.72,-3.94 -14.59,-1.55 -1.27,0.64 -1.79,2.18 -1.15,3.45 0.64,1.27 2.18,1.79 3.45,1.15 5.72,-2.83 9.29,1.5 12.3,3.26 1.08,0.75 2.51,0.76 3.58,0.1z"
+      android:strokeWidth="3.78"
+      android:fillColor="#ffffff"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="m176.2,49l-22.41,0a4.8,4.8 0,0 1,-4.8 -4.79l0,-20.41a4.8,4.8 0,0 1,4.8 -4.79l22.41,0a4.8,4.8 0,0 1,4.8 4.79l0,20.41a4.8,4.8 0,0 1,-4.8 4.79z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#ffffff"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m167.87,34.79a4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64 4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64zM152.69,34.79a4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64 4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64zM169.94,41.94 L164.05,34.82l-6.73,0l4.63,7.12l7.99,0m-5.89,-7.12 l-2.11,8.8m0,0 l-2.05,0.01m12.57,-8.8 l-3.51,10.39l-3.36,0"
+      android:strokeLineJoin="round"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m165,25c0,-1.5 0.99,-3 3,-3 2.01,0 3.03,1.47 3,3 -0.03,1.52 -0.95,3 -3,3 -2.05,0 -3,-1.5 -3,-3zM166.5,25c-0,0.73 0.5,1.5 1.5,1.5 1.01,0 1.5,-0.74 1.5,-1.5 0,-0.76 -0.5,-1.5 -1.5,-1.5 -1,0 -1.5,0.77 -1.5,1.5z"
+      android:strokeWidth="0.5"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="m158.51,21.99l1.64,0l1.85,2.5 0.5,-0.5l0,-2l1.5,0l0,6l-1.5,0l0,-2.5L160,27.99l-1.65,0l2.65,-2.6z"
+      android:strokeWidth="0.5"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M0,0h112v256h-112z"
+      android:fillColor="#808080"/>
+</vector>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fe27cde6680..6d2b5001463 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1022,6 +1022,7 @@ A level counts as a roof level when its windows are in the roof. Subsequently, r
     <string name="quest_cycleway_value_none_and_oneway">none, no cycling allowed in this direction</string>
     <string name="quest_cycleway_value_shoulder">none, but shoulder is usable for cyclists</string>
     <string name="quest_cycleway_value_sidewalk_ok">none, but sign allows cycling on sidewalk</string>
+    <string name="quest_cycleway_value_sidewalk_ok_dual">none, but sign allows cycling on sidewalk, both directions</string>
     <string name="quest_cycleway_answer_contraflow_cycleway">Also bicycle path on the other side…</string>
     <string name="quest_cycleway_answer_no_bicycle_infrastructure">No bicycle lane or path at all</string>
     <string name="quest_cycleway_answer_no_bicycle_infrastructure_explanation">As usual, tap on one side of the illustration, select a proper answer and do the same for the other side.\n\nNote all the available options: some cases are not obvious, like a oneway road that allows cyclists in both directions!</string>
diff --git a/res/graphics/cycleway/sidewalk_ok_dual.svg b/res/graphics/cycleway/sidewalk_ok_dual.svg
new file mode 100644
index 00000000000..1fabc37ee2f
--- /dev/null
+++ b/res/graphics/cycleway/sidewalk_ok_dual.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#aaa" d="M128 .006h72v255.997h-72z"/><path fill="none" stroke="#999" stroke-width="3.998711" d="M191.999 231.992h8m-8-24h8m-8-24h8m0-24h-8m0-24h8m-8-24h8m-8-24h8m-8-24h8m-8-23.999h8m-8-24h8m-72 215.999h16m-16-24h16m-16-24h16m-16-24h16m-16-24h16m-16-48h16m-16-24h16m-16-47.999h16m-16 24h16m-16 72h16m16 119.999h16m-16-24h16m-16-24h16m-16-24h16m-16-24h16m-16-48h16m-16-24h16m-16-47.999h16m-16 24h16m-16 72h16m-32 131.999h16m-16-24h16m-16-24h16m-16-24h16m-16-24h16m-16-24h16m-16-48h16m-16-23.999h16m-16-48h16m-16 24h16m-16 72h16m32 155.999V-.005m-16 0v255.997m-16 0V-.005m-16 0v255.997m32-228.577h16m-16 24h16m-16 191.998h16m-16-24h16m-16-24h16m-16-24h16m-16-23.999h16m-16-24h16m-16-24h16m-16-24h16m-16-71.999h16"/><path fill="#999" d="M120 .006h8v255.997h-8zM200 .006H208v255.997H200z"/><path fill="#666" d="M111.998.006h8.001v255.997h-8.001z"/><path fill="#fff" d="M44.478 269.6a1.374 1.374 0 1 0 0 2.748 1.374 1.374 0 0 0 0-2.748zm-1.437 3.247c-1.39.52-2.462 1.403-2.474 2.872a.68.68 0 1 0 1.363 0c.01-.375.17-.694.403-.949.007.335.06.71.176 1.127-.071.118-.1.296-.073.555-.24 1.383-1.765 2.283-2.748 2.73a.681.681 0 0 0 .553 1.245c1.305-.68 2.964-1.775 3.441-3.214 1.084.505 2.098 1.64 2.28 2.687a.681.681 0 0 0 1.349-.192c-.282-1.68-1.443-2.925-2.89-3.654-.42-.18-.453-1.159-.43-1.511 1.27.72 2.573 1.043 3.861.41a.681.681 0 0 0-.609-1.218c-1.514.749-2.458-.398-3.255-.862a.87.87 0 0 0-.947-.026z" style="paint-order:stroke fill markers" transform="translate(0 -866.52) scale(3.7795)"/><path fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.9993555" d="M153.735 200.924h22.41a4.795 4.795 0 0 1 4.795 4.795v20.41a4.795 4.795 0 0 1-4.795 4.795h-22.41a4.795 4.795 0 0 1-4.795-4.795v-20.41a4.795 4.795 0 0 1 4.795-4.795z"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5004615000000001" d="M162.065 215.135a4.66 4.64 0 0 1-4.66 4.64 4.66 4.64 0 0 1-4.66-4.64 4.66 4.64 0 0 1 4.66-4.64 4.66 4.64 0 0 1 4.66 4.64zm15.18 0a4.66 4.64 0 0 1-4.66 4.64 4.66 4.64 0 0 1-4.66-4.64 4.66 4.64 0 0 1 4.66-4.64 4.66 4.64 0 0 1 4.66 4.64zm-17.25-7.145 5.89 7.12h6.73l-4.625-7.12H160m5.89 7.12 2.105-8.795m0 0 2.05-.011m-12.565 8.8 3.505-10.39h3.365"/><path d="M164.937 224.924c0 1.5-.995 3-3 3s-3.03-1.475-3-3c.03-1.525.95-3 3-3s3 1.5 3 3zm-1.5 0c.005-.73-.495-1.5-1.5-1.5s-1.5.745-1.5 1.5.5 1.5 1.5 1.5 1.495-.77 1.5-1.5zM171.427 227.948h-1.64l-1.85-2.5-.5.5v2h-1.5v-6h1.5v2.5l2.5-2.5h1.65l-2.65 2.6z"/><path fill="#fff" d="M42.818 255.06a1.374 1.374 0 1 0 0-2.748 1.374 1.374 0 0 0 0 2.748zm1.437-3.247c1.39-.52 2.462-1.403 2.474-2.872a.68.68 0 1 0-1.363 0c-.01.375-.17.694-.403.949a4.617 4.617 0 0 0-.176-1.127c.071-.118.1-.296.073-.555.24-1.383 1.765-2.283 2.748-2.73a.681.681 0 0 0-.553-1.245c-1.305.68-2.964 1.775-3.441 3.214-1.084-.505-2.098-1.64-2.28-2.687a.681.681 0 0 0-1.349.192c.282 1.68 1.443 2.925 2.89 3.654.42.18.453 1.159.43 1.511-1.27-.72-2.573-1.043-3.861-.41a.681.681 0 0 0 .609 1.218c1.514-.749 2.458.398 3.255.862.285.2.664.2.947.026z" style="paint-order:stroke fill markers" transform="translate(0 -866.52) scale(3.7795)"/><path fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.9993555" d="M176.204 48.988h-22.41a4.795 4.795 0 0 1-4.795-4.795v-20.41a4.795 4.795 0 0 1 4.795-4.795h22.41A4.795 4.795 0 0 1 181 23.783v20.41a4.795 4.795 0 0 1-4.795 4.795z"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5004615000000001" d="M167.874 34.777a4.66 4.64 0 0 1 4.66-4.64 4.66 4.64 0 0 1 4.66 4.64 4.66 4.64 0 0 1-4.66 4.64 4.66 4.64 0 0 1-4.66-4.64zm-15.18 0a4.66 4.64 0 0 1 4.66-4.64 4.66 4.64 0 0 1 4.66 4.64 4.66 4.64 0 0 1-4.66 4.64 4.66 4.64 0 0 1-4.66-4.64zm17.25 7.146-5.89-7.12h-6.73l4.625 7.12h7.99m-5.89-7.12-2.105 8.795m0 0-2.05.01m12.565-8.8-3.505 10.39h-3.365"/><path d="M164.998 24.988c0-1.5.995-3 3-3s3.03 1.475 3 3c-.03 1.525-.95 3-3 3s-3-1.5-3-3zm1.5 0c-.005.73.495 1.5 1.5 1.5s1.5-.745 1.5-1.5-.5-1.5-1.5-1.5-1.495.77-1.5 1.5zM158.508 22.003h1.64l1.85 2.5.5-.5v-2h1.5v6h-1.5v-2.5l-2.5 2.5h-1.65l2.65-2.6z"/><path fill="gray" d="M0 0h112v256H0z"/></svg>
\ No newline at end of file

diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt
index 76e0f0523f5..afed76e8a26 100644
--- a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt
+++ b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt
@@ -815,6 +815,71 @@ class CyclewayCreatorKtTest {
             cycleway(null, INVALID).applyTo(StringMapChangesBuilder(mapOf()), false)
         }
     }
+
+    @Test fun `apply allowed on sidewalk with both directions to one side`() {
+        assertEquals(
+            setOf(
+                StringMapEntryAdd("cycleway:right", "no"),
+                StringMapEntryAdd("sidewalk:right:bicycle", "yes"),
+                StringMapEntryAdd("sidewalk:right:bicycle:signed", "yes"),
+                StringMapEntryAdd("sidewalk:right:oneway:bicycle", "no")
+            ),
+            cycleway(
+                null,
+                SIDEWALK_OK to BOTH
+            ).appliedTo(mapOf())
+        )
+    }
+
+    @Test fun `apply allowed on sidewalk with both directions to left side`() {
+        assertEquals(
+            setOf(
+                StringMapEntryAdd("cycleway:left", "no"),
+                StringMapEntryAdd("sidewalk:left:bicycle", "yes"),
+                StringMapEntryAdd("sidewalk:left:bicycle:signed", "yes"),
+                StringMapEntryAdd("sidewalk:left:oneway:bicycle", "no")
+            ),
+            cycleway(
+                SIDEWALK_OK to BOTH,
+                null
+            ).appliedTo(mapOf())
+        )
+    }
+
+    @Test fun `apply allowed on sidewalk with both directions to both sides`() {
+        assertEquals(
+            setOf(
+                StringMapEntryAdd("cycleway:both", "no"),
+                StringMapEntryAdd("sidewalk:both:bicycle", "yes"),
+                StringMapEntryAdd("sidewalk:both:bicycle:signed", "yes"),
+                StringMapEntryAdd("sidewalk:both:oneway:bicycle", "no")
+            ),
+            cycleway(
+                SIDEWALK_OK to BOTH,
+                SIDEWALK_OK to BOTH
+            ).appliedTo(mapOf())
+        )
+    }
+
+    @Test fun `apply allowed on sidewalk in both directions to existing sidewalk ok`() {
+        assertEquals(
+            setOf(
+                StringMapEntryModify("cycleway:right", "no", "no"),
+                StringMapEntryModify("sidewalk:right:bicycle", "yes", "yes"),
+                StringMapEntryModify("sidewalk:right:bicycle:signed", "yes", "yes"),
+                StringMapEntryAdd("sidewalk:right:oneway:bicycle", "no")
+            ),
+            cycleway(
+                null,
+                SIDEWALK_OK to BOTH
+            ).appliedTo(mapOf(
+                "cycleway:right" to "no",
+                "sidewalk:right:bicycle" to "yes",
+                "sidewalk:right:bicycle:signed" to "yes"
+            ))
+        )
+    }
+
 }

 private fun LeftAndRightCycleway.appliedTo(tags: Map<String, String>, isLeftHandTraffic: Boolean = false): Set<StringMapEntryChange> {
diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt
index 927d63de671..d2386ed4f7c 100644
--- a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt
+++ b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt
@@ -2343,6 +2343,75 @@ class CyclewayParserKtTest {
             )
         )
     }
+
+    @Test fun `right side sidewalk ok in both directions`() {
+        assertEquals(
+            cycleway(null, SIDEWALK_OK to BOTH),
+            parse(
+                "sidewalk:right:bicycle" to "yes",
+                "sidewalk:right:bicycle:signed" to "yes",
+                "sidewalk:right:oneway:bicycle" to "no",
+                "cycleway:right" to "no"
+            )
+        )
+    }
+
+    @Test fun `left side sidewalk ok in both directions`() {
+        assertEquals(
+            cycleway(SIDEWALK_OK to BOTH, null),
+            parse(
+                "sidewalk:left:bicycle" to "yes",
+                "sidewalk:left:bicycle:signed" to "yes",
+                "sidewalk:left:oneway:bicycle" to "no",
+                "cycleway:left" to "no"
+            )
+        )
+    }
+
+    @Test fun `both sides sidewalk ok in both directions`() {
+        assertEquals(
+            cycleway(SIDEWALK_OK to BOTH, SIDEWALK_OK to BOTH),
+            parse(
+                "sidewalk:both:bicycle" to "yes",
+                "sidewalk:both:bicycle:signed" to "yes",
+                "sidewalk:both:oneway:bicycle" to "no",
+                "cycleway:both" to "no"
+            )
+        )
+    }
+
+    @Test fun `right side sidewalk ok in both directions but missing signed yes leads to none`() {
+        assertEquals(
+            cycleway(null, NONE),
+            parse(
+                "sidewalk:right:bicycle" to "yes",
+                "sidewalk:right:oneway:bicycle" to "no",
+                "cycleway:right" to "no"
+            )
+        )
+    }
+
+    @Test fun `left side sidewalk ok in both directions but missing signed yes leads to none`() {
+        assertEquals(
+            cycleway(NONE, null),
+            parse(
+                "sidewalk:left:bicycle" to "yes",
+                "sidewalk:left:oneway:bicycle" to "no",
+                "cycleway:left" to "no"
+            )
+        )
+    }
+
+    @Test fun `both sides sidewalk ok in both directions but missing signed yes leads to none`() {
+        assertEquals(
+            cycleway(NONE, NONE),
+            parse(
+                "sidewalk:both:bicycle" to "yes",
+                "sidewalk:both:oneway:bicycle" to "no",
+                "cycleway:both" to "no"
+            )
+        )
+    }
 }

 private fun cycleway(left: Pair<Cycleway, Direction>?, right: Pair<Cycleway, Direction>?) =


callOnUpdated if an updated element does not have a local edit

Fixes #5745 as in the latest comments.
(the new test fails without the changes to `MapDataWithEditsSource`)

Element is not updated after conflict on upload
In building mode: after the building has been selected, it does not change color. You have to make the entry again so that the building is displayed as an apartment building, for example.

**How to Reproduce**
 Go into the building mode, select a building and change the building type to an apartment building. After the confirmation, the building type is not displayed.

**Expected Behavior**
The apartment building is displayed after the confirmation

I can't reproduce this problem with version 58.2. Can you record a screen video?
Yes, of course.
https://github.com/user-attachments/assets/539079f2-46d6-4a2c-a093-144cfa2c84db

Thanks! This might be another instance of #5545. Can you try to reproduce it another time and then immediately after go to Menu -> About -> Show Logs, then change the filter to show only the logs around the time this bug happened and share these here?

Alternatively, if you remember exactly when you reproduced this issue, i.e. apparently today at 16:12, you can also skip trying to reproduce it another time and directly look for the logs at that time.
I hope the screenshots will help you

![Screenshot_20240715_171921_StreetComplete](https://github.com/user-attachments/assets/c0291be1-13d5-440b-ab08-658562c59b17)
![Screenshot_20240715_171907_StreetComplete](https://github.com/user-attachments/assets/aea174a2-9e91-4549-8e40-78eeacf94086)

You can also press the share button, it would be much easier for me. (Do select the time range first in the filter dialog)
2024-07-15T16:04:38.065: I [Preloader] Loaded country boundaries in 0.2s
2024-07-15T16:04:38.438: I [Preloader] Loaded features dictionary in 0.6s
2024-07-15T16:04:38.439: I [Preloader] Preloading data took 0.6s
2024-07-15T16:04:38.792: W [ChangesetManager] Couldn't close changeset #153969903 because it has already been closed
2024-07-15T16:04:38.912: I [Upload] Starting upload
2024-07-15T16:04:38.922: I [Upload] Finished upload
2024-07-15T16:04:39.063: D [OsmAvatarsDownload] Downloaded file: /data/user/0/de.westnordost.streetcomplete/cache/osm_user_avatars/6319200
2024-07-15T16:04:39.135: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8071982,10.0288599
2024-07-15T16:04:39.735: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:09:32.120: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8071982,10.0288599
2024-07-15T16:09:32.136: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:09:32.253: I [Upload] Starting upload
2024-07-15T16:09:32.375: I [Upload] Finished upload
2024-07-15T16:09:32.499: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8071875,10.0288713
2024-07-15T16:09:32.518: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:09:33.309: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8072517,10.0288983
2024-07-15T16:09:33.331: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:09:45.211: D [ElementEditsController] Add BuildingsOverlay for WAY#397687166
2024-07-15T16:09:45.249: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397687166
2024-07-15T16:09:45.299: I [Upload] Starting upload
2024-07-15T16:09:45.326: D [OsmQuestController] Created AddBuildingLevels for WAY#397687166
2024-07-15T16:09:45.326: D [OsmQuestController] Created AddRoofShape for WAY#397687166
2024-07-15T16:09:45.677: I [OsmQuestController] Created 2 quests for 1 updated elements in 443ms
2024-07-15T16:09:45.689: I [ChangesetManager] Created changeset #153974585
2024-07-15T16:09:45.697: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.0s
2024-07-15T16:09:45.793: D [ElementEditsUploader] Uploaded a UpdateElementTagsAction
2024-07-15T16:09:45.858: I [Upload] Finished upload
2024-07-15T16:09:52.885: D [ElementEditsController] Add BuildingsOverlay for WAY#397687186
2024-07-15T16:09:52.897: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397687186
2024-07-15T16:09:52.899: D [OsmQuestController] Created AddBuildingLevels for WAY#397687186
2024-07-15T16:09:52.899: D [OsmQuestController] Created AddRoofShape for WAY#397687186
2024-07-15T16:09:52.934: I [Upload] Starting upload
2024-07-15T16:09:52.961: I [OsmQuestController] Created 2 quests for 1 updated elements in 67ms
2024-07-15T16:09:52.974: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.0s
2024-07-15T16:09:53.172: D [ElementEditsUploader] Dropped a UpdateElementTagsAction: Conflict while applying the changes
2024-07-15T16:09:53.189: D [OsmQuestController] Created AddBuildingType for WAY#397687186
2024-07-15T16:09:53.225: I [OsmQuestController] Created 1 quests for 1 updated elements in 40ms
2024-07-15T16:09:53.232: I [OsmQuestController] Persisted 1 new and removed 2 already resolved quests in 0.0s
2024-07-15T16:09:53.314: I [Upload] Finished upload
2024-07-15T16:12:46.390: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8072517,10.0288983
2024-07-15T16:12:46.426: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:12:46.912: I [Upload] Starting upload
2024-07-15T16:12:46.935: I [Upload] Finished upload
2024-07-15T16:12:47.185: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8074556,10.0287353
2024-07-15T16:12:47.221: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:12:48.408: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8073517,10.0287767
2024-07-15T16:12:48.440: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:12:53.136: D [ElementEditsController] Add BuildingsOverlay for WAY#397687189
2024-07-15T16:12:53.181: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397687189
2024-07-15T16:12:53.189: D [OsmQuestController] Created AddRoofShape for WAY#397687189
2024-07-15T16:12:53.189: D [OsmQuestController] Created AddBuildingLevels for WAY#397687189
2024-07-15T16:12:53.296: I [Upload] Starting upload
2024-07-15T16:12:53.747: I [OsmQuestController] Created 2 quests for 1 updated elements in 575ms
2024-07-15T16:12:53.799: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.1s
2024-07-15T16:12:54.051: D [ElementEditsUploader] Dropped a UpdateElementTagsAction: Conflict while applying the changes
2024-07-15T16:12:54.109: D [OsmQuestController] Created AddBuildingType for WAY#397687189
2024-07-15T16:12:54.162: I [OsmQuestController] Created 1 quests for 1 updated elements in 90ms
2024-07-15T16:12:54.171: I [OsmQuestController] Persisted 1 new and removed 2 already resolved quests in 0.0s
2024-07-15T16:12:54.265: I [Upload] Finished upload
2024-07-15T16:12:59.176: D [ElementEditsController] Add BuildingsOverlay for WAY#397687189
2024-07-15T16:12:59.209: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397687189
2024-07-15T16:12:59.212: D [OsmQuestController] Created AddBuildingLevels for WAY#397687189
2024-07-15T16:12:59.213: D [OsmQuestController] Created AddRoofShape for WAY#397687189
2024-07-15T16:12:59.272: I [Upload] Starting upload
2024-07-15T16:12:59.286: I [OsmQuestController] Created 2 quests for 1 updated elements in 91ms
2024-07-15T16:12:59.313: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.0s
2024-07-15T16:12:59.427: D [ElementEditsUploader] Uploaded a UpdateElementTagsAction
2024-07-15T16:12:59.475: I [Upload] Finished upload
2024-07-15T16:14:48.104: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8073517,10.0287767
2024-07-15T16:14:48.123: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:14:48.316: I [Upload] Starting upload
2024-07-15T16:14:48.345: I [Upload] Finished upload
2024-07-15T16:14:49.426: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8072617,10.0287117
2024-07-15T16:14:49.454: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:50:40.629: I [Preloader] Loaded country boundaries in 0.2s
2024-07-15T16:50:41.067: I [Preloader] Loaded features dictionary in 0.7s
2024-07-15T16:50:41.067: I [Preloader] Preloading data took 0.7s
2024-07-15T17:13:52.089: I [Preloader] Loaded country boundaries in 0.2s
2024-07-15T17:13:52.751: I [Preloader] Loaded features dictionary in 0.9s
2024-07-15T17:13:52.752: I [Preloader] Preloading data took 0.9s
2024-07-15T17:13:52.809: W [ChangesetManager] Couldn't close changeset #153974585 because it has already been closed
2024-07-15T17:13:52.967: I [Upload] Starting upload
2024-07-15T17:13:52.976: I [Upload] Finished upload
2024-07-15T17:13:53.006: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8071934,10.0288643
2024-07-15T17:13:53.058: D [OsmAvatarsDownload] Downloaded file: /data/user/0/de.westnordost.streetcomplete/cache/osm_user_avatars/6319200
2024-07-15T17:13:53.660: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T17:14:03.452: D [ElementEditsController] Add BuildingsOverlay for WAY#397687197
2024-07-15T17:14:03.481: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397687197
2024-07-15T17:14:03.535: I [Upload] Starting upload
2024-07-15T17:14:03.560: D [OsmQuestController] Created AddBuildingLevels for WAY#397687197
2024-07-15T17:14:03.560: D [OsmQuestController] Created AddRoofShape for WAY#397687197
2024-07-15T17:14:03.874: I [OsmQuestController] Created 2 quests for 1 updated elements in 405ms
2024-07-15T17:14:03.876: I [ChangesetManager] Created changeset #153977305
2024-07-15T17:14:03.888: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.0s
2024-07-15T17:14:04.044: D [ElementEditsUploader] Dropped a UpdateElementTagsAction: Conflict while applying the changes
2024-07-15T17:14:04.078: D [OsmQuestController] Created AddBuildingType for WAY#397687197
2024-07-15T17:14:04.139: I [OsmQuestController] Created 1 quests for 1 updated elements in 72ms
2024-07-15T17:14:04.146: I [OsmQuestController] Persisted 1 new and removed 2 already resolved quests in 0.0s
2024-07-15T17:14:04.216: I [Upload] Finished upload
2024-07-15T17:14:09.508: D [ElementEditsController] Add BuildingsOverlay for WAY#397687197
2024-07-15T17:14:09.527: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397687197
2024-07-15T17:14:09.529: D [OsmQuestController] Created AddBuildingLevels for WAY#397687197
2024-07-15T17:14:09.529: D [OsmQuestController] Created AddRoofShape for WAY#397687197
2024-07-15T17:14:09.566: I [Upload] Starting upload
2024-07-15T17:14:09.570: I [OsmQuestController] Created 2 quests for 1 updated elements in 45ms
2024-07-15T17:14:09.582: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.0s
2024-07-15T17:14:09.726: D [ElementEditsUploader] Uploaded a UpdateElementTagsAction
2024-07-15T17:14:09.759: I [Upload] Finished upload
2024-07-15T17:22:07.925: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8071934,10.0288643
2024-07-15T17:22:08.152: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8072514,10.0285792
2024-07-15T17:22:08.176: I [Upload] Starting upload
2024-07-15T17:22:08.491: I [Upload] Finished upload
2024-07-15T17:22:09.245: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T17:22:09.259: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T17:22:25.573: D [ElementEditsController] Add BuildingsOverlay for WAY#397967546
2024-07-15T17:22:25.596: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397967546
2024-07-15T17:22:25.600: D [OsmQuestController] Created AddBuildingLevels for WAY#397967546
2024-07-15T17:22:25.600: D [OsmQuestController] Created AddRoofShape for WAY#397967546
2024-07-15T17:22:25.635: I [Upload] Starting upload
2024-07-15T17:22:25.952: I [OsmQuestController] Created 2 quests for 1 updated elements in 359ms
2024-07-15T17:22:25.966: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.0s
2024-07-15T17:22:26.349: D [ElementEditsUploader] Dropped a UpdateElementTagsAction: Conflict while applying the changes
2024-07-15T17:22:26.394: D [OsmQuestController] Created AddBuildingType for WAY#397967546
2024-07-15T17:22:26.442: I [OsmQuestController] Created 1 quests for 1 updated elements in 69ms
2024-07-15T17:22:26.449: I [OsmQuestController] Persisted 1 new and removed 2 already resolved quests in 0.0s
2024-07-15T17:22:26.513: I [Upload] Finished upload
2024-07-15T17:22:33.177: D [ElementEditsController] Add BuildingsOverlay for WAY#397967541
2024-07-15T17:22:33.203: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397967541
2024-07-15T17:22:33.206: D [OsmQuestController] Created AddBuildingLevels for WAY#397967541
2024-07-15T17:22:33.206: D [OsmQuestController] Created AddRoofShape for WAY#397967541
2024-07-15T17:22:33.230: I [Upload] Starting upload
2024-07-15T17:22:33.264: I [OsmQuestController] Created 2 quests for 1 updated elements in 71ms
2024-07-15T17:22:33.281: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.0s
2024-07-15T17:22:33.472: D [ElementEditsUploader] Dropped a UpdateElementTagsAction: Conflict while applying the changes
2024-07-15T17:22:33.501: D [OsmQuestController] Created AddBuildingType for WAY#397967541
2024-07-15T17:22:33.538: I [OsmQuestController] Created 1 quests for 1 updated elements in 46ms
2024-07-15T17:22:33.545: I [OsmQuestController] Persisted 1 new and removed 2 already resolved quests in 0.0s
2024-07-15T17:22:33.609: I [Upload] Finished upload
2024-07-15T17:22:37.392: D [ElementEditsController] Add BuildingsOverlay for WAY#397967536
2024-07-15T17:22:37.416: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397967536
2024-07-15T17:22:37.417: D [OsmQuestController] Created AddBuildingLevels for WAY#397967536
2024-07-15T17:22:37.417: D [OsmQuestController] Created AddRoofShape for WAY#397967536
2024-07-15T17:22:37.457: I [OsmQuestController] Created 2 quests for 1 updated elements in 50ms
2024-07-15T17:22:37.462: I [Upload] Starting upload
2024-07-15T17:22:37.469: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.0s
2024-07-15T17:22:37.656: D [ElementEditsUploader] Dropped a UpdateElementTagsAction: Conflict while applying the changes
2024-07-15T17:22:37.672: D [OsmQuestController] Created AddBuildingType for WAY#397967536
2024-07-15T17:22:37.712: I [OsmQuestController] Created 1 quests for 1 updated elements in 43ms
2024-07-15T17:22:37.720: I [OsmQuestController] Persisted 1 new and removed 2 already resolved quests in 0.0s
2024-07-15T17:22:37.790: I [Upload] Finished upload
2024-07-15T17:23:01.630: D [ElementEditsController] Add WayLitOverlay for WAY#24146588
2024-07-15T17:23:01.654: D [OsmQuestController] AddMaxHeight requires surrounding map data to determine applicability to WAY#24146588
2024-07-15T17:23:01.662: D [OsmQuestController] Created AddRoadSurface for WAY#24146588
2024-07-15T17:23:01.689: I [Upload] Starting upload
2024-07-15T17:23:01.710: I [OsmQuestController] Created 1 quests for 1 updated elements in 63ms
2024-07-15T17:23:01.718: I [OsmQuestController] Persisted 1 new and removed 0 already resolved quests in 0.0s
2024-07-15T17:23:01.887: I [ChangesetManager] Created changeset #153977710
2024-07-15T17:23:02.008: D [ElementEditsUploader] Uploaded a UpdateElementTagsAction
2024-07-15T17:23:02.050: I [Upload] Finished upload
2024-07-15T17:23:35.752: D [ElementEditsController] Add WayLitOverlay for WAY#34123872
2024-07-15T17:23:35.773: D [OsmQuestController] AddMaxHeight requires surrounding map data to determine applicability to WAY#34123872
2024-07-15T17:23:35.778: D [OsmQuestController] Created AddCycleway for WAY#34123872
2024-07-15T17:23:35.778: D [OsmQuestController] Created AddSidewalk for WAY#34123872
2024-07-15T17:23:35.778: D [OsmQuestController] Created AddRoadSmoothness for WAY#34123872
2024-07-15T17:23:35.818: I [OsmQuestController] Created 3 quests for 1 updated elements in 46ms
2024-07-15T17:23:35.827: I [OsmQuestController] Persisted 3 new and removed 1 already resolved quests in 0.0s
2024-07-15T17:23:35.870: I [Upload] Starting upload
2024-07-15T17:23:36.012: D [ElementEditsUploader] Uploaded a UpdateElementTagsAction
2024-07-15T17:23:36.056: I [Upload] Finished upload
2024-07-15T17:23:41.573: D [ElementEditsController] Add WayLitOverlay for WAY#555383320
2024-07-15T17:23:41.590: D [OsmQuestController] AddMaxHeight requires surrounding map data to determine applicability to WAY#555383320
2024-07-15T17:23:41.593: D [OsmQuestController] Created AddCycleway for WAY#555383320
2024-07-15T17:23:41.593: D [OsmQuestController] Created AddSidewalk for WAY#555383320
2024-07-15T17:23:41.594: D [OsmQuestController] Created AddLanes for WAY#555383320
2024-07-15T17:23:41.594: D [OsmQuestController] Created AddRoadSmoothness for WAY#555383320
2024-07-15T17:23:41.643: I [Upload] Starting upload
2024-07-15T17:23:41.656: I [OsmQuestController] Created 4 quests for 1 updated elements in 67ms
2024-07-15T17:23:41.671: I [OsmQuestController] Persisted 4 new and removed 1 already resolved quests in 0.0s
2024-07-15T17:23:41.786: D [ElementEditsUploader] Uploaded a UpdateElementTagsAction
2024-07-15T17:23:41.837: I [Upload] Finished upload
2024-07-15T17:23:45.020: D [ElementEditsController] Add WayLitOverlay for WAY#555383323
2024-07-15T17:23:45.033: D [OsmQuestController] AddMaxHeight requires surrounding map data to determine applicability to WAY#555383323
2024-07-15T17:23:45.035: D [OsmQuestController] Created AddCycleway for WAY#555383323
2024-07-15T17:23:45.036: D [OsmQuestController] Created AddSidewalk for WAY#555383323
2024-07-15T17:23:45.036: D [OsmQuestController] Created AddLanes for WAY#555383323
2024-07-15T17:23:45.036: D [OsmQuestController] Created AddRoadSmoothness for WAY#555383323
2024-07-15T17:23:45.080: I [OsmQuestController] Created 4 quests for 1 updated elements in 49ms
2024-07-15T17:23:45.089: I [OsmQuestController] Persisted 4 new and removed 1 already resolved quests in 0.0s
2024-07-15T17:23:45.094: I [Upload] Starting upload
2024-07-15T17:23:45.192: D [ElementEditsUploader] Uploaded a UpdateElementTagsAction
2024-07-15T17:23:45.223: I [Upload] Finished upload
2024-07-15T17:33:34.425: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8072514,10.0285792
2024-07-15T17:33:34.470: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T17:33:34.965: I [Upload] Starting upload
2024-07-15T17:33:34.990: I [Upload] Finished upload
2024-07-15T17:33:36.470: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8080067,10.0291950
2024-07-15T17:33:36.519: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (9 tiles)
Thank you!
The relevant section should be this, as in the video one can see that the issue was reproduced 16:12.

```
2024-07-15T16:12:46.390: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8072517,10.0288983
2024-07-15T16:12:46.426: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:12:46.912: I [Upload] Starting upload
2024-07-15T16:12:46.935: I [Upload] Finished upload
2024-07-15T16:12:47.185: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8074556,10.0287353
2024-07-15T16:12:47.221: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:12:48.408: I [QuestAutoSyncer] Checking whether to automatically download new quests at 48.8073517,10.0287767
2024-07-15T16:12:48.440: I [QuestAutoSyncer] All downloaded in radius of 472 meters around user (12 tiles)
2024-07-15T16:12:53.136: D [ElementEditsController] Add BuildingsOverlay for WAY#397687189
2024-07-15T16:12:53.181: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397687189
2024-07-15T16:12:53.189: D [OsmQuestController] Created AddRoofShape for WAY#397687189
2024-07-15T16:12:53.189: D [OsmQuestController] Created AddBuildingLevels for WAY#397687189
2024-07-15T16:12:53.296: I [Upload] Starting upload
2024-07-15T16:12:53.747: I [OsmQuestController] Created 2 quests for 1 updated elements in 575ms
2024-07-15T16:12:53.799: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.1s
2024-07-15T16:12:54.051: D [ElementEditsUploader] Dropped a UpdateElementTagsAction: Conflict while applying the changes
2024-07-15T16:12:54.109: D [OsmQuestController] Created AddBuildingType for WAY#397687189
2024-07-15T16:12:54.162: I [OsmQuestController] Created 1 quests for 1 updated elements in 90ms
2024-07-15T16:12:54.171: I [OsmQuestController] Persisted 1 new and removed 2 already resolved quests in 0.0s
2024-07-15T16:12:54.265: I [Upload] Finished upload
2024-07-15T16:12:59.176: D [ElementEditsController] Add BuildingsOverlay for WAY#397687189
2024-07-15T16:12:59.209: D [OsmQuestController] AddRoofShape requires surrounding map data to determine applicability to WAY#397687189
2024-07-15T16:12:59.212: D [OsmQuestController] Created AddBuildingLevels for WAY#397687189
2024-07-15T16:12:59.213: D [OsmQuestController] Created AddRoofShape for WAY#397687189
2024-07-15T16:12:59.272: I [Upload] Starting upload
2024-07-15T16:12:59.286: I [OsmQuestController] Created 2 quests for 1 updated elements in 91ms
2024-07-15T16:12:59.313: I [OsmQuestController] Persisted 2 new and removed 1 already resolved quests in 0.0s
2024-07-15T16:12:59.427: D [ElementEditsUploader] Uploaded a UpdateElementTagsAction
2024-07-15T16:12:59.475: I [Upload] Finished upload
```

And the error happens here:
```
2024-07-15T16:12:54.051: D [ElementEditsUploader] Dropped a UpdateElementTagsAction: Conflict while applying the changes
```

When you create an edit, by default, it is immediately uploaded to OpenStreetMap. If a conflict occurs during upload, your change will be discarded because the assumption is that the data you just edited has been edited by someone else in the meantime and thus the premise under which the quest (etc.) has been created might not even be correct anymore.

It looks like the building you edited was https://www.openstreetmap.org/way/397687189/history

And it looks like that just three hours before you edited the building, someone else (Hello, @mcliquid ! The world is small!) also edited the same property. Judging by that all these buildings are displayed in red, I conclude that StreetComplete downloaded the area when these buildings were still tagged with `building=yes`.

So, it looks like in the end it is absolutely the correct behavior to refuse your edit.

Now, a few open questions, though:

- If a conflict is detected, the **current** version of the element should be downloaded and persisted locally and subsequently be displayed. This does not seem to be happening here, as the display of the building reverts to being red (=`building=yes`)

- If a conflict is detected, IIRC, it should be triggered that the whole area is downloaded again to prevent any further conflicts. This does not seem to be happening here. Or is the map data just marked as being to-be-refreshed?
Yes, I confess that after checking changesets using OsmCha, I found several individual terraced houses in my area with `building=terrace`. I then changed these to `building=house` and `house=terraced` and then changed all the buildings to which this applies in JOSM at once. It seems obvious that I was too quick with that correction.

But I would say it's a good idea to re-download the data if this occurs.
@Helium314 you mentioned recently you would have a look into this, but I assume that you have all hands full at the moment with the merge, right?
Currently I'm a little occupied by trying to make SCEE run again, but I think I'll wait with re-implementing broken features until the main screen is switched to compose...
So I might have time a little sooner.
My plan was to first get the v59.0 alpha (i.e. release on github but no beta-release on google play) ready before finishing my work on composing the controls etc. on the main screen (it's not everything on the main screen, not the bottom sheet, not the map. Just the controls, edit history sidebar). Getting v59-alpha1 ready entails for me 1. rummaging through the issue tracker a bit more and fixing small things, especially stuff requiring translations and 2. dev-testing the version a bit to see and fix if there are any obvious issues with maplibre.

Whether or not I will also squeeze mainscreen-compose into v59.0 I am not sure yet. I understand it would be more convenient for you if I put that into v59, too?
> it's not everything on the main screen, not the bottom sheet, not the map. Just the controls, edit history sidebar

Oh, then I'll need to do the fixes anyway. I'm not even sure whether there is anything related to those controls that needs fixing.
So then I'd gain (almost) nothing from putting more compose work into 59.0.
The area seems to be invalidated correctly. The element is updated correctly with the new tags. But the overlay displays red, instead of the new value.
The overlay is updated correctly on reloading (e.g. switching overlay and back again).
When adding some logging, it looks like [`nothingChanged`](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSource.kt#L102-L107) is true, so update of quests and overlays is not triggered.
In more detail:
1. upload fails
2. edit is deleted (`elementEditsController.markSyncFailed`), which triggers creation of quests for initial element
3. the updated element is downloaded
4. updates are persisted in the database (`mapDataController.updateAll`)
5. `MapDataWithEditsSource.onUpdated` is called. None of the local changes refers to the updated element, so nothing is changed and listeners (quest controller and overlay manager) are not informed about the update. This is because the download of a single element was not considered in #4472

A workaround (fix?) could be calling `markSyncFailed` only after updating map data, but I didn't check whether this might create other issues.
Hmm, I was about to write: no, because if the edit is still around when `onUpdated` is called, it will be applied on top of the freshly downloaded data for display and then the display doesn't change.

But if the upload fails due to a conflict, the "faulty" edit should also produce a conflict when trying to apply it locally onto the the updated data and hence is not applied.

So, moving markSyncFailed in ElementEditsUploader below the map data update might just work! But this must be tested, of course.
But wait, isn't there a simpler solution? The `elementsThatMightHaveChangedByKey` checks seem to be missing one important part:

Checking whether there's any element in `updated` that isn't in any of `updateElementes` or `deletedElements` yet. And to me, this sounds like a bug right now.
So if `updated.any { it.key !in updatedElements }` then `nothingChanged` must be false?

./gradlew :app:testDebugUnitTest --tests "de.westnordost.streetcomplete.data.osm.edits.MapDataWithEditsSourceTest"

diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSource.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSource.kt
index 8b1a2c5c064..4d003048b92 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSource.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSource.kt
@@ -78,14 +78,16 @@ class MapDataWithEditsSource internal constructor(
             val modifiedDeleted = ArrayList<ElementKey>()
             synchronized(this) {
                 /* We don't want to callOnUpdated if none of the changes affects map data provided
-                 * by MapDataWithEditsSource.
+                 * by MapDataWithEditsSource
                  * This is the case if
                  *  * All keys in deleted are already in deletedElements.
                  *  * The modified versions of all elements in updated are the same before and after
                  *    rebuildLocalChanges, except for the timestamp (expected to have few ms
                  *    difference) and version (never updated locally).
+                 *   * No new elements are being added
                  */
                 val deletedIsUnchanged = deletedElements.containsAll(deleted)
+                val hasNewElements = updated.any { it.key !in updatedElements }
                 val elementsThatMightHaveChangedByKey = updated.mapNotNull { element ->
                     val key = element.key
                     if (element.isEqualExceptVersionAndTimestamp(updatedElements[key])) {
@@ -99,7 +101,7 @@ class MapDataWithEditsSource internal constructor(

                 /* nothingChanged can be false at this point when e.g. there are two edits on the
                    same element, and onUpdated is called after the first edit is uploaded. */
-                val nothingChanged = deletedIsUnchanged && elementsThatMightHaveChangedByKey.all {
+                val nothingChanged = deletedIsUnchanged && !hasNewElements && elementsThatMightHaveChangedByKey.all {
                     val updatedElement = get(it.first.type, it.first.id)
                     // old and new elements are equal except version and timestamp, or both are null
                     it.second?.isEqualExceptVersionAndTimestamp(updatedElement) ?: (updatedElement == null)

diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSourceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSourceTest.kt
index 422a3577c76..11cc5e7e7d8 100644
--- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSourceTest.kt
+++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/edits/MapDataWithEditsSourceTest.kt
@@ -1222,6 +1222,29 @@ class MapDataWithEditsSourceTest {
         verify(listener).onUpdated(eq(updatedMapData), eq(emptyList()))
     }

+    @Test
+    fun `does call onUpdated when updated element is not in local changes`() {
+        val nd = node(1, p(0.1, 0.0))
+        val ndModified = node(1, p(0.3, 0.0))
+        val nd2 = node(2, p(0.5, 0.4))
+        val p2 = ElementGeometryEntry(NODE, 2, pGeom(0.5, 0.4))
+
+        originalElementsAre(nd, nd2)
+        mapDataChangesAre(modifications = listOf(ndModified))
+
+        val s = create()
+        val listener = mock<MapDataWithEditsSource.Listener>()
+        s.addListener(listener)
+
+        val updatedMapData = MutableMapDataWithGeometry(
+            elements = listOf(nd2),
+            geometryEntries = listOf(p2)
+        )
+        mapDataListener.onUpdated(updatedMapData, emptyList())
+
+        verify(listener).onUpdated(eq(updatedMapData), eq(emptyList()))
+    }
+
     //endregion

     //region MapDataController.Listener ::onReplacedForBBox