@buoy-gg/js-top 7.0.41 → 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.
- package/README.md +12 -71
- package/lib/commonjs/index.js +1 -99
- package/lib/commonjs/js-top/components/BusySparkline.js +1 -56
- package/lib/commonjs/js-top/components/JsTopHudOverlay.js +1 -79
- package/lib/commonjs/js-top/components/JsTopModal.js +1 -349
- package/lib/commonjs/js-top/components/TaskDetailView.js +1 -330
- package/lib/commonjs/js-top/components/TaskTable.js +1 -360
- package/lib/commonjs/js-top/engine/JsTopController.js +1 -350
- package/lib/commonjs/js-top/engine/aggregator.js +1 -148
- package/lib/commonjs/js-top/engine/busyProbe.js +1 -101
- package/lib/commonjs/js-top/engine/frameStack.js +1 -133
- package/lib/commonjs/js-top/engine/longtasks.js +1 -141
- package/lib/commonjs/js-top/engine/now.js +1 -38
- package/lib/commonjs/js-top/engine/originRegistry.js +1 -402
- package/lib/commonjs/js-top/engine/patches/bridge.js +1 -60
- package/lib/commonjs/js-top/engine/patches/microtasks.js +1 -27
- package/lib/commonjs/js-top/engine/patches/promises.js +1 -65
- package/lib/commonjs/js-top/engine/patches/timers.js +1 -66
- package/lib/commonjs/js-top/engine/state.js +1 -63
- package/lib/commonjs/js-top/engine/symbolicate.js +1 -95
- package/lib/commonjs/js-top/sync/jsTopSyncAdapter.js +1 -74
- package/lib/commonjs/js-top/types/index.js +1 -46
- package/lib/commonjs/js-top/utils/preferences.js +1 -31
- package/lib/commonjs/preset.js +1 -75
- package/lib/module/index.js +1 -49
- package/lib/module/js-top/components/BusySparkline.js +1 -52
- package/lib/module/js-top/components/JsTopHudOverlay.js +1 -75
- package/lib/module/js-top/components/JsTopModal.js +1 -345
- package/lib/module/js-top/components/TaskDetailView.js +1 -326
- package/lib/module/js-top/components/TaskTable.js +1 -353
- package/lib/module/js-top/engine/JsTopController.js +1 -347
- package/lib/module/js-top/engine/aggregator.js +1 -136
- package/lib/module/js-top/engine/busyProbe.js +1 -94
- package/lib/module/js-top/engine/frameStack.js +1 -126
- package/lib/module/js-top/engine/longtasks.js +1 -133
- package/lib/module/js-top/engine/now.js +1 -33
- package/lib/module/js-top/engine/originRegistry.js +1 -388
- package/lib/module/js-top/engine/patches/bridge.js +1 -56
- package/lib/module/js-top/engine/patches/microtasks.js +1 -23
- package/lib/module/js-top/engine/patches/promises.js +1 -62
- package/lib/module/js-top/engine/patches/timers.js +1 -62
- package/lib/module/js-top/engine/state.js +1 -54
- package/lib/module/js-top/engine/symbolicate.js +1 -91
- package/lib/module/js-top/sync/jsTopSyncAdapter.js +1 -70
- package/lib/module/js-top/types/index.js +1 -42
- package/lib/module/js-top/utils/preferences.js +1 -26
- package/lib/module/preset.js +1 -71
- package/lib/web/index.mjs +2 -3
- package/package.json +2 -2
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/js-top/components/BusySparkline.js.map +0 -1
- package/lib/commonjs/js-top/components/JsTopHudOverlay.js.map +0 -1
- package/lib/commonjs/js-top/components/JsTopModal.js.map +0 -1
- package/lib/commonjs/js-top/components/TaskDetailView.js.map +0 -1
- package/lib/commonjs/js-top/components/TaskTable.js.map +0 -1
- package/lib/commonjs/js-top/engine/JsTopController.js.map +0 -1
- package/lib/commonjs/js-top/engine/aggregator.js.map +0 -1
- package/lib/commonjs/js-top/engine/busyProbe.js.map +0 -1
- package/lib/commonjs/js-top/engine/frameStack.js.map +0 -1
- package/lib/commonjs/js-top/engine/longtasks.js.map +0 -1
- package/lib/commonjs/js-top/engine/now.js.map +0 -1
- package/lib/commonjs/js-top/engine/originRegistry.js.map +0 -1
- package/lib/commonjs/js-top/engine/patches/bridge.js.map +0 -1
- package/lib/commonjs/js-top/engine/patches/microtasks.js.map +0 -1
- package/lib/commonjs/js-top/engine/patches/promises.js.map +0 -1
- package/lib/commonjs/js-top/engine/patches/timers.js.map +0 -1
- package/lib/commonjs/js-top/engine/state.js.map +0 -1
- package/lib/commonjs/js-top/engine/symbolicate.js.map +0 -1
- package/lib/commonjs/js-top/sync/jsTopSyncAdapter.js.map +0 -1
- package/lib/commonjs/js-top/types/index.js.map +0 -1
- package/lib/commonjs/js-top/utils/preferences.js.map +0 -1
- package/lib/commonjs/preset.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/js-top/components/BusySparkline.js.map +0 -1
- package/lib/module/js-top/components/JsTopHudOverlay.js.map +0 -1
- package/lib/module/js-top/components/JsTopModal.js.map +0 -1
- package/lib/module/js-top/components/TaskDetailView.js.map +0 -1
- package/lib/module/js-top/components/TaskTable.js.map +0 -1
- package/lib/module/js-top/engine/JsTopController.js.map +0 -1
- package/lib/module/js-top/engine/aggregator.js.map +0 -1
- package/lib/module/js-top/engine/busyProbe.js.map +0 -1
- package/lib/module/js-top/engine/frameStack.js.map +0 -1
- package/lib/module/js-top/engine/longtasks.js.map +0 -1
- package/lib/module/js-top/engine/now.js.map +0 -1
- package/lib/module/js-top/engine/originRegistry.js.map +0 -1
- package/lib/module/js-top/engine/patches/bridge.js.map +0 -1
- package/lib/module/js-top/engine/patches/microtasks.js.map +0 -1
- package/lib/module/js-top/engine/patches/promises.js.map +0 -1
- package/lib/module/js-top/engine/patches/timers.js.map +0 -1
- package/lib/module/js-top/engine/state.js.map +0 -1
- package/lib/module/js-top/engine/symbolicate.js.map +0 -1
- package/lib/module/js-top/sync/jsTopSyncAdapter.js.map +0 -1
- package/lib/module/js-top/types/index.js.map +0 -1
- package/lib/module/js-top/utils/preferences.js.map +0 -1
- package/lib/module/preset.js.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/js-top/components/BusySparkline.d.ts.map +0 -1
- package/lib/typescript/js-top/components/JsTopHudOverlay.d.ts.map +0 -1
- package/lib/typescript/js-top/components/JsTopModal.d.ts.map +0 -1
- package/lib/typescript/js-top/components/TaskDetailView.d.ts.map +0 -1
- package/lib/typescript/js-top/components/TaskTable.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/JsTopController.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/aggregator.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/busyProbe.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/frameStack.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/longtasks.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/now.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/originRegistry.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/patches/bridge.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/patches/microtasks.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/patches/promises.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/patches/timers.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/state.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/symbolicate.d.ts.map +0 -1
- package/lib/typescript/js-top/sync/jsTopSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/js-top/types/index.d.ts.map +0 -1
- package/lib/typescript/js-top/utils/preferences.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
|
@@ -1,345 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* JS TOP modal — the Task Manager window.
|
|
5
|
-
*
|
|
6
|
-
* Layout: header (busy% pill + pause/clear controls) → busy sparkline →
|
|
7
|
-
* coverage banner (when bridge tier is unavailable) → ranked task table →
|
|
8
|
-
* blocking-task list.
|
|
9
|
-
*
|
|
10
|
-
* Data source is the local JsTopController by default. The desktop
|
|
11
|
-
* dashboard mounts this same component via react-native-web and injects the
|
|
12
|
-
* synced device snapshot + remote actions through `remoteSnapshot` /
|
|
13
|
-
* `remoteActions` — the modal never samples the Electron renderer.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
17
|
-
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
18
|
-
import { Activity, JsModal, macOSColors, night, ModalHeader, Pause, Play, Trash2 } from "@buoy-gg/shared-ui";
|
|
19
|
-
import { JsTopController } from "../engine/JsTopController";
|
|
20
|
-
import { BusySparkline } from "./BusySparkline";
|
|
21
|
-
import { TaskTable } from "./TaskTable";
|
|
22
|
-
import { TaskDetailView } from "./TaskDetailView";
|
|
23
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
24
|
-
function busyColor(pct) {
|
|
25
|
-
if (pct >= 80) return macOSColors.semantic.error;
|
|
26
|
-
if (pct >= 40) return macOSColors.semantic.warning;
|
|
27
|
-
return macOSColors.semantic.success;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** ≥100ms is a dropped-tap-level freeze — escalate to red. */
|
|
31
|
-
function freezeColor(worstMs) {
|
|
32
|
-
return worstMs >= 100 ? macOSColors.semantic.error : macOSColors.semantic.warning;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* The one-line answer to "what should I look at?" — rendered above the
|
|
37
|
-
* table so triage never requires reading it. Priority: a named culprit >
|
|
38
|
-
* freezes > saturated-by-UI-work > healthy.
|
|
39
|
-
*/
|
|
40
|
-
function diagnose(s) {
|
|
41
|
-
const culprit = s.rows.find(r => r.api !== "unattributed" && r.api !== "other");
|
|
42
|
-
if (culprit && culprit.pctOfBusy >= 25 && s.busyPct >= 15) {
|
|
43
|
-
return {
|
|
44
|
-
color: culprit.pctOfBusy >= 50 ? macOSColors.semantic.error : macOSColors.semantic.warning,
|
|
45
|
-
icon: "▲",
|
|
46
|
-
text: `${culprit.label} is eating ${Math.round(culprit.pctOfBusy)}% of thread time — start there.`
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
if (s.freezeSummary.count > 0) {
|
|
50
|
-
return {
|
|
51
|
-
color: freezeColor(s.freezeSummary.worstMs),
|
|
52
|
-
icon: "▲",
|
|
53
|
-
text: `${s.freezeSummary.count} freeze${s.freezeSummary.count === 1 ? "" : "s"} in the last 60s (worst ${s.freezeSummary.worstMs}ms) — check rows marked ⚠.`
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
if (s.busyPct >= 80) {
|
|
57
|
-
return {
|
|
58
|
-
color: macOSColors.semantic.warning,
|
|
59
|
-
icon: "▲",
|
|
60
|
-
text: "Thread saturated by UI/React work — open Highlight Updates to find re-renders."
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
color: macOSColors.semantic.success,
|
|
65
|
-
icon: "●",
|
|
66
|
-
text: `Thread healthy at ${Math.round(s.busyPct)}% load — nothing worth chasing.`
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
export function JsTopModal({
|
|
70
|
-
visible,
|
|
71
|
-
onClose,
|
|
72
|
-
onBack,
|
|
73
|
-
onMinimize,
|
|
74
|
-
initialModalState,
|
|
75
|
-
minimizeTargetPosition,
|
|
76
|
-
remoteSnapshot,
|
|
77
|
-
remoteActions,
|
|
78
|
-
embedded = false
|
|
79
|
-
}) {
|
|
80
|
-
const isRemote = remoteSnapshot !== undefined;
|
|
81
|
-
const [localSnapshot, setLocalSnapshot] = useState(null);
|
|
82
|
-
|
|
83
|
-
// Local mode: subscribing is what starts the engine (and unsubscribing on
|
|
84
|
-
// close is what stops it — zero overhead when the window isn't open).
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
if (isRemote || !visible) return;
|
|
87
|
-
return JsTopController.subscribe(setLocalSnapshot);
|
|
88
|
-
}, [isRemote, visible]);
|
|
89
|
-
const snapshot = isRemote ? remoteSnapshot : localSnapshot;
|
|
90
|
-
const actions = useMemo(() => remoteActions ?? {
|
|
91
|
-
pause: () => JsTopController.pause(),
|
|
92
|
-
resume: () => JsTopController.resume(),
|
|
93
|
-
clear: () => JsTopController.clear()
|
|
94
|
-
}, [remoteActions]);
|
|
95
|
-
const paused = snapshot?.paused ?? false;
|
|
96
|
-
const hasData = (snapshot?.rows.length ?? 0) > 0;
|
|
97
|
-
|
|
98
|
-
// Tap-through detail. Local mode re-pulls the detail on every snapshot
|
|
99
|
-
// tick (this component re-renders), so the detail view stays live.
|
|
100
|
-
// Remote mode (desktop) has no local engine — rows aren't tappable there.
|
|
101
|
-
const [detailKey, setDetailKey] = useState(null);
|
|
102
|
-
const onSelectRow = useCallback(row => setDetailKey(row.key), []);
|
|
103
|
-
const detail = !isRemote && detailKey ? JsTopController.getOriginDetail(detailKey) : null;
|
|
104
|
-
const inDetail = detailKey !== null;
|
|
105
|
-
|
|
106
|
-
// Shared ModalHeader compound — same structure as Network/Events/Console:
|
|
107
|
-
// Content (brand icon + title + live busy pill) and 32px icon Actions.
|
|
108
|
-
// In the detail view the chevron pops back to the table.
|
|
109
|
-
const headerContent = /*#__PURE__*/_jsxs(ModalHeader, {
|
|
110
|
-
children: [inDetail ? /*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
111
|
-
onBack: () => setDetailKey(null)
|
|
112
|
-
}) : onBack ? /*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
113
|
-
onBack: onBack
|
|
114
|
-
}) : null, /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
115
|
-
title: "",
|
|
116
|
-
noMargin: true,
|
|
117
|
-
children: /*#__PURE__*/_jsxs(View, {
|
|
118
|
-
style: styles.titleRow,
|
|
119
|
-
children: [/*#__PURE__*/_jsx(Activity, {
|
|
120
|
-
size: 14,
|
|
121
|
-
color: "#34D399"
|
|
122
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
123
|
-
style: styles.title,
|
|
124
|
-
children: "JS Top"
|
|
125
|
-
}), snapshot && /*#__PURE__*/_jsx(View, {
|
|
126
|
-
style: [styles.busyPill, {
|
|
127
|
-
borderColor: busyColor(snapshot.busyPct)
|
|
128
|
-
}],
|
|
129
|
-
children: /*#__PURE__*/_jsxs(Text, {
|
|
130
|
-
style: [styles.busyText, {
|
|
131
|
-
color: busyColor(snapshot.busyPct)
|
|
132
|
-
}],
|
|
133
|
-
children: [snapshot.busyPct.toFixed(0), "% BUSY"]
|
|
134
|
-
})
|
|
135
|
-
}), snapshot && snapshot.freezeSummary.count > 0 && /*#__PURE__*/_jsx(View, {
|
|
136
|
-
style: [styles.busyPill, {
|
|
137
|
-
borderColor: freezeColor(snapshot.freezeSummary.worstMs)
|
|
138
|
-
}],
|
|
139
|
-
children: /*#__PURE__*/_jsxs(Text, {
|
|
140
|
-
style: [styles.busyText, {
|
|
141
|
-
color: freezeColor(snapshot.freezeSummary.worstMs)
|
|
142
|
-
}],
|
|
143
|
-
children: ["\u26A0 ", snapshot.freezeSummary.count, " \xB7", " ", snapshot.freezeSummary.worstMs, "ms"]
|
|
144
|
-
})
|
|
145
|
-
})]
|
|
146
|
-
})
|
|
147
|
-
}), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
|
|
148
|
-
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
149
|
-
onPress: paused ? actions.resume : actions.pause,
|
|
150
|
-
style: [styles.actionBtn, paused && styles.actionBtnActive],
|
|
151
|
-
accessibilityLabel: paused ? "Resume sampling" : "Pause sampling",
|
|
152
|
-
children: paused ? /*#__PURE__*/_jsx(Play, {
|
|
153
|
-
size: 14,
|
|
154
|
-
color: night.color.accent
|
|
155
|
-
}) : /*#__PURE__*/_jsx(Pause, {
|
|
156
|
-
size: 14,
|
|
157
|
-
color: night.color.textSecondary
|
|
158
|
-
})
|
|
159
|
-
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
160
|
-
onPress: actions.clear,
|
|
161
|
-
disabled: !hasData,
|
|
162
|
-
style: [styles.actionBtn, !hasData && styles.actionBtnDisabled],
|
|
163
|
-
accessibilityLabel: "Clear stats",
|
|
164
|
-
children: /*#__PURE__*/_jsx(Trash2, {
|
|
165
|
-
size: 14,
|
|
166
|
-
color: hasData ? night.color.textTertiary : night.color.textTertiary
|
|
167
|
-
})
|
|
168
|
-
})]
|
|
169
|
-
})]
|
|
170
|
-
});
|
|
171
|
-
const body = /*#__PURE__*/_jsx(View, {
|
|
172
|
-
style: styles.body,
|
|
173
|
-
children: inDetail ? detail ? /*#__PURE__*/_jsx(TaskDetailView, {
|
|
174
|
-
detail: detail
|
|
175
|
-
}) : /*#__PURE__*/_jsx(Text, {
|
|
176
|
-
style: styles.waiting,
|
|
177
|
-
children: "This origin is no longer tracked (cleared or evicted)."
|
|
178
|
-
}) : snapshot ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
179
|
-
children: [(() => {
|
|
180
|
-
const d = diagnose(snapshot);
|
|
181
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
182
|
-
style: [styles.diagnosis, {
|
|
183
|
-
borderLeftColor: d.color
|
|
184
|
-
}],
|
|
185
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
186
|
-
style: [styles.diagnosisIcon, {
|
|
187
|
-
color: d.color
|
|
188
|
-
}],
|
|
189
|
-
children: d.icon
|
|
190
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
191
|
-
style: styles.diagnosisText,
|
|
192
|
-
children: d.text
|
|
193
|
-
})]
|
|
194
|
-
});
|
|
195
|
-
})(), /*#__PURE__*/_jsxs(View, {
|
|
196
|
-
style: styles.sparklineRow,
|
|
197
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
198
|
-
style: styles.sparklineCaption,
|
|
199
|
-
children: "JS THREAD LOAD \xB7 LAST 30s"
|
|
200
|
-
}), /*#__PURE__*/_jsx(BusySparkline, {
|
|
201
|
-
history: snapshot.busyHistory
|
|
202
|
-
})]
|
|
203
|
-
}), !snapshot.tiers.bridge && /*#__PURE__*/_jsx(Text, {
|
|
204
|
-
style: styles.tierBanner,
|
|
205
|
-
children: "\u201CSystem\u201D rows are UI/React/native work pure JS can\u2019t name \u2014 timers, rAF and Promise chains are fully attributed."
|
|
206
|
-
}), /*#__PURE__*/_jsx(TaskTable, {
|
|
207
|
-
rows: snapshot.rows,
|
|
208
|
-
onSelectRow: isRemote ? undefined : onSelectRow
|
|
209
|
-
})]
|
|
210
|
-
}) : /*#__PURE__*/_jsx(Text, {
|
|
211
|
-
style: styles.waiting,
|
|
212
|
-
children: isRemote ? "Waiting for device…" : "Starting engine…"
|
|
213
|
-
})
|
|
214
|
-
});
|
|
215
|
-
if (embedded) {
|
|
216
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
217
|
-
style: styles.embeddedRoot,
|
|
218
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
219
|
-
style: styles.embeddedHeader,
|
|
220
|
-
children: headerContent
|
|
221
|
-
}), body]
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
return /*#__PURE__*/_jsx(JsModal, {
|
|
225
|
-
visible: visible,
|
|
226
|
-
onClose: onClose,
|
|
227
|
-
onMinimize: onMinimize,
|
|
228
|
-
minimizeTargetPosition: minimizeTargetPosition,
|
|
229
|
-
initialModalState: initialModalState,
|
|
230
|
-
header: {
|
|
231
|
-
showToggleButton: true,
|
|
232
|
-
customContent: headerContent
|
|
233
|
-
},
|
|
234
|
-
persistenceKey: "buoy-js-top-modal",
|
|
235
|
-
enablePersistence: true,
|
|
236
|
-
initialMode: "bottomSheet",
|
|
237
|
-
disableScrollWrapper: true,
|
|
238
|
-
children: body
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
const styles = StyleSheet.create({
|
|
242
|
-
titleRow: {
|
|
243
|
-
flexDirection: "row",
|
|
244
|
-
alignItems: "center",
|
|
245
|
-
gap: 8
|
|
246
|
-
},
|
|
247
|
-
title: {
|
|
248
|
-
color: night.color.text,
|
|
249
|
-
fontSize: 14,
|
|
250
|
-
fontWeight: "500"
|
|
251
|
-
},
|
|
252
|
-
busyPill: {
|
|
253
|
-
borderWidth: 1,
|
|
254
|
-
borderRadius: 4,
|
|
255
|
-
paddingHorizontal: 6,
|
|
256
|
-
paddingVertical: 2
|
|
257
|
-
},
|
|
258
|
-
busyText: {
|
|
259
|
-
fontSize: 10,
|
|
260
|
-
fontWeight: "700",
|
|
261
|
-
fontVariant: ["tabular-nums"]
|
|
262
|
-
},
|
|
263
|
-
actionBtn: {
|
|
264
|
-
width: 32,
|
|
265
|
-
height: 32,
|
|
266
|
-
borderRadius: 8,
|
|
267
|
-
backgroundColor: night.color.buttonSurface,
|
|
268
|
-
borderWidth: 1,
|
|
269
|
-
borderColor: night.color.border,
|
|
270
|
-
alignItems: "center",
|
|
271
|
-
justifyContent: "center"
|
|
272
|
-
},
|
|
273
|
-
actionBtnActive: {
|
|
274
|
-
backgroundColor: night.color.accentSoft,
|
|
275
|
-
borderColor: night.color.accentBorderStrong
|
|
276
|
-
},
|
|
277
|
-
actionBtnDisabled: {
|
|
278
|
-
opacity: 0.55
|
|
279
|
-
},
|
|
280
|
-
body: {
|
|
281
|
-
flex: 1,
|
|
282
|
-
minHeight: 0,
|
|
283
|
-
// Transparent so JsModal's ToolBackground shows through (the modal paints
|
|
284
|
-
// opaque base beneath it). The embedded path keeps `embeddedRoot`'s base fill.
|
|
285
|
-
backgroundColor: "transparent"
|
|
286
|
-
},
|
|
287
|
-
diagnosis: {
|
|
288
|
-
flexDirection: "row",
|
|
289
|
-
alignItems: "center",
|
|
290
|
-
gap: 8,
|
|
291
|
-
marginHorizontal: 10,
|
|
292
|
-
marginTop: 8,
|
|
293
|
-
paddingHorizontal: 10,
|
|
294
|
-
paddingVertical: 8,
|
|
295
|
-
backgroundColor: night.color.surface,
|
|
296
|
-
borderRadius: 6,
|
|
297
|
-
borderLeftWidth: 3
|
|
298
|
-
},
|
|
299
|
-
diagnosisIcon: {
|
|
300
|
-
fontSize: 11,
|
|
301
|
-
fontWeight: "700"
|
|
302
|
-
},
|
|
303
|
-
diagnosisText: {
|
|
304
|
-
flex: 1,
|
|
305
|
-
color: night.color.text,
|
|
306
|
-
fontSize: 12,
|
|
307
|
-
lineHeight: 16
|
|
308
|
-
},
|
|
309
|
-
sparklineRow: {
|
|
310
|
-
paddingHorizontal: 10,
|
|
311
|
-
paddingVertical: 8,
|
|
312
|
-
gap: 4
|
|
313
|
-
},
|
|
314
|
-
sparklineCaption: {
|
|
315
|
-
color: night.color.textTertiary,
|
|
316
|
-
fontSize: 8,
|
|
317
|
-
fontWeight: "700",
|
|
318
|
-
letterSpacing: 0.5
|
|
319
|
-
},
|
|
320
|
-
tierBanner: {
|
|
321
|
-
color: night.color.textTertiary,
|
|
322
|
-
fontSize: 10,
|
|
323
|
-
paddingHorizontal: 10,
|
|
324
|
-
paddingBottom: 6
|
|
325
|
-
},
|
|
326
|
-
waiting: {
|
|
327
|
-
color: night.color.textTertiary,
|
|
328
|
-
fontSize: 12,
|
|
329
|
-
textAlign: "center",
|
|
330
|
-
paddingVertical: 32
|
|
331
|
-
},
|
|
332
|
-
embeddedRoot: {
|
|
333
|
-
position: "absolute",
|
|
334
|
-
top: 0,
|
|
335
|
-
left: 0,
|
|
336
|
-
right: 0,
|
|
337
|
-
bottom: 0,
|
|
338
|
-
backgroundColor: macOSColors.background.base
|
|
339
|
-
},
|
|
340
|
-
embeddedHeader: {
|
|
341
|
-
borderBottomWidth: 1,
|
|
342
|
-
borderBottomColor: macOSColors.border.default
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
//# sourceMappingURL=JsTopModal.js.map
|
|
1
|
+
"use strict";import{useCallback as O,useEffect as V,useMemo as j,useState as P}from"react";import{StyleSheet as J,Text as a,TouchableOpacity as R,View as l}from"react-native";import{Activity as $,JsModal as F,macOSColors as n,night as i,ModalHeader as y,Pause as U,Play as K,Trash2 as _}from"@buoy-gg/shared-ui";import{JsTopController as g}from"../engine/JsTopController";import{BusySparkline as E}from"./BusySparkline";import{TaskTable as N}from"./TaskTable";import{TaskDetailView as Y}from"./TaskDetailView";import{jsx as e,jsxs as s,Fragment as q}from"react/jsx-runtime";function M(r){if(r>=80)return n.semantic.error;if(r>=40)return n.semantic.warning;return n.semantic.success}function S(r){return r>=100?n.semantic.error:n.semantic.warning}function G(r){const c=r.rows.find(m=>m.api!=="unattributed"&&m.api!=="other");if(c&&c.pctOfBusy>=25&&r.busyPct>=15){return{color:c.pctOfBusy>=50?n.semantic.error:n.semantic.warning,icon:"\u25B2",text:`${c.label} is eating ${Math.round(c.pctOfBusy)}% of thread time \u2014 start there.`}}if(r.freezeSummary.count>0){return{color:S(r.freezeSummary.worstMs),icon:"\u25B2",text:`${r.freezeSummary.count} freeze${r.freezeSummary.count===1?"":"s"} in the last 60s (worst ${r.freezeSummary.worstMs}ms) \u2014 check rows marked \u26A0.`}}if(r.busyPct>=80){return{color:n.semantic.warning,icon:"\u25B2",text:"Thread saturated by UI/React work \u2014 open Highlight Updates to find re-renders."}}return{color:n.semantic.success,icon:"\u25CF",text:`Thread healthy at ${Math.round(r.busyPct)}% load \u2014 nothing worth chasing.`}}export function JsTopModal({visible:r,onClose:c,onBack:m,onMinimize:D,initialModalState:H,minimizeTargetPosition:W,remoteSnapshot:w,remoteActions:x,embedded:A=false}){const d=w!==void 0;const[v,I]=P(null);V(()=>{if(d||!r)return;return g.subscribe(I)},[d,r]);const o=d?w:v;const h=j(()=>x??{pause:()=>g.pause(),resume:()=>g.resume(),clear:()=>g.clear()},[x]);const f=o?.paused??false;const b=(o?.rows.length??0)>0;const[p,z]=P(null);const L=O(u=>z(u.key),[]);const T=!d&&p?g.getOriginDetail(p):null;const C=p!==null;const B=s(y,{children:[C?e(y.Navigation,{onBack:()=>z(null)}):m?e(y.Navigation,{onBack:m}):null,e(y.Content,{title:"",noMargin:true,children:s(l,{style:t.titleRow,children:[e($,{size:14,color:"#34D399"}),e(a,{style:t.title,children:"JS Top"}),o&&e(l,{style:[t.busyPill,{borderColor:M(o.busyPct)}],children:s(a,{style:[t.busyText,{color:M(o.busyPct)}],children:[o.busyPct.toFixed(0),"% BUSY"]})}),o&&o.freezeSummary.count>0&&e(l,{style:[t.busyPill,{borderColor:S(o.freezeSummary.worstMs)}],children:s(a,{style:[t.busyText,{color:S(o.freezeSummary.worstMs)}],children:["\u26A0 ",o.freezeSummary.count," \xB7"," ",o.freezeSummary.worstMs,"ms"]})})]})}),s(y.Actions,{children:[e(R,{onPress:f?h.resume:h.pause,style:[t.actionBtn,f&&t.actionBtnActive],accessibilityLabel:f?"Resume sampling":"Pause sampling",children:f?e(K,{size:14,color:i.color.accent}):e(U,{size:14,color:i.color.textSecondary})}),e(R,{onPress:h.clear,disabled:!b,style:[t.actionBtn,!b&&t.actionBtnDisabled],accessibilityLabel:"Clear stats",children:e(_,{size:14,color:b?i.color.textTertiary:i.color.textTertiary})})]})]});const k=e(l,{style:t.body,children:C?T?e(Y,{detail:T}):e(a,{style:t.waiting,children:"This origin is no longer tracked (cleared or evicted)."}):o?s(q,{children:[(()=>{const u=G(o);return s(l,{style:[t.diagnosis,{borderLeftColor:u.color}],children:[e(a,{style:[t.diagnosisIcon,{color:u.color}],children:u.icon}),e(a,{style:t.diagnosisText,children:u.text})]})})(),s(l,{style:t.sparklineRow,children:[e(a,{style:t.sparklineCaption,children:"JS THREAD LOAD \xB7 LAST 30s"}),e(E,{history:o.busyHistory})]}),!o.tiers.bridge&&e(a,{style:t.tierBanner,children:"\u201CSystem\u201D rows are UI/React/native work pure JS can\u2019t name \u2014 timers, rAF and Promise chains are fully attributed."}),e(N,{rows:o.rows,onSelectRow:d?void 0:L})]}):e(a,{style:t.waiting,children:d?"Waiting for device\u2026":"Starting engine\u2026"})});if(A){return s(l,{style:t.embeddedRoot,children:[e(l,{style:t.embeddedHeader,children:B}),k]})}return e(F,{visible:r,onClose:c,onMinimize:D,minimizeTargetPosition:W,initialModalState:H,header:{showToggleButton:true,customContent:B},persistenceKey:"buoy-js-top-modal",enablePersistence:true,initialMode:"bottomSheet",disableScrollWrapper:true,children:k})}const t=J.create({titleRow:{flexDirection:"row",alignItems:"center",gap:8},title:{color:i.color.text,fontSize:14,fontWeight:"500"},busyPill:{borderWidth:1,borderRadius:4,paddingHorizontal:6,paddingVertical:2},busyText:{fontSize:10,fontWeight:"700",fontVariant:["tabular-nums"]},actionBtn:{width:32,height:32,borderRadius:8,backgroundColor:i.color.buttonSurface,borderWidth:1,borderColor:i.color.border,alignItems:"center",justifyContent:"center"},actionBtnActive:{backgroundColor:i.color.accentSoft,borderColor:i.color.accentBorderStrong},actionBtnDisabled:{opacity:.55},body:{flex:1,minHeight:0,backgroundColor:"transparent"},diagnosis:{flexDirection:"row",alignItems:"center",gap:8,marginHorizontal:10,marginTop:8,paddingHorizontal:10,paddingVertical:8,backgroundColor:i.color.surface,borderRadius:6,borderLeftWidth:3},diagnosisIcon:{fontSize:11,fontWeight:"700"},diagnosisText:{flex:1,color:i.color.text,fontSize:12,lineHeight:16},sparklineRow:{paddingHorizontal:10,paddingVertical:8,gap:4},sparklineCaption:{color:i.color.textTertiary,fontSize:8,fontWeight:"700",letterSpacing:.5},tierBanner:{color:i.color.textTertiary,fontSize:10,paddingHorizontal:10,paddingBottom:6},waiting:{color:i.color.textTertiary,fontSize:12,textAlign:"center",paddingVertical:32},embeddedRoot:{position:"absolute",top:0,left:0,right:0,bottom:0,backgroundColor:n.background.base},embeddedHeader:{borderBottomWidth:1,borderBottomColor:n.border.default}});
|
|
@@ -1,326 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TaskDetailView — the "inspect process" screen behind a row tap.
|
|
5
|
-
*
|
|
6
|
-
* Shows everything the engine knows about one origin: a 5s activity bar
|
|
7
|
-
* chart (the per-origin bucket ring, rendered nowhere else), the full stat
|
|
8
|
-
* grid (schedules vs runs, avg/max/total, freezes), the captured scheduling
|
|
9
|
-
* site (caller + raw frames), and a plain-language explanation of what this
|
|
10
|
-
* origin type means. Data refreshes with the modal's snapshot tick — the
|
|
11
|
-
* parent re-renders us and we re-pull the detail.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { memo } from "react";
|
|
15
|
-
import { ScrollView, StyleSheet, Text, View } from "react-native";
|
|
16
|
-
import { macOSColors, night, useSafeAreaInsets } from "@buoy-gg/shared-ui";
|
|
17
|
-
import { badgeFor, displayName } from "./TaskTable";
|
|
18
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
|
-
const API_EXPLAINER = {
|
|
20
|
-
timeout: "Work scheduled with setTimeout. Time shown is the callback's exclusive run time on the JS thread.",
|
|
21
|
-
interval: "A repeating setInterval. One scheduling site, many runs — a hot interval is the classic silent JS-FPS killer.",
|
|
22
|
-
immediate: "Work scheduled with setImmediate — runs at the end of the current event-loop turn.",
|
|
23
|
-
raf: "requestAnimationFrame work — runs once per frame while scheduled. Cost here directly trades against 60fps.",
|
|
24
|
-
microtask: "queueMicrotask work — runs after the current task, before the thread yields.",
|
|
25
|
-
then: "Promise reaction (.then/.catch/.finally). Named by the function that attached the reaction. Note: async/await bodies bypass .then and land in the System row instead.",
|
|
26
|
-
bridge: "A native→JS call-in over the legacy bridge (events, module callbacks), measured as its un-nested remainder.",
|
|
27
|
-
unattributed: "Thread time the probe measured but no JS wrapper could claim: touch handlers, React commits, native call-ins, and async/await bodies. High here on New Architecture is normal — it's where UI work lives.",
|
|
28
|
-
other: "Origins evicted from the table when the registry hit its cap — their totals are preserved here."
|
|
29
|
-
};
|
|
30
|
-
function explainerFor(api) {
|
|
31
|
-
return API_EXPLAINER[api] ?? API_EXPLAINER.other;
|
|
32
|
-
}
|
|
33
|
-
function fmt(n) {
|
|
34
|
-
if (n >= 10_000) return `${Math.round(n / 1000)}k`;
|
|
35
|
-
if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
|
|
36
|
-
if (n >= 100) return `${Math.round(n)}`;
|
|
37
|
-
return `${Math.round(n * 100) / 100}`;
|
|
38
|
-
}
|
|
39
|
-
function agoFmt(ms) {
|
|
40
|
-
if (ms <= 0) return "—";
|
|
41
|
-
const sec = ms / 1000;
|
|
42
|
-
if (sec < 1) return "just now";
|
|
43
|
-
if (sec < 60) return `${Math.round(sec)}s ago`;
|
|
44
|
-
return `${Math.floor(sec / 60)}m ago`;
|
|
45
|
-
}
|
|
46
|
-
function Stat({
|
|
47
|
-
label,
|
|
48
|
-
value,
|
|
49
|
-
accent
|
|
50
|
-
}) {
|
|
51
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
52
|
-
style: styles.stat,
|
|
53
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
54
|
-
style: styles.statLabel,
|
|
55
|
-
children: label
|
|
56
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
57
|
-
style: [styles.statValue, accent ? {
|
|
58
|
-
color: accent
|
|
59
|
-
} : null],
|
|
60
|
-
children: value
|
|
61
|
-
})]
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
export const TaskDetailView = /*#__PURE__*/memo(function TaskDetailView({
|
|
65
|
-
detail
|
|
66
|
-
}) {
|
|
67
|
-
const badge = badgeFor(detail.api);
|
|
68
|
-
const insets = useSafeAreaInsets({
|
|
69
|
-
minBottom: 12
|
|
70
|
-
});
|
|
71
|
-
const maxBucket = Math.max(1, ...detail.buckets);
|
|
72
|
-
return /*#__PURE__*/_jsxs(ScrollView, {
|
|
73
|
-
style: styles.root,
|
|
74
|
-
contentContainerStyle: {
|
|
75
|
-
paddingBottom: insets.bottom
|
|
76
|
-
},
|
|
77
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
78
|
-
style: styles.identityRow,
|
|
79
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
80
|
-
style: [styles.badge, {
|
|
81
|
-
borderColor: badge.color,
|
|
82
|
-
backgroundColor: `${badge.color}1A`
|
|
83
|
-
}],
|
|
84
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
85
|
-
style: [styles.badgeText, {
|
|
86
|
-
color: badge.color
|
|
87
|
-
}],
|
|
88
|
-
children: badge.text
|
|
89
|
-
})
|
|
90
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
91
|
-
style: styles.name,
|
|
92
|
-
numberOfLines: 2,
|
|
93
|
-
children: displayName(detail)
|
|
94
|
-
})]
|
|
95
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
96
|
-
style: styles.explainer,
|
|
97
|
-
children: explainerFor(detail.api)
|
|
98
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
99
|
-
style: styles.sectionTitle,
|
|
100
|
-
children: "ACTIVITY \xB7 LAST 5S"
|
|
101
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
102
|
-
style: styles.chartRow,
|
|
103
|
-
children: detail.buckets.map((ms, i) => /*#__PURE__*/_jsx(View, {
|
|
104
|
-
style: styles.chartBarSlot,
|
|
105
|
-
children: /*#__PURE__*/_jsx(View, {
|
|
106
|
-
style: [styles.chartBar, {
|
|
107
|
-
height: Math.max(2, ms / maxBucket * 44),
|
|
108
|
-
backgroundColor: badge.color
|
|
109
|
-
}]
|
|
110
|
-
})
|
|
111
|
-
}, `bk${i}`))
|
|
112
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
113
|
-
style: styles.chartLegend,
|
|
114
|
-
children: [/*#__PURE__*/_jsxs(Text, {
|
|
115
|
-
style: styles.chartLegendText,
|
|
116
|
-
children: [fmt(detail.windowMs), " ms in window \xB7 ", detail.pctOfBusy.toFixed(1), "% of busy time"]
|
|
117
|
-
}), /*#__PURE__*/_jsxs(Text, {
|
|
118
|
-
style: styles.chartLegendText,
|
|
119
|
-
children: ["peak ", fmt(maxBucket), " ms/250ms"]
|
|
120
|
-
})]
|
|
121
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
122
|
-
style: styles.sectionTitle,
|
|
123
|
-
children: "STATS \xB7 SINCE START"
|
|
124
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
125
|
-
style: styles.statGrid,
|
|
126
|
-
children: [/*#__PURE__*/_jsx(Stat, {
|
|
127
|
-
label: "RUNS",
|
|
128
|
-
value: detail.calls > 0 ? fmt(detail.calls) : "—"
|
|
129
|
-
}), /*#__PURE__*/_jsx(Stat, {
|
|
130
|
-
label: "SCHEDULED",
|
|
131
|
-
value: detail.scheduleCount > 0 ? fmt(detail.scheduleCount) : "—"
|
|
132
|
-
}), /*#__PURE__*/_jsx(Stat, {
|
|
133
|
-
label: "AVG / RUN",
|
|
134
|
-
value: detail.calls > 0 ? `${fmt(detail.avgMs)} ms` : "—"
|
|
135
|
-
}), /*#__PURE__*/_jsx(Stat, {
|
|
136
|
-
label: "MAX / RUN",
|
|
137
|
-
value: detail.maxMs > 0 ? `${fmt(detail.maxMs)} ms` : "—",
|
|
138
|
-
accent: detail.maxMs >= 50 ? macOSColors.semantic.warning : undefined
|
|
139
|
-
}), /*#__PURE__*/_jsx(Stat, {
|
|
140
|
-
label: "TOTAL",
|
|
141
|
-
value: detail.totalMs > 0 ? `${fmt(detail.totalMs)} ms` : "—"
|
|
142
|
-
}), /*#__PURE__*/_jsx(Stat, {
|
|
143
|
-
label: "LAST RUN",
|
|
144
|
-
value: agoFmt(detail.lastSeenAgoMs)
|
|
145
|
-
}), /*#__PURE__*/_jsx(Stat, {
|
|
146
|
-
label: "FREEZES \xB7 60S",
|
|
147
|
-
value: detail.freezes > 0 ? `${detail.freezes}` : "0",
|
|
148
|
-
accent: detail.freezes > 0 ? macOSColors.semantic.error : undefined
|
|
149
|
-
}), /*#__PURE__*/_jsx(Stat, {
|
|
150
|
-
label: "WORST FREEZE",
|
|
151
|
-
value: detail.worstFreezeMs > 0 ? `${detail.worstFreezeMs} ms` : "—",
|
|
152
|
-
accent: detail.worstFreezeMs >= 100 ? macOSColors.semantic.error : detail.worstFreezeMs > 0 ? macOSColors.semantic.warning : undefined
|
|
153
|
-
})]
|
|
154
|
-
}), (detail.caller || (detail.frames?.length ?? 0) > 0) && /*#__PURE__*/_jsxs(_Fragment, {
|
|
155
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
156
|
-
style: styles.sectionTitle,
|
|
157
|
-
children: "SCHEDULED FROM"
|
|
158
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
159
|
-
style: styles.siteCard,
|
|
160
|
-
children: [detail.caller && /*#__PURE__*/_jsxs(Text, {
|
|
161
|
-
style: styles.siteCaller,
|
|
162
|
-
children: [detail.caller, "()"]
|
|
163
|
-
}), detail.file && /*#__PURE__*/_jsxs(Text, {
|
|
164
|
-
style: styles.siteFile,
|
|
165
|
-
children: [detail.file, detail.line != null ? `:${detail.line}` : ""]
|
|
166
|
-
}), detail.frames?.map((f, i) => /*#__PURE__*/_jsxs(Text, {
|
|
167
|
-
style: styles.siteFrame,
|
|
168
|
-
numberOfLines: 1,
|
|
169
|
-
children: [f.file, ":", f.line, ":", f.col]
|
|
170
|
-
}, `fr${i}`)), /*#__PURE__*/_jsx(Text, {
|
|
171
|
-
style: styles.siteNote,
|
|
172
|
-
children: "Frames are bundle offsets (Hermes) \u2014 the caller name above is the readable part. Captured once when this origin got hot."
|
|
173
|
-
})]
|
|
174
|
-
})]
|
|
175
|
-
}), !detail.caller && (detail.frames?.length ?? 0) === 0 && detail.api !== "unattributed" && detail.api !== "other" && /*#__PURE__*/_jsxs(_Fragment, {
|
|
176
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
177
|
-
style: styles.sectionTitle,
|
|
178
|
-
children: "SCHEDULED FROM"
|
|
179
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
180
|
-
style: styles.sitePending,
|
|
181
|
-
children: detail.promoted ? "Stack capture pending — next schedule from this site will record it." : "Not hot enough yet — the scheduling stack is captured once an origin passes 25 schedules or 50ms total."
|
|
182
|
-
})]
|
|
183
|
-
})]
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
const styles = StyleSheet.create({
|
|
187
|
-
root: {
|
|
188
|
-
flex: 1,
|
|
189
|
-
// Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
|
|
190
|
-
backgroundColor: "transparent",
|
|
191
|
-
paddingHorizontal: 12
|
|
192
|
-
},
|
|
193
|
-
identityRow: {
|
|
194
|
-
flexDirection: "row",
|
|
195
|
-
alignItems: "center",
|
|
196
|
-
gap: 8,
|
|
197
|
-
paddingTop: 12
|
|
198
|
-
},
|
|
199
|
-
badge: {
|
|
200
|
-
borderWidth: 1,
|
|
201
|
-
borderRadius: 3,
|
|
202
|
-
paddingHorizontal: 5,
|
|
203
|
-
paddingVertical: 2
|
|
204
|
-
},
|
|
205
|
-
badgeText: {
|
|
206
|
-
fontSize: 9,
|
|
207
|
-
fontWeight: "700",
|
|
208
|
-
letterSpacing: 0.4
|
|
209
|
-
},
|
|
210
|
-
name: {
|
|
211
|
-
flex: 1,
|
|
212
|
-
color: night.color.text,
|
|
213
|
-
fontSize: 15,
|
|
214
|
-
fontWeight: "600"
|
|
215
|
-
},
|
|
216
|
-
explainer: {
|
|
217
|
-
color: night.color.textTertiary,
|
|
218
|
-
fontSize: 11,
|
|
219
|
-
lineHeight: 15,
|
|
220
|
-
marginTop: 6,
|
|
221
|
-
marginBottom: 4
|
|
222
|
-
},
|
|
223
|
-
sectionTitle: {
|
|
224
|
-
color: night.color.textTertiary,
|
|
225
|
-
fontSize: 9,
|
|
226
|
-
fontWeight: "700",
|
|
227
|
-
letterSpacing: 0.5,
|
|
228
|
-
marginTop: 14,
|
|
229
|
-
marginBottom: 6
|
|
230
|
-
},
|
|
231
|
-
chartRow: {
|
|
232
|
-
flexDirection: "row",
|
|
233
|
-
alignItems: "flex-end",
|
|
234
|
-
height: 48,
|
|
235
|
-
gap: 2,
|
|
236
|
-
backgroundColor: night.color.surface,
|
|
237
|
-
borderRadius: 6,
|
|
238
|
-
borderWidth: 1,
|
|
239
|
-
borderColor: night.color.border,
|
|
240
|
-
paddingHorizontal: 6,
|
|
241
|
-
paddingTop: 2
|
|
242
|
-
},
|
|
243
|
-
chartBarSlot: {
|
|
244
|
-
flex: 1,
|
|
245
|
-
alignItems: "center",
|
|
246
|
-
justifyContent: "flex-end"
|
|
247
|
-
},
|
|
248
|
-
chartBar: {
|
|
249
|
-
width: "70%",
|
|
250
|
-
borderTopLeftRadius: 2,
|
|
251
|
-
borderTopRightRadius: 2,
|
|
252
|
-
opacity: 0.9
|
|
253
|
-
},
|
|
254
|
-
chartLegend: {
|
|
255
|
-
flexDirection: "row",
|
|
256
|
-
justifyContent: "space-between",
|
|
257
|
-
marginTop: 4
|
|
258
|
-
},
|
|
259
|
-
chartLegendText: {
|
|
260
|
-
color: night.color.textTertiary,
|
|
261
|
-
fontSize: 10,
|
|
262
|
-
fontVariant: ["tabular-nums"]
|
|
263
|
-
},
|
|
264
|
-
statGrid: {
|
|
265
|
-
flexDirection: "row",
|
|
266
|
-
flexWrap: "wrap",
|
|
267
|
-
gap: 8
|
|
268
|
-
},
|
|
269
|
-
stat: {
|
|
270
|
-
width: "23%",
|
|
271
|
-
minWidth: 76,
|
|
272
|
-
flexGrow: 1,
|
|
273
|
-
backgroundColor: night.color.surface,
|
|
274
|
-
borderWidth: 1,
|
|
275
|
-
borderColor: night.color.border,
|
|
276
|
-
borderRadius: 6,
|
|
277
|
-
paddingHorizontal: 8,
|
|
278
|
-
paddingVertical: 6
|
|
279
|
-
},
|
|
280
|
-
statLabel: {
|
|
281
|
-
color: night.color.textTertiary,
|
|
282
|
-
fontSize: 8,
|
|
283
|
-
fontWeight: "700",
|
|
284
|
-
letterSpacing: 0.4,
|
|
285
|
-
marginBottom: 3
|
|
286
|
-
},
|
|
287
|
-
statValue: {
|
|
288
|
-
color: night.color.text,
|
|
289
|
-
fontSize: 13,
|
|
290
|
-
fontWeight: "600",
|
|
291
|
-
fontVariant: ["tabular-nums"]
|
|
292
|
-
},
|
|
293
|
-
siteCard: {
|
|
294
|
-
backgroundColor: night.color.surface,
|
|
295
|
-
borderWidth: 1,
|
|
296
|
-
borderColor: night.color.border,
|
|
297
|
-
borderRadius: 6,
|
|
298
|
-
padding: 10,
|
|
299
|
-
gap: 3
|
|
300
|
-
},
|
|
301
|
-
siteCaller: {
|
|
302
|
-
color: night.color.text,
|
|
303
|
-
fontSize: 13,
|
|
304
|
-
fontWeight: "600"
|
|
305
|
-
},
|
|
306
|
-
siteFile: {
|
|
307
|
-
color: night.color.info,
|
|
308
|
-
fontSize: 11
|
|
309
|
-
},
|
|
310
|
-
siteFrame: {
|
|
311
|
-
color: night.color.textTertiary,
|
|
312
|
-
fontSize: 10,
|
|
313
|
-
fontVariant: ["tabular-nums"]
|
|
314
|
-
},
|
|
315
|
-
siteNote: {
|
|
316
|
-
color: night.color.textTertiary,
|
|
317
|
-
fontSize: 9,
|
|
318
|
-
opacity: 0.8,
|
|
319
|
-
marginTop: 4
|
|
320
|
-
},
|
|
321
|
-
sitePending: {
|
|
322
|
-
color: night.color.textTertiary,
|
|
323
|
-
fontSize: 11
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
//# sourceMappingURL=TaskDetailView.js.map
|
|
1
|
+
"use strict";import{memo as y}from"react";import{ScrollView as x,StyleSheet as S,Text as n,View as s}from"react-native";import{macOSColors as u,night as o,useSafeAreaInsets as w}from"@buoy-gg/shared-ui";import{badgeFor as T,displayName as k}from"./TaskTable";import{jsx as r,jsxs as a,Fragment as g}from"react/jsx-runtime";const b={timeout:"Work scheduled with setTimeout. Time shown is the callback's exclusive run time on the JS thread.",interval:"A repeating setInterval. One scheduling site, many runs \u2014 a hot interval is the classic silent JS-FPS killer.",immediate:"Work scheduled with setImmediate \u2014 runs at the end of the current event-loop turn.",raf:"requestAnimationFrame work \u2014 runs once per frame while scheduled. Cost here directly trades against 60fps.",microtask:"queueMicrotask work \u2014 runs after the current task, before the thread yields.",then:"Promise reaction (.then/.catch/.finally). Named by the function that attached the reaction. Note: async/await bodies bypass .then and land in the System row instead.",bridge:"A native\u2192JS call-in over the legacy bridge (events, module callbacks), measured as its un-nested remainder.",unattributed:"Thread time the probe measured but no JS wrapper could claim: touch handlers, React commits, native call-ins, and async/await bodies. High here on New Architecture is normal \u2014 it's where UI work lives.",other:"Origins evicted from the table when the registry hit its cap \u2014 their totals are preserved here."};function C(i){return b[i]??b.other}function d(i){if(i>=1e4)return`${Math.round(i/1e3)}k`;if(i>=1e3)return`${(i/1e3).toFixed(1)}k`;if(i>=100)return`${Math.round(i)}`;return`${Math.round(i*100)/100}`}function z(i){if(i<=0)return"\u2014";const e=i/1e3;if(e<1)return"just now";if(e<60)return`${Math.round(e)}s ago`;return`${Math.floor(e/60)}m ago`}function c({label:i,value:e,accent:l}){return a(s,{style:t.stat,children:[r(n,{style:t.statLabel,children:i}),r(n,{style:[t.statValue,l?{color:l}:null],children:e})]})}export const TaskDetailView=y(function i({detail:e}){const l=T(e.api);const p=w({minBottom:12});const f=Math.max(1,...e.buckets);return a(x,{style:t.root,contentContainerStyle:{paddingBottom:p.bottom},children:[a(s,{style:t.identityRow,children:[r(s,{style:[t.badge,{borderColor:l.color,backgroundColor:`${l.color}1A`}],children:r(n,{style:[t.badgeText,{color:l.color}],children:l.text})}),r(n,{style:t.name,numberOfLines:2,children:k(e)})]}),r(n,{style:t.explainer,children:C(e.api)}),r(n,{style:t.sectionTitle,children:"ACTIVITY \xB7 LAST 5S"}),r(s,{style:t.chartRow,children:e.buckets.map((h,m)=>r(s,{style:t.chartBarSlot,children:r(s,{style:[t.chartBar,{height:Math.max(2,h/f*44),backgroundColor:l.color}]})},`bk${m}`))}),a(s,{style:t.chartLegend,children:[a(n,{style:t.chartLegendText,children:[d(e.windowMs)," ms in window \xB7 ",e.pctOfBusy.toFixed(1),"% of busy time"]}),a(n,{style:t.chartLegendText,children:["peak ",d(f)," ms/250ms"]})]}),r(n,{style:t.sectionTitle,children:"STATS \xB7 SINCE START"}),a(s,{style:t.statGrid,children:[r(c,{label:"RUNS",value:e.calls>0?d(e.calls):"\u2014"}),r(c,{label:"SCHEDULED",value:e.scheduleCount>0?d(e.scheduleCount):"\u2014"}),r(c,{label:"AVG / RUN",value:e.calls>0?`${d(e.avgMs)} ms`:"\u2014"}),r(c,{label:"MAX / RUN",value:e.maxMs>0?`${d(e.maxMs)} ms`:"\u2014",accent:e.maxMs>=50?u.semantic.warning:void 0}),r(c,{label:"TOTAL",value:e.totalMs>0?`${d(e.totalMs)} ms`:"\u2014"}),r(c,{label:"LAST RUN",value:z(e.lastSeenAgoMs)}),r(c,{label:"FREEZES \xB7 60S",value:e.freezes>0?`${e.freezes}`:"0",accent:e.freezes>0?u.semantic.error:void 0}),r(c,{label:"WORST FREEZE",value:e.worstFreezeMs>0?`${e.worstFreezeMs} ms`:"\u2014",accent:e.worstFreezeMs>=100?u.semantic.error:e.worstFreezeMs>0?u.semantic.warning:void 0})]}),(e.caller||(e.frames?.length??0)>0)&&a(g,{children:[r(n,{style:t.sectionTitle,children:"SCHEDULED FROM"}),a(s,{style:t.siteCard,children:[e.caller&&a(n,{style:t.siteCaller,children:[e.caller,"()"]}),e.file&&a(n,{style:t.siteFile,children:[e.file,e.line!=null?`:${e.line}`:""]}),e.frames?.map((h,m)=>a(n,{style:t.siteFrame,numberOfLines:1,children:[h.file,":",h.line,":",h.col]},`fr${m}`)),r(n,{style:t.siteNote,children:"Frames are bundle offsets (Hermes) \u2014 the caller name above is the readable part. Captured once when this origin got hot."})]})]}),!e.caller&&(e.frames?.length??0)===0&&e.api!=="unattributed"&&e.api!=="other"&&a(g,{children:[r(n,{style:t.sectionTitle,children:"SCHEDULED FROM"}),r(n,{style:t.sitePending,children:e.promoted?"Stack capture pending \u2014 next schedule from this site will record it.":"Not hot enough yet \u2014 the scheduling stack is captured once an origin passes 25 schedules or 50ms total."})]})]})});const t=S.create({root:{flex:1,backgroundColor:"transparent",paddingHorizontal:12},identityRow:{flexDirection:"row",alignItems:"center",gap:8,paddingTop:12},badge:{borderWidth:1,borderRadius:3,paddingHorizontal:5,paddingVertical:2},badgeText:{fontSize:9,fontWeight:"700",letterSpacing:.4},name:{flex:1,color:o.color.text,fontSize:15,fontWeight:"600"},explainer:{color:o.color.textTertiary,fontSize:11,lineHeight:15,marginTop:6,marginBottom:4},sectionTitle:{color:o.color.textTertiary,fontSize:9,fontWeight:"700",letterSpacing:.5,marginTop:14,marginBottom:6},chartRow:{flexDirection:"row",alignItems:"flex-end",height:48,gap:2,backgroundColor:o.color.surface,borderRadius:6,borderWidth:1,borderColor:o.color.border,paddingHorizontal:6,paddingTop:2},chartBarSlot:{flex:1,alignItems:"center",justifyContent:"flex-end"},chartBar:{width:"70%",borderTopLeftRadius:2,borderTopRightRadius:2,opacity:.9},chartLegend:{flexDirection:"row",justifyContent:"space-between",marginTop:4},chartLegendText:{color:o.color.textTertiary,fontSize:10,fontVariant:["tabular-nums"]},statGrid:{flexDirection:"row",flexWrap:"wrap",gap:8},stat:{width:"23%",minWidth:76,flexGrow:1,backgroundColor:o.color.surface,borderWidth:1,borderColor:o.color.border,borderRadius:6,paddingHorizontal:8,paddingVertical:6},statLabel:{color:o.color.textTertiary,fontSize:8,fontWeight:"700",letterSpacing:.4,marginBottom:3},statValue:{color:o.color.text,fontSize:13,fontWeight:"600",fontVariant:["tabular-nums"]},siteCard:{backgroundColor:o.color.surface,borderWidth:1,borderColor:o.color.border,borderRadius:6,padding:10,gap:3},siteCaller:{color:o.color.text,fontSize:13,fontWeight:"600"},siteFile:{color:o.color.info,fontSize:11},siteFrame:{color:o.color.textTertiary,fontSize:10,fontVariant:["tabular-nums"]},siteNote:{color:o.color.textTertiary,fontSize:9,opacity:.8,marginTop:4},sitePending:{color:o.color.textTertiary,fontSize:11}});
|