@buoy-gg/console 7.0.29 → 7.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,388 +0,0 @@
1
- import { memo } from "react";
2
- /**
3
- * ConsoleMessageRow — RN port of a single DevTools `.console-message-wrapper`.
4
- *
5
- * Anatomy (mirrors ConsoleViewMessage):
6
- * [nesting markers] [group triangle] [level icon] [timestamp] [content] [repeat badge]
7
- * Error/warning rows get the surface background + text colors; non-error/warning
8
- * rows get a hairline top divider. Group headers show a bold title with an
9
- * expand triangle; error/warn/trace rows can expand a captured stack trace.
10
- */
11
-
12
- import React, { useState } from "react";
13
- import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
14
- import { consoleTheme, levelStyles, monoFont } from "./theme";
15
- import { formatConsoleMessage } from "./consoleFormat";
16
- import { ConsoleValue } from "./ConsoleValue";
17
- import type { DisplayRow } from "./useConsoleMessages";
18
-
19
- function formatTimestamp(ts: number): string {
20
- const d = new Date(ts);
21
- const pad = (n: number, len = 2) => String(n).padStart(len, "0");
22
- return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(
23
- d.getMilliseconds(),
24
- 3
25
- )}`;
26
- }
27
-
28
- /** Small filled level glyph (warning triangle / error cross), DevTools-style. */
29
- function LevelIcon({ icon, color }: { icon: "warning" | "error"; color: string }) {
30
- return <Text style={[styles.levelIcon, { color }]}>{icon === "warning" ? "⚠" : "⊘"}</Text>;
31
- }
32
-
33
- /** The repeat-count bubble (dt-small-bubble). */
34
- function RepeatBadge({ count, color }: { count: number; color: string }) {
35
- return (
36
- <View style={[styles.repeatBadge, { borderColor: color }]}>
37
- <Text style={[styles.repeatBadgeText, { color }]}>{count}</Text>
38
- </View>
39
- );
40
- }
41
-
42
- interface ConsoleMessageRowProps {
43
- row: DisplayRow;
44
- showTimestamps: boolean;
45
- onToggleGroup: (groupId: number, currentlyCollapsed: boolean) => void;
46
- onToggleCluster: (key: string) => void;
47
- }
48
-
49
- // Memo with a precise comparator, justified by profiling (tool-bench
50
- // 2026-07-06): ~30 windowed rows re-rendered on every commit (172 row
51
- // renders in a 5s dwell) because buildDisplayRows recreates row OBJECTS
52
- // each emission — so default shallow compare can never bail. Entries are
53
- // immutable; the comparator lists every display-affecting row field.
54
- export const ConsoleMessageRow = memo(function ConsoleMessageRow({
55
- row,
56
- showTimestamps,
57
- onToggleGroup,
58
- onToggleCluster,
59
- }: ConsoleMessageRowProps) {
60
- const { entry } = row;
61
- const level = levelStyles[entry.level];
62
- const isErrorOrWarn = entry.level === "error" || entry.level === "warning";
63
- const textColor = level.text;
64
- // One "open" state per row: shows the stack trace (when there is one) AND
65
- // lifts the single-line clamp so long/structured messages stay reachable.
66
- const [expanded, setExpanded] = useState(entry.method === "trace");
67
-
68
- // Collapsed/large cluster header (Grouped mode): tap to expand/collapse.
69
- if (row.clusterHeader) {
70
- const ch = row.clusterHeader;
71
- return (
72
- <TouchableOpacity
73
- style={[styles.wrapper, styles.dividerTop]}
74
- activeOpacity={0.7}
75
- onPress={() => onToggleCluster(ch.key)}
76
- >
77
- <View style={styles.rowWrapper}>
78
- <Text style={[styles.originGutter, { color: row.originColor }]}>
79
- {ch.expanded ? "▼" : "▶"}
80
- </Text>
81
- <View style={styles.messageColumn}>
82
- <View style={styles.messageLine}>
83
- <Text style={[styles.text, styles.clusterTitle, { color: row.originColor }]}>
84
- {row.originLabel}
85
- </Text>
86
- <Text style={styles.clusterCount}> · {ch.count} logs</Text>
87
- </View>
88
- </View>
89
- </View>
90
- </TouchableOpacity>
91
- );
92
- }
93
-
94
- const tokens = formatConsoleMessage(entry.args);
95
- const hasStack = Boolean(entry.stack) && (isErrorOrWarn || entry.method === "trace");
96
-
97
- const content = (
98
- <View style={[styles.contentWrap, !expanded && styles.contentWrapClamped]}>
99
- {tokens.map((token, i) =>
100
- token.type === "string" ? (
101
- <Text
102
- key={i}
103
- style={[
104
- styles.text,
105
- { color: textColor },
106
- row.isGroupHeader && styles.groupTitle,
107
- token.style,
108
- ]}
109
- >
110
- {token.value}
111
- </Text>
112
- ) : (
113
- <ConsoleValue key={i} value={token.value} generic={token.mode === "generic"} />
114
- )
115
- )}
116
- </View>
117
- );
118
-
119
- const inner = (
120
- <View style={styles.messageColumn}>
121
- {row.originLabel ? (
122
- <Text
123
- style={[styles.originLabel, { color: row.originColor }]}
124
- numberOfLines={1}
125
- >
126
- {row.originLabel}
127
- </Text>
128
- ) : null}
129
- <View style={styles.messageLine}>
130
- {/* Group expand triangle */}
131
- {row.isGroupHeader ? (
132
- <Text style={[styles.groupTriangle, { color: textColor }]}>
133
- {row.groupCollapsed ? "▶" : "▼"}
134
- </Text>
135
- ) : null}
136
-
137
- {/* Level icon */}
138
- {level.icon ? <LevelIcon icon={level.icon} color={textColor} /> : null}
139
-
140
- {/* Timestamp */}
141
- {showTimestamps ? (
142
- <Text style={styles.timestamp}>{formatTimestamp(entry.timestamp)}</Text>
143
- ) : null}
144
-
145
- {/* Stack expand toggle */}
146
- {hasStack ? (
147
- <Text style={[styles.stackTriangle, { color: textColor }]}>
148
- {expanded ? "▼" : "▶"}
149
- </Text>
150
- ) : null}
151
-
152
- {/* Message content */}
153
- {content}
154
-
155
- {/* Repeat badge */}
156
- {row.repeatCount > 1 ? <RepeatBadge count={row.repeatCount} color={textColor} /> : null}
157
- </View>
158
-
159
- {/* Stack trace */}
160
- {hasStack && expanded ? (
161
- <View style={styles.stackTrace}>
162
- {(entry.stack ?? "")
163
- .split("\n")
164
- .filter((l) => l.trim())
165
- .map((line, i) => (
166
- <Text key={i} style={styles.stackFrame}>
167
- {line.trim()}
168
- </Text>
169
- ))}
170
- </View>
171
- ) : null}
172
- </View>
173
- );
174
-
175
- const rowStyle = [
176
- styles.wrapper,
177
- level.background ? { backgroundColor: level.background } : styles.dividerTop,
178
- ];
179
-
180
- // Nesting indentation markers.
181
- const markers = Array.from({ length: row.depth }, (_, i) => (
182
- <View key={i} style={styles.nestingMarker} />
183
- ));
184
-
185
- // Origin gutter: swimlane columns (multi-track) or a single bracket.
186
- const gutter = row.lanes ? (
187
- <View style={styles.laneGutter}>
188
- {row.lanes.map((cell, i) => (
189
- <Text key={i} style={[styles.laneCell, cell.color ? { color: cell.color } : null]}>
190
- {cell.glyph}
191
- </Text>
192
- ))}
193
- </View>
194
- ) : row.bracketGlyph ? (
195
- <Text style={[styles.originGutter, { color: row.originColor }]}>{row.bracketGlyph}</Text>
196
- ) : null;
197
-
198
- const body = (
199
- <View style={styles.rowWrapper}>
200
- {gutter}
201
- {markers}
202
- {inner}
203
- </View>
204
- );
205
-
206
- const rowTestID = `buoy-console-row-${entry.id}`;
207
- const rowLabel = [row.originLabel, tokens.find((t) => t.type === "string")?.value]
208
- .filter(Boolean)
209
- .join(" — ")
210
- .slice(0, 80);
211
-
212
- // Group headers and stack-bearing rows are pressable.
213
- if (row.isGroupHeader && row.groupId !== undefined) {
214
- const gid = row.groupId;
215
- return (
216
- <TouchableOpacity
217
- style={rowStyle}
218
- activeOpacity={0.7}
219
- onPress={() => onToggleGroup(gid, Boolean(row.groupCollapsed))}
220
- testID={rowTestID}
221
- accessibilityLabel={rowLabel || "Console group"}
222
- >
223
- {body}
224
- </TouchableOpacity>
225
- );
226
- }
227
- // Every other row toggles expanded on tap — the collapsed line is clamped
228
- // to a single line, so the tap is the way to read a long/structured log.
229
- return (
230
- <TouchableOpacity
231
- style={rowStyle}
232
- activeOpacity={0.7}
233
- onPress={() => setExpanded((o) => !o)}
234
- testID={rowTestID}
235
- accessibilityLabel={rowLabel || "Console message"}
236
- >
237
- {body}
238
- </TouchableOpacity>
239
- );
240
- },
241
- (prev: ConsoleMessageRowProps, next: ConsoleMessageRowProps) =>
242
- prev.row.key === next.row.key &&
243
- // Reference check: swimlane gutter cells are rebuilt per emission, so
244
- // swimlane mode simply keeps its previous (correct) behavior.
245
- prev.row.lanes === next.row.lanes &&
246
- prev.row.repeatCount === next.row.repeatCount &&
247
- prev.row.groupCollapsed === next.row.groupCollapsed &&
248
- prev.row.depth === next.row.depth &&
249
- prev.row.bracketGlyph === next.row.bracketGlyph &&
250
- prev.row.originLabel === next.row.originLabel &&
251
- prev.row.clusterHeader?.count === next.row.clusterHeader?.count &&
252
- prev.row.clusterHeader?.expanded === next.row.clusterHeader?.expanded &&
253
- prev.showTimestamps === next.showTimestamps &&
254
- prev.onToggleGroup === next.onToggleGroup &&
255
- prev.onToggleCluster === next.onToggleCluster);
256
-
257
- const styles = StyleSheet.create({
258
- wrapper: {
259
- paddingVertical: 2,
260
- paddingRight: 8,
261
- minHeight: 18,
262
- },
263
- dividerTop: {
264
- borderTopWidth: StyleSheet.hairlineWidth,
265
- borderTopColor: consoleTheme.divider,
266
- },
267
- rowWrapper: {
268
- flexDirection: "row",
269
- alignItems: "flex-start",
270
- },
271
- originGutter: {
272
- width: 11,
273
- fontFamily: monoFont,
274
- fontSize: 13,
275
- lineHeight: 18,
276
- textAlign: "center",
277
- },
278
- laneGutter: {
279
- flexDirection: "row",
280
- },
281
- laneCell: {
282
- width: 9,
283
- fontFamily: monoFont,
284
- fontSize: 13,
285
- lineHeight: 18,
286
- textAlign: "center",
287
- },
288
- originLabel: {
289
- fontFamily: monoFont,
290
- fontSize: 10,
291
- lineHeight: 16,
292
- marginBottom: 1,
293
- opacity: 0.9,
294
- },
295
- nestingMarker: {
296
- width: 14,
297
- alignSelf: "stretch",
298
- borderRightWidth: StyleSheet.hairlineWidth,
299
- borderRightColor: consoleTheme.divider,
300
- marginLeft: 2,
301
- },
302
- messageColumn: {
303
- flex: 1,
304
- flexDirection: "column",
305
- paddingLeft: 6,
306
- },
307
- messageLine: {
308
- flexDirection: "row",
309
- flexWrap: "wrap",
310
- alignItems: "flex-start",
311
- },
312
- contentWrap: {
313
- flexDirection: "row",
314
- flexWrap: "wrap",
315
- alignItems: "flex-start",
316
- flexShrink: 1,
317
- },
318
- // Collapsed rows clamp to one line; tap the row to expand.
319
- contentWrapClamped: {
320
- flexWrap: "nowrap",
321
- overflow: "hidden",
322
- maxHeight: 16,
323
- },
324
- text: {
325
- fontFamily: monoFont,
326
- fontSize: 12,
327
- lineHeight: 16,
328
- },
329
- groupTitle: {
330
- fontWeight: "bold",
331
- },
332
- clusterTitle: {
333
- fontWeight: "600",
334
- },
335
- clusterCount: {
336
- color: consoleTheme.tokenSubtle,
337
- fontFamily: monoFont,
338
- fontSize: 12,
339
- lineHeight: 16,
340
- },
341
- groupTriangle: {
342
- fontSize: 9,
343
- lineHeight: 16,
344
- marginRight: 4,
345
- },
346
- stackTriangle: {
347
- fontSize: 8,
348
- lineHeight: 16,
349
- marginRight: 4,
350
- },
351
- levelIcon: {
352
- fontSize: 12,
353
- lineHeight: 16,
354
- marginRight: 5,
355
- },
356
- timestamp: {
357
- color: consoleTheme.tokenSubtle,
358
- fontFamily: monoFont,
359
- fontSize: 11,
360
- lineHeight: 16,
361
- marginRight: 6,
362
- },
363
- repeatBadge: {
364
- borderWidth: 1,
365
- borderRadius: 9,
366
- paddingHorizontal: 5,
367
- minWidth: 16,
368
- alignItems: "center",
369
- justifyContent: "center",
370
- marginLeft: 6,
371
- marginTop: 1,
372
- },
373
- repeatBadgeText: {
374
- fontSize: 10,
375
- fontWeight: "700",
376
- lineHeight: 14,
377
- },
378
- stackTrace: {
379
- marginTop: 2,
380
- marginLeft: 12,
381
- },
382
- stackFrame: {
383
- color: consoleTheme.tokenSubtle,
384
- fontFamily: monoFont,
385
- fontSize: 11,
386
- lineHeight: 15,
387
- },
388
- });
@@ -1,265 +0,0 @@
1
- /**
2
- * ConsoleValue — RN approximation of DevTools' object/value rendering
3
- * (ObjectPropertiesSection + RemoteObjectPreviewFormatter).
4
- *
5
- * Primitives render with the DevTools dark-theme `object-value-*` token colors.
6
- * Objects/arrays render an italic one-line preview with an expand triangle;
7
- * expanding reveals a property tree (lazy, recursive). Matches the console's
8
- * collapsed-preview / click-to-expand behavior.
9
- */
10
-
11
- import React, { useState } from "react";
12
- import { Text, View, TouchableOpacity, StyleSheet } from "react-native";
13
- import type { TextStyle } from "react-native";
14
- import { consoleTheme, monoFont } from "./theme";
15
-
16
- const MAX_PREVIEW_PROPERTIES = 5;
17
- const MAX_PREVIEW_ARRAY = 6;
18
-
19
- type ValueKind =
20
- | "number"
21
- | "bigint"
22
- | "boolean"
23
- | "string"
24
- | "null"
25
- | "undefined"
26
- | "function"
27
- | "symbol"
28
- | "array"
29
- | "object"
30
- | "error";
31
-
32
- function kindOf(value: unknown): ValueKind {
33
- if (value === null) return "null";
34
- if (value === undefined) return "undefined";
35
- if (Array.isArray(value)) return "array";
36
- if (value instanceof Error) return "error";
37
- const t = typeof value;
38
- if (t === "number") return "number";
39
- if (t === "bigint") return "bigint";
40
- if (t === "boolean") return "boolean";
41
- if (t === "string") return "string";
42
- if (t === "function") return "function";
43
- if (t === "symbol") return "symbol";
44
- return "object";
45
- }
46
-
47
- const KIND_COLOR: Partial<Record<ValueKind, string>> = {
48
- number: consoleTheme.valueNumber,
49
- bigint: consoleTheme.valueBigint,
50
- boolean: consoleTheme.valueNumber,
51
- string: consoleTheme.valueString,
52
- null: consoleTheme.valueNullish,
53
- undefined: consoleTheme.valueNullish,
54
- symbol: consoleTheme.valueString,
55
- function: consoleTheme.valueNode,
56
- };
57
-
58
- /** The constructor/type label DevTools shows before an object's preview. */
59
- function constructorName(value: object): string {
60
- if (Array.isArray(value)) return "";
61
- const proto = Object.getPrototypeOf(value);
62
- if (!proto) return "Object";
63
- const name = proto.constructor?.name;
64
- return !name || name === "Object" ? "" : name;
65
- }
66
-
67
- /** One-line scalar text for a value (used inside previews). */
68
- function scalarText(value: unknown): string {
69
- const kind = kindOf(value);
70
- switch (kind) {
71
- case "string":
72
- return `'${value as string}'`;
73
- case "function": {
74
- const name = (value as { name?: string }).name;
75
- return `ƒ ${name || ""}`.trimEnd();
76
- }
77
- case "symbol":
78
- return (value as symbol).toString();
79
- case "bigint":
80
- return `${String(value)}n`;
81
- case "array":
82
- return `Array(${(value as unknown[]).length})`;
83
- case "error":
84
- return (value as Error).name;
85
- case "object": {
86
- const name = constructorName(value as object);
87
- return name ? `${name} {…}` : "{…}";
88
- }
89
- default:
90
- return String(value);
91
- }
92
- }
93
-
94
- /** Build the collapsed italic preview string for an object/array. */
95
- function buildPreview(value: object): string {
96
- if (Array.isArray(value)) {
97
- const head = value
98
- .slice(0, MAX_PREVIEW_ARRAY)
99
- .map((v) => scalarText(v))
100
- .join(", ");
101
- const more = value.length > MAX_PREVIEW_ARRAY ? ", …" : "";
102
- return `(${value.length}) [${head}${more}]`;
103
- }
104
- const entries = Object.entries(value);
105
- const head = entries
106
- .slice(0, MAX_PREVIEW_PROPERTIES)
107
- .map(([k, v]) => `${k}: ${scalarText(v)}`)
108
- .join(", ");
109
- const more = entries.length > MAX_PREVIEW_PROPERTIES ? ", …" : "";
110
- const ctor = constructorName(value);
111
- const prefix = ctor ? `${ctor} ` : "";
112
- return `${prefix}{${head}${more}}`;
113
- }
114
-
115
- function isExpandable(value: unknown): value is object {
116
- const kind = kindOf(value);
117
- if (kind === "array") return (value as unknown[]).length > 0;
118
- if (kind === "object") return Object.keys(value as object).length > 0;
119
- if (kind === "error") return true;
120
- return false;
121
- }
122
-
123
- const Triangle = ({ open }: { open: boolean }) => (
124
- <Text style={[styles.triangle, open && styles.triangleOpen]}>▶</Text>
125
- );
126
-
127
- interface ConsoleValueProps {
128
- value: unknown;
129
- /** Force the generic (non-preview) object tree, like %O. */
130
- generic?: boolean;
131
- /** Nesting depth guard. */
132
- depth?: number;
133
- }
134
-
135
- /** Render a single console argument / property value. */
136
- export function ConsoleValue({ value, depth = 0 }: ConsoleValueProps) {
137
- const [expanded, setExpanded] = useState(false);
138
- const kind = kindOf(value);
139
-
140
- // Errors: collapsed to a single "Name: message" line (like Chrome / the web
141
- // dashboard), expandable to the full stack. Avoids dumping a 100-line stack
142
- // inline on native, where the arg is a real Error (not a sanitized object).
143
- if (kind === "error") {
144
- const err = value as Error;
145
- const head = `${err.name}: ${err.message}`.split("\n")[0];
146
- const stack = err.stack && err.stack.trim() !== head.trim() ? err.stack : undefined;
147
- if (!stack) {
148
- return (
149
- <Text style={[styles.mono, { color: consoleTheme.onErrorContainer }]} numberOfLines={1}>
150
- {head}
151
- </Text>
152
- );
153
- }
154
- return (
155
- <View style={styles.objectContainer}>
156
- <TouchableOpacity
157
- style={styles.objectHeader}
158
- activeOpacity={0.6}
159
- onPress={() => setExpanded((e) => !e)}
160
- >
161
- <Triangle open={expanded} />
162
- <Text
163
- style={[styles.mono, styles.errorHead]}
164
- numberOfLines={expanded ? undefined : 1}
165
- >
166
- {head}
167
- </Text>
168
- </TouchableOpacity>
169
- {expanded ? <Text style={[styles.mono, styles.errorStack]}>{stack}</Text> : null}
170
- </View>
171
- );
172
- }
173
-
174
- if (!isExpandable(value)) {
175
- const color = KIND_COLOR[kind] ?? consoleTheme.onSurface;
176
- return <Text style={[styles.mono, { color }]}>{scalarText(value)}</Text>;
177
- }
178
-
179
- const obj = value as object;
180
- const entries = Array.isArray(obj)
181
- ? obj.map((v, i) => [String(i), v] as [string, unknown])
182
- : Object.entries(obj);
183
-
184
- return (
185
- <View style={styles.objectContainer}>
186
- <TouchableOpacity
187
- onPress={() => setExpanded((e) => !e)}
188
- activeOpacity={0.6}
189
- style={styles.objectHeader}
190
- >
191
- <Triangle open={expanded} />
192
- <Text style={[styles.mono, styles.preview]} numberOfLines={expanded ? undefined : 1}>
193
- {buildPreview(obj)}
194
- </Text>
195
- </TouchableOpacity>
196
- {expanded && depth < 6 ? (
197
- <View style={styles.children}>
198
- {entries.map(([key, v]) => (
199
- <View key={key} style={styles.propertyRow}>
200
- <Text style={[styles.mono, styles.propertyKey]}>{key}:</Text>
201
- <View style={styles.propertyValue}>
202
- <ConsoleValue value={v} depth={depth + 1} />
203
- </View>
204
- </View>
205
- ))}
206
- </View>
207
- ) : null}
208
- </View>
209
- );
210
- }
211
-
212
- const monoStyle: TextStyle = {
213
- fontFamily: monoFont,
214
- fontSize: 12,
215
- lineHeight: 16,
216
- };
217
-
218
- const styles = StyleSheet.create({
219
- mono: monoStyle,
220
- objectContainer: {
221
- flexDirection: "column",
222
- },
223
- objectHeader: {
224
- flexDirection: "row",
225
- alignItems: "flex-start",
226
- },
227
- triangle: {
228
- color: consoleTheme.tokenSubtle,
229
- fontSize: 8,
230
- lineHeight: 16,
231
- width: 12,
232
- },
233
- triangleOpen: {
234
- transform: [{ rotate: "90deg" }],
235
- },
236
- preview: {
237
- color: consoleTheme.valuePreview,
238
- fontStyle: "italic",
239
- flexShrink: 1,
240
- },
241
- errorHead: {
242
- color: consoleTheme.onErrorContainer,
243
- flexShrink: 1,
244
- },
245
- errorStack: {
246
- color: consoleTheme.tokenSubtle,
247
- marginLeft: 14,
248
- marginTop: 1,
249
- },
250
- children: {
251
- marginLeft: 14,
252
- flexDirection: "column",
253
- },
254
- propertyRow: {
255
- flexDirection: "row",
256
- alignItems: "flex-start",
257
- },
258
- propertyKey: {
259
- color: consoleTheme.valueNode,
260
- marginRight: 6,
261
- },
262
- propertyValue: {
263
- flexShrink: 1,
264
- },
265
- });
@@ -1,43 +0,0 @@
1
- /**
2
- * ConsoleView — self-contained Console panel (header bar + body) for embedding
3
- * outside a JsModal (the desktop dashboard inline tool, or any standalone use).
4
- *
5
- * Inside the mobile floating menu, ConsoleModal composes the same pieces but
6
- * routes the header into JsModal's `customContent` instead.
7
- */
8
-
9
- import React from "react";
10
- import { View, StyleSheet } from "react-native";
11
- import { macOSColors } from "@buoy-gg/shared-ui";
12
- import { ConsoleHeader } from "./ConsoleHeader";
13
- import { ConsoleBody } from "./ConsoleBody";
14
- import { useConsoleController } from "./useConsoleController";
15
-
16
- export function ConsoleView() {
17
- const c = useConsoleController();
18
- return (
19
- <View style={styles.container}>
20
- <View style={styles.headerBar}>
21
- <ConsoleHeader c={c} />
22
- </View>
23
- <ConsoleBody c={c} />
24
- </View>
25
- );
26
- }
27
-
28
- const styles = StyleSheet.create({
29
- container: {
30
- flex: 1,
31
- // Transparent so the desktop workbench's hoisted sky (one field spanning
32
- // sidebar + panel) shows through — the shell paints its base beneath it.
33
- backgroundColor: "transparent",
34
- },
35
- headerBar: {
36
- paddingHorizontal: 12,
37
- paddingVertical: 8,
38
- // Clear, like Bench — the host's hoisted sky shows through the header.
39
- backgroundColor: "transparent",
40
- borderBottomWidth: StyleSheet.hairlineWidth,
41
- borderBottomColor: macOSColors.border.default,
42
- },
43
- });