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.
0. Where things stand
Section titled “0. Where things stand”| Before | Now | |
|---|---|---|
| Turning it on | a row in the task map’s ⋯ menu | a bar button in the trailing group, next to the location button on the empty map |
| After turning it on | the layer picker opened itself | the legend appears asking for a layer |
| Naming the layer | a glass pill over the top-left of the map | the legend card |
| The legend | a bare colour strip down the trailing edge | a card with the layer, the hour, the scale and its values |
| Putting it away | not possible | drag 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.
2. Things that are easy to get wrong here
Section titled “2. Things that are easy to get wrong here”- 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.loadingTextandmapDataSource.loadingTextnever publish on a new task or the empty map, so aCombineLatestof 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 withnil, andfullyRenderedMapSubject(a current-value subject) is what actually gates the card. Defaults[…]reads aBool?key as non-optional.?? trueis dead code. The legend’s state is therefore stored the negative way round, asVultureTaskLegendCollapsed, 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
preferredScreenEdgesDeferringSystemGesturesare 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.
3. Two crashes that reached a device
Section titled “3. Two crashes that reached a device”Both were Auto Layout, both compiled cleanly, and neither could have been caught by building. Worth knowing before touching these constraints again.
NSGenericException— “no common ancestor”. The card’s trailing edge was constrained tonavigationBar.layoutMarginsGuide. A navigation bar and a screen’s own view are not guaranteed to share an ancestor whenviewDidLayoutSubviewsruns. Cross-hierarchy constraints trap rather than degrade.NSInvalidArgumentException— “units are not compatible”. The card was switched toleft/rightanchors to mirror the graph panel, and the tab kepttrailingAnchoragainst the view’srightAnchor. 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.
4. Open
Section titled “4. Open”- 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.satellitereads the same whether the button downloads or toggles. Fixing it needs a new key in all eight locales. - The task map’s
⌘Lstill 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.
