feat(directions): keyless A→B directions with turn-by-turn steps and FLY (#564)

Supersedes #219 by @spcpza, ported onto the current component layout with fixes from review.

Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied.

Co-authored-by: spcpza <[email protected]>
This commit is contained in:
Bilawal Sidhuandspcpza authored and GitHub committed 2026-09-14 20:09:41 -05:00
1 parent 9fc8d1966b
commit d78696dc17
29 files changed
+4658 -65

No files matched your search

+32
View File
@@ -265,6 +265,7 @@ export class StyleManager {
// Bloom/sharpen state
this._globeResetPromise = null;
this._dataManager = null;
this._directionsShellModule = null;
this._windowResizeHandler = null;
this._cctvRequestFocusHandler = null;
@@ -929,6 +930,34 @@ export class StyleManager {
return this._runExplicitNavigation(noun, navigate, releaseOptions);
}
/**
* Hand the Directions layer the camera seams its FLY chip needs: the same
* immediate-navigation facade voice route flights go through, so there is
* one camera owner rather than a second one inside a data layer, the shared
* ground-floor read/warm the route dolly flies over, and the app's own toast
* so the layer can speak where the rest of the UI speaks.
* @returns {void}
*/
_connectDirectionsCamera() {
if (!this._dataManager) {
// Detaching: the layer outlives this shell, so it must not keep calling
// a facade whose viewer is going away.
this._directionsShellModule?.attachShellServices?.(null);
this._directionsShellModule = null;
return;
}
const directions = this._dataManager.layers?.get('directions')?.module;
if (typeof directions?.attachShellServices !== 'function') return;
this._directionsShellModule = directions;
directions.attachShellServices({
runNavigation: (navigate) =>
this.runImmediateNavigation('route', navigate),
floorFn: (lat, lon) => this.services.cachedGroundFloor(lat, lon),
warmFn: (cells) => this.services.warmGroundFloor(cells),
showToast: (message) => this._showToast(message),
});
}
/** Supersede deferred work when an owner-specific route handles release. */
supersedeDeferredNavigation() {
return this._stampNavigation();
@@ -1654,6 +1683,7 @@ export class StyleManager {
this._syncContextModeButtons();
this._cctvControls.connect();
this._radioControls.connect();
this._connectDirectionsCamera();
if (!this._awarenessSelectedHandler) {
this._awarenessSelectedHandler = (event) =>
this._persistAwarenessSelection(event, false);
@@ -3683,6 +3713,8 @@ export class StyleManager {
this._contextControls.disconnect();
this._dataManagerUnsubscribe?.();
this._dataManagerUnsubscribe = null;
this._directionsShellModule?.attachShellServices?.(null);
this._directionsShellModule = null;
if (this._windowResizeHandler) {
window.removeEventListener('resize', this._windowResizeHandler);