···2121 Legend
2222);
23232424-const ActivityChart = ({ records, collections }) => {
2424+const ActivityChart = ({ records, collections, loading = false }) => {
2525 const [timePeriod, setTimePeriod] = useState('7days');
2626 const [chartData, setChartData] = useState({
2727 labels: [],
···312312 }
313313 }
314314 };
315315+316316+ // Show loading state when fetching records deeply
317317+ if (loading) {
318318+ return (
319319+ <div className="activity-chart-container">
320320+ <div className="activity-chart-loading">
321321+ <div className="chart-loading-spinner"></div>
322322+ <p>Loading record data for visualization...</p>
323323+ <p className="chart-loading-note">This may take a moment for accounts with many records</p>
324324+ </div>
325325+ </div>
326326+ );
327327+ }
315328316329 // Ensure records array exists and has items
317330 if (!records || records.length === 0) {
+142-59
src/components/CollectionsFeed/CollectionsFeed.js
···2323 const [allRecordsForChart, setAllRecordsForChart] = useState([]); // All records for chart visualization
2424 const [loading, setLoading] = useState(false);
2525 const [initialLoad, setInitialLoad] = useState(true);
2626+ const [chartLoading, setChartLoading] = useState(false); // Separate loading state for chart data
2627 const [error, setError] = useState('');
2728 const [collectionCursors, setCollectionCursors] = useState({});
2829 const [fetchingMore, setFetchingMore] = useState(false);
···181182182183 // Array to store all fetched records
183184 let allRecords = isLoadMore ? [...records] : [];
185185+ let allChartRecords = isLoadMore ? [...allRecordsForChart] : [];
184186 const newCursors = { ...collectionCursors };
185187186188 // Calculate a cutoff date (90 days ago by default for chart visualization)
187189 const cutoffDate = new Date();
188190 cutoffDate.setDate(cutoffDate.getDate() - 90); // 3 months
189191190190- // Fetch records for each collection in parallel
191191- const fetchPromises = collectionsList.map(async (collection) => {
192192- // Fetch up to 100 records per collection to ensure we get a good sample
193193- let url = `${endpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(userDid)}&collection=${encodeURIComponent(collection)}&limit=100`;
192192+ // Track if this is the initial load for charting purposes
193193+ const isInitialLoad = !isLoadMore && allRecordsForChart.length === 0;
194194+195195+ // Set chart loading state if we're doing deep pagination for the chart
196196+ if (isInitialLoad) {
197197+ setChartLoading(true);
198198+ }
199199+200200+ // Fetch records for each collection
201201+ for (const collection of collectionsList) {
202202+ let hasMoreRecords = true;
203203+ let cursor = isLoadMore ? newCursors[collection] : null;
204204+ let pageCount = 0;
205205+ let collectionRecords = [];
206206+ let reachedCutoff = false;
194207195195- // Add cursor if loading more and we have a cursor for this collection
196196- if (isLoadMore && newCursors[collection]) {
197197- url += `&cursor=${encodeURIComponent(newCursors[collection])}`;
198198- }
208208+ // For initial load, we need to do deep pagination to get all data for charting
209209+ // For load more, we just get the next page
210210+ const maxPages = isInitialLoad ? 50 : 1; // Limit for safety
199211200200- const response = await fetch(url);
201201-202202- if (!response.ok) {
203203- console.error(`Error fetching records for ${collection}: ${response.statusText}`);
204204- return [];
212212+ while (hasMoreRecords && pageCount < maxPages && !reachedCutoff) {
213213+ // Fetch up to 100 records per page
214214+ let url = `${endpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(userDid)}&collection=${encodeURIComponent(collection)}&limit=100`;
215215+216216+ // Add cursor if we have one
217217+ if (cursor) {
218218+ url += `&cursor=${encodeURIComponent(cursor)}`;
219219+ }
220220+221221+ console.log(`Fetching ${collection} page ${pageCount + 1}${cursor ? ' with cursor' : ''}`);
222222+223223+ const response = await fetch(url);
224224+225225+ if (!response.ok) {
226226+ console.error(`Error fetching records for ${collection}: ${response.statusText}`);
227227+ break;
228228+ }
229229+230230+ const data = await response.json();
231231+ pageCount++;
232232+233233+ // Process records from this page
234234+ if (data.records && data.records.length > 0) {
235235+ const processedRecords = data.records.map(record => {
236236+ const contentTimestamp = extractTimestamp(record);
237237+ const rkey = record.uri.split('/').pop();
238238+ const rkeyTimestamp = tidToTimestamp(rkey);
239239+240240+ return {
241241+ ...record,
242242+ collection,
243243+ collectionType: record.value?.$type || collection,
244244+ contentTimestamp,
245245+ rkeyTimestamp,
246246+ rkey,
247247+ };
248248+ });
249249+250250+ // Check if we've reached older records
251251+ if (isInitialLoad) {
252252+ // For chart data, determine if any records are beyond our cutoff
253253+ const oldestRecordTime = processedRecords.reduce((oldest, record) => {
254254+ const timestamp = record.contentTimestamp || record.rkeyTimestamp;
255255+ if (!timestamp) return oldest;
256256+257257+ const recordTime = new Date(timestamp).getTime();
258258+ return recordTime < oldest ? recordTime : oldest;
259259+ }, Date.now());
260260+261261+ // If the oldest record on this page is older than our cutoff, we can stop
262262+ if (oldestRecordTime < cutoffDate.getTime()) {
263263+ reachedCutoff = true;
264264+ console.log(`Reached cutoff date for ${collection} on page ${pageCount}`);
265265+266266+ // Filter records from this page to only include those after cutoff
267267+ const filteredRecords = processedRecords.filter(record => {
268268+ const timestamp = record.contentTimestamp || record.rkeyTimestamp;
269269+ if (!timestamp) return false;
270270+ return new Date(timestamp) >= cutoffDate;
271271+ });
272272+273273+ collectionRecords.push(...filteredRecords);
274274+ } else {
275275+ // All records on this page are within our date range
276276+ collectionRecords.push(...processedRecords);
277277+ }
278278+ } else {
279279+ // For regular timeline browsing, just add all records
280280+ collectionRecords.push(...processedRecords);
281281+ }
282282+ }
283283+284284+ // Check if there are more pages
285285+ if (data.cursor) {
286286+ cursor = data.cursor;
287287+ } else {
288288+ hasMoreRecords = false;
289289+ }
290290+291291+ // If we're not doing initial load for chart, break after first page
292292+ if (!isInitialLoad) {
293293+ break;
294294+ }
205295 }
206296207207- const data = await response.json();
208208-209209- // Save cursor for next pagination
210210- if (data.cursor) {
211211- newCursors[collection] = data.cursor;
297297+ // Save the cursor for this collection for future pagination
298298+ if (cursor) {
299299+ newCursors[collection] = cursor;
212300 } else {
213301 // No more records for this collection
214302 delete newCursors[collection];
215303 }
216304217217- // Process and format records
218218- return data.records.map(record => {
219219- const contentTimestamp = extractTimestamp(record);
220220- const rkey = record.uri.split('/').pop();
221221- const rkeyTimestamp = tidToTimestamp(rkey);
222222-223223- return {
224224- ...record,
225225- collection,
226226- collectionType: record.value?.$type || collection,
227227- contentTimestamp,
228228- rkeyTimestamp,
229229- // We'll decide which timestamp to use when filtering/sorting
230230- rkey,
231231- };
232232- });
233233- });
305305+ // Add this collection's records to our overall array
306306+ if (isInitialLoad) {
307307+ // For chart initialization, add all records to chart data
308308+ allChartRecords = [...allChartRecords, ...collectionRecords];
309309+ } else if (isLoadMore) {
310310+ // For load more, only add to both arrays if within display limit
311311+ allChartRecords = [...allChartRecords, ...collectionRecords];
312312+ allRecords = [...allRecords, ...collectionRecords];
313313+ } else {
314314+ // For regular display, add to both
315315+ allChartRecords = [...allChartRecords, ...collectionRecords];
316316+ allRecords = [...allRecords, ...collectionRecords];
317317+ }
318318+ }
234319235235- // Wait for all fetch operations to complete
236236- const collectionRecords = await Promise.all(fetchPromises);
237237-238238- // Combine and flatten records from all collections
239239- collectionRecords.forEach(records => {
240240- allRecords = [...allRecords, ...records];
241241- });
242242-243243- // Filter and sort records based on the selected timestamp source
244244- allRecords = allRecords.filter(record => {
320320+ // Filter and sort all records based on the selected timestamp source
321321+ const filteredChartRecords = allChartRecords.filter(record => {
245322 if (useRkeyTimestamp) {
246246- // When using rkey timestamps, include all records (all valid TIDs should have timestamps)
247323 return record.rkeyTimestamp !== null;
248324 } else {
249249- // When using content timestamps, only include records with valid content timestamps
250325 return record.contentTimestamp !== null;
251326 }
252252- }).sort((a, b) => {
253253- // Sort based on the selected timestamp type
327327+ });
328328+329329+ // Sort by timestamp (newest first)
330330+ const sortedChartRecords = [...filteredChartRecords].sort((a, b) => {
254331 const aTime = useRkeyTimestamp ? a.rkeyTimestamp : a.contentTimestamp;
255332 const bTime = useRkeyTimestamp ? b.rkeyTimestamp : b.contentTimestamp;
256256-257257- return new Date(bTime) - new Date(aTime); // Newest first
333333+ return new Date(bTime) - new Date(aTime);
258334 });
259335260260- // We'll keep all records for charting purposes, but only show the most recent ones in the timeline
261261- const chartRecords = [...allRecords];
262262-263263- // If not loading more, limit to 20 most recent records for initial display in timeline
336336+ // For timeline display, limit records to most recent ones
337337+ let displayRecords = [...sortedChartRecords];
264338 if (!isLoadMore) {
265265- allRecords = allRecords.slice(0, 20);
339339+ displayRecords = displayRecords.slice(0, 20);
266340 }
267341268268- // Update state with both the display records and the full set for charting
269269- setRecords(allRecords);
270270- setAllRecordsForChart(prev => isLoadMore ? [...prev, ...chartRecords] : chartRecords);
342342+ console.log(`Fetched total of ${sortedChartRecords.length} records for chart, displaying ${displayRecords.length}`);
343343+344344+ // Update state
345345+ setRecords(displayRecords);
346346+ setAllRecordsForChart(sortedChartRecords);
271347 setCollectionCursors(newCursors);
272348 setFetchingMore(false);
349349+350350+ // Turn off chart loading if it was on
351351+ if (chartLoading) {
352352+ setChartLoading(false);
353353+ }
273354 } catch (err) {
274355 console.error('Error fetching collection records:', err);
275356 setError('Failed to fetch records. Please try again.');
276357 setFetchingMore(false);
358358+ setChartLoading(false); // Make sure we turn off chart loading on error
277359 }
278360 };
279361···392474 <ActivityChart
393475 records={allRecordsForChart}
394476 collections={collections}
477477+ loading={chartLoading}
395478 />
396479397480 <div className="feed-controls">