Skip to content

SkySight on the map — the toggle and the legend

Moved from vulture-ios/Docs/skysight-map-controls.md

Status: built 2026-09-14 (f3a328cb4, 04024eacb), not yet seen on a device. Written as the hand-off: what was decided and why, what broke on the way, and what is still open.

This is about the controls — how a pilot turns the forecast on and reads which layer is up. The layers and the tile endpoints themselves are skysight-layers-investigation.md; the flight map’s cloud imagery is satellite-cloud-overlay.md.

BeforeNow
Turning it ona row in the task map’s ⋯ menua bar button in the trailing group, next to the location button on the empty map
After turning it onthe layer picker opened itselfthe legend appears asking for a layer
Naming the layera glass pill over the top-left of the mapthe legend card
The legenda bare colour strip down the trailing edgea card with the layer, the hour, the scale and its values
Putting it awaynot possibledrag it out; a tab brings it back, and the choice is remembered

SkySight is on the task map and the in-flight map, never the flight map: its data source is built with shouldShowWeatherContent: false and the pilot confirmed on 2026-09-13 that it stays that way — which is also why the cloud family was free for the flight map’s satellite button (cloud / cloud.fill) without a collision.

Since 2026-09-14 the in-flight map works the same way as the task map: the cloud pair in the bar’s trailing group beside the more menu, the legend card riding above the data panel (the width on a phone, a 220 pt column above the drawer anywhere wider, and away altogether on a phone held sideways, where it climbed up under the bar) and the same tab, drag and remembered state. The old SkySightInformationView pill is gone, and so is the SkySight entry of the in-flight more menu. What the in-flight button toggles is the overlay, not Settings.isSkySightEnabled: the screen’s data source is built with forceWeatherContent, so a layer picked in flight shows whether or not the pilot ever found the switch. Both maps host the card through LegendDock (Map/Views/LegendDock.swift), which owns the card, the tab, the drag and the put-away memory; a map view only says where the card sits and how wide it is.

1. The decisions, and what they were weighed against

Section titled “1. The decisions, and what they were weighed against”

Two design rounds were published as artifacts before building:

  • Symbols and where the overlay names itself — https://claude.ai/code/artifact/298dd348-d5f7-4424-bdbd-764b9a87e273
  • The collapsible legend and how it shares the map with the banner — https://claude.ai/code/artifact/150aff43-8c81-4fae-be1b-4deb29b8d35c

cloud.sun / cloud.sun.fill for the toggle. Alternatives considered were sun.max, thermometer.sun, humidity, sun.dust and a layers glyph, all chosen to avoid the flight map’s satellite cloud. Once SkySight was confirmed task-map only that constraint fell away and the cloud pair — which the old menu row already used — was the obvious answer.

Enabling does not open the picker. It used to. A popover arriving uninvited covers the map the pilot just turned the forecast on to look at. The legend instead appears reading “SkySight — Select a weather overlay”: those two strings were already in the catalogue in all eight locales, written for a case the card had always been gated out of showing (overlay != nil).

The picker stays a popover. .popover adapts to a sheet on compact width by itself, so a phone gets a sheet and an iPad a popover from one code path. On a wide screen the map is the context you are choosing against, and a modal would cover the thing being decided about.

The legend is a card, not a strip. The strip showed colours with no labels unless held open. The card carries the layer name, the abbreviated date with weekday (Sat 23 Aug · 14:00 — SkySightOverlay.shortPeriodString), the ramp, and as many values under it as fit.

How many values. Always the two ends; up to five when there is room. The count is measured, not derived from traits: the widest value string is sized in the scale’s own font and as many fit as leave an 8 pt gap. A long unit (3.00 m/s) pulls the count down by itself. SkySight’s stops are evenly spaced and CAGradientLayer draws them evenly, so every n-th stop lands under the colour it names.

Stacked or side by side. On compact width × regular height — a phone held upright — the card takes the full width and sits above the task banner, so both are readable. Anywhere wider it keeps a 220 pt column and the banner stops where the card starts. That needs no coordination: a card that has been put away has its leading edge at the screen’s own, so the banner takes the room back by itself.

The banner was nearly handled the wrong way. The first plan was to collapse the legend whenever the banner appeared. showBanner() has no counterpart — nothing ever hides the banner — so that would have disabled the legend for the life of every task.

  • A spring’s overshoot is invisible when it leaves the screen. Dragging the card out felt dead while bringing it back felt alive, on the same spring. The bounce is carried by the tab arriving, which lands inside the screen; hence the tab travels rather than fades.
  • Both loading texts are passthrough subjects. viewModel.loadingText and mapDataSource.loadingText never publish on a new task or the empty map, so a CombineLatest of them never fires at all — which is why “hide the legend while loading” did nothing on precisely the two screens that needed it. They are prepended with nil, and fullyRenderedMapSubject (a current-value subject) is what actually gates the card.
  • Defaults[…] reads a Bool? key as non-optional. ?? true is dead code. The legend’s state is therefore stored the negative way round, as VultureTaskLegendCollapsed, so an unset flag means “on screen”.
  • The right screen-edge zone overlaps a card inset by 8 pt. An edge pan recogniser on the controller’s view receives touches that land on the card, so a drag starting there went to the edge gesture instead. That recogniser and preferredScreenEdgesDeferringSystemGestures are gone; the tab and the drag replace what they were for.
  • The pop gesture yields to the drag via require(toFail:), narrowly: the drag is attached to the card alone and declines anything not mostly horizontal and outward, so it fails instantly everywhere else.

Both were Auto Layout, both compiled cleanly, and neither could have been caught by building. Worth knowing before touching these constraints again.

  1. NSGenericException — “no common ancestor”. The card’s trailing edge was constrained to navigationBar.layoutMarginsGuide. A navigation bar and a screen’s own view are not guaranteed to share an ancestor when viewDidLayoutSubviews runs. Cross-hierarchy constraints trap rather than degrade.
  2. NSInvalidArgumentException — “units are not compatible”. The card was switched to left/right anchors to mirror the graph panel, and the tab kept trailingAnchor against the view’s rightAnchor. Auto Layout refuses to mix the directional and absolute families.

The lesson, for whoever is next: when a view’s anchoring changes, re-read every constraint that mentions it, not only the lines edited.

  • The homebase button is squeezed off the empty map. The flight map’s cloud toggle (commit e3cdf53de) made that bar four items wide. The homebase item is the only one carrying a title and an image, and it is appended last, so it is the one with nowhere to go. Three ways out, in preference order: drop its title and keep the pin plus its menu (the name is already in the menu with the current one ticked); hide the cloud on the empty map; or move homebase into the ⋯ menu there. Undecided.
  • None of this has been seen on a device. Every step was built, linted and architecture-checked; almost nothing was run. Give it a pass on a phone and an iPad — particularly the stacked layout, the drag, and the legend’s timing against the map’s first render.
  • The accessibility label does not change with the state on the flight map’s cloud button: accessibility.button.satellite reads the same whether the button downloads or toggles. Fixing it needs a new key in all eight locales.
  • The task map’s ⌘L still toggles the legend, and the more menu no longer mentions SkySight at all. Worth a look on the Mac, where menu coverage is part of the definition of done.
  • The in-flight legend has been seen on the simulator only (phone and iPad, the card asking for a layer), never against a live SkySight account: the button needs the service connected, and the toggle, the tab and the drag were verified on the task map only.