All files / src/components JournalSidebar.jsx

17.39% Statements 4/23
45.45% Branches 10/22
16.66% Functions 2/12
25% Lines 4/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86                    3x     3x               3x                                     2x                                                                                          
import React from 'react';
import { useLabState } from '../useLabState';
import { BookOpen, X, PenLine, PlayCircle, Trash2 } from 'lucide-react';
import { api } from '../api';
import { vscodeNative } from '../vscodeNative';
import MarkdownText from './MarkdownText';
 
export default function JournalSidebar() {
  const {
    journalEntries, setJournalEntries, journalInput, setJournalInput,
    setJournalOpen, selectedArtifact, setSelectedArtifact, messages,
  } = useLabState();
 
  const confirmDeleteEntry = async (entry) => {
    const result = await vscodeNative.confirm('Delete journal entry?', 'Delete', 'Cancel');
    if (result === 'Delete') {
      await api.deleteJournalEntry(entry.id);
      setJournalEntries(prev => prev.filter(e => e.id !== entry.id));
    }
  };
 
  return (
    <div className="w-80 bg-lab-bg-secondary/95 border-l border-lab-border flex flex-col overflow-hidden">
      <div className="flex items-center justify-between p-4 border-b border-lab-border bg-lab-bg-secondary/80">
        <div className="flex items-center gap-2">
          <BookOpen className="w-4 h-4 text-lab-warning" />
          <h3 className="text-xs font-bold tracking-tight text-lab-fg">Research Journal</h3>
          <span className="text-[9px] text-lab-fg-muted font-mono">{journalEntries.length}</span>
        </div>
        <button onClick={() => setJournalOpen(false)} className="p-1 text-lab-fg-muted hover:text-lab-fg"><X className="w-3.5 h-3.5" /></button>
      </div>
      <div className="flex-1 overflow-y-auto p-3 space-y-2 custom-scrollbar">
        {journalEntries.length === 0 && (
          <div className="flex flex-col items-center justify-center py-12 text-center">
            <PenLine className="w-8 h-8 text-lab-fg-muted mb-3" />
            <p className="text-[10px] text-lab-fg-muted">No entries yet.</p>
            <p className="text-[9px] text-lab-fg-muted mt-1">The AI will log observations as it works, or add your own notes below.</p>
          </div>
        )}
        {journalEntries.map(entry => (
          <div key={entry.id} className="group p-3 bg-lab-bg/50 border border-lab-border rounded-xl space-y-1.5 hover:border-lab-border transition-colors">
            <div className="flex items-start justify-between gap-2">
              <div className="flex items-center gap-1.5 min-w-0">
                <span className={`shrink-0 px-1.5 py-0.5 rounded text-[7px] font-bold uppercase ${entry.author === 'agent' ? 'bg-lab-warning/10 text-lab-warning border border-amber-500/20' : 'bg-lab-accent/10 text-lab-accent border border-lab-accent/20'}`}>
                  {entry.author === 'agent' ? 'AI' : 'You'}
                </span>
                <div className="text-[11px] font-bold text-lab-fg truncate">
                  <MarkdownText content={entry.title || 'Untitled'} />
                </div>
              </div>
              <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
                {entry.messageId && (
                  <button onClick={() => { const msg = messages.find(m => m.id === entry.messageId); if (msg) setSelectedArtifact(msg); }} className="p-0.5 text-lab-fg-muted hover:text-lab-warning" title="Jump to simulation">
                    <PlayCircle className="w-3 h-3" />
                  </button>
                )}
                <button onClick={() => confirmDeleteEntry(entry)} className="p-0.5 text-lab-fg-muted hover:text-lab-error" title="Delete">
                  <Trash2 className="w-3 h-3" />
                </button>
              </div>
            </div>
            {entry.phase && (
              <span className="inline-block px-1.5 py-0.5 bg-lab-bg-elevated text-[8px] font-bold text-lab-fg-secondary rounded uppercase">{entry.phase}</span>
            )}
            <div className="text-[10px] text-lab-fg-secondary leading-relaxed">
              <MarkdownText content={entry.note || ''} />
            </div>
            <span className="text-[8px] text-lab-fg-muted">{new Date(entry.timestamp).toLocaleString()}</span>
          </div>
        ))}
      </div>
      <div className="p-3 border-t border-lab-border bg-lab-bg-secondary/80">
        <textarea value={journalInput} onChange={(e) => setJournalInput(e.target.value)} placeholder="Add a note..." className="w-full bg-lab-bg border border-lab-border rounded-lg py-2 px-3 text-[11px] focus:ring-1 focus:ring-amber-500/50 focus:border-amber-500/50 outline-none h-16 resize-none placeholder:text-lab-fg-muted" />
        <button onClick={async () => {
          if (!journalInput.trim()) return;
          const saved = await api.addJournalEntry({ title: 'Note', note: journalInput.trim(), author: 'user', messageId: selectedArtifact?.id || null, timestamp: Date.now() });
          setJournalEntries(prev => [...prev, saved]);
          setJournalInput('');
        }} disabled={!journalInput.trim()} className="mt-2 w-full py-2 bg-amber-600 hover:bg-lab-warning disabled:opacity-30 rounded-lg text-[10px] font-bold text-white flex items-center justify-center gap-1.5 transition-all active:scale-95">
          <PenLine className="w-3 h-3" /> Add Note
        </button>
      </div>
    </div>
  );
}