@buoy-gg/assets 7.0.29 → 7.0.31

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 (95) hide show
  1. package/lib/commonjs/components/AssetDetail.js +10 -1
  2. package/lib/commonjs/components/AssetDetail.js.map +1 -1
  3. package/lib/commonjs/components/AssetRow.js +13 -4
  4. package/lib/commonjs/components/AssetRow.js.map +1 -1
  5. package/lib/commonjs/components/AssetsFilterBar.js +4 -0
  6. package/lib/commonjs/components/AssetsFilterBar.js.map +1 -1
  7. package/lib/commonjs/components/AssetsHeader.js +11 -5
  8. package/lib/commonjs/components/AssetsHeader.js.map +1 -1
  9. package/lib/commonjs/components/AssetsListControls.js +5 -3
  10. package/lib/commonjs/components/AssetsListControls.js.map +1 -1
  11. package/lib/commonjs/components/AssetsModal.js +10 -0
  12. package/lib/commonjs/components/AssetsModal.js.map +1 -1
  13. package/lib/commonjs/components/BaselineBar.js +5 -0
  14. package/lib/commonjs/components/BaselineBar.js.map +1 -1
  15. package/lib/commonjs/index.js +53 -0
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/store/assetStore.js +8 -1
  18. package/lib/commonjs/store/assetStore.js.map +1 -1
  19. package/lib/commonjs/store/baseline.js +9 -1
  20. package/lib/commonjs/store/baseline.js.map +1 -1
  21. package/lib/commonjs/store/insights.js +2 -11
  22. package/lib/commonjs/store/insights.js.map +1 -1
  23. package/lib/commonjs/utils/deviceScale.js +50 -0
  24. package/lib/commonjs/utils/deviceScale.js.map +1 -0
  25. package/lib/commonjs/utils/testIDs.js +114 -0
  26. package/lib/commonjs/utils/testIDs.js.map +1 -0
  27. package/lib/module/components/AssetDetail.js +10 -1
  28. package/lib/module/components/AssetDetail.js.map +1 -1
  29. package/lib/module/components/AssetRow.js +13 -4
  30. package/lib/module/components/AssetRow.js.map +1 -1
  31. package/lib/module/components/AssetsFilterBar.js +4 -0
  32. package/lib/module/components/AssetsFilterBar.js.map +1 -1
  33. package/lib/module/components/AssetsHeader.js +11 -5
  34. package/lib/module/components/AssetsHeader.js.map +1 -1
  35. package/lib/module/components/AssetsListControls.js +5 -3
  36. package/lib/module/components/AssetsListControls.js.map +1 -1
  37. package/lib/module/components/AssetsModal.js +10 -0
  38. package/lib/module/components/AssetsModal.js.map +1 -1
  39. package/lib/module/components/BaselineBar.js +5 -0
  40. package/lib/module/components/BaselineBar.js.map +1 -1
  41. package/lib/module/index.js +4 -0
  42. package/lib/module/index.js.map +1 -1
  43. package/lib/module/store/assetStore.js +8 -1
  44. package/lib/module/store/assetStore.js.map +1 -1
  45. package/lib/module/store/baseline.js +9 -1
  46. package/lib/module/store/baseline.js.map +1 -1
  47. package/lib/module/store/insights.js +1 -10
  48. package/lib/module/store/insights.js.map +1 -1
  49. package/lib/module/utils/deviceScale.js +46 -0
  50. package/lib/module/utils/deviceScale.js.map +1 -0
  51. package/lib/module/utils/testIDs.js +103 -0
  52. package/lib/module/utils/testIDs.js.map +1 -0
  53. package/lib/typescript/components/AssetDetail.d.ts.map +1 -1
  54. package/lib/typescript/components/AssetRow.d.ts.map +1 -1
  55. package/lib/typescript/components/AssetsFilterBar.d.ts.map +1 -1
  56. package/lib/typescript/components/AssetsHeader.d.ts.map +1 -1
  57. package/lib/typescript/components/AssetsListControls.d.ts.map +1 -1
  58. package/lib/typescript/components/AssetsModal.d.ts.map +1 -1
  59. package/lib/typescript/components/BaselineBar.d.ts.map +1 -1
  60. package/lib/typescript/index.d.ts +1 -0
  61. package/lib/typescript/index.d.ts.map +1 -1
  62. package/lib/typescript/store/assetStore.d.ts +6 -0
  63. package/lib/typescript/store/assetStore.d.ts.map +1 -1
  64. package/lib/typescript/store/baseline.d.ts.map +1 -1
  65. package/lib/typescript/store/insights.d.ts.map +1 -1
  66. package/lib/typescript/utils/deviceScale.d.ts +25 -0
  67. package/lib/typescript/utils/deviceScale.d.ts.map +1 -0
  68. package/lib/typescript/utils/testIDs.d.ts +75 -0
  69. package/lib/typescript/utils/testIDs.d.ts.map +1 -0
  70. package/package.json +5 -6
  71. package/src/capture/expo.ts +0 -45
  72. package/src/capture/install.ts +0 -73
  73. package/src/capture/measure.ts +0 -167
  74. package/src/capture/metroGraph.ts +0 -146
  75. package/src/capture/registry.ts +0 -137
  76. package/src/components/AssetDetail.tsx +0 -207
  77. package/src/components/AssetRow.tsx +0 -243
  78. package/src/components/AssetsFilterBar.tsx +0 -99
  79. package/src/components/AssetsHeader.tsx +0 -75
  80. package/src/components/AssetsIcon.tsx +0 -9
  81. package/src/components/AssetsListControls.tsx +0 -101
  82. package/src/components/AssetsModal.tsx +0 -371
  83. package/src/components/BaselineBar.tsx +0 -98
  84. package/src/export.ts +0 -110
  85. package/src/format.ts +0 -53
  86. package/src/hooks/useAssetRecords.ts +0 -26
  87. package/src/index.tsx +0 -87
  88. package/src/preset.tsx +0 -35
  89. package/src/remote.ts +0 -65
  90. package/src/state.ts +0 -84
  91. package/src/store/assetStore.ts +0 -209
  92. package/src/store/baseline.ts +0 -152
  93. package/src/store/insights.ts +0 -145
  94. package/src/sync/assetsSyncAdapter.ts +0 -151
  95. package/src/types.ts +0 -160
package/src/index.tsx DELETED
@@ -1,87 +0,0 @@
1
- /**
2
- * @buoy-gg/assets — bundled-asset inventory for React Native / Expo.
3
- *
4
- * Enumerates every asset in the app (images, fonts, video, audio, …) via the
5
- * runtime asset registry, enriched in dev with the Metro graph (full-bundle
6
- * coverage, byte sizes, never-loaded detection) and on Expo with loaded font
7
- * families + the updates localAssets map. Zero configuration: importing this
8
- * package installs the (cheap, synchronous) registry capture.
9
- */
10
-
11
- // ---------------------------------------------------------------------------
12
- // Tool preset (FloatingDevTools)
13
- // ---------------------------------------------------------------------------
14
- export { assetsToolPreset, createAssetsTool } from "./preset";
15
-
16
- // ---------------------------------------------------------------------------
17
- // Components
18
- // ---------------------------------------------------------------------------
19
- export { AssetsModal, AssetsToolView } from "./components/AssetsModal";
20
- export { AssetsIcon, ASSETS_ICON_COLOR } from "./components/AssetsIcon";
21
-
22
- // ---------------------------------------------------------------------------
23
- // Capture / scanning
24
- // ---------------------------------------------------------------------------
25
- export { installAssetCapture, rescan, getScanStatus } from "./capture/install";
26
- export { measureSizes } from "./capture/measure";
27
- export { fetchAssetGraph, getDevServerOrigin } from "./capture/metroGraph";
28
- export { getLoadedFontFamilies, getLocalAssets } from "./capture/expo";
29
-
30
- // ---------------------------------------------------------------------------
31
- // Store
32
- // ---------------------------------------------------------------------------
33
- export {
34
- clearRecords,
35
- computeStats,
36
- estDecodedBytes,
37
- getRecord,
38
- getSnapshot,
39
- subscribe,
40
- } from "./store/assetStore";
41
- export { computeInsights } from "./store/insights";
42
- export {
43
- clearBaseline,
44
- computeDiff,
45
- getDiff,
46
- loadBaseline,
47
- saveBaseline,
48
- } from "./store/baseline";
49
- export { useAssetRecords } from "./hooks/useAssetRecords";
50
-
51
- // ---------------------------------------------------------------------------
52
- // Reports
53
- // ---------------------------------------------------------------------------
54
- export { formatAssetMarkdown, formatInventoryMarkdown } from "./export";
55
-
56
- // ---------------------------------------------------------------------------
57
- // External sync (desktop dashboard + MCP)
58
- // ---------------------------------------------------------------------------
59
- export { assetsSyncAdapter } from "./sync/assetsSyncAdapter";
60
- export {
61
- disableAssetsCapture,
62
- replaceAssetsFromSnapshot,
63
- setRemoteAssetsHandler,
64
- } from "./remote";
65
- export type { AssetsRemoteDispatch } from "./remote";
66
-
67
- // ---------------------------------------------------------------------------
68
- // Types
69
- // ---------------------------------------------------------------------------
70
- export type {
71
- AssetKind,
72
- AssetOrigin,
73
- AssetRecord,
74
- AssetRecordWire,
75
- AssetsBaseline,
76
- AssetsDiff,
77
- AssetsInsights,
78
- AssetsScanStatus,
79
- AssetsSnapshotPayload,
80
- AssetsStats,
81
- DuplicateInsight,
82
- } from "./types";
83
-
84
- // Side effect: install the registry capture at import (walk + patch — cheap,
85
- // synchronous, no network). The Metro graph fetch waits for the modal/action.
86
- import { installAssetCapture as installOnImport } from "./capture/install";
87
- installOnImport();
package/src/preset.tsx DELETED
@@ -1,35 +0,0 @@
1
- /**
2
- * Pre-configured Assets tool for FloatingDevTools.
3
- *
4
- * @example
5
- * ```tsx
6
- * import { assetsToolPreset } from '@buoy-gg/assets';
7
- *
8
- * <FloatingDevTools apps={[assetsToolPreset]} />
9
- * ```
10
- */
11
-
12
- import { AssetsIcon, ASSETS_ICON_COLOR } from "./components/AssetsIcon";
13
- import { AssetsModal } from "./components/AssetsModal";
14
-
15
- export const assetsToolPreset = {
16
- id: "assets",
17
- name: "ASSETS",
18
- description: "Every bundled asset — sizes, scale coverage, unused detection",
19
- slot: "both" as const,
20
- icon: ({ size, color }: { size: number; color?: string }) => (
21
- <AssetsIcon size={size} color={color || ASSETS_ICON_COLOR} />
22
- ),
23
- component: AssetsModal,
24
- };
25
-
26
- /** Create a custom Assets tool configuration. */
27
- export function createAssetsTool(
28
- options: { name?: string; description?: string } = {}
29
- ) {
30
- return {
31
- ...assetsToolPreset,
32
- name: options.name ?? assetsToolPreset.name,
33
- description: options.description ?? assetsToolPreset.description,
34
- };
35
- }
package/src/remote.ts DELETED
@@ -1,65 +0,0 @@
1
- /**
2
- * Remote-dispatch seam for dashboard mirrors (Buoy Desktop).
3
- *
4
- * On device this stays null and every action runs locally. A dashboard sets
5
- * a handler that forwards action name + params over external-sync to the
6
- * SAME adapter actions the MCP uses (sync/assetsSyncAdapter.ts), so one
7
- * action vocabulary serves phone UI, desktop UI, and LLM automation.
8
- */
9
-
10
- import { assetsState } from "./state";
11
- import { invalidate } from "./store/assetStore";
12
- import type { AssetsSnapshotPayload } from "./types";
13
-
14
- export type AssetsRemoteDispatch = (
15
- action: string,
16
- params?: Record<string, unknown>
17
- ) => Promise<unknown>;
18
-
19
- export function setRemoteAssetsHandler(
20
- handler: AssetsRemoteDispatch | null
21
- ): void {
22
- assetsState().remoteDispatch = handler;
23
- }
24
-
25
- export function remoteAssetsDispatch(): AssetsRemoteDispatch | null {
26
- return assetsState().remoteDispatch;
27
- }
28
-
29
- /** Dashboards never scan their own bundle — the store is purely a mirror. */
30
- export function disableAssetsCapture(): void {
31
- assetsState().captureDisabled = true;
32
- }
33
-
34
- /** Replace the whole store from a synced adapter snapshot. */
35
- export function replaceAssetsFromSnapshot(payload: AssetsSnapshotPayload): void {
36
- const s = assetsState();
37
- const wire = payload.records ?? [];
38
- s.records = wire.map((w) => ({
39
- id: w.id,
40
- key: w.key,
41
- registryId: w.registryId ?? undefined,
42
- name: w.name,
43
- type: w.type,
44
- kind: w.kind,
45
- hash: w.hash ?? undefined,
46
- httpServerLocation: w.location ?? undefined,
47
- width: w.width ?? undefined,
48
- height: w.height ?? undefined,
49
- scales: w.scales,
50
- resolvedUri: w.uri ?? undefined,
51
- loaded: w.loaded,
52
- origin: w.registryId != null ? ("registry" as const) : ("graph" as const),
53
- sizeBytes: w.sizeBytes ?? undefined,
54
- sizeSource: w.sizeSource ?? undefined,
55
- firstSeenAt: Date.now(),
56
- }));
57
- s.byKey = new Map(s.records.map((r) => [r.key, r]));
58
- s.byId = new Map(s.records.map((r) => [r.id, r]));
59
- if (payload.scanStatus) {
60
- s.mirrorScanStatus = payload.scanStatus;
61
- s.fontFamilies = payload.scanStatus.fontFamilies ?? [];
62
- }
63
- s.mirrorDiff = payload.diff ?? null;
64
- invalidate();
65
- }
package/src/state.ts DELETED
@@ -1,84 +0,0 @@
1
- /**
2
- * Global singleton state for the Assets tool.
3
- *
4
- * Same rationale as the images tool: the package can be evaluated as two
5
- * module instances (lib vs src resolution), and the registerAsset patch must
6
- * share one store with whichever copy the UI renders from. All mutable state
7
- * lives on globalThis under a well-known symbol.
8
- */
9
-
10
- import type { AssetRecord, AssetsBaseline, AssetsDiff, AssetsScanStatus } from "./types";
11
-
12
- export interface AssetsGlobalState {
13
- // store
14
- records: AssetRecord[];
15
- byKey: Map<string, AssetRecord>;
16
- byId: Map<number, AssetRecord>;
17
- nextId: number;
18
- listeners: Set<() => void>;
19
- snapshot: AssetRecord[];
20
- emitScheduled: boolean;
21
- // install / scan status
22
- installed: boolean;
23
- patched: boolean;
24
- registryAvailable: boolean;
25
- graphSupported: boolean;
26
- graphCount: number | null;
27
- graphError: string | null;
28
- graphFetchInFlight: boolean;
29
- lastScanAt: number | null;
30
- measuring: boolean;
31
- measuredCount: number;
32
- fontFamilies: string[];
33
- localAssetCount: number | null;
34
- // baseline diff
35
- baseline: AssetsBaseline | null;
36
- baselineLoaded: boolean;
37
- // mirror mode (desktop dashboard)
38
- captureDisabled: boolean;
39
- remoteDispatch:
40
- | ((action: string, params?: Record<string, unknown>) => Promise<unknown>)
41
- | null;
42
- mirrorScanStatus: AssetsScanStatus | null;
43
- /** Device PixelRatio adopted from a synced snapshot (scale-gap math). */
44
- mirrorPixelRatio: number | null;
45
- /** Device-computed diff adopted from a synced snapshot. */
46
- mirrorDiff: AssetsDiff | null;
47
- }
48
-
49
- const KEY = Symbol.for("buoy.assets.globalState.v1");
50
-
51
- export function assetsState(): AssetsGlobalState {
52
- const g = globalThis as Record<symbol, AssetsGlobalState | undefined>;
53
- if (!g[KEY]) {
54
- g[KEY] = {
55
- records: [],
56
- byKey: new Map(),
57
- byId: new Map(),
58
- nextId: 1,
59
- listeners: new Set(),
60
- snapshot: [],
61
- emitScheduled: false,
62
- installed: false,
63
- patched: false,
64
- registryAvailable: false,
65
- graphSupported: false,
66
- graphCount: null,
67
- graphError: null,
68
- graphFetchInFlight: false,
69
- lastScanAt: null,
70
- measuring: false,
71
- measuredCount: 0,
72
- fontFamilies: [],
73
- localAssetCount: null,
74
- baseline: null,
75
- baselineLoaded: false,
76
- captureDisabled: false,
77
- remoteDispatch: null,
78
- mirrorScanStatus: null,
79
- mirrorPixelRatio: null,
80
- mirrorDiff: null,
81
- };
82
- }
83
- return g[KEY];
84
- }
@@ -1,209 +0,0 @@
1
- /**
2
- * assetStore
3
- *
4
- * Bounded store of AssetRecords keyed by their stable asset key
5
- * (`location/name.type`). Follows the images tool's store shape — Set of
6
- * listeners, frame-coalesced emit, all mutable state on the globalThis
7
- * singleton (../state.ts) so dual module instances share one store.
8
- *
9
- * Unlike the images tool this is an INVENTORY, not an event log: upserts
10
- * merge into the existing record for a key instead of appending, and there
11
- * is no ring-buffer cap (a bundle has a bounded asset count by construction).
12
- */
13
-
14
- import { assetsState } from "../state";
15
- import type { AssetKind, AssetRecord, AssetsStats } from "../types";
16
-
17
- function emit(): void {
18
- const s = assetsState();
19
- if (s.emitScheduled) return;
20
- s.emitScheduled = true;
21
- requestAnimationFrame(() => {
22
- s.emitScheduled = false;
23
- for (const listener of s.listeners) listener();
24
- });
25
- }
26
-
27
- /** Snapshot identity only changes on emit, so useSyncExternalStore stays cheap. */
28
- export function invalidate(): void {
29
- const s = assetsState();
30
- s.snapshot = [...s.records];
31
- emit();
32
- }
33
-
34
- const KIND_BY_EXT: Record<string, AssetKind> = {
35
- png: "image", jpg: "image", jpeg: "image", gif: "image", webp: "image",
36
- bmp: "image", psd: "image", svg: "image", tiff: "image", ktx: "image",
37
- heic: "image", heif: "image", avif: "image",
38
- ttf: "font", otf: "font", woff: "font", woff2: "font", ttc: "font",
39
- mp4: "video", m4v: "video", mov: "video", mpeg: "video", mpg: "video",
40
- webm: "video", avi: "video",
41
- mp3: "audio", wav: "audio", aac: "audio", aiff: "audio", caf: "audio",
42
- m4a: "audio", ogg: "audio",
43
- json: "data", db: "data", yaml: "data", yml: "data", xml: "data",
44
- html: "data", pdf: "data", zip: "data",
45
- };
46
-
47
- export function kindForType(type: string): AssetKind {
48
- return KIND_BY_EXT[type.toLowerCase()] ?? "other";
49
- }
50
-
51
- export function assetKey(
52
- httpServerLocation: string | undefined,
53
- name: string,
54
- type: string
55
- ): string {
56
- // The runtime registry percent-encodes ?unstable_path= locations while the
57
- // Metro graph endpoint returns them decoded — normalize or every asset
58
- // splits into a registry record + a graph record.
59
- let location = httpServerLocation ?? "";
60
- try {
61
- location = decodeURIComponent(location);
62
- } catch {
63
- // malformed escapes: keep the raw string
64
- }
65
- return `${location}/${name}.${type}`;
66
- }
67
-
68
- export interface AssetUpsert {
69
- key: string;
70
- name: string;
71
- type: string;
72
- origin: AssetRecord["origin"];
73
- registryId?: number;
74
- hash?: string;
75
- httpServerLocation?: string;
76
- width?: number;
77
- height?: number;
78
- scales?: number[];
79
- resolvedUri?: string;
80
- resolvedScale?: number;
81
- loaded?: boolean;
82
- fileSystemLocation?: string;
83
- files?: string[];
84
- fileHashes?: string[];
85
- }
86
-
87
- /** Merge an asset sighting into the store; creates the record on first sight. */
88
- export function upsertRecord(input: AssetUpsert): AssetRecord {
89
- const s = assetsState();
90
- let record = s.byKey.get(input.key);
91
- if (!record) {
92
- record = {
93
- id: s.nextId++,
94
- key: input.key,
95
- name: input.name,
96
- type: input.type,
97
- kind: kindForType(input.type),
98
- scales: input.scales ?? [],
99
- loaded: input.loaded ?? false,
100
- origin: input.origin,
101
- firstSeenAt: Date.now(),
102
- };
103
- s.records.push(record);
104
- s.byKey.set(record.key, record);
105
- s.byId.set(record.id, record);
106
- }
107
- if (input.registryId != null) record.registryId = input.registryId;
108
- if (input.hash != null) record.hash = input.hash;
109
- if (input.httpServerLocation != null) record.httpServerLocation = input.httpServerLocation;
110
- if (input.width != null) record.width = input.width;
111
- if (input.height != null) record.height = input.height;
112
- if (input.scales && input.scales.length) {
113
- // Dedupe: foo.png + foo@1x.png both existing yields [1, 1, …] from Metro.
114
- record.scales = [...new Set(input.scales)].sort((a, b) => a - b);
115
- }
116
- if (input.resolvedUri != null) record.resolvedUri = input.resolvedUri;
117
- if (input.resolvedScale != null) record.resolvedScale = input.resolvedScale;
118
- if (input.loaded) record.loaded = true;
119
- if (input.fileSystemLocation != null) record.fileSystemLocation = input.fileSystemLocation;
120
- if (input.files) record.files = input.files;
121
- if (input.fileHashes) record.fileHashes = input.fileHashes;
122
- invalidate();
123
- return record;
124
- }
125
-
126
- /** Mutate a record in place (records are owned by the store) and notify. */
127
- export function updateRecord(
128
- id: number,
129
- patch: Partial<AssetRecord>
130
- ): AssetRecord | undefined {
131
- const s = assetsState();
132
- const record = s.byId.get(id);
133
- if (!record) return undefined;
134
- Object.assign(record, patch);
135
- invalidate();
136
- return record;
137
- }
138
-
139
- export function getRecord(id: number): AssetRecord | undefined {
140
- return assetsState().byId.get(id);
141
- }
142
-
143
- export function getRecordByKey(key: string): AssetRecord | undefined {
144
- return assetsState().byKey.get(key);
145
- }
146
-
147
- export function getSnapshot(): AssetRecord[] {
148
- return assetsState().snapshot;
149
- }
150
-
151
- export function subscribe(listener: () => void): () => void {
152
- const s = assetsState();
153
- s.listeners.add(listener);
154
- return () => assetsState().listeners.delete(listener);
155
- }
156
-
157
- export function clearRecords(): void {
158
- const s = assetsState();
159
- s.records = [];
160
- s.byKey = new Map();
161
- s.byId = new Map();
162
- s.measuredCount = 0;
163
- s.graphCount = null;
164
- invalidate();
165
- }
166
-
167
- /**
168
- * Estimated decoded-bitmap memory (RGBA8888) at the scale this device would
169
- * pick — the size fallback when bytes can't be measured (release builds).
170
- */
171
- export function estDecodedBytes(record: AssetRecord): number {
172
- if (record.kind !== "image" || !record.width || !record.height) return 0;
173
- const scale =
174
- record.resolvedScale ??
175
- (record.scales.length ? record.scales[record.scales.length - 1] : 1);
176
- return Math.round(record.width * scale * record.height * scale * 4);
177
- }
178
-
179
- export function computeStats(list: AssetRecord[]): AssetsStats {
180
- const s = assetsState();
181
- const byKind: AssetsStats["byKind"] = {
182
- image: { count: 0, bytes: 0 },
183
- font: { count: 0, bytes: 0 },
184
- video: { count: 0, bytes: 0 },
185
- audio: { count: 0, bytes: 0 },
186
- data: { count: 0, bytes: 0 },
187
- other: { count: 0, bytes: 0 },
188
- };
189
- const stats: AssetsStats = {
190
- total: list.length,
191
- loaded: 0,
192
- neverLoaded: 0,
193
- byKind,
194
- totalBytes: 0,
195
- measuredCount: 0,
196
- fontFamilies: s.fontFamilies.length,
197
- };
198
- for (const r of list) {
199
- if (r.loaded) stats.loaded++;
200
- else stats.neverLoaded++;
201
- byKind[r.kind].count++;
202
- if (r.sizeBytes != null && r.sizeSource === "measured") {
203
- byKind[r.kind].bytes += r.sizeBytes;
204
- stats.totalBytes += r.sizeBytes;
205
- stats.measuredCount++;
206
- }
207
- }
208
- return stats;
209
- }
@@ -1,152 +0,0 @@
1
- /**
2
- * Baseline diff — the regression-prevention half of the tool.
3
- *
4
- * "Save baseline" persists a slim {hash, size} snapshot of the inventory via
5
- * shared-ui's persistentStorage (FileSystem → AsyncStorage → memory), and
6
- * every later scan diffs against it: new assets, removed assets, grown
7
- * assets, net bytes. Run it before and after a branch and you get "this
8
- * branch added 2.3 MB" without any CI tooling.
9
- *
10
- * On dashboard mirrors the actions forward to the device (the baseline
11
- * belongs to the app being inspected, not the dashboard) and the computed
12
- * diff arrives back inside the synced snapshot.
13
- */
14
-
15
- import { persistentStorage } from "@buoy-gg/shared-ui";
16
- import { remoteAssetsDispatch } from "../remote";
17
- import { assetsState } from "../state";
18
- import { getSnapshot, invalidate } from "./assetStore";
19
- import type { AssetRecord, AssetsBaseline, AssetsDiff } from "../types";
20
-
21
- const STORAGE_KEY = "@react_buoy/assets/baseline";
22
-
23
- /** ≥1 KB counts as growth; below that is codec/metadata noise. */
24
- const GROWN_THRESHOLD_BYTES = 1024;
25
-
26
- /** Load the persisted baseline into state once; safe to call repeatedly. */
27
- export async function loadBaseline(): Promise<AssetsBaseline | null> {
28
- const s = assetsState();
29
- if (s.baselineLoaded) return s.baseline;
30
- s.baselineLoaded = true;
31
- try {
32
- const raw = await persistentStorage.getItem(STORAGE_KEY);
33
- if (raw) {
34
- const parsed = JSON.parse(raw) as AssetsBaseline;
35
- if (parsed && typeof parsed.savedAt === "number" && parsed.entries) {
36
- s.baseline = parsed;
37
- invalidate();
38
- }
39
- }
40
- } catch {
41
- // unreadable baseline = no baseline
42
- }
43
- return s.baseline;
44
- }
45
-
46
- export async function saveBaseline(): Promise<AssetsBaseline> {
47
- const remote = remoteAssetsDispatch();
48
- if (remote) {
49
- await remote("saveBaseline");
50
- return assetsState().baseline ?? { savedAt: Date.now(), entries: {} };
51
- }
52
- const s = assetsState();
53
- const entries: AssetsBaseline["entries"] = {};
54
- for (const r of getSnapshot()) {
55
- entries[r.key] = {
56
- hash: r.hash ?? null,
57
- sizeBytes: r.sizeSource === "measured" ? (r.sizeBytes ?? null) : null,
58
- name: r.name,
59
- type: r.type,
60
- };
61
- }
62
- const baseline: AssetsBaseline = { savedAt: Date.now(), entries };
63
- s.baseline = baseline;
64
- s.baselineLoaded = true;
65
- invalidate();
66
- try {
67
- await persistentStorage.setItem(STORAGE_KEY, JSON.stringify(baseline));
68
- } catch {
69
- // in-memory baseline still works for this session
70
- }
71
- return baseline;
72
- }
73
-
74
- export async function clearBaseline(): Promise<void> {
75
- const remote = remoteAssetsDispatch();
76
- if (remote) {
77
- await remote("clearBaseline");
78
- return;
79
- }
80
- const s = assetsState();
81
- s.baseline = null;
82
- s.baselineLoaded = true;
83
- invalidate();
84
- try {
85
- await persistentStorage.removeItem(STORAGE_KEY);
86
- } catch {
87
- // nothing to clean up
88
- }
89
- }
90
-
91
- export function computeDiff(
92
- records: AssetRecord[],
93
- baseline: AssetsBaseline
94
- ): AssetsDiff {
95
- const added: number[] = [];
96
- const grown: Array<{ id: number; deltaBytes: number }> = [];
97
- const seen = new Set<string>();
98
- let currentBytes = 0;
99
- let baselineBytes = 0;
100
- let comparable = false;
101
-
102
- for (const r of records) {
103
- seen.add(r.key);
104
- const then = baseline.entries[r.key];
105
- if (!then) {
106
- added.push(r.id);
107
- continue;
108
- }
109
- const now = r.sizeSource === "measured" ? (r.sizeBytes ?? null) : null;
110
- if (now != null && then.sizeBytes != null) {
111
- comparable = true;
112
- currentBytes += now;
113
- baselineBytes += then.sizeBytes;
114
- if (now - then.sizeBytes >= GROWN_THRESHOLD_BYTES) {
115
- grown.push({ id: r.id, deltaBytes: now - then.sizeBytes });
116
- }
117
- }
118
- }
119
-
120
- const removed: AssetsDiff["removed"] = [];
121
- for (const [key, entry] of Object.entries(baseline.entries)) {
122
- if (!seen.has(key)) {
123
- removed.push({ key, name: entry.name, type: entry.type });
124
- if (entry.sizeBytes != null) baselineBytes += entry.sizeBytes;
125
- }
126
- }
127
- // Added assets' measured bytes count toward the net even without a
128
- // baseline counterpart — that's exactly the growth being reported.
129
- for (const id of added) {
130
- const r = records.find((rec) => rec.id === id);
131
- if (r?.sizeSource === "measured" && r.sizeBytes != null) {
132
- comparable = true;
133
- currentBytes += r.sizeBytes;
134
- }
135
- }
136
-
137
- return {
138
- baselineSavedAt: baseline.savedAt,
139
- added,
140
- removed,
141
- grown: grown.sort((a, b) => b.deltaBytes - a.deltaBytes),
142
- netBytes: comparable ? currentBytes - baselineBytes : null,
143
- };
144
- }
145
-
146
- /** Current diff: the synced device's on mirrors, computed locally on device. */
147
- export function getDiff(): AssetsDiff | null {
148
- const s = assetsState();
149
- if (s.mirrorDiff) return s.mirrorDiff;
150
- if (!s.baseline) return null;
151
- return computeDiff(getSnapshot(), s.baseline);
152
- }