- Long pastes into Observe/Interpret/Apply are tagged as pasted; notes show Mine / N pasted and an own-words share. Pastes follow text through merges. - The "-> Claude" buttons on the Script stage ask first when a chunk lacks enough of the user's own (non-pasted) words. - Weekly recall card on Home: write a passage from memory, compare with notes, self-rate; recalls are saved on the chunk and synced. - Recall history on Home with an eight-week strip and the full list. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
48 lines
3.0 KiB
React
48 lines
3.0 KiB
React
// Home: how your weekly recalls have gone — the last eight weeks at a glance, then every recall you've written.
|
|
import { useMemo, useState } from 'react';
|
|
import { useApp } from '../context/AppContext.js';
|
|
import { loadProjectById } from '../utils/projectStorage.js';
|
|
import { recallHistory, recallSummary } from '../utils/recall.js';
|
|
|
|
const LABEL = { solid: 'Had it', partly: 'Partly', forgot: 'Slipped' };
|
|
const DOT = { solid: 'bg-[var(--r-accent)] border-[var(--r-accent)]', partly: 'bg-[var(--r-hl)] border-[var(--r-accent)]', forgot: 'border-[var(--r-sub)]' };
|
|
|
|
export default function RecallHistory() {
|
|
const { projectIndex, openStudyChunk } = useApp();
|
|
const [open, setOpen] = useState(false);
|
|
const history = useMemo(() => recallHistory(projectIndex.map((e) => loadProjectById(e.id)).filter(Boolean)), [projectIndex]);
|
|
if (history.length === 0) return null;
|
|
const { counts, total, recent } = recallSummary(history);
|
|
|
|
return (
|
|
<section aria-label="Recall history" className="mb-4 rounded-xl border border-[var(--r-line)] bg-[var(--r-bar)] px-4 py-3 text-sm">
|
|
<button type="button" onClick={() => setOpen((o) => !o)} aria-expanded={open} className="flex w-full flex-wrap items-center justify-between gap-3 text-left">
|
|
<span><span className="font-medium">Recall history</span> <span className="text-xs text-[var(--r-sub)]">{total} recall{total === 1 ? '' : 's'} · {counts.solid} had it · {counts.partly} partly · {counts.forgot} slipped</span></span>
|
|
<span className="flex items-center gap-1" aria-label="Last eight weeks">
|
|
{recent.map((w) => (
|
|
<span key={w.start} title={w.rating ? LABEL[w.rating] : 'No recall that week'}
|
|
className={`h-3 w-3 rounded-full border ${w.rating ? DOT[w.rating] : 'border-[var(--r-line)]'}`} />
|
|
))}
|
|
<span className="ml-1 text-xs text-[var(--r-sub)]">{open ? '▾' : '▸'}</span>
|
|
</span>
|
|
</button>
|
|
{open && (
|
|
<ul className="mt-3 space-y-2.5">
|
|
{history.map((h) => (
|
|
<li key={`${h.chunkId}-${h.at}`} className="rounded-lg border border-[var(--r-line)] bg-[var(--r-bg)] p-3">
|
|
<div className="flex flex-wrap items-center justify-between gap-2 text-xs">
|
|
<span><span className="font-medium text-sm">{h.reference}</span> <span className="text-[var(--r-sub)]">· {h.projectTitle} · {new Date(h.at).toLocaleDateString()}</span></span>
|
|
<span className="flex items-center gap-2">
|
|
<span className="rounded-full border border-[var(--r-line)] px-2 py-0.5">{LABEL[h.rating] ?? '—'}</span>
|
|
<button type="button" onClick={() => openStudyChunk(h.projectId, h.chunkId)} className="text-[var(--r-accent)] hover:underline">Open</button>
|
|
</span>
|
|
</div>
|
|
{h.text && <p className="mt-1.5 line-clamp-3 whitespace-pre-line font-serif text-[13px] leading-relaxed text-[var(--r-sub)]">{h.text}</p>}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|