@buoy-gg/assets 7.0.40 → 7.0.42

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