From 7ed67b559ade5f0bd37ccd253d97e887f736c4f4 Mon Sep 17 00:00:00 2001 From: "Claw (AINYC Agent)" Date: Mon, 30 Mar 2026 16:52:21 +0000 Subject: [PATCH] feat: tabbed sidebar with Competitors and Sources tabs (#196) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure the evidence detail modal sidebar into two tabs: - Competitors (default): shows domains cited instead of you, with rank, competitor tags, and your-domain position - Sources: shows grounding sources and evidence URLs Previously all data was shown in a single flat list under the misleading label 'Who was cited — in order'. The new tabs separate competitive intelligence from reference sources. Relabeled 'Who was cited' to 'Domains cited instead' for clarity. --- .../components/layout/EvidenceDetailModal.tsx | 150 +++++++++++------- apps/web/src/styles.css | 13 ++ 2 files changed, 106 insertions(+), 57 deletions(-) diff --git a/apps/web/src/components/layout/EvidenceDetailModal.tsx b/apps/web/src/components/layout/EvidenceDetailModal.tsx index dd2d902..eee19da 100644 --- a/apps/web/src/components/layout/EvidenceDetailModal.tsx +++ b/apps/web/src/components/layout/EvidenceDetailModal.tsx @@ -33,6 +33,7 @@ export function EvidenceDetailModal({ onClose: () => void }) { const [showFullAnswer, setShowFullAnswer] = useState(false) + const [sidebarTab, setSidebarTab] = useState<'competitors' | 'sources'>('competitors') const [selectedRunIdx, setSelectedRunIdx] = useState(-1) // -1 = latest (current) const [historicalSnapshot, setHistoricalSnapshot] = useState(null) const [loadingHistory, setLoadingHistory] = useState(false) @@ -470,68 +471,103 @@ export function EvidenceDetailModal({ {/* Right: leaderboard + sources */}
- {/* Citation leaderboard */} - {display.citedDomains.length > 0 && ( -
-

Who was cited \u2014 in order

-
- {display.citedDomains.map((domain, i) => { - const norm = domain.toLowerCase().replace(/^www\./, '') - const isYou = myDomains.has(norm) - const isCompetitor = !isYou && display.competitorDomains.some( - c => c.toLowerCase().replace(/^www\./, '') === norm, - ) - const variant = isYou ? 'you' : isCompetitor ? 'competitor' : 'other' - return ( -
- #{i + 1} - {domain} - {isYou && You} - {isCompetitor && Competitor} -
- ) - })} - {!isCited && ( -
- {'\u2014'} - {project.project.canonicalDomain} - Not cited -
- )} -
+ {/* Tabbed sidebar navigation */} + {(display.citedDomains.length > 0 || display.groundingSources.length > 0 || display.evidenceUrls.length > 0) && ( +
+ +
)} - {/* Grounding sources */} - {display.groundingSources.length > 0 && ( -
-

Grounding sources ({display.groundingSources.length})

- -
+ {/* Competitors tab — domains cited instead */} + {sidebarTab === 'competitors' && ( + <> + {display.citedDomains.length > 0 ? ( +
+

Domains cited instead

+
+ {display.citedDomains.map((domain, i) => { + const norm = domain.toLowerCase().replace(/^www\./, '') + const isYou = myDomains.has(norm) + const isCompetitor = !isYou && display.competitorDomains.some( + c => c.toLowerCase().replace(/^www\./, '') === norm, + ) + const variant = isYou ? 'you' : isCompetitor ? 'competitor' : 'other' + return ( +
+ #{i + 1} + {domain} + {isYou && You} + {isCompetitor && Competitor} +
+ ) + })} + {!isCited && ( +
+ {'\u2014'} + {project.project.canonicalDomain} + Not cited +
+ )} +
+
+ ) : ( +
+ No competitor data {isViewingHistory ? 'for this run' : 'yet'} +
+ )} + )} - {/* Evidence URLs */} - {display.evidenceUrls.length > 0 && ( -
-

Evidence URLs

-
    - {display.evidenceUrls.map((url) => ( -
  • - - {url} - -
  • - ))} -
-
+ {/* Sources tab — grounding sources + evidence URLs */} + {sidebarTab === 'sources' && ( + <> + {display.groundingSources.length > 0 && ( +
+

Grounding sources ({display.groundingSources.length})

+ +
+ )} + + {display.evidenceUrls.length > 0 && ( +
+

Evidence URLs

+
    + {display.evidenceUrls.map((url) => ( +
  • + + {url} + +
  • + ))} +
+
+ )} + + {display.groundingSources.length === 0 && display.evidenceUrls.length === 0 && ( +
+ No source data {isViewingHistory ? 'for this run' : 'yet'} +
+ )} + )} {/* No data state */} diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 8e34796..532a213 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -1397,6 +1397,19 @@ @apply md:border-l md:border-zinc-800/40 md:pl-6; } + .sidebar-tabs { + @apply flex gap-1 p-0.5 rounded-lg bg-zinc-900/60 border border-zinc-800/40; + } + + .sidebar-tab { + @apply flex-1 px-3 py-1.5 text-xs font-medium rounded-md transition-colors; + @apply text-zinc-500 hover:text-zinc-300; + } + + .sidebar-tab--active { + @apply bg-zinc-800 text-zinc-200 shadow-sm; + } + .evidence-answer-collapsed { max-height: 200px; overflow: hidden;