From d52e2fccb0a61a2c91e75d6e76da8c427b3c6422 Mon Sep 17 00:00:00 2001 From: Ethayer_cobank Date: Wed, 21 Jan 2026 11:07:56 -0700 Subject: [PATCH 1/7] refactor: improve case study data mapping from snake_case to camelCase and remove unused demo assets. --- App.tsx | 289 ++++++++++++++++++------------------ components/ArticleView.tsx | 2 +- components/Drawer.tsx | 7 +- components/Navigation.tsx | 29 +--- components/TimelineView.tsx | 53 +++---- index.html | 5 +- index.tsx | 1 - 7 files changed, 180 insertions(+), 206 deletions(-) diff --git a/App.tsx b/App.tsx index 646665a..1890797 100644 --- a/App.tsx +++ b/App.tsx @@ -13,12 +13,11 @@ import { EditorView } from './components/EditorView'; import { ProcessingModal } from './components/ProcessingModal'; import { ProcessingStatus } from './components/ProcessingStatus'; import { SystemHud } from './components/SystemHud'; -import { Icon } from './components/Icon'; import { Toast } from './components/Toast'; import { ManualAssetModal } from './components/ManualAssetModal'; import { analyzeAsset, generateCaseStudy } from './services/geminiService'; import { saveCaseStudy, getCaseStudies } from './services/dbService'; -import { DEMO_STUDIES, DEMO_ASSETS } from './utils/demoData'; +import { DEMO_STUDIES } from './utils/demoData'; const DEFAULT_PREFERENCES: UserPreferences = { theme: 'light', @@ -41,10 +40,10 @@ const App: React.FC = () => { const [selectedArticle, setSelectedArticle] = useState(null); const [isThinkingEnabled, setIsThinkingEnabled] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - + const [processingStep, setProcessingStep] = useState<'analyzing' | 'generating' | 'finalizing'>('analyzing'); const [processingProgress, setProcessingProgress] = useState(0); - + const cancelRef = useRef(false); useEffect(() => { @@ -52,33 +51,33 @@ const App: React.FC = () => { try { const savedStudies = await getCaseStudies(); if (savedStudies && savedStudies.length > 0) { - const mappedStudies: CaseStudy[] = savedStudies.map((s: any) => ({ - id: s.id, - title: s.title, - status: s.status, - date: s.created_at, // Map created_at to date - tags: s.tags || [], - problem: s.problem, - approach: s.approach, - outcome: s.outcome, - nextSteps: s.next_steps, // Map snake_case - seoMetadata: s.seo_metadata || { title: '', description: '', keywords: [] }, // Map snake_case - artifacts: (s.assets || []).map((a: any) => ({ - id: a.id, - originalName: a.original_name, // Map snake_case - aiName: a.ai_name, // Map snake_case - type: a.type, - topic: a.topic, - context: a.context, - variant: a.variant, - version: a.version, - fileType: a.file_type, // Map snake_case - url: a.url, - size: a.size - })) - })); - - setCaseStudies(mappedStudies); + const mappedStudies: CaseStudy[] = savedStudies.map((s: any) => ({ + id: s.id, + title: s.title, + status: s.status, + date: s.created_at, // Map created_at to date + tags: s.tags || [], + problem: s.problem, + approach: s.approach, + outcome: s.outcome, + nextSteps: s.next_steps, // Map snake_case + seoMetadata: s.seo_metadata || { title: '', description: '', keywords: [] }, // Map snake_case + artifacts: (s.assets || []).map((a: any) => ({ + id: a.id, + originalName: a.original_name, // Map snake_case + aiName: a.ai_name, // Map snake_case + type: a.type, + topic: a.topic, + context: a.context, + variant: a.variant, + version: a.version, + fileType: a.file_type, // Map snake_case + url: a.url, + size: a.size + })) + })); + + setCaseStudies(mappedStudies); } } catch (e) { console.error("Failed to load from DB", e); @@ -87,17 +86,17 @@ const App: React.FC = () => { // but typically we'd want DB to be the source of truth if connected. } }; - + // Check if we have Supabase creds/connection before relying entirely? // Since we are "integrated", let's try loading. loadData(); - + // We keep the local storage logic as a secondary check or for "offline" dev // if I wanted to merge them, but simplest is: "If DB has data, use it. Else check local." // However, the current code structure below runs unconditionally. // Let's modify it to only load local if state is empty? // Or just run both and let React handle the updates (DB will usually be slower and overwrite local). - + const saved = localStorage.getItem('devsigner_data_v3'); if (saved) { const parsed = JSON.parse(saved); @@ -107,7 +106,7 @@ const App: React.FC = () => { // Actually, if we just want to see the DB data, we should probably ignore local storage // if we are fully switching to Supabase. // User said "I don't see my records". - + // Let's prevent local storage from overwriting if DB load is in progress? // No, setCaseStudies from loadData will happen later (async) and will overwrite this initial sync set. setCaseStudies(parsed.caseStudies || []); @@ -128,20 +127,20 @@ const App: React.FC = () => { const processFile = async (file: File | Blob, name: string, mimeType: string, index: number, total: number): Promise => { try { let assetData: Partial = {}; - + const fileToProcess = file instanceof File ? file : new File([file], name, { type: mimeType }); if (preferences.autoRename) { assetData = await analyzeAsset(fileToProcess, mimeType || 'text/plain', isThinkingEnabled); } - + if (cancelRef.current) return null; const extension = name.split('.').pop() || 'txt'; const asset: Asset = { id: Math.random().toString(36).substr(2, 9), originalName: name, - aiName: preferences.autoRename + aiName: preferences.autoRename ? `${assetData.topic || 'misc'}-${assetData.type || 'file'}-${assetData.context || 'dev'}-${assetData.variant || 'v1'}-${assetData.version || '1.0'}-${extension}` : name, type: assetData.type || 'unknown', @@ -153,7 +152,7 @@ const App: React.FC = () => { url: (fileToProcess.size < 30000000 && !mimeType.includes('zip')) ? URL.createObjectURL(fileToProcess) : '', size: fileToProcess.size }; - + setProcessingProgress(((index + 1) / total) * 100); return asset; } catch (err: any) { @@ -172,7 +171,7 @@ const App: React.FC = () => { setProcessingStep('analyzing'); setProcessingProgress(0); cancelRef.current = false; - + const assetsToProcess: { file: File | Blob; name: string; type: string }[] = []; for (let i = 0; i < files.length; i++) { @@ -189,10 +188,10 @@ const App: React.FC = () => { for (const zipFileName of zipFiles) { const zipFile = contents.files[zipFileName]; const blob = await zipFile.async('blob'); - assetsToProcess.push({ - file: blob, - name: zipFileName, - type: zipFileName.split('.').pop() || 'txt' + assetsToProcess.push({ + file: blob, + name: zipFileName, + type: zipFileName.split('.').pop() || 'txt' }); } } catch (err) { @@ -215,7 +214,7 @@ const App: React.FC = () => { if (!cancelRef.current) { setAssets(prev => [...prev, ...processedAssets]); } - + setIsUploading(false); setIsMinimized(true); }; @@ -263,7 +262,7 @@ const App: React.FC = () => { }; setProcessingProgress(100); - + // Save draft to Supabase immediately try { const { caseStudy: savedStudy, assets: savedAssets } = await saveCaseStudy(fullStudy, assets); @@ -271,17 +270,17 @@ const App: React.FC = () => { fullStudy.id = savedStudy.id; // Update artifacts with real URLs fullStudy.artifacts = savedAssets.map((a: any) => ({ - id: Math.random().toString(36).substr(2, 9), - originalName: a.original_name, - aiName: a.ai_name, - type: a.type, - topic: a.topic, - context: a.context, - variant: a.variant, - version: a.version, - fileType: a.file_type, - url: a.url, - size: a.size + id: Math.random().toString(36).substr(2, 9), + originalName: a.original_name, + aiName: a.ai_name, + type: a.type, + topic: a.topic, + context: a.context, + variant: a.variant, + version: a.version, + fileType: a.file_type, + url: a.url, + size: a.size })); } catch (e: any) { console.error("Failed to auto-save draft", e); @@ -313,59 +312,59 @@ const App: React.FC = () => { setCaseStudies(prev => prev.map(s => s.id === updatedStudy.id ? updatedStudy : s)); setSelectedArticle(updatedStudy); setView('article'); - + // Save to Supabase // We pass the assets that belong to this study. // In this app structure, 'assets' state seems to be a global "staging" area? // Or 'updatedStudy.artifacts'? // The Type definition says CaseStudy has 'artifacts: Asset[]'. const { caseStudy: savedRecord, assets: savedAssets } = await saveCaseStudy(updatedStudy, updatedStudy.artifacts); - + // Update local state with real ID if it changed (e.g. first save of a draft) if (savedRecord.id !== updatedStudy.id) { - const finalStudy = { - ...updatedStudy, - id: savedRecord.id, - // Update artifacts with the ones returned from DB (containing new URLs) - artifacts: savedAssets.map((a: any) => ({ - id: Math.random().toString(36).substr(2, 9), // DB doesn't return ID immediately in our insert map, but that's ok for now - originalName: a.original_name, - aiName: a.ai_name, - type: a.type, - topic: a.topic, - context: a.context, - variant: a.variant, - version: a.version, - fileType: a.file_type, - url: a.url, - size: a.size - })) - }; - setCaseStudies(prev => prev.map(s => s.id === updatedStudy.id ? finalStudy : s)); - setSelectedArticle(finalStudy); + const finalStudy = { + ...updatedStudy, + id: savedRecord.id, + // Update artifacts with the ones returned from DB (containing new URLs) + artifacts: savedAssets.map((a: any) => ({ + id: Math.random().toString(36).substr(2, 9), // DB doesn't return ID immediately in our insert map, but that's ok for now + originalName: a.original_name, + aiName: a.ai_name, + type: a.type, + topic: a.topic, + context: a.context, + variant: a.variant, + version: a.version, + fileType: a.file_type, + url: a.url, + size: a.size + })) + }; + setCaseStudies(prev => prev.map(s => s.id === updatedStudy.id ? finalStudy : s)); + setSelectedArticle(finalStudy); } else { // Even if ID didn't change, URLs might have (blob -> storage) // We should update the study in place - const finalStudy = { - ...updatedStudy, - artifacts: savedAssets.map((a: any) => ({ - id: Math.random().toString(36).substr(2, 9), - originalName: a.original_name, - aiName: a.ai_name, - type: a.type, - topic: a.topic, - context: a.context, - variant: a.variant, - version: a.version, - fileType: a.file_type, - url: a.url, - size: a.size - })) - }; - setCaseStudies(prev => prev.map(s => s.id === updatedStudy.id ? finalStudy : s)); - setSelectedArticle(finalStudy); + const finalStudy = { + ...updatedStudy, + artifacts: savedAssets.map((a: any) => ({ + id: Math.random().toString(36).substr(2, 9), + originalName: a.original_name, + aiName: a.ai_name, + type: a.type, + topic: a.topic, + context: a.context, + variant: a.variant, + version: a.version, + fileType: a.file_type, + url: a.url, + size: a.size + })) + }; + setCaseStudies(prev => prev.map(s => s.id === updatedStudy.id ? finalStudy : s)); + setSelectedArticle(finalStudy); } - + // Show success toast? (Maybe later) } catch (e: any) { console.error("Failed to save to Supabase", e); @@ -376,32 +375,32 @@ const App: React.FC = () => { const handlePublishStudy = async (study: CaseStudy) => { try { const publishedStudy = { ...study, status: 'published' as const }; - + // Optimistic update setCaseStudies(prev => prev.map(s => s.id === study.id ? publishedStudy : s)); setSelectedArticle(publishedStudy); - + const { caseStudy: savedRecord, assets: savedAssets } = await saveCaseStudy(publishedStudy, study.artifacts); - + // Update local state with real ID - const finalStudy = { - ...publishedStudy, + const finalStudy = { + ...publishedStudy, id: savedRecord.id, artifacts: savedAssets.map((a: any) => ({ - id: Math.random().toString(36).substr(2, 9), - originalName: a.original_name, - aiName: a.ai_name, - type: a.type, - topic: a.topic, - context: a.context, - variant: a.variant, - version: a.version, - fileType: a.file_type, - url: a.url, - size: a.size + id: Math.random().toString(36).substr(2, 9), + originalName: a.original_name, + aiName: a.ai_name, + type: a.type, + topic: a.topic, + context: a.context, + variant: a.variant, + version: a.version, + fileType: a.file_type, + url: a.url, + size: a.size })) }; - + setCaseStudies(prev => prev.map(s => s.id === study.id ? finalStudy : s)); setSelectedArticle(finalStudy); @@ -437,20 +436,20 @@ const App: React.FC = () => { }; return ( -
+
{ - setView(v); - setSelectedArticle(null); + activeView={view === 'editor' ? 'article' : view} + onViewChange={(v) => { + setView(v); + setSelectedArticle(null); setIsUploadOpen(false); setIsSettingsOpen(false); - }} + }} /> - +
- 0} @@ -458,39 +457,39 @@ const App: React.FC = () => { onToggleSettings={toggleSettings} onBack={view === 'article' || view === 'editor' ? () => { setView('timeline'); setSelectedArticle(null); } : undefined} /> - -
-
+ +
+
{view === 'timeline' && ( - acc + curr.artifacts.length, 0)} onSelectStudy={(study) => { setSelectedArticle(study); setView('article'); }} /> )} - + {view === 'article' && selectedArticle && ( - setView('timeline')} + setView('timeline')} onEdit={(study) => { setSelectedArticle(study); setView('editor'); }} onPublish={handlePublishStudy} /> )} {view === 'editor' && selectedArticle && ( - setView('article')} /> )} -
+
v1.0.0-alpha // OFFLINE CACHE: OK -
+
@@ -498,7 +497,7 @@ const App: React.FC = () => { {isUploadOpen ? ( - { onOpenManualModal={() => setIsManualModalOpen(true)} /> ) : isSettingsOpen ? ( - { localStorage.removeItem('devsigner_data_v3'); window.location.reload(); }} + onClearData={() => { localStorage.removeItem('devsigner_data_v3'); window.location.reload(); }} /> ) : null} @@ -526,7 +525,7 @@ const App: React.FC = () => {
{isUploading && isMinimized && !isUploadOpen && ( - setIsMinimized(false)} @@ -536,7 +535,7 @@ const App: React.FC = () => {
{isUploading && !isMinimized && ( - { )} {isManualModalOpen && ( - setAssets(prev => [...prev, asset])} onClose={() => setIsManualModalOpen(false)} /> )} {errorMessage && ( - setErrorMessage(null)} + setErrorMessage(null)} type="error" /> )} diff --git a/components/ArticleView.tsx b/components/ArticleView.tsx index f812461..b0a792f 100644 --- a/components/ArticleView.tsx +++ b/components/ArticleView.tsx @@ -187,7 +187,7 @@ Generated by DEVSIGNER LOG

Evidence_Artifacts

- {study.artifacts.map((a, idx) => ( + {study.artifacts.map((a) => (
diff --git a/components/Drawer.tsx b/components/Drawer.tsx index 02a1ad7..1e3fab5 100644 --- a/components/Drawer.tsx +++ b/components/Drawer.tsx @@ -9,10 +9,9 @@ interface DrawerProps { export const Drawer: React.FC = ({ isOpen, children, width = 'w-[450px]' }) => { return ( -