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;
const point = payload[0].payload;
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 (
<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="text-fg-muted">
Close: <span className="text-fg font-medium">${point.close.toFixed(2)}</span>
</div>
{allFires.length > 0 && (
{uniqueFires.length > 0 && (
<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">
<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>
@@ -185,12 +192,19 @@ export function ConfluenceChart({ symbol }: ConfluenceChartProps) {
const data: ChartPoint[] = candles.map((c) => {
const key = toDateKey(c.ts);
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 {
date: c.ts,
dateShort: formatDate(c.ts),
close: c.c,
bullFires: dayFires.filter((f) => f.body === 'bull'),
bearFires: dayFires.filter((f) => f.body === 'bear' || f.body === 'exit'),
bullFires: uniqueDayFires.filter((f) => f.body === 'bull'),
bearFires: uniqueDayFires.filter((f) => f.body === 'bear' || f.body === 'exit'),
};
});
+20 -18
View File
@@ -119,19 +119,21 @@ export function ConfluencePanel() {
{/* Header */}
<div className="flex flex-wrap items-end gap-4">
<div>
<label className="block text-xs text-fg-muted mb-1">Symbol</label>
<div className="relative w-48">
<SymbolAutocomplete
onPick={pickSymbol}
onSubmitRaw={(raw) => pickSymbol(raw.toUpperCase())}
placeholder="Search symbol…"
ariaLabel="Select symbol for signal confluence"
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 className="flex items-end gap-2">
<div>
<label className="block text-xs text-fg-muted mb-1">Symbol</label>
<div className="relative w-48">
<SymbolAutocomplete
onPick={pickSymbol}
onSubmitRaw={(raw) => pickSymbol(raw.toUpperCase())}
placeholder="Search symbol…"
ariaLabel="Select symbol for signal confluence"
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>
{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}
</span>
)}
@@ -187,6 +189,13 @@ export function ConfluencePanel() {
</div>
) : null}
{/* Signal Timeline chart */}
{symbol && (
<CollapsibleSection title="Signal Timeline" defaultOpen>
<ConfluenceChart symbol={symbol} />
</CollapsibleSection>
)}
{/* Slot evidence grid */}
{assessments.length > 0 && (
<CollapsibleSection title="Slot Evidence" defaultOpen>
@@ -211,13 +220,6 @@ export function ConfluencePanel() {
</CollapsibleSection>
)}
{/* Signal Timeline chart */}
{symbol && (
<CollapsibleSection title="Signal Timeline" defaultOpen>
<ConfluenceChart symbol={symbol} />
</CollapsibleSection>
)}
{/* Scorecard */}
{scorecard && scorecard.slots.length > 0 && (
<CollapsibleSection title="Reliability Scorecard">