Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

Commit fd0754b

Browse files
staticoclaude
andcommitted
Simplify LogPanel layout to reduce flickering
- Replace nested Box components with fragments and Text elements - Use text-based separator instead of border box - Use ▶ prefix for selection instead of background color - Add explicit height to containers for stable layout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fc1fb11 commit fd0754b

1 file changed

Lines changed: 53 additions & 109 deletions

File tree

src/ui/components/LogPanel.tsx

Lines changed: 53 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ interface LogPanelProps {
1414
}
1515

1616
export function LogPanel({ responses, selectedIndex, height, nodeStore }: LogPanelProps) {
17+
// Fixed height calculations
18+
const listHeight = Math.max(5, Math.floor(height / 2));
19+
const inspectorHeight = height - listHeight - 1;
20+
1721
if (responses.length === 0) {
1822
return (
1923
<Box flexDirection="column" paddingX={1} paddingY={1} height={height}>
@@ -28,30 +32,22 @@ export function LogPanel({ responses, selectedIndex, height, nodeStore }: LogPan
2832
);
2933
}
3034

31-
// Split pane: upper half list, lower half inspector
32-
const listHeight = Math.max(5, Math.floor((height - 1) * 0.5));
33-
const inspectorHeight = Math.max(5, height - listHeight - 1);
34-
3535
const selectedResponse = responses[selectedIndex];
3636

3737
return (
38-
<Box flexDirection="column" width="100%" height={height}>
39-
<Box height={listHeight} flexDirection="column" flexShrink={0}>
40-
<LogList
41-
responses={responses}
42-
selectedIndex={selectedIndex}
43-
height={listHeight}
44-
nodeStore={nodeStore}
45-
/>
46-
</Box>
47-
<Box flexShrink={0} borderStyle="single" borderColor={theme.border.normal} borderTop={true} borderBottom={false} borderLeft={false} borderRight={false} />
48-
<Box height={inspectorHeight} flexDirection="column" flexShrink={0}>
49-
<LogInspector
50-
response={selectedResponse}
51-
nodeStore={nodeStore}
52-
height={inspectorHeight}
53-
/>
54-
</Box>
38+
<Box flexDirection="column" paddingX={1} width="100%" height={height}>
39+
<LogList
40+
responses={responses}
41+
selectedIndex={selectedIndex}
42+
height={listHeight}
43+
nodeStore={nodeStore}
44+
/>
45+
<Text color={theme.border.normal}>{"─".repeat(70)}</Text>
46+
<LogInspector
47+
response={selectedResponse}
48+
nodeStore={nodeStore}
49+
height={inspectorHeight}
50+
/>
5551
</Box>
5652
);
5753
}
@@ -62,7 +58,7 @@ function LogList({ responses, selectedIndex, height, nodeStore }: {
6258
height: number;
6359
nodeStore: NodeStore;
6460
}) {
65-
const visibleCount = Math.max(1, height - 2);
61+
const visibleCount = Math.max(1, height - 1);
6662

6763
let startIndex = 0;
6864
if (responses.length > visibleCount) {
@@ -76,23 +72,21 @@ function LogList({ responses, selectedIndex, height, nodeStore }: {
7672
const visibleResponses = responses.slice(startIndex, startIndex + visibleCount);
7773

7874
return (
79-
<Box flexDirection="column" paddingX={1}>
80-
<Box>
81-
<Text color={theme.fg.muted}>
82-
{"TYPE".padEnd(12)}
83-
{"FROM".padEnd(12)}
84-
{"TIME"}
85-
</Text>
86-
</Box>
75+
<>
76+
<Text color={theme.fg.muted}>
77+
{"TYPE".padEnd(12)}
78+
{"FROM".padEnd(12)}
79+
{"TIME"}
80+
</Text>
8781
{visibleResponses.map((response, i) => (
8882
<LogRow
89-
key={response.id || `${response.timestamp}-${i}`}
83+
key={`log-${response.id || response.timestamp}-${i}`}
9084
response={response}
9185
isSelected={startIndex + i === selectedIndex}
9286
nodeStore={nodeStore}
9387
/>
9488
))}
95-
</Box>
89+
</>
9690
);
9791
}
9892

@@ -109,22 +103,21 @@ function LogRow({ response, isSelected, nodeStore }: {
109103
isSelected: boolean;
110104
nodeStore: NodeStore;
111105
}) {
112-
const bgColor = isSelected ? theme.bg.selected : undefined;
113106
const isPosition = isPositionResponse(response);
114107
const isNodeInfo = isNodeInfoResponse(response);
115108
const type = isPosition ? "POSITION" : isNodeInfo ? "NODEINFO" : "TRACEROUTE";
116109
const typeColor = isPosition ? theme.packet.position : isNodeInfo ? theme.packet.nodeinfo : theme.packet.traceroute;
117110
const fromName = nodeStore.getNodeName(response.fromNode);
118111
const time = new Date(response.timestamp * 1000).toLocaleTimeString("en-US", { hour12: false });
112+
const prefix = isSelected ? "▶ " : " ";
119113

120114
return (
121-
<Box backgroundColor={bgColor}>
122-
<Text wrap="truncate">
123-
<Text color={typeColor}>{type.padEnd(12)}</Text>
124-
<Text color={theme.fg.accent}>{fromName.slice(0, 10).padEnd(12)}</Text>
125-
<Text color={theme.fg.secondary}>{time}</Text>
126-
</Text>
127-
</Box>
115+
<Text>
116+
<Text color={theme.fg.accent}>{prefix}</Text>
117+
<Text color={typeColor}>{type.padEnd(12)}</Text>
118+
<Text color={theme.fg.accent}>{fromName.slice(0, 10).padEnd(12)}</Text>
119+
<Text color={theme.fg.secondary}>{time}</Text>
120+
</Text>
128121
);
129122
}
130123

@@ -134,11 +127,7 @@ function LogInspector({ response, nodeStore, height }: {
134127
height: number;
135128
}) {
136129
if (!response) {
137-
return (
138-
<Box paddingX={1}>
139-
<Text color={theme.fg.muted}>No response selected</Text>
140-
</Box>
141-
);
130+
return <Text color={theme.fg.muted}>No response selected</Text>;
142131
}
143132

144133
const fromName = nodeStore.getNodeName(response.fromNode);
@@ -149,31 +138,18 @@ function LogInspector({ response, nodeStore, height }: {
149138
const lon = pos.longitudeI != null ? pos.longitudeI / 1e7 : null;
150139

151140
return (
152-
<Box flexDirection="column" paddingX={1}>
153-
<Box>
154-
<Text color={theme.fg.muted}>From: </Text>
155-
<Text color={theme.fg.accent}>{fromName}</Text>
156-
<Text color={theme.fg.muted}> ({formatNodeId(pos.fromNode)})</Text>
157-
</Box>
141+
<>
142+
<Text><Text color={theme.fg.muted}>From: </Text><Text color={theme.fg.accent}>{fromName}</Text><Text color={theme.fg.muted}> ({formatNodeId(pos.fromNode)})</Text></Text>
158143
{lat != null && lon != null && (
159-
<Box>
160-
<Text color={theme.fg.muted}>Position: </Text>
161-
<Text color={theme.packet.position}>{lat.toFixed(6)}, {lon.toFixed(6)}</Text>
162-
</Box>
144+
<Text><Text color={theme.fg.muted}>Position: </Text><Text color={theme.packet.position}>{lat.toFixed(6)}, {lon.toFixed(6)}</Text></Text>
163145
)}
164146
{pos.altitude != null && (
165-
<Box>
166-
<Text color={theme.fg.muted}>Altitude: </Text>
167-
<Text color={theme.fg.primary}>{pos.altitude}m</Text>
168-
</Box>
147+
<Text><Text color={theme.fg.muted}>Altitude: </Text><Text color={theme.fg.primary}>{pos.altitude}m</Text></Text>
169148
)}
170149
{pos.satsInView != null && (
171-
<Box>
172-
<Text color={theme.fg.muted}>Satellites: </Text>
173-
<Text color={theme.fg.primary}>{pos.satsInView}</Text>
174-
</Box>
150+
<Text><Text color={theme.fg.muted}>Satellites: </Text><Text color={theme.fg.primary}>{pos.satsInView}</Text></Text>
175151
)}
176-
</Box>
152+
</>
177153
);
178154
}
179155

@@ -183,29 +159,16 @@ function LogInspector({ response, nodeStore, height }: {
183159
const hwModelName = ni.hwModel != null ? Mesh.HardwareModel[ni.hwModel] || `Unknown (${ni.hwModel})` : "Unknown";
184160

185161
return (
186-
<Box flexDirection="column" paddingX={1}>
187-
<Box>
188-
<Text color={theme.fg.muted}>From: </Text>
189-
<Text color={theme.fg.accent}>{fromName}</Text>
190-
<Text color={theme.fg.muted}> ({formatNodeId(ni.fromNode)})</Text>
191-
</Box>
162+
<>
163+
<Text><Text color={theme.fg.muted}>From: </Text><Text color={theme.fg.accent}>{fromName}</Text><Text color={theme.fg.muted}> ({formatNodeId(ni.fromNode)})</Text></Text>
192164
{ni.longName && (
193-
<Box>
194-
<Text color={theme.fg.muted}>Long Name: </Text>
195-
<Text color={theme.packet.nodeinfo}>{ni.longName}</Text>
196-
</Box>
165+
<Text><Text color={theme.fg.muted}>Long Name: </Text><Text color={theme.packet.nodeinfo}>{ni.longName}</Text></Text>
197166
)}
198167
{ni.shortName && (
199-
<Box>
200-
<Text color={theme.fg.muted}>Short Name: </Text>
201-
<Text color={theme.packet.nodeinfo}>{ni.shortName}</Text>
202-
</Box>
168+
<Text><Text color={theme.fg.muted}>Short Name: </Text><Text color={theme.packet.nodeinfo}>{ni.shortName}</Text></Text>
203169
)}
204-
<Box>
205-
<Text color={theme.fg.muted}>Hardware: </Text>
206-
<Text color={theme.fg.primary}>{hwModelName}</Text>
207-
</Box>
208-
</Box>
170+
<Text><Text color={theme.fg.muted}>Hardware: </Text><Text color={theme.fg.primary}>{hwModelName}</Text></Text>
171+
</>
209172
);
210173
}
211174

@@ -215,42 +178,23 @@ function LogInspector({ response, nodeStore, height }: {
215178
const snrTowards: number[] = tr.snrTowards || [];
216179

217180
return (
218-
<Box flexDirection="column" paddingX={1}>
219-
<Box>
220-
<Text color={theme.fg.muted}>To: </Text>
221-
<Text color={theme.fg.accent}>{fromName}</Text>
222-
<Text color={theme.fg.muted}> ({formatNodeId(tr.fromNode)})</Text>
223-
</Box>
224-
<Box>
225-
<Text color={theme.fg.muted}>Hop Limit: </Text>
226-
<Text color={theme.fg.primary}>{tr.hopLimit}</Text>
227-
{tr.hopLimit === 0 && <Text color={theme.packet.direct}> (direct ping)</Text>}
228-
</Box>
181+
<>
182+
<Text><Text color={theme.fg.muted}>To: </Text><Text color={theme.fg.accent}>{fromName}</Text><Text color={theme.fg.muted}> ({formatNodeId(tr.fromNode)})</Text></Text>
183+
<Text><Text color={theme.fg.muted}>Hop Limit: </Text><Text color={theme.fg.primary}>{tr.hopLimit}</Text>{tr.hopLimit === 0 && <Text color={theme.packet.direct}> (direct ping)</Text>}</Text>
229184
{route.length === 0 ? (
230-
<Box>
231-
<Text color={theme.packet.direct}>Direct connection (0 hops)</Text>
232-
</Box>
185+
<Text color={theme.packet.direct}>Direct connection (0 hops)</Text>
233186
) : (
234187
<>
235-
<Box>
236-
<Text color={theme.fg.muted}>Route: </Text>
237-
<Text color={theme.packet.traceroute}>{route.length} hop{route.length !== 1 ? "s" : ""}</Text>
238-
</Box>
188+
<Text><Text color={theme.fg.muted}>Route: </Text><Text color={theme.packet.traceroute}>{route.length} hop{route.length !== 1 ? "s" : ""}</Text></Text>
239189
{route.slice(0, height - 4).map((nodeNum, i) => {
240190
const name = nodeStore.getNodeName(nodeNum);
241191
const snr = snrTowards[i];
242192
return (
243-
<Box key={nodeNum}>
244-
<Text color={theme.fg.muted}> {i + 1}. </Text>
245-
<Text color={theme.fg.accent}>{name}</Text>
246-
{snr != null && (
247-
<Text color={theme.fg.secondary}> SNR: {(snr / 4).toFixed(1)}dB</Text>
248-
)}
249-
</Box>
193+
<Text key={`hop-${i}`}><Text color={theme.fg.muted}> {i + 1}. </Text><Text color={theme.fg.accent}>{name}</Text>{snr != null && (<Text color={theme.fg.secondary}> SNR: {(snr / 4).toFixed(1)}dB</Text>)}</Text>
250194
);
251195
})}
252196
</>
253197
)}
254-
</Box>
198+
</>
255199
);
256200
}

0 commit comments

Comments
 (0)