@buoy-gg/assets 7.0.35 → 7.0.38

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 (165) hide show
  1. package/README.md +41 -12
  2. package/lib/commonjs/capture/expo.js +1 -46
  3. package/lib/commonjs/capture/install.js +1 -78
  4. package/lib/commonjs/capture/isBuoyAsset.js +1 -0
  5. package/lib/commonjs/capture/measure.js +1 -186
  6. package/lib/commonjs/capture/metroGraph.js +1 -148
  7. package/lib/commonjs/capture/registry.js +1 -127
  8. package/lib/commonjs/components/AssetDetail.js +1 -214
  9. package/lib/commonjs/components/AssetImage.js +1 -0
  10. package/lib/commonjs/components/AssetRow.js +1 -233
  11. package/lib/commonjs/components/AssetsFilterBar.js +1 -96
  12. package/lib/commonjs/components/AssetsHeader.js +1 -72
  13. package/lib/commonjs/components/AssetsIcon.js +1 -19
  14. package/lib/commonjs/components/AssetsListControls.js +1 -96
  15. package/lib/commonjs/components/AssetsModal.js +1 -347
  16. package/lib/commonjs/components/ScanNotice.js +1 -0
  17. package/lib/commonjs/components/optionalExpoImage.js +1 -0
  18. package/lib/commonjs/components/optionalExpoImage.web.js +1 -0
  19. package/lib/commonjs/export.js +1 -62
  20. package/lib/commonjs/format.js +1 -59
  21. package/lib/commonjs/hooks/useAssetRecords.js +1 -22
  22. package/lib/commonjs/hooks/useAssetScanStatus.js +1 -0
  23. package/lib/commonjs/index.js +1 -313
  24. package/lib/commonjs/preset.js +1 -39
  25. package/lib/commonjs/remote.js +1 -65
  26. package/lib/commonjs/state.js +1 -51
  27. package/lib/commonjs/store/assetStore.js +1 -241
  28. package/lib/commonjs/store/baseline.js +1 -173
  29. package/lib/commonjs/store/insights.js +1 -120
  30. package/lib/commonjs/sync/assetsSyncAdapter.js +1 -157
  31. package/lib/commonjs/types.js +1 -2
  32. package/lib/commonjs/utils/deviceScale.js +1 -50
  33. package/lib/commonjs/utils/testIDs.js +1 -114
  34. package/lib/module/capture/expo.js +1 -41
  35. package/lib/module/capture/install.js +1 -72
  36. package/lib/module/capture/isBuoyAsset.js +1 -0
  37. package/lib/module/capture/measure.js +1 -179
  38. package/lib/module/capture/metroGraph.js +1 -141
  39. package/lib/module/capture/registry.js +1 -120
  40. package/lib/module/components/AssetDetail.js +1 -210
  41. package/lib/module/components/AssetImage.js +1 -0
  42. package/lib/module/components/AssetRow.js +1 -229
  43. package/lib/module/components/AssetsFilterBar.js +1 -92
  44. package/lib/module/components/AssetsHeader.js +1 -68
  45. package/lib/module/components/AssetsIcon.js +1 -12
  46. package/lib/module/components/AssetsListControls.js +1 -92
  47. package/lib/module/components/AssetsModal.js +1 -342
  48. package/lib/module/components/ScanNotice.js +1 -0
  49. package/lib/module/components/optionalExpoImage.js +1 -0
  50. package/lib/module/components/optionalExpoImage.web.js +1 -0
  51. package/lib/module/export.js +1 -57
  52. package/lib/module/format.js +1 -51
  53. package/lib/module/hooks/useAssetRecords.js +1 -18
  54. package/lib/module/hooks/useAssetScanStatus.js +1 -0
  55. package/lib/module/index.js +1 -63
  56. package/lib/module/preset.js +1 -35
  57. package/lib/module/remote.js +1 -58
  58. package/lib/module/state.js +1 -47
  59. package/lib/module/store/assetStore.js +1 -226
  60. package/lib/module/store/baseline.js +1 -165
  61. package/lib/module/store/insights.js +1 -116
  62. package/lib/module/sync/assetsSyncAdapter.js +1 -153
  63. package/lib/module/types.js +1 -2
  64. package/lib/module/utils/deviceScale.js +1 -46
  65. package/lib/module/utils/testIDs.js +1 -103
  66. package/lib/typescript/capture/isBuoyAsset.d.ts +9 -0
  67. package/lib/typescript/capture/registry.d.ts +1 -1
  68. package/lib/typescript/components/AssetDetail.d.ts +2 -2
  69. package/lib/typescript/components/AssetImage.d.ts +9 -0
  70. package/lib/typescript/components/AssetsFilterBar.d.ts +3 -2
  71. package/lib/typescript/components/AssetsHeader.d.ts +2 -1
  72. package/lib/typescript/components/AssetsListControls.d.ts +2 -9
  73. package/lib/typescript/components/ScanNotice.d.ts +7 -0
  74. package/lib/typescript/components/optionalExpoImage.d.ts +13 -0
  75. package/lib/typescript/components/optionalExpoImage.web.d.ts +3 -0
  76. package/lib/typescript/hooks/useAssetScanStatus.d.ts +6 -0
  77. package/lib/typescript/state.d.ts +2 -0
  78. package/lib/typescript/store/assetStore.d.ts +3 -1
  79. package/lib/typescript/types.d.ts +3 -0
  80. package/lib/typescript/utils/testIDs.d.ts +2 -5
  81. package/package.json +8 -4
  82. package/lib/commonjs/capture/expo.js.map +0 -1
  83. package/lib/commonjs/capture/install.js.map +0 -1
  84. package/lib/commonjs/capture/measure.js.map +0 -1
  85. package/lib/commonjs/capture/metroGraph.js.map +0 -1
  86. package/lib/commonjs/capture/registry.js.map +0 -1
  87. package/lib/commonjs/components/AssetDetail.js.map +0 -1
  88. package/lib/commonjs/components/AssetRow.js.map +0 -1
  89. package/lib/commonjs/components/AssetsFilterBar.js.map +0 -1
  90. package/lib/commonjs/components/AssetsHeader.js.map +0 -1
  91. package/lib/commonjs/components/AssetsIcon.js.map +0 -1
  92. package/lib/commonjs/components/AssetsListControls.js.map +0 -1
  93. package/lib/commonjs/components/AssetsModal.js.map +0 -1
  94. package/lib/commonjs/components/BaselineBar.js +0 -115
  95. package/lib/commonjs/components/BaselineBar.js.map +0 -1
  96. package/lib/commonjs/export.js.map +0 -1
  97. package/lib/commonjs/format.js.map +0 -1
  98. package/lib/commonjs/hooks/useAssetRecords.js.map +0 -1
  99. package/lib/commonjs/index.js.map +0 -1
  100. package/lib/commonjs/preset.js.map +0 -1
  101. package/lib/commonjs/remote.js.map +0 -1
  102. package/lib/commonjs/state.js.map +0 -1
  103. package/lib/commonjs/store/assetStore.js.map +0 -1
  104. package/lib/commonjs/store/baseline.js.map +0 -1
  105. package/lib/commonjs/store/insights.js.map +0 -1
  106. package/lib/commonjs/sync/assetsSyncAdapter.js.map +0 -1
  107. package/lib/commonjs/types.js.map +0 -1
  108. package/lib/commonjs/utils/deviceScale.js.map +0 -1
  109. package/lib/commonjs/utils/testIDs.js.map +0 -1
  110. package/lib/module/capture/expo.js.map +0 -1
  111. package/lib/module/capture/install.js.map +0 -1
  112. package/lib/module/capture/measure.js.map +0 -1
  113. package/lib/module/capture/metroGraph.js.map +0 -1
  114. package/lib/module/capture/registry.js.map +0 -1
  115. package/lib/module/components/AssetDetail.js.map +0 -1
  116. package/lib/module/components/AssetRow.js.map +0 -1
  117. package/lib/module/components/AssetsFilterBar.js.map +0 -1
  118. package/lib/module/components/AssetsHeader.js.map +0 -1
  119. package/lib/module/components/AssetsIcon.js.map +0 -1
  120. package/lib/module/components/AssetsListControls.js.map +0 -1
  121. package/lib/module/components/AssetsModal.js.map +0 -1
  122. package/lib/module/components/BaselineBar.js +0 -111
  123. package/lib/module/components/BaselineBar.js.map +0 -1
  124. package/lib/module/export.js.map +0 -1
  125. package/lib/module/format.js.map +0 -1
  126. package/lib/module/hooks/useAssetRecords.js.map +0 -1
  127. package/lib/module/index.js.map +0 -1
  128. package/lib/module/preset.js.map +0 -1
  129. package/lib/module/remote.js.map +0 -1
  130. package/lib/module/state.js.map +0 -1
  131. package/lib/module/store/assetStore.js.map +0 -1
  132. package/lib/module/store/baseline.js.map +0 -1
  133. package/lib/module/store/insights.js.map +0 -1
  134. package/lib/module/sync/assetsSyncAdapter.js.map +0 -1
  135. package/lib/module/types.js.map +0 -1
  136. package/lib/module/utils/deviceScale.js.map +0 -1
  137. package/lib/module/utils/testIDs.js.map +0 -1
  138. package/lib/typescript/capture/expo.d.ts.map +0 -1
  139. package/lib/typescript/capture/install.d.ts.map +0 -1
  140. package/lib/typescript/capture/measure.d.ts.map +0 -1
  141. package/lib/typescript/capture/metroGraph.d.ts.map +0 -1
  142. package/lib/typescript/capture/registry.d.ts.map +0 -1
  143. package/lib/typescript/components/AssetDetail.d.ts.map +0 -1
  144. package/lib/typescript/components/AssetRow.d.ts.map +0 -1
  145. package/lib/typescript/components/AssetsFilterBar.d.ts.map +0 -1
  146. package/lib/typescript/components/AssetsHeader.d.ts.map +0 -1
  147. package/lib/typescript/components/AssetsIcon.d.ts.map +0 -1
  148. package/lib/typescript/components/AssetsListControls.d.ts.map +0 -1
  149. package/lib/typescript/components/AssetsModal.d.ts.map +0 -1
  150. package/lib/typescript/components/BaselineBar.d.ts +0 -13
  151. package/lib/typescript/components/BaselineBar.d.ts.map +0 -1
  152. package/lib/typescript/export.d.ts.map +0 -1
  153. package/lib/typescript/format.d.ts.map +0 -1
  154. package/lib/typescript/hooks/useAssetRecords.d.ts.map +0 -1
  155. package/lib/typescript/index.d.ts.map +0 -1
  156. package/lib/typescript/preset.d.ts.map +0 -1
  157. package/lib/typescript/remote.d.ts.map +0 -1
  158. package/lib/typescript/state.d.ts.map +0 -1
  159. package/lib/typescript/store/assetStore.d.ts.map +0 -1
  160. package/lib/typescript/store/baseline.d.ts.map +0 -1
  161. package/lib/typescript/store/insights.d.ts.map +0 -1
  162. package/lib/typescript/sync/assetsSyncAdapter.d.ts.map +0 -1
  163. package/lib/typescript/types.d.ts.map +0 -1
  164. package/lib/typescript/utils/deviceScale.d.ts.map +0 -1
  165. package/lib/typescript/utils/testIDs.d.ts.map +0 -1
@@ -1,241 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.assetKey = assetKey;
7
- exports.clearRecords = clearRecords;
8
- exports.computeStats = computeStats;
9
- exports.estDecodedBytes = estDecodedBytes;
10
- exports.getRecord = getRecord;
11
- exports.getRecordByKey = getRecordByKey;
12
- exports.getSnapshot = getSnapshot;
13
- exports.invalidate = invalidate;
14
- exports.kindForType = kindForType;
15
- exports.subscribe = subscribe;
16
- exports.updateRecord = updateRecord;
17
- exports.upsertRecord = upsertRecord;
18
- var _state = require("../state");
19
- var _deviceScale = require("../utils/deviceScale");
20
- /**
21
- * assetStore
22
- *
23
- * Bounded store of AssetRecords keyed by their stable asset key
24
- * (`location/name.type`). Follows the images tool's store shape — Set of
25
- * listeners, frame-coalesced emit, all mutable state on the globalThis
26
- * singleton (../state.ts) so dual module instances share one store.
27
- *
28
- * Unlike the images tool this is an INVENTORY, not an event log: upserts
29
- * merge into the existing record for a key instead of appending, and there
30
- * is no ring-buffer cap (a bundle has a bounded asset count by construction).
31
- */
32
-
33
- function emit() {
34
- const s = (0, _state.assetsState)();
35
- if (s.emitScheduled) return;
36
- s.emitScheduled = true;
37
- requestAnimationFrame(() => {
38
- s.emitScheduled = false;
39
- for (const listener of s.listeners) listener();
40
- });
41
- }
42
-
43
- /** Snapshot identity only changes on emit, so useSyncExternalStore stays cheap. */
44
- function invalidate() {
45
- const s = (0, _state.assetsState)();
46
- s.snapshot = [...s.records];
47
- emit();
48
- }
49
- const KIND_BY_EXT = {
50
- png: "image",
51
- jpg: "image",
52
- jpeg: "image",
53
- gif: "image",
54
- webp: "image",
55
- bmp: "image",
56
- psd: "image",
57
- svg: "image",
58
- tiff: "image",
59
- ktx: "image",
60
- heic: "image",
61
- heif: "image",
62
- avif: "image",
63
- ttf: "font",
64
- otf: "font",
65
- woff: "font",
66
- woff2: "font",
67
- ttc: "font",
68
- mp4: "video",
69
- m4v: "video",
70
- mov: "video",
71
- mpeg: "video",
72
- mpg: "video",
73
- webm: "video",
74
- avi: "video",
75
- mp3: "audio",
76
- wav: "audio",
77
- aac: "audio",
78
- aiff: "audio",
79
- caf: "audio",
80
- m4a: "audio",
81
- ogg: "audio",
82
- json: "data",
83
- db: "data",
84
- yaml: "data",
85
- yml: "data",
86
- xml: "data",
87
- html: "data",
88
- pdf: "data",
89
- zip: "data"
90
- };
91
- function kindForType(type) {
92
- return KIND_BY_EXT[type.toLowerCase()] ?? "other";
93
- }
94
- function assetKey(httpServerLocation, name, type) {
95
- // The runtime registry percent-encodes ?unstable_path= locations while the
96
- // Metro graph endpoint returns them decoded — normalize or every asset
97
- // splits into a registry record + a graph record.
98
- let location = httpServerLocation ?? "";
99
- try {
100
- location = decodeURIComponent(location);
101
- } catch {
102
- // malformed escapes: keep the raw string
103
- }
104
- return `${location}/${name}.${type}`;
105
- }
106
- /** Merge an asset sighting into the store; creates the record on first sight. */
107
- function upsertRecord(input) {
108
- const s = (0, _state.assetsState)();
109
- let record = s.byKey.get(input.key);
110
- if (!record) {
111
- record = {
112
- id: s.nextId++,
113
- key: input.key,
114
- name: input.name,
115
- type: input.type,
116
- kind: kindForType(input.type),
117
- scales: input.scales ?? [],
118
- loaded: input.loaded ?? false,
119
- origin: input.origin,
120
- firstSeenAt: Date.now()
121
- };
122
- s.records.push(record);
123
- s.byKey.set(record.key, record);
124
- s.byId.set(record.id, record);
125
- }
126
- if (input.registryId != null) record.registryId = input.registryId;
127
- if (input.hash != null) record.hash = input.hash;
128
- if (input.httpServerLocation != null) record.httpServerLocation = input.httpServerLocation;
129
- if (input.width != null) record.width = input.width;
130
- if (input.height != null) record.height = input.height;
131
- if (input.scales && input.scales.length) {
132
- // Dedupe: foo.png + foo@1x.png both existing yields [1, 1, …] from Metro.
133
- record.scales = [...new Set(input.scales)].sort((a, b) => a - b);
134
- }
135
- if (input.resolvedUri != null) record.resolvedUri = input.resolvedUri;
136
- if (input.resolvedScale != null) record.resolvedScale = input.resolvedScale;
137
- if (input.loaded) record.loaded = true;
138
- if (input.fileSystemLocation != null) record.fileSystemLocation = input.fileSystemLocation;
139
- if (input.files) record.files = input.files;
140
- if (input.fileHashes) record.fileHashes = input.fileHashes;
141
- invalidate();
142
- return record;
143
- }
144
-
145
- /** Mutate a record in place (records are owned by the store) and notify. */
146
- function updateRecord(id, patch) {
147
- const s = (0, _state.assetsState)();
148
- const record = s.byId.get(id);
149
- if (!record) return undefined;
150
- Object.assign(record, patch);
151
- invalidate();
152
- return record;
153
- }
154
- function getRecord(id) {
155
- return (0, _state.assetsState)().byId.get(id);
156
- }
157
- function getRecordByKey(key) {
158
- return (0, _state.assetsState)().byKey.get(key);
159
- }
160
- function getSnapshot() {
161
- return (0, _state.assetsState)().snapshot;
162
- }
163
- function subscribe(listener) {
164
- const s = (0, _state.assetsState)();
165
- s.listeners.add(listener);
166
- return () => (0, _state.assetsState)().listeners.delete(listener);
167
- }
168
- function clearRecords() {
169
- const s = (0, _state.assetsState)();
170
- s.records = [];
171
- s.byKey = new Map();
172
- s.byId = new Map();
173
- s.measuredCount = 0;
174
- s.graphCount = null;
175
- invalidate();
176
- }
177
-
178
- /**
179
- * Estimated decoded-bitmap memory (RGBA8888) at the scale this device would
180
- * pick — the size fallback when bytes can't be measured (release builds).
181
- *
182
- * `resolvedScale` is React Native's own answer and is authoritative, but it
183
- * only exists once the asset has been REGISTERED. For everything still known
184
- * only from the Metro graph — the never-loaded set, which is most of what this
185
- * tool is for — the scale has to be predicted the same way AssetSourceResolver
186
- * would, not taken as the largest variant shipped.
187
- */
188
- function estDecodedBytes(record) {
189
- if (record.kind !== "image" || !record.width || !record.height) return 0;
190
- const scale = record.resolvedScale ?? (0, _deviceScale.pickScale)(record.scales, (0, _deviceScale.effectivePixelRatio)());
191
- return Math.round(record.width * scale * record.height * scale * 4);
192
- }
193
- function computeStats(list) {
194
- const s = (0, _state.assetsState)();
195
- const byKind = {
196
- image: {
197
- count: 0,
198
- bytes: 0
199
- },
200
- font: {
201
- count: 0,
202
- bytes: 0
203
- },
204
- video: {
205
- count: 0,
206
- bytes: 0
207
- },
208
- audio: {
209
- count: 0,
210
- bytes: 0
211
- },
212
- data: {
213
- count: 0,
214
- bytes: 0
215
- },
216
- other: {
217
- count: 0,
218
- bytes: 0
219
- }
220
- };
221
- const stats = {
222
- total: list.length,
223
- loaded: 0,
224
- neverLoaded: 0,
225
- byKind,
226
- totalBytes: 0,
227
- measuredCount: 0,
228
- fontFamilies: s.fontFamilies.length
229
- };
230
- for (const r of list) {
231
- if (r.loaded) stats.loaded++;else stats.neverLoaded++;
232
- byKind[r.kind].count++;
233
- if (r.sizeBytes != null && r.sizeSource === "measured") {
234
- byKind[r.kind].bytes += r.sizeBytes;
235
- stats.totalBytes += r.sizeBytes;
236
- stats.measuredCount++;
237
- }
238
- }
239
- return stats;
240
- }
241
- //# sourceMappingURL=assetStore.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.assetKey=assetKey,exports.clearRecords=clearRecords,exports.computeStats=computeStats,exports.estDecodedBytes=estDecodedBytes,exports.getRecord=getRecord,exports.getRecordByKey=getRecordByKey,exports.getSnapshot=getSnapshot,exports.invalidate=invalidate,exports.kindForType=kindForType,exports.removeBuoyRecords=removeBuoyRecords,exports.subscribe=subscribe,exports.updateRecord=updateRecord,exports.upsertRecord=upsertRecord;var _state=require("../state"),_isBuoyAsset=require("../capture/isBuoyAsset"),_deviceScale=require("../utils/deviceScale");function emit(){const e=(0,_state.assetsState)();e.emitScheduled||(e.emitScheduled=!0,requestAnimationFrame(()=>{e.emitScheduled=!1;for(const t of e.listeners)t()}))}function invalidate(){const e=(0,_state.assetsState)();e.snapshot=[...e.records],emit()}const KIND_BY_EXT={png:"image",jpg:"image",jpeg:"image",gif:"image",webp:"image",bmp:"image",psd:"image",svg:"image",tiff:"image",ktx:"image",heic:"image",heif:"image",avif:"image",ttf:"font",otf:"font",woff:"font",woff2:"font",ttc:"font",mp4:"video",m4v:"video",mov:"video",mpeg:"video",mpg:"video",webm:"video",avi:"video",mp3:"audio",wav:"audio",aac:"audio",aiff:"audio",caf:"audio",m4a:"audio",ogg:"audio",json:"data",db:"data",yaml:"data",yml:"data",xml:"data",html:"data",pdf:"data",zip:"data"};function kindForType(e){return KIND_BY_EXT[e.toLowerCase()]??"other"}function assetKey(e,t,s){let a=e??"";try{a=decodeURIComponent(a)}catch{}return`${a}/${t}.${s}`}function upsertRecord(e){if((0,_isBuoyAsset.isBuoyAsset)(e))return;const t=(0,_state.assetsState)();let s=t.byKey.get(e.key);return s||(s={id:t.nextId++,key:e.key,name:e.name,type:e.type,kind:kindForType(e.type),scales:e.scales??[],loaded:e.loaded??!1,origin:e.origin,firstSeenAt:Date.now()},t.records.push(s),t.byKey.set(s.key,s),t.byId.set(s.id,s)),null!=e.registryId&&(s.registryId=e.registryId),null!=e.hash&&(s.hash=e.hash),null!=e.httpServerLocation&&(s.httpServerLocation=e.httpServerLocation),null!=e.width&&(s.width=e.width),null!=e.height&&(s.height=e.height),e.scales&&e.scales.length&&(s.scales=[...new Set(e.scales)].sort((e,t)=>e-t)),null!=e.resolvedUri&&(s.resolvedUri=e.resolvedUri),null!=e.resolvedScale&&(s.resolvedScale=e.resolvedScale),e.loaded&&(s.loaded=!0),null!=e.fileSystemLocation&&(s.fileSystemLocation=e.fileSystemLocation),e.files&&(s.files=e.files),e.fileHashes&&(s.fileHashes=e.fileHashes),invalidate(),s}function updateRecord(e,t){const s=(0,_state.assetsState)().byId.get(e);if(s)return Object.assign(s,t),invalidate(),s}function getRecord(e){return(0,_state.assetsState)().byId.get(e)}function getRecordByKey(e){return(0,_state.assetsState)().byKey.get(e)}function getSnapshot(){return(0,_state.assetsState)().snapshot}function subscribe(e){return(0,_state.assetsState)().listeners.add(e),()=>(0,_state.assetsState)().listeners.delete(e)}function clearRecords(){const e=(0,_state.assetsState)();e.records=[],e.byKey=new Map,e.byId=new Map,e.measuredCount=0,e.graphCount=null,invalidate()}function removeBuoyRecords(){const e=(0,_state.assetsState)(),t=e.records.filter(e=>!(0,_isBuoyAsset.isBuoyAsset)(e));t.length!==e.records.length&&(e.records=t,e.byKey=new Map(t.map(e=>[e.key,e])),e.byId=new Map(t.map(e=>[e.id,e])),e.measuredCount=t.filter(e=>"measured"===e.sizeSource).length,invalidate())}function estDecodedBytes(e){if("image"!==e.kind||!e.width||!e.height)return 0;const t=e.resolvedScale??(0,_deviceScale.pickScale)(e.scales,(0,_deviceScale.effectivePixelRatio)());return Math.round(e.width*t*e.height*t*4)}function computeStats(e){const t=(0,_state.assetsState)(),s={image:{count:0,bytes:0},font:{count:0,bytes:0},video:{count:0,bytes:0},audio:{count:0,bytes:0},data:{count:0,bytes:0},other:{count:0,bytes:0}},a={total:e.length,loaded:0,neverLoaded:0,byKind:s,totalBytes:0,measuredCount:0,fontFamilies:t.fontFamilies.length};for(const t of e)t.loaded?a.loaded++:a.neverLoaded++,s[t.kind].count++,null!=t.sizeBytes&&"measured"===t.sizeSource&&(s[t.kind].bytes+=t.sizeBytes,a.totalBytes+=t.sizeBytes,a.measuredCount++);return a}
@@ -1,173 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.clearBaseline = clearBaseline;
7
- exports.computeDiff = computeDiff;
8
- exports.getDiff = getDiff;
9
- exports.loadBaseline = loadBaseline;
10
- exports.saveBaseline = saveBaseline;
11
- var _sharedUi = require("@buoy-gg/shared-ui");
12
- var _remote = require("../remote");
13
- var _state = require("../state");
14
- var _assetStore = require("./assetStore");
15
- /**
16
- * Baseline diff — the regression-prevention half of the tool.
17
- *
18
- * "Save baseline" persists a slim {hash, size} snapshot of the inventory via
19
- * shared-ui's persistentStorage (FileSystem → AsyncStorage → memory), and
20
- * every later scan diffs against it: new assets, removed assets, grown
21
- * assets, net bytes. Run it before and after a branch and you get "this
22
- * branch added 2.3 MB" without any CI tooling.
23
- *
24
- * On dashboard mirrors the actions forward to the device (the baseline
25
- * belongs to the app being inspected, not the dashboard) and the computed
26
- * diff arrives back inside the synced snapshot.
27
- */
28
-
29
- const STORAGE_KEY = "@react_buoy/assets/baseline";
30
-
31
- /** ≥1 KB counts as growth; below that is codec/metadata noise. */
32
- const GROWN_THRESHOLD_BYTES = 1024;
33
-
34
- /** Load the persisted baseline into state once; safe to call repeatedly. */
35
- async function loadBaseline() {
36
- const s = (0, _state.assetsState)();
37
- if (s.baselineLoaded) return s.baseline;
38
- s.baselineLoaded = true;
39
- try {
40
- const raw = await _sharedUi.persistentStorage.getItem(STORAGE_KEY);
41
- if (raw) {
42
- const parsed = JSON.parse(raw);
43
- if (parsed && typeof parsed.savedAt === "number" && parsed.entries) {
44
- s.baseline = parsed;
45
- (0, _assetStore.invalidate)();
46
- }
47
- }
48
- } catch {
49
- // unreadable baseline = no baseline
50
- }
51
- return s.baseline;
52
- }
53
- async function saveBaseline() {
54
- const remote = (0, _remote.remoteAssetsDispatch)();
55
- if (remote) {
56
- await remote("saveBaseline");
57
- return (0, _state.assetsState)().baseline ?? {
58
- savedAt: Date.now(),
59
- entries: {}
60
- };
61
- }
62
- const s = (0, _state.assetsState)();
63
- const entries = {};
64
- for (const r of (0, _assetStore.getSnapshot)()) {
65
- entries[r.key] = {
66
- hash: r.hash ?? null,
67
- sizeBytes: r.sizeSource === "measured" ? r.sizeBytes ?? null : null,
68
- name: r.name,
69
- type: r.type
70
- };
71
- }
72
- const baseline = {
73
- savedAt: Date.now(),
74
- entries
75
- };
76
- s.baseline = baseline;
77
- s.baselineLoaded = true;
78
- (0, _assetStore.invalidate)();
79
- try {
80
- await _sharedUi.persistentStorage.setItem(STORAGE_KEY, JSON.stringify(baseline));
81
- } catch {
82
- // in-memory baseline still works for this session
83
- }
84
- return baseline;
85
- }
86
- async function clearBaseline() {
87
- const remote = (0, _remote.remoteAssetsDispatch)();
88
- if (remote) {
89
- await remote("clearBaseline");
90
- return;
91
- }
92
- const s = (0, _state.assetsState)();
93
- s.baseline = null;
94
- s.baselineLoaded = true;
95
- (0, _assetStore.invalidate)();
96
- try {
97
- await _sharedUi.persistentStorage.removeItem(STORAGE_KEY);
98
- } catch {
99
- // nothing to clean up
100
- }
101
- }
102
- function computeDiff(records, baseline) {
103
- const added = [];
104
- const grown = [];
105
- const seen = new Set();
106
- let currentBytes = 0;
107
- let baselineBytes = 0;
108
- let comparable = false;
109
- for (const r of records) {
110
- seen.add(r.key);
111
- const then = baseline.entries[r.key];
112
- if (!then) {
113
- added.push(r.id);
114
- continue;
115
- }
116
- const now = r.sizeSource === "measured" ? r.sizeBytes ?? null : null;
117
- if (now != null && then.sizeBytes != null) {
118
- comparable = true;
119
- currentBytes += now;
120
- baselineBytes += then.sizeBytes;
121
- if (now - then.sizeBytes >= GROWN_THRESHOLD_BYTES) {
122
- grown.push({
123
- id: r.id,
124
- deltaBytes: now - then.sizeBytes
125
- });
126
- }
127
- }
128
- }
129
- const removed = [];
130
- for (const [key, entry] of Object.entries(baseline.entries)) {
131
- if (!seen.has(key)) {
132
- removed.push({
133
- key,
134
- name: entry.name,
135
- type: entry.type
136
- });
137
- if (entry.sizeBytes != null) {
138
- // A removal is as comparable as a survivor: its bytes were measured
139
- // then and are definitionally zero now. Counting them toward the
140
- // baseline total without marking the diff comparable meant a branch
141
- // that only DELETED assets reported "12 removed" and no size at all —
142
- // the one number anyone reads that bar for.
143
- comparable = true;
144
- baselineBytes += entry.sizeBytes;
145
- }
146
- }
147
- }
148
- // Added assets' measured bytes count toward the net even without a
149
- // baseline counterpart — that's exactly the growth being reported.
150
- for (const id of added) {
151
- const r = records.find(rec => rec.id === id);
152
- if (r?.sizeSource === "measured" && r.sizeBytes != null) {
153
- comparable = true;
154
- currentBytes += r.sizeBytes;
155
- }
156
- }
157
- return {
158
- baselineSavedAt: baseline.savedAt,
159
- added,
160
- removed,
161
- grown: grown.sort((a, b) => b.deltaBytes - a.deltaBytes),
162
- netBytes: comparable ? currentBytes - baselineBytes : null
163
- };
164
- }
165
-
166
- /** Current diff: the synced device's on mirrors, computed locally on device. */
167
- function getDiff() {
168
- const s = (0, _state.assetsState)();
169
- if (s.mirrorDiff) return s.mirrorDiff;
170
- if (!s.baseline) return null;
171
- return computeDiff((0, _assetStore.getSnapshot)(), s.baseline);
172
- }
173
- //# sourceMappingURL=baseline.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.clearBaseline=clearBaseline,exports.computeDiff=computeDiff,exports.getDiff=getDiff,exports.loadBaseline=loadBaseline,exports.saveBaseline=saveBaseline;var _sharedUi=require("@buoy-gg/shared-ui"),_remote=require("../remote"),_state=require("../state"),_assetStore=require("./assetStore"),_isBuoyAsset=require("../capture/isBuoyAsset");const STORAGE_KEY="@react_buoy/assets/baseline",GROWN_THRESHOLD_BYTES=1024;async function loadBaseline(){const e=(0,_state.assetsState)();if(e.baselineLoaded)return e.baseline;e.baselineLoaded=!0;try{const s=await _sharedUi.persistentStorage.getItem(STORAGE_KEY);if(s){const t=JSON.parse(s);t&&"number"==typeof t.savedAt&&t.entries&&(e.baseline=t,(0,_assetStore.invalidate)())}}catch{}return e.baseline}async function saveBaseline(){const e=(0,_remote.remoteAssetsDispatch)();if(e)return await e("saveBaseline"),(0,_state.assetsState)().baseline??{savedAt:Date.now(),entries:{}};const s=(0,_state.assetsState)(),t={};for(const e of(0,_assetStore.getSnapshot)())t[e.key]={hash:e.hash??null,sizeBytes:"measured"===e.sizeSource?e.sizeBytes??null:null,name:e.name,type:e.type};const a={savedAt:Date.now(),entries:t};s.baseline=a,s.baselineLoaded=!0,(0,_assetStore.invalidate)();try{await _sharedUi.persistentStorage.setItem(STORAGE_KEY,JSON.stringify(a))}catch{}return a}async function clearBaseline(){const e=(0,_remote.remoteAssetsDispatch)();if(e)return void await e("clearBaseline");const s=(0,_state.assetsState)();s.baseline=null,s.baselineLoaded=!0,(0,_assetStore.invalidate)();try{await _sharedUi.persistentStorage.removeItem(STORAGE_KEY)}catch{}}function computeDiff(e,s){const t=[],a=[],i=new Set;let n=0,r=0,o=!1;for(const l of e){i.add(l.key);const e=s.entries[l.key];if(!e){t.push(l.id);continue}const u="measured"===l.sizeSource?l.sizeBytes??null:null;null!=u&&null!=e.sizeBytes&&(o=!0,n+=u,r+=e.sizeBytes,u-e.sizeBytes>=GROWN_THRESHOLD_BYTES&&a.push({id:l.id,deltaBytes:u-e.sizeBytes}))}const l=[];for(const[e,t]of Object.entries(s.entries))(0,_isBuoyAsset.isBuoyAssetPath)(e)||i.has(e)||(l.push({key:e,name:t.name,type:t.type}),null!=t.sizeBytes&&(o=!0,r+=t.sizeBytes));for(const s of t){const t=e.find(e=>e.id===s);"measured"===t?.sizeSource&&null!=t.sizeBytes&&(o=!0,n+=t.sizeBytes)}return{baselineSavedAt:s.savedAt,added:t,removed:l,grown:a.sort((e,s)=>s.deltaBytes-e.deltaBytes),netBytes:o?n-r:null}}function getDiff(){const e=(0,_state.assetsState)();return e.mirrorDiff?e.mirrorDiff:e.baseline?computeDiff((0,_assetStore.getSnapshot)(),e.baseline):null}
@@ -1,120 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.computeInsights = computeInsights;
7
- var _assetStore = require("./assetStore");
8
- var _deviceScale = require("../utils/deviceScale");
9
- /**
10
- * Derived insights over the asset inventory — the Lighthouse/Emerge-style
11
- * audit findings, each pointing at concrete record ids.
12
- */
13
-
14
- /** png/jpeg at or above this measured size are flagged as WebP candidates. */
15
- const WEBP_CANDIDATE_BYTES = 50 * 1024;
16
- /** GIFs at or above this measured size decode into serious frame memory. */
17
- const HEAVY_GIF_BYTES = 100 * 1024;
18
- /** Fonts at or above this are usually unsubset icon fonts. */
19
- const HEAVY_FONT_BYTES = 150 * 1024;
20
- /** Flag images decoding to more pixels than this many screenfuls. */
21
- const OVER_DECODE_SCREENS = 2;
22
- /** A higher scale variant this much smaller than a lower one = bad export. */
23
- const SCALE_ANOMALY_RATIO = 0.9;
24
- /**
25
- * Anomaly checks only apply when the smaller variant is at least this big —
26
- * for tiny icons PNG overhead dominates and byte ordering is pure noise.
27
- */
28
- const SCALE_ANOMALY_MIN_BYTES = 4 * 1024;
29
-
30
- /** Physical pixels of the entire screen (fallback ≈ a 6.1" phone). */
31
- function screenPixels() {
32
- try {
33
- const {
34
- Dimensions,
35
- PixelRatio
36
- } = require("react-native");
37
- const screen = Dimensions.get("screen");
38
- const ratio = PixelRatio.get();
39
- return screen.width * ratio * screen.height * ratio;
40
- } catch {
41
- return 1170 * 2532;
42
- }
43
- }
44
-
45
- /** True when the measured per-scale sizes contradict the scales (bad export). */
46
- function hasScaleAnomaly(record) {
47
- const sizes = record.sizesByScale;
48
- if (!sizes) return false;
49
- const ordered = Object.entries(sizes).map(([scale, bytes]) => ({
50
- scale: Number(scale),
51
- bytes
52
- })).filter(e => Number.isFinite(e.scale) && e.bytes > 0).sort((a, b) => a.scale - b.scale);
53
- for (let i = 1; i < ordered.length; i++) {
54
- if (ordered[i - 1].bytes >= SCALE_ANOMALY_MIN_BYTES && ordered[i].bytes < ordered[i - 1].bytes * SCALE_ANOMALY_RATIO) {
55
- return true;
56
- }
57
- }
58
- return false;
59
- }
60
- function computeInsights(list) {
61
- const deviceScale = (0, _deviceScale.effectivePixelRatio)();
62
- const byHash = new Map();
63
- for (const r of list) {
64
- if (!r.hash) continue;
65
- const group = byHash.get(r.hash);
66
- if (group) group.push(r);else byHash.set(r.hash, [r]);
67
- }
68
- const duplicates = [];
69
- for (const [hash, group] of byHash) {
70
- if (group.length < 2) continue;
71
- const measured = group.find(r => r.sizeBytes != null);
72
- duplicates.push({
73
- hash,
74
- ids: group.map(r => r.id),
75
- wastedBytes: measured?.sizeBytes != null ? measured.sizeBytes * (group.length - 1) : null
76
- });
77
- }
78
- const screenPx = screenPixels();
79
- const scaleGaps = [];
80
- const webpCandidates = [];
81
- const neverLoaded = [];
82
- const overDecode = [];
83
- const heavyGifs = [];
84
- const heavyFonts = [];
85
- const scaleAnomalies = [];
86
- for (const r of list) {
87
- if (!r.loaded) neverLoaded.push(r.id);
88
- // Only multi-scale assets missing the device scale are flagged — the
89
- // author clearly manages variants and missed one. Single-@1x images are
90
- // the overwhelmingly common case and flagging them all is pure noise.
91
- if (r.kind === "image" && r.scales.length > 1 && !r.scales.some(s => s >= deviceScale)) {
92
- scaleGaps.push(r.id);
93
- }
94
- if ((r.type === "png" || r.type === "jpg" || r.type === "jpeg") && r.sizeBytes != null && r.sizeBytes >= WEBP_CANDIDATE_BYTES) {
95
- webpCandidates.push(r.id);
96
- }
97
- // estDecodedBytes = pixels × 4, so pixels = bytes / 4.
98
- if (r.kind === "image" && (0, _assetStore.estDecodedBytes)(r) / 4 > screenPx * OVER_DECODE_SCREENS) {
99
- overDecode.push(r.id);
100
- }
101
- if (r.type === "gif" && (r.sizeBytes ?? 0) >= HEAVY_GIF_BYTES) {
102
- heavyGifs.push(r.id);
103
- }
104
- if (r.kind === "font" && (r.sizeBytes ?? 0) >= HEAVY_FONT_BYTES) {
105
- heavyFonts.push(r.id);
106
- }
107
- if (hasScaleAnomaly(r)) scaleAnomalies.push(r.id);
108
- }
109
- return {
110
- duplicates,
111
- scaleGaps,
112
- webpCandidates,
113
- neverLoaded,
114
- overDecode,
115
- heavyGifs,
116
- heavyFonts,
117
- scaleAnomalies
118
- };
119
- }
120
- //# sourceMappingURL=insights.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.computeInsights=computeInsights;var _assetStore=require("./assetStore"),_deviceScale=require("../utils/deviceScale");const WEBP_CANDIDATE_BYTES=51200,HEAVY_GIF_BYTES=102400,HEAVY_FONT_BYTES=153600,OVER_DECODE_SCREENS=2,SCALE_ANOMALY_RATIO=.9,SCALE_ANOMALY_MIN_BYTES=4096;function screenPixels(){try{const{Dimensions:e,PixelRatio:s}=require("react-native"),t=e.get("screen"),i=s.get();return t.width*i*t.height*i}catch{return 2962440}}function hasScaleAnomaly(e){const s=e.sizesByScale;if(!s)return!1;const t=Object.entries(s).map(([e,s])=>({scale:Number(e),bytes:s})).filter(e=>Number.isFinite(e.scale)&&e.bytes>0).sort((e,s)=>e.scale-s.scale);for(let e=1;e<t.length;e++)if(t[e-1].bytes>=4096&&t[e].bytes<.9*t[e-1].bytes)return!0;return!1}function computeInsights(e){const s=(0,_deviceScale.effectivePixelRatio)(),t=new Map;for(const s of e){if(!s.hash)continue;const e=t.get(s.hash);e?e.push(s):t.set(s.hash,[s])}const i=[];for(const[e,s]of t){if(s.length<2)continue;const t=s.find(e=>null!=e.sizeBytes);i.push({hash:e,ids:s.map(e=>e.id),wastedBytes:null!=t?.sizeBytes?t.sizeBytes*(s.length-1):null})}const n=screenPixels(),c=[],o=[],a=[],r=[],l=[],u=[],h=[];for(const t of e)t.loaded||a.push(t.id),"image"===t.kind&&t.scales.length>1&&!t.scales.some(e=>e>=s)&&c.push(t.id),("png"===t.type||"jpg"===t.type||"jpeg"===t.type)&&null!=t.sizeBytes&&t.sizeBytes>=WEBP_CANDIDATE_BYTES&&o.push(t.id),"image"===t.kind&&(0,_assetStore.estDecodedBytes)(t)/4>n*OVER_DECODE_SCREENS&&r.push(t.id),"gif"===t.type&&(t.sizeBytes??0)>=HEAVY_GIF_BYTES&&l.push(t.id),"font"===t.kind&&(t.sizeBytes??0)>=HEAVY_FONT_BYTES&&u.push(t.id),hasScaleAnomaly(t)&&h.push(t.id);return{duplicates:i,scaleGaps:c,webpCandidates:o,neverLoaded:a,overDecode:r,heavyGifs:l,heavyFonts:u,scaleAnomalies:h}}