map-gl-offline 0.8.9 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +62 -192
  2. package/dist/idb-offline-sw.js +78 -11
  3. package/dist/index.d.ts +3 -3
  4. package/dist/index.esm.js +2032 -808
  5. package/dist/index.esm.js.map +1 -1
  6. package/dist/index.js +2039 -806
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.umd.js +2367 -933
  9. package/dist/index.umd.js.map +1 -1
  10. package/dist/main.d.ts +3 -0
  11. package/dist/managers/offlineMapManager/analyticsManagement.d.ts +1 -1
  12. package/dist/managers/offlineMapManager/base.d.ts +5 -5
  13. package/dist/managers/offlineMapManager/cleanupManagement.d.ts +6 -1
  14. package/dist/managers/offlineMapManager/importExportManagement.d.ts +1 -1
  15. package/dist/managers/offlineMapManager/maintenanceManagement.d.ts +1 -1
  16. package/dist/managers/offlineMapManager/regionManagement.d.ts +2 -2
  17. package/dist/managers/offlineMapManager/resourceManagement.d.ts +1 -1
  18. package/dist/managers/offlineMapManager/styleManagement.d.ts +1 -1
  19. package/dist/services/analyticsService.d.ts +1 -1
  20. package/dist/services/cleanupService.d.ts +15 -1
  21. package/dist/services/fontService.d.ts +1 -1
  22. package/dist/services/glyphService.d.ts +1 -1
  23. package/dist/services/importExportService.d.ts +1 -1
  24. package/dist/services/maintenanceService.d.ts +2 -2
  25. package/dist/services/modelService.d.ts +3 -1
  26. package/dist/services/regionService.d.ts +21 -11
  27. package/dist/services/resourceService.d.ts +2 -2
  28. package/dist/services/runtimeAssetService.d.ts +12 -0
  29. package/dist/services/spriteService.d.ts +1 -1
  30. package/dist/services/styleService.d.ts +14 -1
  31. package/dist/services/tileService.d.ts +1 -1
  32. package/dist/sheet/collar.d.ts +34 -0
  33. package/dist/storage/indexedDbManager.d.ts +1 -1
  34. package/dist/style.css +2 -2
  35. package/dist/sw/shared.d.ts +31 -3
  36. package/dist/types/import-export.d.ts +5 -0
  37. package/dist/types/maintenance.d.ts +3 -1
  38. package/dist/types/region.d.ts +29 -2
  39. package/dist/types/style.d.ts +4 -0
  40. package/dist/types/tile.d.ts +15 -0
  41. package/dist/ui/components/PanelActions.d.ts +1 -1
  42. package/dist/ui/components/PanelHeader.d.ts +1 -1
  43. package/dist/ui/components/RegionList.d.ts +1 -1
  44. package/dist/ui/components/shared/Button.d.ts +6 -0
  45. package/dist/ui/components/shared/LanguageSelector.d.ts +1 -1
  46. package/dist/ui/components/shared/MapControlButton.d.ts +1 -1
  47. package/dist/ui/components/shared/PanelContent.d.ts +3 -3
  48. package/dist/ui/components/shared/RegionDrawingTool.d.ts +2 -2
  49. package/dist/ui/controls/polygonControl.d.ts +7 -1
  50. package/dist/ui/controls/regionControl.d.ts +3 -3
  51. package/dist/ui/managers/ControlButtonManager.d.ts +1 -1
  52. package/dist/ui/managers/PanelManager.d.ts +16 -3
  53. package/dist/ui/managers/downloadManager.d.ts +7 -3
  54. package/dist/ui/modals/mbtilesModal.d.ts +1 -1
  55. package/dist/ui/modals/regionDetailsModal.d.ts +1 -1
  56. package/dist/ui/modals/regionFormModal.d.ts +11 -1
  57. package/dist/ui/offlineManagerControl.d.ts +4 -1
  58. package/dist/ui/translations/ar.d.ts +15 -1
  59. package/dist/ui/translations/en.d.ts +15 -1
  60. package/dist/ui/translations/index.d.ts +2 -0
  61. package/dist/utils/constants.d.ts +4 -2
  62. package/dist/utils/convertStyleForSW.d.ts +1 -1
  63. package/dist/utils/download.d.ts +1 -1
  64. package/dist/utils/formatting.d.ts +11 -0
  65. package/dist/utils/importResolver.d.ts +1 -1
  66. package/dist/utils/styleConfig.d.ts +39 -0
  67. package/dist/utils/styleProviderUtils.d.ts +21 -8
  68. package/dist/utils/styleUtils.d.ts +61 -10
  69. package/dist/utils/tileRange.d.ts +63 -0
  70. package/dist/utils/validation.d.ts +1 -1
  71. package/package.json +44 -39
package/dist/main.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  import 'maplibre-gl/dist/maplibre-gl.css';
2
2
  import 'mapbox-gl/dist/mapbox-gl.css';
3
3
  import 'map-gl-style-switcher/dist/map-gl-style-switcher.css';
4
+ import '@fontsource/barlow/400.css';
5
+ import '@fontsource/barlow/500.css';
6
+ import '@fontsource/barlow/600.css';
@@ -1,4 +1,4 @@
1
- import type { StorageAnalyticsReport } from '../../types';
1
+ import type { StorageAnalyticsReport } from '../../types/index.js';
2
2
  import type { OfflineManagerServices } from './base';
3
3
  import type { CleanupManagement } from './cleanupManagement';
4
4
  export interface AnalyticsManagement {
@@ -1,8 +1,8 @@
1
- import { CleanupService } from '../../services/cleanupService';
2
- import { RegionService } from '../../services/regionService';
3
- import { ResourceService } from '../../services/resourceService';
4
- import { AnalyticsService } from '../../services/analyticsService';
5
- import { ImportExportService } from '../../services/importExportService';
1
+ import { CleanupService } from '../../services/cleanupService.js';
2
+ import { RegionService } from '../../services/regionService.js';
3
+ import { ResourceService } from '../../services/resourceService.js';
4
+ import { AnalyticsService } from '../../services/analyticsService.js';
5
+ import { ImportExportService } from '../../services/importExportService.js';
6
6
  export interface OfflineManagerServices {
7
7
  regionService: RegionService;
8
8
  cleanupService: CleanupService;
@@ -1,8 +1,13 @@
1
- import type { CleanupResult, RegionAnalytics, RegionCleanupOptions } from '../../types';
1
+ import type { CleanupResult, RegionAnalytics, RegionCleanupOptions } from '../../types/index.js';
2
2
  import type { OfflineManagerServices } from './base';
3
3
  export interface CleanupManagement {
4
4
  getRegionSize(regionId: string, styleId?: string): Promise<number>;
5
+ /**
6
+ * Delete regions last updated more than 30 days ago. Age-based by design —
7
+ * it does not read `region.expiry`; use `forceCleanupExpiredRegions` for that.
8
+ */
5
9
  cleanupExpiredRegions(): Promise<number>;
10
+ /** Delete regions whose absolute `expiry` timestamp has passed. */
6
11
  forceCleanupExpiredRegions(): Promise<number>;
7
12
  setupAutoCleanup(options?: RegionCleanupOptions & {
8
13
  intervalHours?: number;
@@ -1,4 +1,4 @@
1
- import type { ExportResult, ImportExportOptions, ImportResult, MBTilesExportOptions, RegionImportData } from '../../types';
1
+ import type { ExportResult, ImportExportOptions, ImportResult, MBTilesExportOptions, RegionImportData } from '../../types/index.js';
2
2
  import type { OfflineManagerServices } from './base';
3
3
  /**
4
4
  * MBTiles-only import/export surface. Regions are exchanged as real
@@ -1,4 +1,4 @@
1
- import type { MaintenanceOptions, MaintenanceResults } from '../../types';
1
+ import type { MaintenanceOptions, MaintenanceResults } from '../../types/index.js';
2
2
  import type { OfflineManagerServices } from './base';
3
3
  import type { CleanupManagement } from './cleanupManagement';
4
4
  import type { RegionManagement } from './regionManagement';
@@ -1,10 +1,10 @@
1
- import type { DownloadRegionOptions, DownloadRegionResult, OfflineRegionOptions, StoredRegion } from '../../types';
1
+ import type { DownloadRegionOptions, DownloadRegionResult, OfflineRegionOptions, StoredRegion } from '../../types/index.js';
2
2
  import type { OfflineManagerServices } from './base';
3
3
  export interface RegionManagement {
4
4
  addRegion(region: OfflineRegionOptions): Promise<void>;
5
5
  downloadRegion(region: OfflineRegionOptions, options?: DownloadRegionOptions): Promise<DownloadRegionResult>;
6
6
  loadRegion(region: OfflineRegionOptions, options?: DownloadRegionOptions): Promise<DownloadRegionResult>;
7
- deleteRegion(regionId: string): Promise<void>;
7
+ deleteRegion(regionId: string, styleId?: string): Promise<void>;
8
8
  listRegions(): Promise<OfflineRegionOptions[]>;
9
9
  listStoredRegions(): Promise<StoredRegion[]>;
10
10
  getStoredRegion(regionId: string): Promise<StoredRegion | null>;
@@ -1,4 +1,4 @@
1
- import type { ResourceService } from '../../services/resourceService';
1
+ import type { ResourceService } from '../../services/resourceService.js';
2
2
  import type { OfflineManagerServices } from './base';
3
3
  export type ResourceServiceMethod<TMethod extends keyof ResourceService> = ResourceService[TMethod];
4
4
  export interface ResourceManagement {
@@ -1,4 +1,4 @@
1
- import type { EnhancedStyleStats, StyleDownloadOptions, StyleDownloadResult, StyleEntry, StyleProvider } from '../../types';
1
+ import type { EnhancedStyleStats, StyleDownloadOptions, StyleDownloadResult, StyleEntry, StyleProvider } from '../../types/index.js';
2
2
  export interface StyleManagement {
3
3
  downloadStyle(styleUrl: string, options?: StyleDownloadOptions & {
4
4
  provider?: StyleProvider;
@@ -1,5 +1,5 @@
1
1
  import { EnhancedGlyphStats } from './glyphService';
2
- import type { RegionAnalytics, TileStats, StorageAnalyticsReport, EnhancedFontStats, EnhancedSpriteStats } from '../types';
2
+ import type { RegionAnalytics, TileStats, StorageAnalyticsReport, EnhancedFontStats, EnhancedSpriteStats } from '../types/index.js';
3
3
  export declare class AnalyticsService {
4
4
  getAllTileStats(): Promise<TileStats>;
5
5
  getAllFontStats(): Promise<EnhancedFontStats>;
@@ -1,4 +1,4 @@
1
- import type { StoredRegion, RegionCleanupOptions, CleanupResult, RegionAnalytics } from '../types';
1
+ import type { StoredRegion, RegionCleanupOptions, CleanupResult, RegionAnalytics } from '../types/index.js';
2
2
  export declare class CleanupService {
3
3
  private db;
4
4
  private deleteRegionCallback;
@@ -25,6 +25,7 @@ export declare class CleanupService {
25
25
  fonts: number;
26
26
  sprites: number;
27
27
  glyphs: number;
28
+ models: number;
28
29
  total: number;
29
30
  }>;
30
31
  /**
@@ -36,7 +37,19 @@ export declare class CleanupService {
36
37
  freedSpace: number;
37
38
  }>;
38
39
  getAllRegions(): Promise<StoredRegion[]>;
40
+ /**
41
+ * Bytes of the tiles stored for a region: tiles on its style inside its
42
+ * bounds, at the zooms the download pipeline fetched for each source. Tiles
43
+ * are stored per style, so regions that overlap share (and both count)
44
+ * their common tiles; shared style resources are not included.
45
+ */
39
46
  getRegionSize(regionId: string, styleIdParam?: string): Promise<number>;
47
+ /**
48
+ * Bytes stored for a style: its tiles plus the fonts, glyphs, sprites and
49
+ * models keyed to it. Measured before and after a deletion to report the
50
+ * space actually freed (a region's own size over-counts tiles it shares).
51
+ */
52
+ private getStyleStoredBytes;
40
53
  private calculateTotalStorageSize;
41
54
  private selectRegionsForDeletion;
42
55
  private generateRecommendations;
@@ -57,6 +70,7 @@ export declare const getExpiredResourceCount: () => Promise<{
57
70
  fonts: number;
58
71
  sprites: number;
59
72
  glyphs: number;
73
+ models: number;
60
74
  total: number;
61
75
  }>;
62
76
  export declare const cleanupExpiredTiles: (styleId?: string) => Promise<{
@@ -1,4 +1,4 @@
1
- import type { EnhancedFontStats, FontDownloadOptions, FontDownloadResult } from '../types';
1
+ import type { EnhancedFontStats, FontDownloadOptions, FontDownloadResult } from '../types/index.js';
2
2
  export declare class FontService {
3
3
  private db;
4
4
  downloadFonts(fontUrls: string[], styleName?: string, options?: FontDownloadOptions): Promise<FontDownloadResult>;
@@ -1,4 +1,4 @@
1
- import type { GlyphDownloadOptions, GlyphDownloadResult, GlyphEntry, GlyphRange } from '../types';
1
+ import type { GlyphDownloadOptions, GlyphDownloadResult, GlyphEntry, GlyphRange } from '../types/index.js';
2
2
  export interface EnhancedGlyphStats {
3
3
  count: number;
4
4
  totalSize: number;
@@ -1,4 +1,4 @@
1
- import type { RegionImportData, ImportExportOptions, ImportResult, ExportResult, MBTilesExportOptions } from '../types';
1
+ import type { RegionImportData, ImportExportOptions, ImportResult, ExportResult, MBTilesExportOptions } from '../types/index.js';
2
2
  export declare class ImportExportService {
3
3
  private db;
4
4
  constructor();
@@ -1,5 +1,5 @@
1
- import type { StorageAnalyticsReport, MaintenanceOptions, MaintenanceResults, RegionCleanupOptions, CleanupResult } from '../types';
2
- export type { MaintenanceOptions, MaintenanceResults } from '../types/maintenance';
1
+ import type { StorageAnalyticsReport, MaintenanceOptions, MaintenanceResults, RegionCleanupOptions, CleanupResult } from '../types/index.js';
2
+ export type { MaintenanceOptions, MaintenanceResults } from '../types/maintenance.js';
3
3
  export declare class MaintenanceService {
4
4
  private performSmartCleanup;
5
5
  private listRegions;
@@ -1,4 +1,6 @@
1
- import type { ModelEntry, ModelDownloadOptions, ModelDownloadResult, EnhancedModelStats } from '../types';
1
+ import type { ModelEntry, ModelDownloadOptions, ModelDownloadResult, EnhancedModelStats } from '../types/index.js';
2
+ /** Names of the models stored for a style. */
3
+ export declare function storedModelNames(styleId: string): Promise<Set<string>>;
2
4
  /** True when the given key belongs to the given styleId's model store prefix. */
3
5
  export declare function modelKeyBelongsToStyle(key: string, styleId: string): boolean;
4
6
  /**
@@ -1,4 +1,4 @@
1
- import type { DownloadRegionOptions, DownloadRegionResult, OfflineRegionOptions, StoredRegion } from '../types/region';
1
+ import type { DownloadRegionOptions, DownloadRegionResult, OfflineRegionOptions, StoredRegion } from '../types/region.js';
2
2
  /**
3
3
  * True when `key` belongs to the given styleId.
4
4
  *
@@ -13,15 +13,8 @@ import type { DownloadRegionOptions, DownloadRegionResult, OfflineRegionOptions,
13
13
  export declare function resourceKeyBelongsToStyle(key: string, styleId: string): boolean;
14
14
  export declare class RegionService {
15
15
  /**
16
- * Check if a tile overlaps with any region bounds
17
- */
18
- private tileOverlapsWithRegion;
19
- /**
20
- * Check if a tile overlaps with any of the given regions
21
- */
22
- private tileOverlapsWithAnyRegion;
23
- /**
24
- * Delete tiles that don't overlap with any remaining regions
16
+ * Delete tiles no remaining region needs — outside every remaining region's
17
+ * bounds, or at a zoom none of them downloads for that tile's source.
25
18
  */
26
19
  private deleteNonOverlappingTiles;
27
20
  /**
@@ -31,6 +24,17 @@ export declare class RegionService {
31
24
  /**
32
25
  * Delete all tiles for a style
33
26
  */
27
+ /**
28
+ * Delete everything stored for a style except its own entry: tiles, fonts,
29
+ * glyphs, sprites and models.
30
+ */
31
+ /**
32
+ * Undo what a failed or cancelled download left behind: its partly stored
33
+ * tiles and its config change. A style this download created, which no other
34
+ * region uses, goes entirely, and so does a style deleted during the download.
35
+ */
36
+ private cleanUpFailedDownload;
37
+ deleteStyleData(styleId: string): Promise<void>;
34
38
  private deleteAllStyleTiles;
35
39
  addRegion(region: OfflineRegionOptions): Promise<void>;
36
40
  loadRegion(region: OfflineRegionOptions, options?: DownloadRegionOptions): Promise<DownloadRegionResult>;
@@ -44,7 +48,13 @@ export declare class RegionService {
44
48
  * Look up the stored style matching a region — by `styleId` first, then `styleUrl`.
45
49
  */
46
50
  private findStyleEntry;
47
- deleteRegion(regionId: string): Promise<void>;
51
+ /**
52
+ * Delete a region and the tiles/resources only it needed.
53
+ * @param styleId - The style holding the region. Region ids are only unique
54
+ * within a style, so pass it whenever known; without it the first style
55
+ * containing `regionId` is used.
56
+ */
57
+ deleteRegion(regionId: string, styleId?: string): Promise<void>;
48
58
  listRegions(): Promise<OfflineRegionOptions[]>;
49
59
  /**
50
60
  * List all stored regions from styles (used for UI and fitBounds)
@@ -1,5 +1,5 @@
1
1
  import { EnhancedGlyphStats } from './glyphService';
2
- import type { OfflineRegionOptions, MapboxStyle, TileDownloadOptions, TileDownloadResult, TileStats, SpriteDownloadOptions, SpriteDownloadResult, EnhancedSpriteStats, FontDownloadOptions, FontDownloadResult, EnhancedFontStats, GlyphDownloadOptions, GlyphDownloadResult, ModelDownloadOptions, ModelDownloadResult, EnhancedModelStats } from '../types';
2
+ import type { OfflineRegionOptions, MapboxStyle, TileDownloadOptions, TileDownloadResult, TileStats, SpriteDownloadOptions, SpriteDownloadResult, EnhancedSpriteStats, FontDownloadOptions, FontDownloadResult, EnhancedFontStats, GlyphDownloadOptions, GlyphDownloadResult, ModelDownloadOptions, ModelDownloadResult, EnhancedModelStats } from '../types/index.js';
3
3
  export declare class ResourceService {
4
4
  downloadTilesWithOptions(region: OfflineRegionOptions, style: MapboxStyle, styleId: string, options?: TileDownloadOptions): Promise<TileDownloadResult>;
5
5
  getTileStats(styleId?: string): Promise<TileStats>;
@@ -30,7 +30,7 @@ export declare class ResourceService {
30
30
  downloadGlyphsWithOptions(glyphUrl: string, fontstacks: string[], styleName: string, ranges?: string[], options?: GlyphDownloadOptions): Promise<GlyphDownloadResult>;
31
31
  getGlyphStats(): Promise<EnhancedGlyphStats>;
32
32
  getGlyphAnalytics(): Promise<Record<string, unknown>>;
33
- loadGlyphsForStyle(fontstack: string, ranges: string[], styleId?: string): Promise<import("../types").GlyphRange[]>;
33
+ loadGlyphsForStyle(fontstack: string, ranges: string[], styleId?: string): Promise<import("../types/index.js").GlyphRange[]>;
34
34
  cleanupOldGlyphs(styleId?: string, options?: {
35
35
  maxAge?: number;
36
36
  }): Promise<number>;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Cache the code Mapbox GL fetches from api.mapbox.com while rendering: the
3
+ * Draco and meshopt decoders 3D models need, plus the RTL text and procedural
4
+ * building modules. The Service Worker serves them back from this cache. They
5
+ * live in the Cache API rather than IndexedDB because they belong to the
6
+ * renderer, not to any style or region.
7
+ *
8
+ * Failures only log: the rest of a region is still usable without these.
9
+ *
10
+ * @returns how many assets are now cached
11
+ */
12
+ export declare function cacheMapboxRuntimeAssets(): Promise<number>;
@@ -1,4 +1,4 @@
1
- import type { EnhancedSpriteStats, SpriteDownloadOptions, SpriteDownloadResult } from '../types';
1
+ import type { EnhancedSpriteStats, SpriteDownloadOptions, SpriteDownloadResult } from '../types/index.js';
2
2
  /**
3
3
  * Service for managing map sprite assets
4
4
  * Handles downloading, storing, and retrieving sprite images and JSON metadata
@@ -1,9 +1,22 @@
1
- import type { StyleProvider, StyleDownloadOptions, StyleDownloadResult, StyleStorageItem, EnhancedStyleStats } from '../types';
1
+ import type { StyleProvider, StyleDownloadOptions, StyleDownloadResult, StyleStorageItem, EnhancedStyleStats } from '../types/index.js';
2
2
  export declare function downloadStyles(stylesUrl: string, options?: StyleDownloadOptions): Promise<StyleDownloadResult>;
3
3
  export declare function loadStyles(): Promise<StyleStorageItem[]>;
4
4
  export declare function loadStyleById(styleId: string): Promise<StyleStorageItem | null>;
5
+ /** Delete every style and everything stored for them. */
5
6
  export declare function deleteStyles(): Promise<void>;
7
+ /**
8
+ * Delete a style, its regions and everything stored for it. The entry goes last,
9
+ * so a failure part-way leaves the style listed and able to be deleted again.
10
+ */
6
11
  export declare function deleteStyleById(styleId: string): Promise<void>;
12
+ /**
13
+ * Bytes stored for each style, by style id: its tiles, and its other resources
14
+ * (glyphs, sprites, models and fonts). Tiles shared by regions count once.
15
+ */
16
+ export declare function getStyleStorageSizes(): Promise<Record<string, {
17
+ tiles: number;
18
+ resources: number;
19
+ }>>;
7
20
  /**
8
21
  * Get enhanced style statistics
9
22
  */
@@ -1,4 +1,4 @@
1
- import type { TileDownloadOptions, TileDownloadResult, TileStats, OfflineRegionOptions, MapboxStyle } from '../types';
1
+ import type { TileDownloadOptions, TileDownloadResult, TileStats, OfflineRegionOptions, MapboxStyle } from '../types/index.js';
2
2
  /**
3
3
  * Match a tile-URL template against the known-sparse Mapbox Standard
4
4
  * sub-tilesets. Catches:
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The demo's map collar: latitude and longitude ticks along the map's edge and
3
+ * a coordinate readout below it, as in the margin of a printed map sheet.
4
+ * Demo only; nothing here ships in the library.
5
+ */
6
+ /** The parts of a MapLibre or Mapbox GL map the collar reads. */
7
+ export interface CollarMap {
8
+ getBounds(): {
9
+ getWest(): number;
10
+ getEast(): number;
11
+ getNorth(): number;
12
+ getSouth(): number;
13
+ } | null;
14
+ getCenter(): {
15
+ lng: number;
16
+ lat: number;
17
+ };
18
+ getZoom(): number;
19
+ getBearing(): number;
20
+ getPitch(): number;
21
+ project(lngLat: [number, number]): {
22
+ x: number;
23
+ y: number;
24
+ };
25
+ on(event: string, listener: () => void): unknown;
26
+ }
27
+ /** The smallest round interval that keeps ticks at least `minGap` degrees apart. */
28
+ export declare function tickInterval(minGap: number): number;
29
+ /** Round multiples of `interval` between `min` and `max`. */
30
+ export declare function ticksBetween(min: number, max: number, interval: number): number[];
31
+ export declare function formatLng(lng: number, decimals: number): string;
32
+ export declare function formatLat(lat: number, decimals: number): string;
33
+ /** Keep the collar in step with `map`. */
34
+ export declare function attachCollar(map: CollarMap): void;
@@ -1,5 +1,5 @@
1
1
  import { IDBPDatabase } from 'idb';
2
- import { OfflineMapDB } from '../types';
2
+ import { OfflineMapDB } from '../types/index.js';
3
3
  /**
4
4
  * Thrown when the on-disk IndexedDB is at a higher schema version than the
5
5
  * library was built against — i.e. after a downgrade, or when another app