f27c73b469
* feat: Enhance session and summary handling - Update SQL query in SessionStore to exclude null or empty projects. - Add 'investigated' field to Summary interface for better tracking. - Modify App component to handle pagination more efficiently based on current filters. - Update SummaryCard to display the 'investigated' field if present. - Refactor usePagination hook to reset pagination state when filters change. - Adjust mergeAndDeduplicateByProject function to ensure it only merges unfiltered data. * refactor: address PR feedback - remove redundancies and fix dependency cycles Fixes based on PR #70 review feedback: Required: - Fixed useEffect dependency cycle in App.tsx (removed handleLoadMore from deps) Recommended: - Removed redundant filter detection from App.tsx (usePagination handles this) - Removed redundant stateRef update effect from usePagination.ts - Simplified mergeAndDeduplicateByProject by removing defensive validation - Removed unused imports (useRef, useEffect) All changes follow CLAUDE.md principles: DRY, fail-fast, no defensive programming. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Reset paginated data arrays when filter changes Critical fix for data mixing bug identified in PR review follow-up. Problem: When filter changes, usePagination correctly resets its offset to 0, but the paginated state arrays (observations, summaries, prompts) were NOT being reset. This caused data from different projects to mix together because setState appends to the existing array. Example: - User views Project A: [A1, A2, A3] - User switches to Project B - API fetches [B1, B2, B3] - setState does: [...prev, ...new] = [A1, A2, A3, B1, B2, B3] ❌ Solution: Added a separate useEffect that resets all three paginated arrays when currentFilter changes. This happens BEFORE handleLoadMore fetches new data, ensuring clean state for the new filter. Files changed: - src/ui/viewer/App.tsx: Added useEffect to reset arrays on filter change - plugin/ui/viewer-bundle.js: Built UI bundle Testing: 1. Select Project A, verify data loads 2. Switch to Project B 3. Verify ONLY Project B data is shown (no mixing) 4. Switch back to "All Projects" 5. Verify all data appears correctly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: Combine filter change useEffect hooks for guaranteed order Merged two separate useEffect hooks into one to: 1. Guarantee execution order (reset THEN load) 2. Reduce complexity (one hook instead of two) 3. Make intent clearer with single comment Before: - useEffect #1: handleLoadMore() on filter change - useEffect #2: Reset arrays on filter change - React could run these in any order After: - Single useEffect: Reset arrays THEN handleLoadMore() - Execution order is now guaranteed Files changed: - src/ui/viewer/App.tsx: Combined useEffect hooks - plugin/ui/viewer-bundle.js: Built UI bundle 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
125 lines
4.5 KiB
TypeScript
125 lines
4.5 KiB
TypeScript
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
import { Header } from './components/Header';
|
|
import { Feed } from './components/Feed';
|
|
import { Sidebar } from './components/Sidebar';
|
|
import { useSSE } from './hooks/useSSE';
|
|
import { useSettings } from './hooks/useSettings';
|
|
import { useStats } from './hooks/useStats';
|
|
import { usePagination } from './hooks/usePagination';
|
|
import { useTheme } from './hooks/useTheme';
|
|
import { Observation, Summary, UserPrompt } from './types';
|
|
import { mergeAndDeduplicateByProject } from './utils/data';
|
|
|
|
export function App() {
|
|
const [currentFilter, setCurrentFilter] = useState('');
|
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
const [paginatedObservations, setPaginatedObservations] = useState<Observation[]>([]);
|
|
const [paginatedSummaries, setPaginatedSummaries] = useState<Summary[]>([]);
|
|
const [paginatedPrompts, setPaginatedPrompts] = useState<UserPrompt[]>([]);
|
|
|
|
const { observations, summaries, prompts, projects, isProcessing, isConnected } = useSSE();
|
|
const { settings, saveSettings, isSaving, saveStatus } = useSettings();
|
|
const { stats } = useStats();
|
|
const { preference, resolvedTheme, setThemePreference } = useTheme();
|
|
const pagination = usePagination(currentFilter);
|
|
|
|
// When filtering by project: ONLY use paginated data (API-filtered)
|
|
// When showing all projects: merge SSE live data with paginated data
|
|
const allObservations = useMemo(() => {
|
|
if (currentFilter) {
|
|
// Project filter active: API handles filtering, ignore SSE items
|
|
return paginatedObservations;
|
|
}
|
|
// No filter: merge SSE + paginated, deduplicate by ID
|
|
return mergeAndDeduplicateByProject(observations, paginatedObservations);
|
|
}, [observations, paginatedObservations, currentFilter]);
|
|
|
|
const allSummaries = useMemo(() => {
|
|
if (currentFilter) {
|
|
return paginatedSummaries;
|
|
}
|
|
return mergeAndDeduplicateByProject(summaries, paginatedSummaries);
|
|
}, [summaries, paginatedSummaries, currentFilter]);
|
|
|
|
const allPrompts = useMemo(() => {
|
|
if (currentFilter) {
|
|
return paginatedPrompts;
|
|
}
|
|
return mergeAndDeduplicateByProject(prompts, paginatedPrompts);
|
|
}, [prompts, paginatedPrompts, currentFilter]);
|
|
|
|
// Toggle sidebar
|
|
const toggleSidebar = useCallback(() => {
|
|
setSidebarOpen(prev => !prev);
|
|
}, []);
|
|
|
|
// Handle loading more data
|
|
const handleLoadMore = useCallback(async () => {
|
|
try {
|
|
const [newObservations, newSummaries, newPrompts] = await Promise.all([
|
|
pagination.observations.loadMore(),
|
|
pagination.summaries.loadMore(),
|
|
pagination.prompts.loadMore()
|
|
]);
|
|
|
|
if (newObservations.length > 0) {
|
|
setPaginatedObservations(prev => [...prev, ...newObservations]);
|
|
}
|
|
if (newSummaries.length > 0) {
|
|
setPaginatedSummaries(prev => [...prev, ...newSummaries]);
|
|
}
|
|
if (newPrompts.length > 0) {
|
|
setPaginatedPrompts(prev => [...prev, ...newPrompts]);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load more data:', error);
|
|
}
|
|
}, [currentFilter, pagination.observations, pagination.summaries, pagination.prompts]);
|
|
|
|
// Reset paginated data and load first page when filter changes
|
|
useEffect(() => {
|
|
setPaginatedObservations([]);
|
|
setPaginatedSummaries([]);
|
|
setPaginatedPrompts([]);
|
|
handleLoadMore();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [currentFilter]);
|
|
|
|
return (
|
|
<div className="container">
|
|
<div className="main-col">
|
|
<Header
|
|
isConnected={isConnected}
|
|
projects={projects}
|
|
currentFilter={currentFilter}
|
|
onFilterChange={setCurrentFilter}
|
|
onSettingsToggle={toggleSidebar}
|
|
sidebarOpen={sidebarOpen}
|
|
isProcessing={isProcessing}
|
|
themePreference={preference}
|
|
onThemeChange={setThemePreference}
|
|
/>
|
|
<Feed
|
|
observations={allObservations}
|
|
summaries={allSummaries}
|
|
prompts={allPrompts}
|
|
onLoadMore={handleLoadMore}
|
|
isLoading={pagination.observations.isLoading || pagination.summaries.isLoading || pagination.prompts.isLoading}
|
|
hasMore={pagination.observations.hasMore || pagination.summaries.hasMore || pagination.prompts.hasMore}
|
|
/>
|
|
</div>
|
|
|
|
<Sidebar
|
|
isOpen={sidebarOpen}
|
|
settings={settings}
|
|
stats={stats}
|
|
isSaving={isSaving}
|
|
saveStatus={saveStatus}
|
|
isConnected={isConnected}
|
|
onSave={saveSettings}
|
|
onClose={toggleSidebar}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|