fix: alignment, section order, and tooltip duplication

- Move symbol badge inline with input (flex items-end) so symbol and
  rack inputs align at the bottom
- Move Signal Timeline chart above Slot Evidence table
- Deduplicate signal fires by slotId per day in chart data processing
  and by slotName in tooltip rendering (prevents MACD Bullish etc
  from appearing twice)
This commit is contained in:
Investor Flow Build
2026-08-11 23:20:54 -04:00
parent 762d6612f5
commit 9a1d837150
2 changed files with 38 additions and 22 deletions
+18 -4
View File
@@ -83,15 +83,22 @@ function ChartTooltip({ active, payload }: { active?: boolean; payload?: Array<{
if (!active || !payload?.length) return null; if (!active || !payload?.length) return null;
const point = payload[0].payload; const point = payload[0].payload;
const allFires = [...point.bullFires, ...point.bearFires]; const allFires = [...point.bullFires, ...point.bearFires];
// Deduplicate by slot name (a slot can fire multiple times on the same day)
const seen = new Set<string>();
const uniqueFires = allFires.filter((f) => {
if (seen.has(f.slotName)) return false;
seen.add(f.slotName);
return true;
});
return ( return (
<div className="rounded border border-line bg-surface-raised px-3 py-2 text-xs shadow-lg"> <div className="rounded border border-line bg-surface-raised px-3 py-2 text-xs shadow-lg">
<div className="font-medium text-fg mb-1">{formatDateFull(point.date)}</div> <div className="font-medium text-fg mb-1">{formatDateFull(point.date)}</div>
<div className="text-fg-muted"> <div className="text-fg-muted">
Close: <span className="text-fg font-medium">${point.close.toFixed(2)}</span> Close: <span className="text-fg font-medium">${point.close.toFixed(2)}</span>
</div> </div>
{allFires.length > 0 && ( {uniqueFires.length > 0 && (
<div className="mt-1.5 space-y-0.5 border-t border-line/50 pt-1.5"> <div className="mt-1.5 space-y-0.5 border-t border-line/50 pt-1.5">
{allFires.map((f, i) => ( {uniqueFires.map((f, i) => (
<div key={i} className="flex items-center gap-1.5"> <div key={i} className="flex items-center gap-1.5">
<span className="inline-block w-1.5 h-1.5 rounded-full" style={{ backgroundColor: BODY_COLOR[f.body] }} /> <span className="inline-block w-1.5 h-1.5 rounded-full" style={{ backgroundColor: BODY_COLOR[f.body] }} />
<span className="text-fg">{f.slotName}</span> <span className="text-fg">{f.slotName}</span>
@@ -185,12 +192,19 @@ export function ConfluenceChart({ symbol }: ConfluenceChartProps) {
const data: ChartPoint[] = candles.map((c) => { const data: ChartPoint[] = candles.map((c) => {
const key = toDateKey(c.ts); const key = toDateKey(c.ts);
const dayFires = firesByDate.get(key) ?? []; const dayFires = firesByDate.get(key) ?? [];
// Deduplicate by slot name per day (a slot can fire multiple times)
const seenSlots = new Set<string>();
const uniqueDayFires = dayFires.filter((f) => {
if (seenSlots.has(f.slotId)) return false;
seenSlots.add(f.slotId);
return true;
});
return { return {
date: c.ts, date: c.ts,
dateShort: formatDate(c.ts), dateShort: formatDate(c.ts),
close: c.c, close: c.c,
bullFires: dayFires.filter((f) => f.body === 'bull'), bullFires: uniqueDayFires.filter((f) => f.body === 'bull'),
bearFires: dayFires.filter((f) => f.body === 'bear' || f.body === 'exit'), bearFires: uniqueDayFires.filter((f) => f.body === 'bear' || f.body === 'exit'),
}; };
}); });
+10 -8
View File
@@ -119,6 +119,7 @@ export function ConfluencePanel() {
{/* Header */} {/* Header */}
<div className="flex flex-wrap items-end gap-4"> <div className="flex flex-wrap items-end gap-4">
<div className="flex items-end gap-2">
<div> <div>
<label className="block text-xs text-fg-muted mb-1">Symbol</label> <label className="block text-xs text-fg-muted mb-1">Symbol</label>
<div className="relative w-48"> <div className="relative w-48">
@@ -130,8 +131,9 @@ export function ConfluencePanel() {
inputClassName="w-full bg-surface-sunken border border-line rounded-lg px-3 py-1.5 text-xs text-fg placeholder:text-fg-muted focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" inputClassName="w-full bg-surface-sunken border border-line rounded-lg px-3 py-1.5 text-xs text-fg placeholder:text-fg-muted focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent"
/> />
</div> </div>
</div>
{symbol && ( {symbol && (
<span className="mt-1 inline-flex items-center rounded bg-accent/10 border border-accent/30 px-2 py-0.5 text-xs font-medium text-accent"> <span className="mb-0.5 inline-flex items-center rounded bg-accent/10 border border-accent/30 px-2 py-1.5 text-xs font-medium text-accent">
{symbol} {symbol}
</span> </span>
)} )}
@@ -187,6 +189,13 @@ export function ConfluencePanel() {
</div> </div>
) : null} ) : null}
{/* Signal Timeline chart */}
{symbol && (
<CollapsibleSection title="Signal Timeline" defaultOpen>
<ConfluenceChart symbol={symbol} />
</CollapsibleSection>
)}
{/* Slot evidence grid */} {/* Slot evidence grid */}
{assessments.length > 0 && ( {assessments.length > 0 && (
<CollapsibleSection title="Slot Evidence" defaultOpen> <CollapsibleSection title="Slot Evidence" defaultOpen>
@@ -211,13 +220,6 @@ export function ConfluencePanel() {
</CollapsibleSection> </CollapsibleSection>
)} )}
{/* Signal Timeline chart */}
{symbol && (
<CollapsibleSection title="Signal Timeline" defaultOpen>
<ConfluenceChart symbol={symbol} />
</CollapsibleSection>
)}
{/* Scorecard */} {/* Scorecard */}
{scorecard && scorecard.slots.length > 0 && ( {scorecard && scorecard.slots.length > 0 && (
<CollapsibleSection title="Reliability Scorecard"> <CollapsibleSection title="Reliability Scorecard">