diff --git a/.gitignore b/.gitignore index 4c4563d..e6b98d0 100644 --- a/.gitignore +++ b/.gitignore @@ -100,9 +100,10 @@ TESTSPRITE_GUIDE.md # AI & LLM Agents .gemini/ -# Testing Caches -.pytest_cache/ -.mypy_cache/ - -# Un-ignore the frontend lib folder which was caught by the Python lib/ rule -!frontend/src/lib/ \ No newline at end of file +# AI Agent Reports & Metadata +.agents/ +impeccable.md +.impeccable.md +audit_report.md +critique_report.md +Team_T8_report_*.pdf \ No newline at end of file diff --git a/Team_T8_report_20260318.pdf b/Team_T8_report_20260318.pdf deleted file mode 100644 index d73f688..0000000 Binary files a/Team_T8_report_20260318.pdf and /dev/null differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f04a7e8..646190b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -312,7 +312,7 @@ export default function App() {
-
+
-
-
-
-

Overview

-

Protein Structure

-
-
-
- 🕐 - 24H - 📅 - - {new Date().toLocaleDateString('en-US', { - day: 'numeric', - month: 'long', - year: 'numeric', - })} - -
- -
+
+
+
+ + +
-
-
-
- - - +
+
+
+
+

Overview

+

Protein Structure

+
+
+
+ 🕐 + 24H + 📅 + + {new Date().toLocaleDateString('en-US', { + day: 'numeric', + month: 'long', + year: 'numeric', + })} + +
+
- -
handlePredict()} + onPredict={handlePredict} status={status} /> @@ -392,7 +382,6 @@ export default function App() { />
- )} {/* ========== DISCOVERY VIEW ========== */} @@ -425,35 +414,33 @@ export default function App() { {/* ========== ANALYSIS VIEW ========== */} {view === 'analysis' && ( - <> -
-
-
-

{selectedProtein?.proteinName || 'Analyzing Protein…'}

-

- {selectedProtein - ? `${selectedProtein.accession} · ${selectedProtein.organism}` - : 'Fetching protein data…'} -

-
-
- -
+
+
+
+ + +
-
-
-
- - - +
+
+
+
+

{selectedProtein?.proteinName || 'Analyzing Protein…'}

+

+ {selectedProtein + ? `${selectedProtein.accession} · ${selectedProtein.organism}` + : 'Fetching protein data…'} +

+
+
+ +
- -
@@ -474,9 +461,8 @@ export default function App() {
- )} -
+
diff --git a/frontend/src/components/MolViewer.jsx b/frontend/src/components/MolViewer.jsx index 1c741bb..fe5c0d4 100644 --- a/frontend/src/components/MolViewer.jsx +++ b/frontend/src/components/MolViewer.jsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, useCallback } from 'react'; +import { useEffect, useRef, useState, useCallback, memo } from 'react'; const VIZ_STYLES = [ { key: 'cartoon', label: 'Cartoon', icon: '🎨' }, @@ -7,7 +7,7 @@ const VIZ_STYLES = [ { key: 'surface', label: 'Surface', icon: '◐' }, ]; -export default function MolViewer({ pdbData, status }) { +export default memo(function MolViewer({ pdbData, status }) { const containerRef = useRef(null); const viewerRef = useRef(null); const [vizStyle, setVizStyle] = useState('cartoon'); @@ -117,6 +117,7 @@ export default function MolViewer({ pdbData, status }) { key={s.key} className={`mol-viewer__style-btn${vizStyle === s.key ? ' mol-viewer__style-btn--active' : ''}`} title={s.label} + aria-label={s.label} onClick={() => { setVizStyle(s.key); applyStyle(s.key); @@ -131,6 +132,7 @@ export default function MolViewer({ pdbData, status }) {
); -} +}); diff --git a/frontend/src/components/PldtMetrics.jsx b/frontend/src/components/PldtMetrics.jsx index 5e555ea..ba29b23 100644 --- a/frontend/src/components/PldtMetrics.jsx +++ b/frontend/src/components/PldtMetrics.jsx @@ -86,7 +86,7 @@ export default function PldtMetrics({ plddtData, sequence }) {
❤️ @@ -120,7 +120,7 @@ export default function PldtMetrics({ plddtData, sequence }) { padding: '2px 10px', fontWeight: 700, fontSize: 13, - color: 'var(--navy)', + color: 'var(--text-primary)', }} > {mean !== null ? mean.toFixed(2) : '—'} diff --git a/frontend/src/components/ProteinMetrics.jsx b/frontend/src/components/ProteinMetrics.jsx index 751cc6d..f2ae3d8 100644 --- a/frontend/src/components/ProteinMetrics.jsx +++ b/frontend/src/components/ProteinMetrics.jsx @@ -10,7 +10,7 @@ export default function ProteinMetrics({ plddtData, seqLength }) {
📊 @@ -81,7 +81,7 @@ export default function ProteinMetrics({ plddtData, seqLength }) { cy="60" r="50" fill="none" - stroke="var(--navy)" + stroke="var(--text-primary)" strokeWidth="8" strokeLinecap="round" strokeDasharray={`${(plddtData.mean / 100) * 314} 314`} @@ -95,7 +95,7 @@ export default function ProteinMetrics({ plddtData, seqLength }) { textAnchor="middle" fontSize="22" fontWeight="800" - fill="var(--navy)" + fill="var(--text-primary)" > {plddtData ? Math.round(plddtData.mean) : '—'} diff --git a/frontend/src/components/SequenceInput.jsx b/frontend/src/components/SequenceInput.jsx index 16ccdf3..98cdb18 100644 --- a/frontend/src/components/SequenceInput.jsx +++ b/frontend/src/components/SequenceInput.jsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useMemo, useState, memo } from 'react'; const defaultAminoAcids = 'A,C,D,E,F,G,H,I,K,L,M,N,P,Q,R,S,T,V,W,Y'; const aminoAcidsStr = (import.meta.env.VITE_VALID_AMINO_ACIDS || defaultAminoAcids).replace( @@ -7,20 +7,33 @@ const aminoAcidsStr = (import.meta.env.VITE_VALID_AMINO_ACIDS || defaultAminoAci ); const VALID_AMINO_ACIDS = new Set(aminoAcidsStr.split('')); -export default function SequenceInput({ sequence, setSequence, onPredict, status }) { +const SequenceInput = memo(function SequenceInput({ sequence: externalSeq, setSequence: setExternalSeq, onPredict, status }) { + const [localSeq, setLocalSeq] = useState(externalSeq || ''); + const [prevExternalSeq, setPrevExternalSeq] = useState(externalSeq); + + if (externalSeq !== prevExternalSeq) { + setPrevExternalSeq(externalSeq); + setLocalSeq(externalSeq || ''); + } + const isLoading = status === 'processing'; - const charCount = sequence.trim().length; + const charCount = localSeq.trim().length; const invalidChars = useMemo(() => { const bad = new Set(); - for (const ch of sequence) { + for (const ch of localSeq) { if (!VALID_AMINO_ACIDS.has(ch)) bad.add(ch); } return [...bad].sort(); - }, [sequence]); + }, [localSeq]); const hasInvalid = invalidChars.length > 0; + const handlePredictClicked = () => { + setExternalSeq(localSeq); + onPredict(localSeq); + }; + return (
@@ -40,7 +53,7 @@ export default function SequenceInput({ sequence, setSequence, onPredict, status onClick={async () => { try { const text = await navigator.clipboard.readText(); - if (text) setSequence(text.toUpperCase().replace(/[^A-Z]/g, '')); + if (text) setLocalSeq(text.toUpperCase().replace(/[^A-Z]/g, '')); } catch { /* clipboard permission denied */ } @@ -60,7 +73,7 @@ export default function SequenceInput({ sequence, setSequence, onPredict, status -
@@ -71,8 +84,8 @@ export default function SequenceInput({ sequence, setSequence, onPredict, status id="sequence-textarea" className="sequence-input__textarea" placeholder="Paste your amino-acid sequence here (single letter codes: A, C, D, E, F, G, H, I, K, L, M, N, P, Q, R, S, T, V, W, Y)…" - value={sequence} - onChange={(e) => setSequence(e.target.value.toUpperCase().replace(/[^A-Z]/g, ''))} + value={localSeq} + onChange={(e) => setLocalSeq(e.target.value.toUpperCase().replace(/[^A-Z]/g, ''))} spellCheck={false} style={hasInvalid ? { borderColor: 'var(--warning)' } : undefined} /> @@ -104,7 +117,7 @@ export default function SequenceInput({ sequence, setSequence, onPredict, status
); -} +}); + +export default SequenceInput; diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index 6b0daa1..ea0bb80 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -24,6 +24,7 @@ export default function Sidebar({ activeView, onViewChange, user, onSignOut }) { - - - @@ -130,7 +131,7 @@ export default function TopBar({
)} -