ReviewOS

stacks/stx

fix(native): a bound `:geometry` on MapPolyline is dropped, so a route from data never draws

#2073
Closed glennmichael123 opened this 8 days ago ·
8 days ago

#2072 shipped the Map, MapPolyline and MapMarker types in 0.2.520 and they compile. But a MapPolyline whose coordinates come from a signal gets no coordinates at all: the bound attribute is dropped from the document, from the node's bindings, and from the manifest, without a diagnostic.

Since every route in a real app arrives from an API, this means the map renders empty for its actual use.

Evidence

One screen, three bound attributes, compiled with compileNativeBundle:

<Map fitToBounds="true">
  <MapPolyline id="r1" :geometry="route()" :strokeColor="color()" />
</Map>
<svg viewBox="0 0 20 20"><path :d="d()" /></svg>

What came out:

bindingnode propsbindingsmanifest
:strokeColor on MapPolyline—{ "stroke-color": "color()" }:stroke-color, kind attr
:d on an SVG Pathgeometry: []{ d: "d()" }:d, kind attr
:geometry on MapPolylinenothingnothingnothing
{ type: "MapPolyline", props: { "data-stx-parent-bindings": "stroke-color", id: "r1" },
  bindingId: 0, bindings: { "stroke-color": "color()" } }

So bindings work on this node — :strokeColor registers correctly. geometry specifically is swallowed.

A static geometry does survive, which is why this is easy to miss: :geometry="[47.0, 11.0]" on a MapMarker compiles to geometry: [47, 11], because the compiler can evaluate it at compile time. Only the dynamic case is lost.

It is not a spelling I got wrong

Five names on five polylines in one compile:

<MapPolyline id="a" :geometry="route()" />
<MapPolyline id="b" :points="route()" />
<MapPolyline id="c" :coordinates="route()" />
<MapPolyline id="d" :coords="route()" />
<MapPolyline id="e" :path="route()" />
manifest = { entries: [] }
props: { id: "a" } … props: { id: "e" }

Every one dropped. There is no spelling that binds.

Likely cause

geometry is a compiler-computed prop for SVG shapes — the template binds :d or :points and svgGeometry derives geometry from it, with affectsGeometry telling the host to recompute. That is the right model for a Path.

A map overlay has no source attribute to derive from: geometry is the authorable attribute. So whatever filter keeps a template from setting geometry directly needs an exception for the map types — or the map types need their own entry in the geometry-attribute table so a bound value recomputes the way a shape's does. This is point 4 of #2072, which the rest of that issue landed without.

Smaller ask, same as last time

A bound attribute that the compiler decides not to keep should say so. This is the third native bug in a row — #2067, #2072, this — whose whole cost was that it looked correct from every angle a test could check: the screen compiled, the node rendered, the map appeared, and only the line was missing. One diagnostic on a dropped binding would have caught all three.

Where this leaves the caller

craft-native 0.0.174 draws MKPolyline from a flat [lat, lng, …] array and reconciles overlays in place; the renderer is ready. wildloop's routes are already [lat, lng] pairs, so the data needs nothing but .flat(). The eight trail screens that waited on craft#340 are now waiting on this instead.

8 days ago

Verified fixed on current main in 8b1a22a. The MapPolyline parent-binding path already preserves :geometry and emits the flat route array on mount and after a signal update; this commit adds a production-bundle regression for both states. The native regression set passed 41/41, with root typecheck and lint green.

Sign in to comment on this issue.