diff --git a/claudash/src/App.css b/claudash/src/App.css
index 3504db9..840beb3 100644
--- a/claudash/src/App.css
+++ b/claudash/src/App.css
@@ -27,7 +27,6 @@ body {
h1, h2, h3, h4, h5, h6 {
font-family: "Russo One", sans-serif !important;
- font-size:1.2rem;
}
/* App theme containers */
.app-dark {
@@ -59,7 +58,6 @@ h1, h2, h3, h4, h5, h6 {
flex-grow: 2;
}
.widget-row.condensed {
- flex-wrap: wrap;
justify-content: space-evenly;
}
.news-widget img {
diff --git a/claudash/src/components/Dashboard.jsx b/claudash/src/components/Dashboard.jsx
index c947b7a..f7820c2 100644
--- a/claudash/src/components/Dashboard.jsx
+++ b/claudash/src/components/Dashboard.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useRef } from 'react';
import { Box, Container, CircularProgress, IconButton, Alert } from '@mui/material';
import { Brightness4, Brightness7, Refresh } from '@mui/icons-material';
import { fetchAllSources } from '../services/dataFetcher';
@@ -18,15 +18,27 @@ function Dashboard({ themeToggle, isDark }) {
const [refreshing, setRefreshing] = useState(false);
const [tokenUsage, setTokenUsage] = useState(null);
const [cacheInfo, setCacheInfo] = useState(null);
+ const isLoadingRef = useRef(false);
useEffect(() => {
+ console.log('=== Dashboard mounting, calling loadDashboard ===');
loadDashboard();
}, []);
async function loadDashboard(forceRefresh = false) {
+ // Prevent concurrent requests using ref for immediate check
+ if (isLoadingRef.current) {
+ console.log('=== loadDashboard SKIPPED - already in progress ===');
+ console.log('Attempted at:', new Date().toISOString());
+ return;
+ }
+
+ isLoadingRef.current = true;
+
try {
console.log('=== loadDashboard called ===');
console.log('Force refresh:', forceRefresh);
+ console.log('Request timestamp:', new Date().toISOString());
setError(null);
// Check if we already have today's briefing (unless forcing refresh)
@@ -126,11 +138,15 @@ function Dashboard({ themeToggle, isDark }) {
} finally {
setLoading(false);
setRefreshing(false);
+ isLoadingRef.current = false;
+ console.log('=== loadDashboard completed at:', new Date().toISOString(), '===');
}
}
const handleRefresh = () => {
setRefreshing(true);
+ setLoading(true);
+ setLoadingStatus('Refreshing your dashboard...');
loadDashboard(true);
};
@@ -221,17 +237,28 @@ function Dashboard({ themeToggle, isDark }) {
/>
- {/* Render rows based on the new data structure */}
+ {/* Render rows based on the data structure */}
{Object.entries(sourceData).map(([key, data]) => {
- // Skip special entries like weather and legacy flat entries
- if (key === 'weather' || key.startsWith('reddit_') || key.startsWith('rss_') || key.startsWith('source_')) {
+
+ // Skip non-array entries (like weather)
+ if (!Array.isArray(data) || data.length === 0) {
return null;
}
- // Only process row entries (row1, row2, etc.)
- if (key.startsWith('row') && Array.isArray(data)) {
- return (
-
+ return (
+
+ {/* Display row header if not using legacy row naming */}
+ {!key.startsWith('row') && (
+
+ {key}
+
+ )}
+
{data.map((widgetData, index) => (
))}
- );
- }
-
- return null;
+
+ );
})}
-
- {/* Fallback for legacy format - if no rows detected, render flat structure */}
- {!Object.keys(sourceData).some(k => k.startsWith('row')) && (
-
- {Object.entries(sourceData).map(([key, data]) => {
- if (key === 'weather' || !data) return null;
- return (
-
- );
- })}
-
- )}
diff --git a/claudash/src/main.jsx b/claudash/src/main.jsx
index 6161d18..1f58390 100644
--- a/claudash/src/main.jsx
+++ b/claudash/src/main.jsx
@@ -3,7 +3,5 @@ import ReactDOM from 'react-dom/client'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
-
-
- ,
-)
\ No newline at end of file
+
+)
diff --git a/claudash/src/services/claudeService.js b/claudash/src/services/claudeService.js
index a0e9e47..ecab7b8 100644
--- a/claudash/src/services/claudeService.js
+++ b/claudash/src/services/claudeService.js
@@ -123,7 +123,15 @@ CURRENT DATA:
- Includes relevant weather information if available
- Ends with something engaging or thought-provoking
-Focus on the most newsworthy and interesting items. Don't try to mention everything.`;
+Focus on the most newsworthy and interesting items. Don't try to mention everything.
+
+IMPORTANT: Provide your response as valid JSON with exactly two fields:
+{
+ "fullBriefing": "Your complete 300-400 word friendly briefing text here",
+ "condensedSummary": "A 50-75 word ultra-condensed summary of the key topics and main stories for historical context"
+}
+
+The JSON must be properly formatted with escaped quotes where necessary.`;
return prompt;
}
@@ -171,13 +179,46 @@ export async function generateBriefing(currentData, previousBriefing) {
const prompt = buildBriefingPrompt(currentData, previousBriefing);
const result = await callClaude(prompt);
- // Generate condensed summary for historical storage
- const condensedSummary = await generateCondensedSummary(result.text);
-
- return {
- ...result,
- condensedSummary: condensedSummary
- };
+ // Parse JSON response that now includes both fullBriefing and condensedSummary
+ try {
+ // Strip markdown code blocks if present
+ let jsonText = result.text;
+ if (jsonText.includes('```json')) {
+ jsonText = jsonText.replace(/```json\s*/g, '').replace(/```\s*/g, '');
+ } else if (jsonText.includes('```')) {
+ jsonText = jsonText.replace(/```\s*/g, '');
+ }
+ jsonText = jsonText.trim();
+
+ const parsed = JSON.parse(jsonText);
+
+ // Validate the JSON structure
+ if (!parsed.fullBriefing || !parsed.condensedSummary) {
+ throw new Error('Invalid response structure from Claude');
+ }
+
+ console.log('Successfully parsed briefing with condensed summary');
+ console.log('Condensed summary:', parsed.condensedSummary.substring(0, 100) + '...');
+
+ return {
+ text: parsed.fullBriefing,
+ condensedSummary: parsed.condensedSummary,
+ usage: result.usage
+ };
+ } catch (parseError) {
+ console.error('Failed to parse JSON response, attempting fallback:', parseError);
+
+ // Fallback: if JSON parsing fails, use the raw text and generate summary separately
+ // This maintains backward compatibility but should rarely happen
+ console.warn('Using fallback: generating condensed summary separately');
+ const condensedSummary = await generateCondensedSummary(result.text);
+
+ return {
+ text: result.text,
+ condensedSummary: condensedSummary,
+ usage: result.usage
+ };
+ }
} catch (error) {
console.error('Error generating briefing:', error);