import { useCallback, useEffect, useMemo, useState } from 'react'; import { api } from '../api'; import { fmtId } from '../util'; import type { MissionProjection, MissionsResponse } from '../types'; interface MemoryNote { id: string; title: string; body: string; tags: string[]; created_at: string; mission_id?: string; source: 'operator' | 'mission'; } const STORAGE_KEY = 'aiwg.cockpit.memory.notes.v1'; export function Memory({ refreshTick = 0 }: { refreshTick?: number }) { const [notes, setNotes] = useState(() => readNotes()); const [missions, setMissions] = useState([]); const [query, setQuery] = useState(''); const [title, setTitle] = useState(''); const [body, setBody] = useState(''); const [tags, setTags] = useState(''); const [err, setErr] = useState(''); const persist = useCallback((next: MemoryNote[]) => { setNotes(next); localStorage.setItem(STORAGE_KEY, JSON.stringify(next)); }, []); useEffect(() => { api('/api/missions').then((data) => { setMissions(data.missions); setErr(''); const next = mergeMissionNotes(notes, data.missions); if (next !== notes) persist(next); }).catch((e) => setErr((e as Error).message)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [refreshTick]); const filtered = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return notes; return notes.filter((note) => [ note.title, note.body, note.source, note.mission_id ?? '', ...note.tags, ].some((v) => v.toLowerCase().includes(q))); }, [notes, query]); const add = () => { if (!title.trim()) return; persist([{ id: `note-${Date.now()}`, title: title.trim(), body: body.trim(), tags: tags.split(',').map((t) => t.trim()).filter(Boolean), created_at: new Date().toISOString(), source: 'operator', }, ...notes]); setTitle(''); setBody(''); setTags(''); }; const remove = (id: string) => persist(notes.filter((note) => note.id !== id)); return ( <>

Operator memory for Cockpit: local notes plus automatic Mission completion notes from durable Mission Control state. Notes stay in this browser profile; AIWG source files are not modified.

{err &&

Mission memory sync failed: {err}

}
{ e.preventDefault(); add(); }} aria-label="Add memory note">

Add Note