@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.
- package/README.md +41 -12
- package/lib/commonjs/capture/expo.js +1 -46
- package/lib/commonjs/capture/install.js +1 -78
- package/lib/commonjs/capture/isBuoyAsset.js +1 -0
- package/lib/commonjs/capture/measure.js +1 -186
- package/lib/commonjs/capture/metroGraph.js +1 -148
- package/lib/commonjs/capture/registry.js +1 -127
- package/lib/commonjs/components/AssetDetail.js +1 -214
- package/lib/commonjs/components/AssetImage.js +1 -0
- package/lib/commonjs/components/AssetRow.js +1 -233
- package/lib/commonjs/components/AssetsFilterBar.js +1 -96
- package/lib/commonjs/components/AssetsHeader.js +1 -72
- package/lib/commonjs/components/AssetsIcon.js +1 -19
- package/lib/commonjs/components/AssetsListControls.js +1 -96
- package/lib/commonjs/components/AssetsModal.js +1 -347
- package/lib/commonjs/components/ScanNotice.js +1 -0
- package/lib/commonjs/components/optionalExpoImage.js +1 -0
- package/lib/commonjs/components/optionalExpoImage.web.js +1 -0
- package/lib/commonjs/export.js +1 -62
- package/lib/commonjs/format.js +1 -59
- package/lib/commonjs/hooks/useAssetRecords.js +1 -22
- package/lib/commonjs/hooks/useAssetScanStatus.js +1 -0
- package/lib/commonjs/index.js +1 -313
- package/lib/commonjs/preset.js +1 -39
- package/lib/commonjs/remote.js +1 -65
- package/lib/commonjs/state.js +1 -51
- package/lib/commonjs/store/assetStore.js +1 -241
- package/lib/commonjs/store/baseline.js +1 -173
- package/lib/commonjs/store/insights.js +1 -120
- package/lib/commonjs/sync/assetsSyncAdapter.js +1 -157
- package/lib/commonjs/types.js +1 -2
- package/lib/commonjs/utils/deviceScale.js +1 -50
- package/lib/commonjs/utils/testIDs.js +1 -114
- package/lib/module/capture/expo.js +1 -41
- package/lib/module/capture/install.js +1 -72
- package/lib/module/capture/isBuoyAsset.js +1 -0
- package/lib/module/capture/measure.js +1 -179
- package/lib/module/capture/metroGraph.js +1 -141
- package/lib/module/capture/registry.js +1 -120
- package/lib/module/components/AssetDetail.js +1 -210
- package/lib/module/components/AssetImage.js +1 -0
- package/lib/module/components/AssetRow.js +1 -229
- package/lib/module/components/AssetsFilterBar.js +1 -92
- package/lib/module/components/AssetsHeader.js +1 -68
- package/lib/module/components/AssetsIcon.js +1 -12
- package/lib/module/components/AssetsListControls.js +1 -92
- package/lib/module/components/AssetsModal.js +1 -342
- package/lib/module/components/ScanNotice.js +1 -0
- package/lib/module/components/optionalExpoImage.js +1 -0
- package/lib/module/components/optionalExpoImage.web.js +1 -0
- package/lib/module/export.js +1 -57
- package/lib/module/format.js +1 -51
- package/lib/module/hooks/useAssetRecords.js +1 -18
- package/lib/module/hooks/useAssetScanStatus.js +1 -0
- package/lib/module/index.js +1 -63
- package/lib/module/preset.js +1 -35
- package/lib/module/remote.js +1 -58
- package/lib/module/state.js +1 -47
- package/lib/module/store/assetStore.js +1 -226
- package/lib/module/store/baseline.js +1 -165
- package/lib/module/store/insights.js +1 -116
- package/lib/module/sync/assetsSyncAdapter.js +1 -153
- package/lib/module/types.js +1 -2
- package/lib/module/utils/deviceScale.js +1 -46
- package/lib/module/utils/testIDs.js +1 -103
- package/lib/typescript/capture/isBuoyAsset.d.ts +9 -0
- package/lib/typescript/capture/registry.d.ts +1 -1
- package/lib/typescript/components/AssetDetail.d.ts +2 -2
- package/lib/typescript/components/AssetImage.d.ts +9 -0
- package/lib/typescript/components/AssetsFilterBar.d.ts +3 -2
- package/lib/typescript/components/AssetsHeader.d.ts +2 -1
- package/lib/typescript/components/AssetsListControls.d.ts +2 -9
- package/lib/typescript/components/ScanNotice.d.ts +7 -0
- package/lib/typescript/components/optionalExpoImage.d.ts +13 -0
- package/lib/typescript/components/optionalExpoImage.web.d.ts +3 -0
- package/lib/typescript/hooks/useAssetScanStatus.d.ts +6 -0
- package/lib/typescript/state.d.ts +2 -0
- package/lib/typescript/store/assetStore.d.ts +3 -1
- package/lib/typescript/types.d.ts +3 -0
- package/lib/typescript/utils/testIDs.d.ts +2 -5
- package/package.json +8 -4
- package/lib/commonjs/capture/expo.js.map +0 -1
- package/lib/commonjs/capture/install.js.map +0 -1
- package/lib/commonjs/capture/measure.js.map +0 -1
- package/lib/commonjs/capture/metroGraph.js.map +0 -1
- package/lib/commonjs/capture/registry.js.map +0 -1
- package/lib/commonjs/components/AssetDetail.js.map +0 -1
- package/lib/commonjs/components/AssetRow.js.map +0 -1
- package/lib/commonjs/components/AssetsFilterBar.js.map +0 -1
- package/lib/commonjs/components/AssetsHeader.js.map +0 -1
- package/lib/commonjs/components/AssetsIcon.js.map +0 -1
- package/lib/commonjs/components/AssetsListControls.js.map +0 -1
- package/lib/commonjs/components/AssetsModal.js.map +0 -1
- package/lib/commonjs/components/BaselineBar.js +0 -115
- package/lib/commonjs/components/BaselineBar.js.map +0 -1
- package/lib/commonjs/export.js.map +0 -1
- package/lib/commonjs/format.js.map +0 -1
- package/lib/commonjs/hooks/useAssetRecords.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/preset.js.map +0 -1
- package/lib/commonjs/remote.js.map +0 -1
- package/lib/commonjs/state.js.map +0 -1
- package/lib/commonjs/store/assetStore.js.map +0 -1
- package/lib/commonjs/store/baseline.js.map +0 -1
- package/lib/commonjs/store/insights.js.map +0 -1
- package/lib/commonjs/sync/assetsSyncAdapter.js.map +0 -1
- package/lib/commonjs/types.js.map +0 -1
- package/lib/commonjs/utils/deviceScale.js.map +0 -1
- package/lib/commonjs/utils/testIDs.js.map +0 -1
- package/lib/module/capture/expo.js.map +0 -1
- package/lib/module/capture/install.js.map +0 -1
- package/lib/module/capture/measure.js.map +0 -1
- package/lib/module/capture/metroGraph.js.map +0 -1
- package/lib/module/capture/registry.js.map +0 -1
- package/lib/module/components/AssetDetail.js.map +0 -1
- package/lib/module/components/AssetRow.js.map +0 -1
- package/lib/module/components/AssetsFilterBar.js.map +0 -1
- package/lib/module/components/AssetsHeader.js.map +0 -1
- package/lib/module/components/AssetsIcon.js.map +0 -1
- package/lib/module/components/AssetsListControls.js.map +0 -1
- package/lib/module/components/AssetsModal.js.map +0 -1
- package/lib/module/components/BaselineBar.js +0 -111
- package/lib/module/components/BaselineBar.js.map +0 -1
- package/lib/module/export.js.map +0 -1
- package/lib/module/format.js.map +0 -1
- package/lib/module/hooks/useAssetRecords.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/preset.js.map +0 -1
- package/lib/module/remote.js.map +0 -1
- package/lib/module/state.js.map +0 -1
- package/lib/module/store/assetStore.js.map +0 -1
- package/lib/module/store/baseline.js.map +0 -1
- package/lib/module/store/insights.js.map +0 -1
- package/lib/module/sync/assetsSyncAdapter.js.map +0 -1
- package/lib/module/types.js.map +0 -1
- package/lib/module/utils/deviceScale.js.map +0 -1
- package/lib/module/utils/testIDs.js.map +0 -1
- package/lib/typescript/capture/expo.d.ts.map +0 -1
- package/lib/typescript/capture/install.d.ts.map +0 -1
- package/lib/typescript/capture/measure.d.ts.map +0 -1
- package/lib/typescript/capture/metroGraph.d.ts.map +0 -1
- package/lib/typescript/capture/registry.d.ts.map +0 -1
- package/lib/typescript/components/AssetDetail.d.ts.map +0 -1
- package/lib/typescript/components/AssetRow.d.ts.map +0 -1
- package/lib/typescript/components/AssetsFilterBar.d.ts.map +0 -1
- package/lib/typescript/components/AssetsHeader.d.ts.map +0 -1
- package/lib/typescript/components/AssetsIcon.d.ts.map +0 -1
- package/lib/typescript/components/AssetsListControls.d.ts.map +0 -1
- package/lib/typescript/components/AssetsModal.d.ts.map +0 -1
- package/lib/typescript/components/BaselineBar.d.ts +0 -13
- package/lib/typescript/components/BaselineBar.d.ts.map +0 -1
- package/lib/typescript/export.d.ts.map +0 -1
- package/lib/typescript/format.d.ts.map +0 -1
- package/lib/typescript/hooks/useAssetRecords.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/remote.d.ts.map +0 -1
- package/lib/typescript/state.d.ts.map +0 -1
- package/lib/typescript/store/assetStore.d.ts.map +0 -1
- package/lib/typescript/store/baseline.d.ts.map +0 -1
- package/lib/typescript/store/insights.d.ts.map +0 -1
- package/lib/typescript/sync/assetsSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
- package/lib/typescript/utils/deviceScale.d.ts.map +0 -1
- package/lib/typescript/utils/testIDs.d.ts.map +0 -1
|
@@ -1,342 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* AssetsModal — bottom-sheet wrapper rendered by FloatingDevTools when the
|
|
5
|
-
* Assets tool is opened. Inventory list (largest first) → tap for detail.
|
|
6
|
-
* Opening triggers a full rescan (registry walk + Metro graph + size
|
|
7
|
-
* measurement); merely installing the package never hits the network.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { useEffect, useState } from "react";
|
|
11
|
-
import { FlatList, StyleSheet, Text, View } from "react-native";
|
|
12
|
-
import { EventStepperFooter, JsModal, macOSColors, night, windowDragProps, ToolEmptyState } from "@buoy-gg/shared-ui";
|
|
13
|
-
import { getScanStatus, installAssetCapture, rescan } from "../capture/install";
|
|
14
|
-
import { formatAssetMarkdown, formatInventoryMarkdown } from "../export";
|
|
15
|
-
import { useAssetRecords } from "../hooks/useAssetRecords";
|
|
16
|
-
import { remoteAssetsDispatch } from "../remote";
|
|
17
|
-
import { clearRecords, estDecodedBytes, getRecord } from "../store/assetStore";
|
|
18
|
-
import { getDiff, loadBaseline } from "../store/baseline";
|
|
19
|
-
import { AssetDetail } from "./AssetDetail";
|
|
20
|
-
import { AssetRow } from "./AssetRow";
|
|
21
|
-
import { AssetsFilterBar } from "./AssetsFilterBar";
|
|
22
|
-
import { AssetsHeader } from "./AssetsHeader";
|
|
23
|
-
import { AssetsListControls } from "./AssetsListControls";
|
|
24
|
-
import { BaselineBar } from "./BaselineBar";
|
|
25
|
-
import { ASSETS_TEST_IDS, assetFontChipTestID } from "../utils/testIDs";
|
|
26
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
27
|
-
function ScanNotice() {
|
|
28
|
-
const status = getScanStatus();
|
|
29
|
-
const notices = [];
|
|
30
|
-
if (!status.registryAvailable) {
|
|
31
|
-
notices.push("Asset registry unavailable — this environment doesn't expose @react-native/assets-registry.");
|
|
32
|
-
} else if (!status.graphSupported && status.graphError) {
|
|
33
|
-
notices.push("No Metro dev server — showing runtime-registered assets only. The full bundle inventory and byte sizes need a dev build.");
|
|
34
|
-
}
|
|
35
|
-
if (status.measuring) notices.push("Measuring sizes from the dev server…");
|
|
36
|
-
if (notices.length === 0) return null;
|
|
37
|
-
return /*#__PURE__*/_jsx(View, {
|
|
38
|
-
style: styles.notice,
|
|
39
|
-
testID: ASSETS_TEST_IDS.notice,
|
|
40
|
-
children: notices.map(n => /*#__PURE__*/_jsx(Text, {
|
|
41
|
-
style: styles.noticeText,
|
|
42
|
-
testID: ASSETS_TEST_IDS.noticeText,
|
|
43
|
-
children: n
|
|
44
|
-
}, n))
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
function FontFamilies() {
|
|
48
|
-
const families = getScanStatus().fontFamilies;
|
|
49
|
-
if (!families.length) return null;
|
|
50
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
51
|
-
style: styles.fontsBox,
|
|
52
|
-
testID: ASSETS_TEST_IDS.fonts,
|
|
53
|
-
children: [/*#__PURE__*/_jsxs(Text, {
|
|
54
|
-
style: styles.fontsTitle,
|
|
55
|
-
testID: ASSETS_TEST_IDS.fontsTitle,
|
|
56
|
-
children: ["LOADED FONT FAMILIES (", families.length, ")"]
|
|
57
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
58
|
-
style: styles.fontsWrap,
|
|
59
|
-
children: families.map(family => /*#__PURE__*/_jsx(Text, {
|
|
60
|
-
style: styles.fontChip,
|
|
61
|
-
testID: assetFontChipTestID(family),
|
|
62
|
-
children: family
|
|
63
|
-
}, family))
|
|
64
|
-
})]
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
function EmptyState() {
|
|
68
|
-
return /*#__PURE__*/_jsx(ToolEmptyState, {
|
|
69
|
-
testID: ASSETS_TEST_IDS.empty
|
|
70
|
-
// scanning: a scan ran and found nothing — the copy says "try Rescan".
|
|
71
|
-
,
|
|
72
|
-
otto: "scanning",
|
|
73
|
-
title: "No assets found yet",
|
|
74
|
-
message: "Every bundled asset — images, fonts, video, audio — appears here with sizes, scale coverage, and never-loaded detection. Try Rescan after navigating around the app."
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Largest first — measured bytes, falling back to decoded estimate. */
|
|
79
|
-
function bySizeDesc(a, b) {
|
|
80
|
-
return (b.sizeBytes ?? estDecodedBytes(b)) - (a.sizeBytes ?? estDecodedBytes(a));
|
|
81
|
-
}
|
|
82
|
-
const COMPARATORS = {
|
|
83
|
-
size: bySizeDesc,
|
|
84
|
-
name: (a, b) => a.name.localeCompare(b.name) || a.type.localeCompare(b.type),
|
|
85
|
-
type: (a, b) => a.type.localeCompare(b.type) || bySizeDesc(a, b)
|
|
86
|
-
};
|
|
87
|
-
export function AssetsModal({
|
|
88
|
-
visible,
|
|
89
|
-
onClose,
|
|
90
|
-
onBack,
|
|
91
|
-
onMinimize,
|
|
92
|
-
persistenceKey,
|
|
93
|
-
inline = false
|
|
94
|
-
}) {
|
|
95
|
-
// Safety net: the index import normally installed this already.
|
|
96
|
-
installAssetCapture();
|
|
97
|
-
const {
|
|
98
|
-
records,
|
|
99
|
-
stats,
|
|
100
|
-
insights
|
|
101
|
-
} = useAssetRecords();
|
|
102
|
-
const [selectedId, setSelectedId] = useState(null);
|
|
103
|
-
const [filter, setFilter] = useState("all");
|
|
104
|
-
const [query, setQuery] = useState("");
|
|
105
|
-
const [sort, setSort] = useState("size");
|
|
106
|
-
const selected = selectedId != null ? getRecord(selectedId) : undefined;
|
|
107
|
-
|
|
108
|
-
// Full scan on open — locally on device, forwarded on dashboard mirrors.
|
|
109
|
-
// The baseline load is async and re-renders through the store emit.
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
if (!visible) return;
|
|
112
|
-
void loadBaseline();
|
|
113
|
-
const remote = remoteAssetsDispatch();
|
|
114
|
-
if (remote) {
|
|
115
|
-
remote("rescan").catch(() => {});
|
|
116
|
-
} else {
|
|
117
|
-
void rescan();
|
|
118
|
-
}
|
|
119
|
-
}, [visible]);
|
|
120
|
-
const sorted = [...records].sort(COMPARATORS[sort]);
|
|
121
|
-
const trimmedQuery = query.trim().toLowerCase();
|
|
122
|
-
const filtered = sorted.filter(r => {
|
|
123
|
-
if (filter === "unused" && r.loaded) return false;
|
|
124
|
-
if (filter !== "all" && filter !== "unused" && r.kind !== filter) return false;
|
|
125
|
-
if (trimmedQuery && !`${r.name}.${r.type}`.toLowerCase().includes(trimmedQuery)) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
return true;
|
|
129
|
-
});
|
|
130
|
-
const selectedIndex = selected ? filtered.findIndex(r => r.id === selected.id) : -1;
|
|
131
|
-
const stepTo = index => {
|
|
132
|
-
const target = filtered[index];
|
|
133
|
-
if (target) setSelectedId(target.id);
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Set lookups so each row's badge checks are O(1).
|
|
137
|
-
const flagSets = {
|
|
138
|
-
scaleGaps: new Set(insights.scaleGaps),
|
|
139
|
-
scaleAnomalies: new Set(insights.scaleAnomalies),
|
|
140
|
-
duplicates: new Set(insights.duplicates.flatMap(d => d.ids)),
|
|
141
|
-
webpCandidates: new Set(insights.webpCandidates),
|
|
142
|
-
overDecode: new Set(insights.overDecode),
|
|
143
|
-
heavy: new Set([...insights.heavyGifs, ...insights.heavyFonts])
|
|
144
|
-
};
|
|
145
|
-
const diff = getDiff();
|
|
146
|
-
|
|
147
|
-
// The proportional size bar is relative to the largest asset in view.
|
|
148
|
-
let maxBytes = 0;
|
|
149
|
-
for (const r of filtered) {
|
|
150
|
-
const bytes = r.sizeBytes ?? estDecodedBytes(r);
|
|
151
|
-
if (bytes > maxBytes) maxBytes = bytes;
|
|
152
|
-
}
|
|
153
|
-
const getReport = () => selected ? formatAssetMarkdown(selected) : formatInventoryMarkdown(sorted, stats, insights, getScanStatus(), getDiff());
|
|
154
|
-
const headerContent = /*#__PURE__*/_jsx(AssetsHeader, {
|
|
155
|
-
stats: stats,
|
|
156
|
-
measuring: getScanStatus().measuring,
|
|
157
|
-
detailOpen: selected != null,
|
|
158
|
-
detailIndex: selectedIndex,
|
|
159
|
-
detailTotal: filtered.length,
|
|
160
|
-
onCloseDetail: () => setSelectedId(null),
|
|
161
|
-
onClear: clearRecords,
|
|
162
|
-
getReport: getReport,
|
|
163
|
-
onBack: onBack
|
|
164
|
-
});
|
|
165
|
-
const body = selected ? /*#__PURE__*/_jsxs(View, {
|
|
166
|
-
style: styles.detailContainer,
|
|
167
|
-
children: [/*#__PURE__*/_jsx(AssetDetail, {
|
|
168
|
-
record: selected,
|
|
169
|
-
insights: insights
|
|
170
|
-
}), selectedIndex >= 0 ? /*#__PURE__*/_jsx(EventStepperFooter, {
|
|
171
|
-
currentIndex: selectedIndex,
|
|
172
|
-
totalItems: filtered.length,
|
|
173
|
-
itemLabel: "Asset",
|
|
174
|
-
onPrevious: () => stepTo(selectedIndex - 1),
|
|
175
|
-
onNext: () => stepTo(selectedIndex + 1)
|
|
176
|
-
}) : null]
|
|
177
|
-
}) : /*#__PURE__*/_jsxs(View, {
|
|
178
|
-
style: styles.listContainer,
|
|
179
|
-
children: [/*#__PURE__*/_jsx(AssetsFilterBar, {
|
|
180
|
-
stats: stats,
|
|
181
|
-
filter: filter,
|
|
182
|
-
onChange: setFilter
|
|
183
|
-
}), /*#__PURE__*/_jsx(AssetsListControls, {
|
|
184
|
-
query: query,
|
|
185
|
-
onQueryChange: setQuery,
|
|
186
|
-
sort: sort,
|
|
187
|
-
onSortChange: setSort,
|
|
188
|
-
showBaselineButton: diff == null
|
|
189
|
-
}), /*#__PURE__*/_jsx(BaselineBar, {
|
|
190
|
-
diff: diff
|
|
191
|
-
}), /*#__PURE__*/_jsx(ScanNotice, {}), filter === "font" ? /*#__PURE__*/_jsx(FontFamilies, {}) : null, /*#__PURE__*/_jsx(FlatList, {
|
|
192
|
-
testID: ASSETS_TEST_IDS.list,
|
|
193
|
-
data: filtered,
|
|
194
|
-
keyExtractor: item => String(item.id),
|
|
195
|
-
contentContainerStyle: styles.listContent,
|
|
196
|
-
renderItem: ({
|
|
197
|
-
item
|
|
198
|
-
}) => /*#__PURE__*/_jsx(AssetRow, {
|
|
199
|
-
record: item,
|
|
200
|
-
flags: {
|
|
201
|
-
scaleGap: flagSets.scaleGaps.has(item.id),
|
|
202
|
-
scaleAnomaly: flagSets.scaleAnomalies.has(item.id),
|
|
203
|
-
duplicate: flagSets.duplicates.has(item.id),
|
|
204
|
-
webp: flagSets.webpCandidates.has(item.id),
|
|
205
|
-
oversize: flagSets.overDecode.has(item.id),
|
|
206
|
-
heavy: flagSets.heavy.has(item.id)
|
|
207
|
-
},
|
|
208
|
-
sizeFraction: maxBytes > 0 ? (item.sizeBytes ?? estDecodedBytes(item)) / maxBytes : 0
|
|
209
|
-
// Guarded: programmatic invocations (automation) can call this
|
|
210
|
-
// with an event object instead of an id.
|
|
211
|
-
,
|
|
212
|
-
onPress: id => setSelectedId(typeof id === "number" ? id : null)
|
|
213
|
-
}),
|
|
214
|
-
ListEmptyComponent: /*#__PURE__*/_jsx(EmptyState, {})
|
|
215
|
-
})]
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// The ONLY difference between phone and desktop is which shell the header
|
|
219
|
-
// and body go in (same pattern as ImagesModal).
|
|
220
|
-
if (inline) {
|
|
221
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
222
|
-
style: styles.inlineRoot,
|
|
223
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
224
|
-
style: styles.inlineHeader,
|
|
225
|
-
...windowDragProps,
|
|
226
|
-
children: headerContent
|
|
227
|
-
}), body]
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
return /*#__PURE__*/_jsx(JsModal, {
|
|
231
|
-
visible: visible,
|
|
232
|
-
onClose: onClose,
|
|
233
|
-
onMinimize: onMinimize,
|
|
234
|
-
persistenceKey: persistenceKey,
|
|
235
|
-
header: {
|
|
236
|
-
showToggleButton: true,
|
|
237
|
-
customContent: headerContent
|
|
238
|
-
},
|
|
239
|
-
enablePersistence: true,
|
|
240
|
-
initialMode: "bottomSheet",
|
|
241
|
-
disableScrollWrapper: true,
|
|
242
|
-
children: body
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* The same Assets tool rendered INLINE for hosts that place tools in a panel
|
|
248
|
-
* rather than a modal (the desktop dashboard). One implementation only.
|
|
249
|
-
*/
|
|
250
|
-
export function AssetsToolView(props = {}) {
|
|
251
|
-
return /*#__PURE__*/_jsx(AssetsModal, {
|
|
252
|
-
...props,
|
|
253
|
-
inline: true,
|
|
254
|
-
visible: true,
|
|
255
|
-
onClose: () => {}
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
const styles = StyleSheet.create({
|
|
259
|
-
inlineRoot: {
|
|
260
|
-
flex: 1,
|
|
261
|
-
minHeight: 0,
|
|
262
|
-
// Transparent so the desktop workbench's hoisted sky (one field spanning
|
|
263
|
-
// sidebar + panel) shows through — the shell paints its base beneath it.
|
|
264
|
-
backgroundColor: "transparent"
|
|
265
|
-
},
|
|
266
|
-
inlineHeader: {
|
|
267
|
-
paddingHorizontal: 12,
|
|
268
|
-
paddingVertical: 8,
|
|
269
|
-
borderBottomWidth: 1,
|
|
270
|
-
borderBottomColor: macOSColors.border.default,
|
|
271
|
-
// Clear, like Bench — the host's hoisted sky shows through the header.
|
|
272
|
-
backgroundColor: "transparent"
|
|
273
|
-
},
|
|
274
|
-
listContainer: {
|
|
275
|
-
flex: 1
|
|
276
|
-
},
|
|
277
|
-
listContent: {
|
|
278
|
-
paddingBottom: 16
|
|
279
|
-
},
|
|
280
|
-
detailContainer: {
|
|
281
|
-
flex: 1
|
|
282
|
-
},
|
|
283
|
-
notice: {
|
|
284
|
-
marginHorizontal: 10,
|
|
285
|
-
marginBottom: 8,
|
|
286
|
-
padding: 10,
|
|
287
|
-
borderRadius: 8,
|
|
288
|
-
borderWidth: 1,
|
|
289
|
-
borderColor: night.color.border,
|
|
290
|
-
backgroundColor: night.color.surface,
|
|
291
|
-
gap: 4
|
|
292
|
-
},
|
|
293
|
-
noticeText: {
|
|
294
|
-
color: night.color.textSecondary,
|
|
295
|
-
fontSize: 11
|
|
296
|
-
},
|
|
297
|
-
fontsBox: {
|
|
298
|
-
marginHorizontal: 10,
|
|
299
|
-
marginTop: 8,
|
|
300
|
-
padding: 10,
|
|
301
|
-
borderRadius: 8,
|
|
302
|
-
backgroundColor: night.color.surface,
|
|
303
|
-
gap: 6
|
|
304
|
-
},
|
|
305
|
-
fontsTitle: {
|
|
306
|
-
color: night.color.textTertiary,
|
|
307
|
-
fontSize: 9,
|
|
308
|
-
fontWeight: "700",
|
|
309
|
-
letterSpacing: 0.5
|
|
310
|
-
},
|
|
311
|
-
fontsWrap: {
|
|
312
|
-
flexDirection: "row",
|
|
313
|
-
flexWrap: "wrap",
|
|
314
|
-
gap: 6
|
|
315
|
-
},
|
|
316
|
-
fontChip: {
|
|
317
|
-
color: night.color.textSecondary,
|
|
318
|
-
fontSize: 10,
|
|
319
|
-
backgroundColor: night.color.bg,
|
|
320
|
-
borderRadius: 999,
|
|
321
|
-
paddingHorizontal: 8,
|
|
322
|
-
paddingVertical: 3,
|
|
323
|
-
overflow: "hidden"
|
|
324
|
-
},
|
|
325
|
-
empty: {
|
|
326
|
-
alignItems: "center",
|
|
327
|
-
padding: 32,
|
|
328
|
-
gap: 8
|
|
329
|
-
},
|
|
330
|
-
emptyTitle: {
|
|
331
|
-
color: night.color.text,
|
|
332
|
-
fontSize: 13,
|
|
333
|
-
fontWeight: "600"
|
|
334
|
-
},
|
|
335
|
-
emptyText: {
|
|
336
|
-
color: night.color.textSecondary,
|
|
337
|
-
fontSize: 11,
|
|
338
|
-
textAlign: "center",
|
|
339
|
-
lineHeight: 16
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
//# sourceMappingURL=AssetsModal.js.map
|
|
1
|
+
"use strict";import{useEffect,useState}from"react";import{FlatList,StyleSheet,Text,TouchableOpacity,View}from"react-native";import{EventStepperFooter,JsModal,macOSColors,night,windowDragProps,ToolEmptyState}from"@buoy-gg/shared-ui";import{getScanStatus,installAssetCapture,rescan}from"../capture/install";import{formatAssetMarkdown,formatInventoryMarkdown}from"../export";import{useAssetRecords}from"../hooks/useAssetRecords";import{useAssetScanStatus}from"../hooks/useAssetScanStatus";import{remoteAssetsDispatch}from"../remote";import{clearRecords,estDecodedBytes,getRecord}from"../store/assetStore";import{getDiff,loadBaseline}from"../store/baseline";import{AssetDetail}from"./AssetDetail";import{AssetRow}from"./AssetRow";import{AssetsFilterBar}from"./AssetsFilterBar";import{AssetsHeader}from"./AssetsHeader";import{AssetsListControls}from"./AssetsListControls";import{ScanNotice}from"./ScanNotice";import{ASSETS_TEST_IDS,assetFontChipTestID}from"../utils/testIDs";import{jsxs as _jsxs,jsx as _jsx}from"react/jsx-runtime";function FontFamilies({names:e}){const t=[...new Set(e)];return t.length?_jsxs(View,{style:styles.fontsBox,testID:ASSETS_TEST_IDS.fonts,children:[_jsxs(Text,{style:styles.fontsTitle,testID:ASSETS_TEST_IDS.fontsTitle,children:["LOADED FONT FAMILIES (",t.length,")"]}),_jsx(View,{style:styles.fontsWrap,children:t.map(e=>_jsx(Text,{style:styles.fontChip,testID:assetFontChipTestID(e),children:e},e))})]}):null}function EmptyState({duplicates:e=!1}){return _jsx(ToolEmptyState,{testID:ASSETS_TEST_IDS.empty,otto:"scanning",title:e?"No duplicates found":"No assets found yet",message:e?"No assets in this view share a content hash.":"Every bundled asset — images, fonts, video, audio — appears here with sizes, scale coverage, and runtime registration status. Try Rescan after navigating around the app."})}function bySizeDesc(e,t){return(t.sizeBytes??estDecodedBytes(t))-(e.sizeBytes??estDecodedBytes(e))}const COMPARATORS={size:bySizeDesc,name:(e,t)=>e.name.localeCompare(t.name)||e.type.localeCompare(t.type),type:(e,t)=>e.type.localeCompare(t.type)||bySizeDesc(e,t)};export function AssetsModal({visible:e,onClose:t,onBack:s,onMinimize:o,persistenceKey:n,inline:i=!1}){installAssetCapture();const{records:r,stats:a,insights:l}=useAssetRecords(),c=useAssetScanStatus(),[d,p]=useState(!1);useEffect(()=>{p(!1)},[c.graphFetchInFlight,c.lastScanAt]);const u=()=>{p(!1);const e=remoteAssetsDispatch();e?e("rescan").catch(()=>p(!0)):rescan()},[h,m]=useState(null),[y,S]=useState("all"),[g,f]=useState(""),[x,A]=useState("size"),C=null!=h?getRecord(h):void 0;useEffect(()=>{if(!e)return;loadBaseline(),p(!1);const t=remoteAssetsDispatch();t?t("rescan").catch(()=>p(!0)):rescan()},[e]);const T=new Set(l.duplicates.flatMap(e=>e.ids)),w=[...r].sort(COMPARATORS[x]),D=g.trim().toLowerCase(),_=w.filter(e=>!("unused"===y&&e.loaded||"duplicates"===y&&!T.has(e.id)||"all"!==y&&"unused"!==y&&"duplicates"!==y&&e.kind!==y||D&&!`${e.name}.${e.type}`.toLowerCase().includes(D))),b=C?_.findIndex(e=>e.id===C.id):-1,j=e=>{const t=_[e];t&&m(t.id)},v={scaleGaps:new Set(l.scaleGaps),scaleAnomalies:new Set(l.scaleAnomalies),duplicates:T,webpCandidates:new Set(l.webpCandidates),overDecode:new Set(l.overDecode),heavy:new Set([...l.heavyGifs,...l.heavyFonts])};let I=0;for(const e of _){const t=e.sizeBytes??estDecodedBytes(e);t>I&&(I=t)}const F=_jsx(AssetsHeader,{stats:a,measuring:c.measuring,discovering:c.graphFetchInFlight,detailOpen:null!=C,detailIndex:b,detailTotal:_.length,onCloseDetail:()=>m(null),onClear:clearRecords,getReport:()=>C?formatAssetMarkdown(C):formatInventoryMarkdown(w,a,l,getScanStatus(),getDiff()),onBack:s}),z=_jsxs(View,C?{style:styles.detailContainer,children:[_jsx(AssetDetail,{record:C,insights:l}),b>=0?_jsx(EventStepperFooter,{currentIndex:b,totalItems:_.length,itemLabel:"Asset",onPrevious:()=>j(b-1),onNext:()=>j(b+1)}):null]}:{style:styles.listContainer,children:[_jsx(AssetsFilterBar,{stats:a,duplicateCount:T.size,filter:y,onChange:e=>{S(e),"duplicates"===e&&f("")}}),_jsx(AssetsListControls,{query:g,onQueryChange:f,sort:x,onSortChange:A}),_jsx(ScanNotice,{status:c,total:a.total,onRetry:u}),d&&!c.graphFetchInFlight?_jsxs(View,{style:styles.notice,children:[_jsx(Text,{style:styles.noticeText,children:"Couldn't request an asset scan from the device."}),_jsx(TouchableOpacity,{accessibilityRole:"button",onPress:u,children:_jsx(Text,{style:styles.retryText,children:"Retry"})})]}):null,"unused"===y?_jsx(View,{style:styles.notice,children:_jsx(Text,{style:styles.noticeText,children:"Not seen in React Native's asset registry this app run. Assets loaded through other paths may appear here; this does not prove they are unused."})}):null,"font"===y?_jsx(FontFamilies,{names:c.fontFamilies}):null,_jsx(FlatList,{testID:ASSETS_TEST_IDS.list,data:_,keyExtractor:e=>String(e.id),contentContainerStyle:styles.listContent,renderItem:({item:e})=>_jsx(AssetRow,{record:e,flags:{scaleGap:v.scaleGaps.has(e.id),scaleAnomaly:v.scaleAnomalies.has(e.id),duplicate:v.duplicates.has(e.id),webp:v.webpCandidates.has(e.id),oversize:v.overDecode.has(e.id),heavy:v.heavy.has(e.id)},sizeFraction:I>0?(e.sizeBytes??estDecodedBytes(e))/I:0,onPress:e=>m("number"==typeof e?e:null)}),ListEmptyComponent:c.graphFetchInFlight?null:_jsx(EmptyState,{duplicates:"duplicates"===y})})]});return i?_jsxs(View,{style:styles.inlineRoot,children:[_jsx(View,{style:styles.inlineHeader,...windowDragProps,children:F}),z]}):_jsx(JsModal,{visible:e,onClose:t,onMinimize:o,persistenceKey:n,header:{showToggleButton:!0,customContent:F},enablePersistence:!0,initialMode:"bottomSheet",disableScrollWrapper:!0,children:z})}export function AssetsToolView(e={}){return _jsx(AssetsModal,{...e,inline:!0,visible:!0,onClose:()=>{}})}const styles=StyleSheet.create({inlineRoot:{flex:1,minHeight:0,backgroundColor:"transparent"},inlineHeader:{paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:macOSColors.border.default,backgroundColor:"transparent"},listContainer:{flex:1},listContent:{paddingBottom:16},detailContainer:{flex:1},notice:{marginHorizontal:10,marginBottom:8,padding:10,borderRadius:8,borderWidth:1,borderColor:night.color.border,backgroundColor:night.color.surface,gap:4},noticeText:{color:night.color.textSecondary,fontSize:11},retryText:{color:night.color.text,fontSize:12,paddingVertical:6},fontsBox:{marginHorizontal:10,marginTop:8,padding:10,borderRadius:8,backgroundColor:night.color.surface,gap:6},fontsTitle:{color:night.color.textTertiary,fontSize:9,fontWeight:"700",letterSpacing:.5},fontsWrap:{flexDirection:"row",flexWrap:"wrap",gap:6},fontChip:{color:night.color.textSecondary,fontSize:10,backgroundColor:night.color.bg,borderRadius:999,paddingHorizontal:8,paddingVertical:3,overflow:"hidden"},empty:{alignItems:"center",padding:32,gap:8},emptyTitle:{color:night.color.text,fontSize:13,fontWeight:"600"},emptyText:{color:night.color.textSecondary,fontSize:11,textAlign:"center",lineHeight:16}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{useEffect,useState}from"react";import{ActivityIndicator,StyleSheet,Text,TouchableOpacity,View}from"react-native";import{night}from"@buoy-gg/shared-ui";import{ASSETS_TEST_IDS}from"../utils/testIDs";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";export function ScanNotice({status:t,total:e,onRetry:r}){const[s,i]=useState(!1);useEffect(()=>{if(i(!1),!t.graphFetchInFlight)return;const e=null==t.graphFetchStartedAt?0:Math.max(0,Date.now()-t.graphFetchStartedAt),r=setTimeout(()=>i(!0),Math.max(0,8e3-e));return()=>clearTimeout(r)},[t.graphFetchInFlight,t.graphFetchStartedAt]);const n=[];t.graphFetchInFlight&&(n.push(`Finding assets… ${e} found so far.`),s&&n.push("Still finding assets. This is taking longer than usual.")),t.registryAvailable||n.push("The asset registry is unavailable in this environment."),t.graphFetchInFlight||t.graphSupported||!t.graphError||n.push("Showing runtime-registered assets only. A development server is needed to find more assets and measure their sizes.");const o=!t.graphFetchInFlight&&t.graphSupported&&!!t.graphError;return o&&n.push(`Couldn't finish finding assets. Showing ${e} assets found so far.`),t.measuring&&!t.graphFetchInFlight&&n.push("Checking asset sizes…"),0===n.length?null:_jsxs(View,{style:styles.notice,testID:ASSETS_TEST_IDS.notice,children:[t.graphFetchInFlight?_jsx(ActivityIndicator,{size:"small",color:night.color.textSecondary,accessibilityLabel:"Finding assets",testID:ASSETS_TEST_IDS.discoverySpinner}):null,n.map(t=>_jsx(Text,{style:styles.noticeText,testID:ASSETS_TEST_IDS.noticeText,children:t},t)),o?_jsx(TouchableOpacity,{accessibilityRole:"button",onPress:r,testID:ASSETS_TEST_IDS.retry,children:_jsx(Text,{style:styles.retryText,children:"Retry"})}):null]})}const styles=StyleSheet.create({notice:{marginHorizontal:10,marginBottom:8,padding:10,borderRadius:8,borderWidth:1,borderColor:night.color.border,backgroundColor:night.color.surface,gap:4},noticeText:{color:night.color.textSecondary,fontSize:11},retryText:{color:night.color.text,fontSize:12,paddingVertical:6}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";let ExpoImage=null;try{ExpoImage=require("expo-image").Image??null}catch{}export function getExpoPreviewImage(){return ExpoImage}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";export function getExpoPreviewImage(){return null}
|
package/lib/module/export.js
CHANGED
|
@@ -1,57 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Markdown reports for the shared ToolbarCopyButton — the paste-into-your-
|
|
5
|
-
* issue-tracker view of the inventory.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { formatBytes, formatDimensions, formatScales } from "./format";
|
|
9
|
-
import { estDecodedBytes } from "./store/assetStore";
|
|
10
|
-
export function formatAssetMarkdown(record) {
|
|
11
|
-
const lines = [`# Asset: ${record.name}.${record.type}`, "", `- Kind: ${record.kind}`, `- Status: ${record.loaded ? "loaded at runtime" : "in bundle, never loaded"}`];
|
|
12
|
-
if (record.kind === "image") lines.push(`- Dimensions: ${formatDimensions(record)} (logical)`);
|
|
13
|
-
if (record.scales.length) lines.push(`- Scales: ${formatScales(record.scales)}`);
|
|
14
|
-
if (record.sizeBytes != null && record.sizeSource === "measured") {
|
|
15
|
-
lines.push(`- Size (all variants): ${formatBytes(record.sizeBytes)}`);
|
|
16
|
-
}
|
|
17
|
-
const estimate = estDecodedBytes(record);
|
|
18
|
-
if (estimate > 0) lines.push(`- Decoded memory: ~${formatBytes(estimate)}`);
|
|
19
|
-
if (record.hash) lines.push(`- Hash: ${record.hash}`);
|
|
20
|
-
if (record.httpServerLocation) lines.push(`- Location: ${record.httpServerLocation}`);
|
|
21
|
-
if (record.fileSystemLocation) lines.push(`- Source dir: ${record.fileSystemLocation}`);
|
|
22
|
-
if (record.resolvedUri) lines.push(`- Resolved URI: ${record.resolvedUri}`);
|
|
23
|
-
return lines.join("\n");
|
|
24
|
-
}
|
|
25
|
-
export function formatInventoryMarkdown(records, stats, insights, scanStatus, diff) {
|
|
26
|
-
const lines = ["# Asset inventory", "", `${stats.total} assets · ${stats.loaded} loaded · ${stats.neverLoaded} never loaded` + (stats.totalBytes > 0 ? ` · ${formatBytes(stats.totalBytes)} measured (${stats.measuredCount}/${stats.total})` : "")];
|
|
27
|
-
if (scanStatus.fontFamilies.length) {
|
|
28
|
-
lines.push(`Fonts loaded: ${scanStatus.fontFamilies.join(", ")}`);
|
|
29
|
-
}
|
|
30
|
-
if (!scanStatus.graphSupported) {
|
|
31
|
-
lines.push(scanStatus.graphError ? `_Metro bundle graph unavailable (${scanStatus.graphError}) — runtime-registered assets only._` : "_No Metro bundle graph — runtime-registered assets only (release build)._");
|
|
32
|
-
}
|
|
33
|
-
if (diff) {
|
|
34
|
-
const net = diff.netBytes != null ? `${diff.netBytes >= 0 ? "+" : "−"}${formatBytes(Math.abs(diff.netBytes))} net` : "net size unknown";
|
|
35
|
-
lines.push("", `## Since baseline (${new Date(diff.baselineSavedAt).toISOString()})`, `- ${diff.added.length} added · ${diff.removed.length} removed · ${diff.grown.length} grew · ${net}`);
|
|
36
|
-
for (const gone of diff.removed.slice(0, 20)) {
|
|
37
|
-
lines.push(`- removed: ${gone.name}.${gone.type}`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const findings = [];
|
|
41
|
-
if (insights.neverLoaded.length) findings.push(`- ${insights.neverLoaded.length} shipped but never loaded`);
|
|
42
|
-
if (insights.duplicates.length) findings.push(`- ${insights.duplicates.length} duplicate content groups`);
|
|
43
|
-
if (insights.webpCandidates.length) findings.push(`- ${insights.webpCandidates.length} WebP conversion candidates`);
|
|
44
|
-
if (insights.scaleGaps.length) findings.push(`- ${insights.scaleGaps.length} scale-coverage gaps`);
|
|
45
|
-
if (insights.overDecode.length) findings.push(`- ${insights.overDecode.length} images decoding larger than 2× the screen`);
|
|
46
|
-
if (insights.heavyGifs.length) findings.push(`- ${insights.heavyGifs.length} heavy GIFs (convert to video/Lottie)`);
|
|
47
|
-
if (insights.heavyFonts.length) findings.push(`- ${insights.heavyFonts.length} heavy fonts (subset candidates)`);
|
|
48
|
-
if (insights.scaleAnomalies.length) findings.push(`- ${insights.scaleAnomalies.length} scale-variant anomalies (check exports)`);
|
|
49
|
-
if (findings.length) lines.push("", "## Findings", ...findings);
|
|
50
|
-
lines.push("", "## Assets (largest first)", "", "| Asset | Kind | Dimensions | Scales | Size | Loaded |", "| --- | --- | --- | --- | --- | --- |");
|
|
51
|
-
for (const r of records) {
|
|
52
|
-
const size = r.sizeBytes != null && r.sizeSource === "measured" ? formatBytes(r.sizeBytes) : estDecodedBytes(r) > 0 ? `~${formatBytes(estDecodedBytes(r))}` : "—";
|
|
53
|
-
lines.push(`| ${r.name}.${r.type} | ${r.kind} | ${r.kind === "image" ? formatDimensions(r) : "—"} | ${formatScales(r.scales)} | ${size} | ${r.loaded ? "yes" : "NO"} |`);
|
|
54
|
-
}
|
|
55
|
-
return lines.join("\n");
|
|
56
|
-
}
|
|
57
|
-
//# sourceMappingURL=export.js.map
|
|
1
|
+
"use strict";import{formatBytes,formatDimensions,formatScales}from"./format";import{estDecodedBytes}from"./store/assetStore";export function formatAssetMarkdown(e){const s=[`# Asset: ${e.name}.${e.type}`,"",`- Kind: ${e.kind}`,"- Status: "+(e.loaded?"loaded at runtime":"not observed in the runtime registry")];"image"===e.kind&&s.push(`- Dimensions: ${formatDimensions(e)} (logical)`),e.scales.length&&s.push(`- Scales: ${formatScales(e.scales)}`),null!=e.sizeBytes&&"measured"===e.sizeSource&&s.push(`- Size (all variants): ${formatBytes(e.sizeBytes)}`);const t=estDecodedBytes(e);return t>0&&s.push(`- Decoded memory: ~${formatBytes(t)}`),e.hash&&s.push(`- Hash: ${e.hash}`),e.httpServerLocation&&s.push(`- Location: ${e.httpServerLocation}`),e.fileSystemLocation&&s.push(`- Source dir: ${e.fileSystemLocation}`),e.resolvedUri&&s.push(`- Resolved URI: ${e.resolvedUri}`),s.join("\n")}export function formatInventoryMarkdown(e,s,t,o,n){const a=["# Asset inventory","",`${s.total} assets · ${s.loaded} loaded · ${s.neverLoaded} not observed`+(s.totalBytes>0?` · ${formatBytes(s.totalBytes)} measured (${s.measuredCount}/${s.total})`:"")];if(o.fontFamilies.length&&a.push(`Fonts loaded: ${o.fontFamilies.join(", ")}`),o.graphSupported||a.push(o.graphError?`_Metro bundle graph unavailable (${o.graphError}) — runtime-registered assets only._`:"_No Metro bundle graph — runtime-registered assets only (release build)._"),n){const e=null!=n.netBytes?`${n.netBytes>=0?"+":"−"}${formatBytes(Math.abs(n.netBytes))} net`:"net size unknown";a.push("",`## Since baseline (${new Date(n.baselineSavedAt).toISOString()})`,`- ${n.added.length} added · ${n.removed.length} removed · ${n.grown.length} grew · ${e}`);for(const e of n.removed.slice(0,20))a.push(`- removed: ${e.name}.${e.type}`)}const r=[];t.neverLoaded.length&&r.push(`- ${t.neverLoaded.length} shipped but not observed`),t.duplicates.length&&r.push(`- ${t.duplicates.length} duplicate content groups`),t.webpCandidates.length&&r.push(`- ${t.webpCandidates.length} WebP conversion candidates`),t.scaleGaps.length&&r.push(`- ${t.scaleGaps.length} scale-coverage gaps`),t.overDecode.length&&r.push(`- ${t.overDecode.length} images decoding larger than 2× the screen`),t.heavyGifs.length&&r.push(`- ${t.heavyGifs.length} heavy GIFs (convert to video/Lottie)`),t.heavyFonts.length&&r.push(`- ${t.heavyFonts.length} heavy fonts (subset candidates)`),t.scaleAnomalies.length&&r.push(`- ${t.scaleAnomalies.length} scale-variant anomalies (check exports)`),r.length&&a.push("","## Findings",...r),a.push("","## Assets (largest first)","","| Asset | Kind | Dimensions | Scales | Size | Loaded |","| --- | --- | --- | --- | --- | --- |");for(const s of e){const e=null!=s.sizeBytes&&"measured"===s.sizeSource?formatBytes(s.sizeBytes):estDecodedBytes(s)>0?`~${formatBytes(estDecodedBytes(s))}`:"—";a.push(`| ${s.name}.${s.type} | ${s.kind} | ${"image"===s.kind?formatDimensions(s):"—"} | ${formatScales(s.scales)} | ${e} | ${s.loaded?"yes":"NO"} |`)}return a.join("\n")}
|
package/lib/module/format.js
CHANGED
|
@@ -1,51 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Shared formatting helpers for rows, detail view, and markdown export.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export function formatBytes(bytes) {
|
|
8
|
-
if (bytes == null || bytes <= 0) return "—";
|
|
9
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
10
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
11
|
-
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
12
|
-
}
|
|
13
|
-
export function formatScales(scales) {
|
|
14
|
-
const unique = [...new Set(scales)].sort((a, b) => a - b);
|
|
15
|
-
if (!unique.length) return "—";
|
|
16
|
-
if (unique.length === 1) return `@${unique[0]}x`;
|
|
17
|
-
if (unique.length <= 3) return unique.map(s => `@${s}x`).join(" ");
|
|
18
|
-
return `@${unique[0]}x–@${unique[unique.length - 1]}x`;
|
|
19
|
-
}
|
|
20
|
-
export function formatDimensions(record) {
|
|
21
|
-
if (record.width == null || record.height == null) return "—";
|
|
22
|
-
return `${Math.round(record.width)}×${Math.round(record.height)}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Display size: measured bytes, or "~" decoded estimate for unmeasured images. */
|
|
26
|
-
export function formatSize(record, estDecoded) {
|
|
27
|
-
if (record.sizeBytes != null && record.sizeSource === "measured") {
|
|
28
|
-
return formatBytes(record.sizeBytes);
|
|
29
|
-
}
|
|
30
|
-
if (estDecoded > 0) return `~${formatBytes(estDecoded)}`;
|
|
31
|
-
return "—";
|
|
32
|
-
}
|
|
33
|
-
export const KIND_LABELS = {
|
|
34
|
-
image: "IMG",
|
|
35
|
-
font: "FONT",
|
|
36
|
-
video: "VID",
|
|
37
|
-
audio: "AUD",
|
|
38
|
-
data: "DATA",
|
|
39
|
-
other: "MISC"
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/** Family accent per kind — the card's left edge, like network's method bar. */
|
|
43
|
-
export const KIND_COLORS = {
|
|
44
|
-
image: "#2DD4BF",
|
|
45
|
-
font: "#C084FC",
|
|
46
|
-
video: "#FF9F1C",
|
|
47
|
-
audio: "#FF3366",
|
|
48
|
-
data: "#00D4FF",
|
|
49
|
-
other: "#8E8E93"
|
|
50
|
-
};
|
|
51
|
-
//# sourceMappingURL=format.js.map
|
|
1
|
+
"use strict";export function formatBytes(t){return null==t||t<=0?"—":t<1024?`${t} B`:t<1048576?`${(t/1024).toFixed(1)} KB`:`${(t/1048576).toFixed(2)} MB`}export function formatScales(t){const e=[...new Set(t)].sort((t,e)=>t-e);return e.length?1===e.length?`@${e[0]}x`:e.length<=3?e.map(t=>`@${t}x`).join(" "):`@${e[0]}x–@${e[e.length-1]}x`:"—"}export function formatDimensions(t){return null==t.width||null==t.height?"—":`${Math.round(t.width)}×${Math.round(t.height)}`}export function formatSize(t,e){return null!=t.sizeBytes&&"measured"===t.sizeSource?formatBytes(t.sizeBytes):e>0?`~${formatBytes(e)}`:"—"}export const KIND_LABELS={image:"IMG",font:"FONT",video:"VID",audio:"AUD",data:"DATA",other:"MISC"};export const KIND_COLORS={image:"#2DD4BF",font:"#C084FC",video:"#FF9F1C",audio:"#FF3366",data:"#00D4FF",other:"#8E8E93"};
|
|
@@ -1,18 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* React bindings for the asset store.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useMemo, useSyncExternalStore } from "react";
|
|
8
|
-
import { computeStats, getSnapshot, subscribe } from "../store/assetStore";
|
|
9
|
-
import { computeInsights } from "../store/insights";
|
|
10
|
-
export function useAssetRecords() {
|
|
11
|
-
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
12
|
-
return useMemo(() => ({
|
|
13
|
-
records: snapshot,
|
|
14
|
-
stats: computeStats(snapshot),
|
|
15
|
-
insights: computeInsights(snapshot)
|
|
16
|
-
}), [snapshot]);
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=useAssetRecords.js.map
|
|
1
|
+
"use strict";import{useMemo,useSyncExternalStore}from"react";import{computeStats,getSnapshot,subscribe}from"../store/assetStore";import{computeInsights}from"../store/insights";export function useAssetRecords(){const t=useSyncExternalStore(subscribe,getSnapshot,getSnapshot);return useMemo(()=>({records:t,stats:computeStats(t),insights:computeInsights(t)}),[t])}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{useSyncExternalStore}from"react";import{getScanStatus}from"../capture/install";import{getSnapshot,subscribe}from"../store/assetStore";let previousRecords,previousStatus;export function getAssetScanSnapshot(){const t=getSnapshot();return t!==previousRecords&&(previousRecords=t,previousStatus=getScanStatus()),previousStatus}export function useAssetScanStatus(){return useSyncExternalStore(subscribe,getAssetScanSnapshot,getAssetScanSnapshot)}
|
package/lib/module/index.js
CHANGED
|
@@ -1,63 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @buoy-gg/assets — bundled-asset inventory for React Native / Expo.
|
|
5
|
-
*
|
|
6
|
-
* Enumerates every asset in the app (images, fonts, video, audio, …) via the
|
|
7
|
-
* runtime asset registry, enriched in dev with the Metro graph (full-bundle
|
|
8
|
-
* coverage, byte sizes, never-loaded detection) and on Expo with loaded font
|
|
9
|
-
* families + the updates localAssets map. Zero configuration: importing this
|
|
10
|
-
* package installs the (cheap, synchronous) registry capture.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Tool preset (FloatingDevTools)
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
export { assetsToolPreset, createAssetsTool } from "./preset";
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Components
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
export { AssetsModal, AssetsToolView } from "./components/AssetsModal";
|
|
22
|
-
export { AssetsIcon, ASSETS_ICON_COLOR } from "./components/AssetsIcon";
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// Capture / scanning
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
export { installAssetCapture, rescan, getScanStatus } from "./capture/install";
|
|
28
|
-
export { measureSizes } from "./capture/measure";
|
|
29
|
-
export { fetchAssetGraph, getDevServerOrigin } from "./capture/metroGraph";
|
|
30
|
-
export { getLoadedFontFamilies, getLocalAssets } from "./capture/expo";
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Store
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
export { clearRecords, computeStats, estDecodedBytes, getRecord, getSnapshot, subscribe } from "./store/assetStore";
|
|
36
|
-
export { computeInsights } from "./store/insights";
|
|
37
|
-
export { clearBaseline, computeDiff, getDiff, loadBaseline, saveBaseline } from "./store/baseline";
|
|
38
|
-
export { useAssetRecords } from "./hooks/useAssetRecords";
|
|
39
|
-
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// Reports
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
export { formatAssetMarkdown, formatInventoryMarkdown } from "./export";
|
|
44
|
-
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
// External sync (desktop dashboard + MCP)
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
export { assetsSyncAdapter } from "./sync/assetsSyncAdapter";
|
|
49
|
-
export { disableAssetsCapture, replaceAssetsFromSnapshot, setRemoteAssetsHandler } from "./remote";
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Automation handles
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
export { ASSETS_TEST_IDS, assetBadgeTestID, assetDetailRowTestID, assetFilterTestID, assetFindingTestID, assetFontChipTestID, assetRowTestID, assetSlug } from "./utils/testIDs";
|
|
54
|
-
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
// Types
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
|
|
59
|
-
// Side effect: install the registry capture at import (walk + patch — cheap,
|
|
60
|
-
// synchronous, no network). The Metro graph fetch waits for the modal/action.
|
|
61
|
-
import { installAssetCapture as installOnImport } from "./capture/install";
|
|
62
|
-
installOnImport();
|
|
63
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
"use strict";export{assetsToolPreset,createAssetsTool}from"./preset";export{AssetsModal,AssetsToolView}from"./components/AssetsModal";export{AssetsIcon,ASSETS_ICON_COLOR}from"./components/AssetsIcon";export{installAssetCapture,rescan,getScanStatus}from"./capture/install";export{measureSizes}from"./capture/measure";export{fetchAssetGraph,getDevServerOrigin}from"./capture/metroGraph";export{getLoadedFontFamilies,getLocalAssets}from"./capture/expo";export{clearRecords,computeStats,estDecodedBytes,getRecord,getSnapshot,subscribe}from"./store/assetStore";export{computeInsights}from"./store/insights";export{clearBaseline,computeDiff,getDiff,loadBaseline,saveBaseline}from"./store/baseline";export{useAssetRecords}from"./hooks/useAssetRecords";export{formatAssetMarkdown,formatInventoryMarkdown}from"./export";export{assetsSyncAdapter}from"./sync/assetsSyncAdapter";export{disableAssetsCapture,replaceAssetsFromSnapshot,setRemoteAssetsHandler}from"./remote";export{ASSETS_TEST_IDS,assetBadgeTestID,assetDetailRowTestID,assetFilterTestID,assetFindingTestID,assetFontChipTestID,assetRowTestID,assetSlug}from"./utils/testIDs";import{installAssetCapture as installOnImport}from"./capture/install";installOnImport();
|
package/lib/module/preset.js
CHANGED
|
@@ -1,35 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Pre-configured Assets tool for FloatingDevTools.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```tsx
|
|
8
|
-
* import { assetsToolPreset } from '@buoy-gg/assets';
|
|
9
|
-
*
|
|
10
|
-
* <FloatingDevTools apps={[assetsToolPreset]} />
|
|
11
|
-
* ```
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { AssetsIcon, ASSETS_ICON_COLOR } from "./components/AssetsIcon";
|
|
15
|
-
import { AssetsModal } from "./components/AssetsModal";
|
|
16
|
-
import { createToolFactory, defineToolPreset } from "@buoy-gg/shared-ui";
|
|
17
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
|
-
export const assetsToolPreset = defineToolPreset({
|
|
19
|
-
id: "assets",
|
|
20
|
-
name: "ASSETS",
|
|
21
|
-
description: "Every bundled asset — sizes, scale coverage, unused detection",
|
|
22
|
-
slot: "both",
|
|
23
|
-
icon: ({
|
|
24
|
-
size,
|
|
25
|
-
color
|
|
26
|
-
}) => /*#__PURE__*/_jsx(AssetsIcon, {
|
|
27
|
-
size: size,
|
|
28
|
-
color: color || ASSETS_ICON_COLOR
|
|
29
|
-
}),
|
|
30
|
-
component: AssetsModal
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
/** Create a custom Assets tool configuration. */
|
|
34
|
-
export const createAssetsTool = createToolFactory(assetsToolPreset);
|
|
35
|
-
//# sourceMappingURL=preset.js.map
|
|
1
|
+
"use strict";import{AssetsIcon,ASSETS_ICON_COLOR}from"./components/AssetsIcon";import{AssetsModal}from"./components/AssetsModal";import{createToolFactory,defineToolPreset}from"@buoy-gg/shared-ui";import{jsx as _jsx}from"react/jsx-runtime";export const assetsToolPreset=defineToolPreset({id:"assets",name:"ASSETS",description:"Every bundled asset — sizes, scale coverage, unused detection",slot:"both",icon:({size:s,color:e})=>_jsx(AssetsIcon,{size:s,color:e||ASSETS_ICON_COLOR}),component:AssetsModal});export const createAssetsTool=createToolFactory(assetsToolPreset);
|