Skip to content

Commit cc12c30

Browse files
shantanu patilclaude
authored andcommitted
Add UI/UX improvements: auto-select page, visible search, config tabs, onboarding
- Auto-select first wiki page on load instead of blank placeholder - Add visible search button with platform-aware keyboard shortcut hint (⌘K/Ctrl+K) - Restructure ConfigurationModal into Basic/Advanced tabs for simpler first experience - Fix sidebar text truncation with title tooltips on hover - Add floating Table of Contents button for non-XL screens - Add first-time user onboarding tooltip sequence (3 steps, localStorage tracking) - Show "Generating content..." intermediate state for pages being generated Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 04bdc6b commit cc12c30

3 files changed

Lines changed: 241 additions & 166 deletions

File tree

src/app/[owner]/[repo]/page.tsx

Lines changed: 149 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import { useLanguage } from '@/contexts/LanguageContext';
1313
import { RepoInfo } from '@/types/repoinfo';
1414
import Link from 'next/link';
1515
import { useParams, useSearchParams } from 'next/navigation';
16-
import React, { useCallback, useMemo, useRef, useState } from 'react';
16+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1717
import { motion } from 'framer-motion';
18-
import { ArrowLeft, Book, BookOpen, MessageSquare, AlertTriangle, Home, Network, Search, RefreshCw, X } from 'lucide-react';
18+
import { ArrowLeft, Book, BookOpen, List, MessageSquare, AlertTriangle, Home, Network, Search, RefreshCw, X } from 'lucide-react';
1919
import DependencyGraph from '@/components/DependencyGraph';
2020
import WikiSidebarSkeleton from '@/components/skeletons/WikiSidebarSkeleton';
2121
import WikiContentSkeleton from '@/components/skeletons/WikiContentSkeleton';
@@ -141,6 +141,9 @@ export default function RepoWikiPage() {
141141
const [showGraph, setShowGraph] = useState(false);
142142
const wikiContentRef = useRef<HTMLDivElement | null>(null);
143143

144+
// State for floating TOC (non-XL screens)
145+
const [isFloatingTocOpen, setIsFloatingTocOpen] = useState(false);
146+
144147
// State for Diagram Detail Panel (Click-to-Explain)
145148
const [isDiagramPanelOpen, setIsDiagramPanelOpen] = useState(false);
146149
const [selectedDiagramNode, setSelectedDiagramNode] = useState<{
@@ -322,9 +325,17 @@ export default function RepoWikiPage() {
322325
const handlePageSelect = (pageId: string) => {
323326
if (currentPageId != pageId) {
324327
setCurrentPageId(pageId);
328+
setIsFloatingTocOpen(false);
325329
}
326330
};
327331

332+
// Auto-select the first page when wiki loads without a selection
333+
useEffect(() => {
334+
if (wikiStructure && !currentPageId && wikiStructure.pages.length > 0) {
335+
setCurrentPageId(wikiStructure.pages[0].id);
336+
}
337+
}, [wikiStructure, currentPageId, setCurrentPageId]);
338+
328339
// Handler for diagram node clicks — opens the detail panel
329340
const handleDiagramNodeClick = useCallback((nodeId: string, label: string, _rect: DOMRect, diagramData?: import('@/types/diagramData').DiagramData) => {
330341
setSelectedDiagramNode({ nodeId, label, diagramData: diagramData ?? null });
@@ -333,6 +344,39 @@ export default function RepoWikiPage() {
333344

334345
const [isModelSelectionModalOpen, setIsModelSelectionModalOpen] = useState(false);
335346

347+
// Onboarding tooltip state
348+
const [onboardingStep, setOnboardingStep] = useState<number | null>(null);
349+
350+
useEffect(() => {
351+
if (!wikiStructure || isLoading) return;
352+
try {
353+
if (typeof window !== 'undefined' && !localStorage.getItem('bcw-onboarded')) {
354+
const timer = setTimeout(() => setOnboardingStep(0), 800);
355+
return () => clearTimeout(timer);
356+
}
357+
} catch {
358+
// localStorage not available
359+
}
360+
}, [wikiStructure, isLoading]);
361+
362+
useEffect(() => {
363+
if (onboardingStep === null) return;
364+
const timer = setTimeout(() => {
365+
if (onboardingStep < 2) {
366+
setOnboardingStep(onboardingStep + 1);
367+
} else {
368+
setOnboardingStep(null);
369+
try { localStorage.setItem('bcw-onboarded', '1'); } catch { /* noop */ }
370+
}
371+
}, 5000);
372+
return () => clearTimeout(timer);
373+
}, [onboardingStep]);
374+
375+
const dismissOnboarding = useCallback(() => {
376+
setOnboardingStep(null);
377+
try { localStorage.setItem('bcw-onboarded', '1'); } catch { /* noop */ }
378+
}, []);
379+
336380
return (
337381
<div className="flex-1 bg-background flex flex-col font-sans">
338382
<style>{wikiStyles}</style>
@@ -371,7 +415,7 @@ export default function RepoWikiPage() {
371415
<Search size={12} className="h-3 w-3" />
372416
<span className="hidden sm:inline">Search</span>
373417
<kbd className="hidden sm:inline-flex items-center gap-0.5 rounded border border-border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground ml-1">
374-
<span>&#8984;</span>K
418+
{typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? '\u2318' : 'Ctrl+'}K
375419
</kbd>
376420
</button>
377421
{/* Graph button */}
@@ -578,6 +622,25 @@ export default function RepoWikiPage() {
578622
</motion.div>
579623
)}
580624

625+
{/* Sidebar search trigger */}
626+
<motion.div
627+
initial={{ opacity: 0 }}
628+
animate={{ opacity: 1 }}
629+
transition={{ duration: 0.3, delay: 0.15, ease: "easeOut" }}
630+
className="px-4 pb-2"
631+
>
632+
<button
633+
onClick={() => setIsSearchOpen(true)}
634+
className="flex items-center gap-2 w-full rounded-md border border-border bg-muted/40 px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
635+
>
636+
<Search size={14} className="h-3.5 w-3.5 shrink-0" />
637+
<span className="flex-1 text-left truncate">Search pages...</span>
638+
<kbd className="hidden sm:inline-flex items-center gap-0.5 rounded border border-border bg-background px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground shrink-0">
639+
{typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? '\u2318' : 'Ctrl+'}K
640+
</kbd>
641+
</button>
642+
</motion.div>
643+
581644
<motion.div
582645
initial={{ opacity: 0 }}
583646
animate={{ opacity: 1 }}
@@ -712,7 +775,7 @@ export default function RepoWikiPage() {
712775
)}
713776
</motion.article>
714777

715-
{/* Floating Table of Contents */}
778+
{/* Table of Contents -- full sidebar on XL screens */}
716779
<aside className="hidden xl:block w-56 shrink-0">
717780
<div className="sticky top-0 pt-2 max-h-[calc(100vh-12rem)] overflow-y-auto">
718781
<TableOfContents
@@ -721,6 +784,46 @@ export default function RepoWikiPage() {
721784
/>
722785
</div>
723786
</aside>
787+
788+
{/* Floating TOC button for non-XL screens */}
789+
<div className="xl:hidden fixed bottom-24 right-8 z-40">
790+
<button
791+
onClick={() => setIsFloatingTocOpen(prev => !prev)}
792+
className={`h-10 w-10 rounded-full shadow-lg flex items-center justify-center transition-all hover:scale-105 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${
793+
isFloatingTocOpen
794+
? 'bg-primary/90 text-primary-foreground ring-2 ring-primary ring-offset-2 ring-offset-background'
795+
: 'bg-card text-foreground border border-border hover:bg-accent'
796+
}`}
797+
aria-label={isFloatingTocOpen ? 'Close table of contents' : 'Table of contents'}
798+
title="Table of contents"
799+
>
800+
{isFloatingTocOpen ? <X size={16} /> : <List size={16} />}
801+
</button>
802+
803+
{/* TOC popover */}
804+
{isFloatingTocOpen && (
805+
<motion.div
806+
initial={{ opacity: 0, y: 8, scale: 0.95 }}
807+
animate={{ opacity: 1, y: 0, scale: 1 }}
808+
exit={{ opacity: 0, y: 8, scale: 0.95 }}
809+
transition={{ duration: 0.15 }}
810+
className="absolute bottom-12 right-0 w-64 max-h-80 overflow-y-auto rounded-xl border border-border bg-card shadow-lg p-4"
811+
>
812+
<TableOfContents
813+
content={generatedPages[currentPageId].content}
814+
scrollContainer={wikiContentRef.current}
815+
/>
816+
</motion.div>
817+
)}
818+
</div>
819+
</div>
820+
) : currentPageId && pagesInProgress.has(currentPageId) ? (
821+
<div className="flex flex-col items-center justify-center h-full text-muted-foreground">
822+
<RefreshCw size={24} className="h-6 w-6 text-primary animate-spin mb-4" />
823+
<p className="text-lg font-medium text-foreground">Generating content...</p>
824+
<p className="text-sm">
825+
{wikiStructure?.pages.find(p => p.id === currentPageId)?.title || 'This page'} is being generated
826+
</p>
724827
</div>
725828
) : (
726829
<div className="flex flex-col items-center justify-center h-full text-muted-foreground">
@@ -879,6 +982,48 @@ export default function RepoWikiPage() {
879982
isOpen={showGraph}
880983
onClose={() => setShowGraph(false)}
881984
/>
985+
986+
{/* Onboarding tooltips */}
987+
{onboardingStep !== null && (
988+
<div className="fixed inset-0 z-[60] pointer-events-none">
989+
{onboardingStep === 0 && (
990+
<motion.div
991+
initial={{ opacity: 0, y: 8 }}
992+
animate={{ opacity: 1, y: 0 }}
993+
className="pointer-events-auto absolute top-32 left-4 lg:left-[calc(1rem)] max-w-[220px] bg-primary text-primary-foreground rounded-lg px-4 py-3 shadow-lg text-sm"
994+
>
995+
<p className="font-medium mb-1">Browse wiki pages</p>
996+
<p className="text-xs opacity-90">Navigate sections and pages in the sidebar</p>
997+
<button onClick={dismissOnboarding} className="mt-2 text-xs underline opacity-75 hover:opacity-100">Dismiss</button>
998+
<div className="absolute -left-1.5 top-4 w-3 h-3 bg-primary rotate-45" />
999+
</motion.div>
1000+
)}
1001+
{onboardingStep === 1 && (
1002+
<motion.div
1003+
initial={{ opacity: 0, y: 8 }}
1004+
animate={{ opacity: 1, y: 0 }}
1005+
className="pointer-events-auto absolute top-2 right-1/2 translate-x-1/2 max-w-[220px] bg-primary text-primary-foreground rounded-lg px-4 py-3 shadow-lg text-sm"
1006+
>
1007+
<p className="font-medium mb-1">Search pages</p>
1008+
<p className="text-xs opacity-90">Press {typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? '\u2318' : 'Ctrl+'}K to quickly find any page</p>
1009+
<button onClick={dismissOnboarding} className="mt-2 text-xs underline opacity-75 hover:opacity-100">Dismiss</button>
1010+
<div className="absolute left-1/2 -translate-x-1/2 -bottom-1.5 w-3 h-3 bg-primary rotate-45" />
1011+
</motion.div>
1012+
)}
1013+
{onboardingStep === 2 && (
1014+
<motion.div
1015+
initial={{ opacity: 0, y: 8 }}
1016+
animate={{ opacity: 1, y: 0 }}
1017+
className="pointer-events-auto absolute bottom-24 right-16 max-w-[220px] bg-primary text-primary-foreground rounded-lg px-4 py-3 shadow-lg text-sm"
1018+
>
1019+
<p className="font-medium mb-1">Ask AI anything</p>
1020+
<p className="text-xs opacity-90">Click the chat button to ask questions about this codebase</p>
1021+
<button onClick={dismissOnboarding} className="mt-2 text-xs underline opacity-75 hover:opacity-100">Dismiss</button>
1022+
<div className="absolute -right-1.5 bottom-4 w-3 h-3 bg-primary rotate-45" />
1023+
</motion.div>
1024+
)}
1025+
</div>
1026+
)}
8821027
</div>
8831028
);
8841029
}

0 commit comments

Comments
 (0)