Skip to content

Commit 3305820

Browse files
authored
feat(ui): add Activity lens for realtime file activity visualization (#7)
- Add lens-based UI shell with Live/Activity tabs (LensSwitcher) rendered between TopBar and the existing ResizablePanelGroup; Live lens is hidden via display:none rather than unmounted so SSE, hooks, and resizable refs remain intact across lens switches. - Add Activity lens: file-centric bubble graph with d3-force layout (60-tick burst with prevPositions seed), node-centric top-3 co-change edges, 3-second highlight on commit-driven changes (green for new, blue for updates, red + strikethrough for deletes), external labels below each bubble for full filename visibility, and responsive container sizing via ResizeObserver. - Extend RealtimeAlert with optional confidence/evidence fields and add RepoView<T> + buildRepoView() wrapper to separate observed facts from derived UI data. - Add @xyflow/react and d3-force dependencies.
1 parent 22dd7f4 commit 3305820

7 files changed

Lines changed: 1421 additions & 0 deletions

File tree

apps/ui/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@
3939
"@radix-ui/react-toggle": "1.1.2",
4040
"@radix-ui/react-toggle-group": "1.1.2",
4141
"@radix-ui/react-tooltip": "1.1.8",
42+
"@xyflow/react": "^12.10.2",
4243
"canvas-confetti": "1.9.4",
4344
"class-variance-authority": "0.7.1",
4445
"clsx": "2.1.1",
4546
"cmdk": "1.1.1",
47+
"d3-force": "^3.0.0",
4648
"date-fns": "3.6.0",
4749
"embla-carousel-react": "8.6.0",
4850
"input-otp": "1.4.2",
@@ -69,6 +71,7 @@
6971
},
7072
"devDependencies": {
7173
"@tailwindcss/vite": "4.1.12",
74+
"@types/d3-force": "^3.0.10",
7275
"@types/prismjs": "^1.26.6",
7376
"@vitejs/plugin-react": "4.7.0",
7477
"tailwindcss": "4.1.12",

apps/ui/src/app/App.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { CommitTimeline } from "./components/refscope/CommitTimeline";
66
import { DetailPanel } from "./components/refscope/DetailPanel";
77
import { CommandPalette } from "./components/refscope/CommandPalette";
88
import { FileHistoryView } from "./components/refscope/FileHistoryView";
9+
import { LensSwitcher, type LensId } from "./components/refscope/LensSwitcher";
10+
import { ActivityLens } from "./components/refscope/ActivityLens";
911
import {
1012
FileHistoryPrompt,
1113
validatePath,
@@ -123,6 +125,8 @@ function persistRecentFileHistoryPaths(paths: string[]): void {
123125
}
124126

125127
export default function App() {
128+
// Lens switcher state — 'live' is the default; other lenses are placeholders.
129+
const [activeLens, setActiveLens] = useState<LensId>('live');
126130
const [repositories, setRepositories] = useState<Repository[]>([]);
127131
const [refs, setRefs] = useState<GitRef[]>([]);
128132
const [commits, setCommits] = useState<Commit[]>([]);
@@ -1085,7 +1089,21 @@ export default function App() {
10851089
workTreeAvailable={Boolean(selectedRepo)}
10861090
onOpenFileHistory={openFileHistoryPrompt}
10871091
/>
1092+
<LensSwitcher activeLens={activeLens} onLensChange={setActiveLens} />
1093+
{activeLens === 'activity' && (
1094+
<div className="flex-1 overflow-hidden" id="lens-panel-activity" role="tabpanel" aria-labelledby="lens-tab-activity">
1095+
<ActivityLens
1096+
repoId={selectedRepo || null}
1097+
commits={commits}
1098+
selectedCommitHash={selected || null}
1099+
onSelectCommit={(hash) => { setSelected(hash); }}
1100+
/>
1101+
</div>
1102+
)}
1103+
{/* Live lens: render the full existing layout. Hidden (not unmounted) when inactive
1104+
so that SSE subscriptions, hooks, and resizable-panel refs stay alive. */}
10881105
<ResizablePanelGroup
1106+
style={{ display: activeLens === 'live' ? undefined : 'none' }}
10891107
direction="horizontal"
10901108
className="flex flex-1 overflow-hidden"
10911109
onLayout={(layout) => {

0 commit comments

Comments
 (0)