Add own-work tracking, Claude hand-off check, and weekly recall
- 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>
This commit is contained in:
1 parent
edc5a65d2b
commit
0c8a632cff
11 files changed
+429
-5
No files matched your search
@@ -2,6 +2,7 @@
|
||||
// area for background notes and tags so the page stays quiet.
|
||||
import { useEffect, useState } from 'react';
|
||||
import DictateButton, { appendSpoken } from '../DictateButton.jsx';
|
||||
import { ownShare, ownWords, pastedWords, withPaste, clearPastes } from '../../utils/ownWork.js';
|
||||
import { templateFromChunk, templatePrompt } from '../../utils/studyTemplates.js';
|
||||
|
||||
const SECTIONS = [
|
||||
@@ -36,11 +37,23 @@ export default function NotesPane({ chunk, updateChunk, allTags, templateId, sav
|
||||
};
|
||||
const suggestions = allTags.filter((t) => !tags.some((x) => x.toLowerCase() === t.toLowerCase()));
|
||||
|
||||
const share = ownShare(chunk);
|
||||
const keepPaste = (field) => (e) => {
|
||||
const next = withPaste(chunk, field, e.clipboardData?.getData('text'));
|
||||
if (next) updateChunk(chunk.id, { aiPastes: next });
|
||||
};
|
||||
|
||||
return (
|
||||
<section aria-label="Study notes" className="space-y-2">
|
||||
{share !== null && (
|
||||
<p className="px-1 text-xs text-[var(--r-sub)]" title="Long pastes into a note box count as pasted, not your own">
|
||||
{share === 100 ? 'All of these notes are your own words.' : `${share}% of these notes are your own words.`}
|
||||
</p>
|
||||
)}
|
||||
{SECTIONS.map(({ field, label, hint, placeholder }) => {
|
||||
const isOpen = open === field;
|
||||
const n = words(chunk[field]);
|
||||
const pasted = pastedWords(chunk, field);
|
||||
return (
|
||||
<div key={field} className={`overflow-hidden rounded-2xl border ${isOpen ? 'border-[var(--r-accent)]' : 'border-[var(--r-line)]'}`}>
|
||||
<button type="button" onClick={() => setOpen(isOpen ? null : field)} aria-expanded={isOpen}
|
||||
@@ -50,12 +63,23 @@ export default function NotesPane({ chunk, updateChunk, allTags, templateId, sav
|
||||
<span className="text-sm font-medium">{label}</span>
|
||||
<span className="text-xs text-[var(--r-sub)]">{hint}</span>
|
||||
</span>
|
||||
<span className="text-xs text-[var(--r-sub)]">{n ? `${n} word${n === 1 ? '' : 's'}` : ''}</span>
|
||||
<span className="flex items-center gap-2 text-xs text-[var(--r-sub)]">
|
||||
{n > 0 && (pasted > 0
|
||||
? <span className="rounded-full bg-[var(--r-hl)] px-2 py-0.5 text-[var(--r-text)]">{ownWords(chunk, field) === 0 ? 'Pasted' : `${pasted} pasted`}</span>
|
||||
: <span className="rounded-full border border-[var(--r-line)] px-2 py-0.5">Mine</span>)}
|
||||
{n ? `${n} word${n === 1 ? '' : 's'}` : ''}
|
||||
</span>
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div className="px-4 pb-4">
|
||||
<textarea aria-label={label} value={chunk[field] ?? ''} rows={7} placeholder={templatePrompt(templateId, field) ?? placeholder}
|
||||
onChange={(e) => updateChunk(chunk.id, { [field]: e.target.value })} className={areaCls} />
|
||||
onPaste={keepPaste(field)} onChange={(e) => updateChunk(chunk.id, { [field]: e.target.value })} className={areaCls} />
|
||||
{pasted > 0 && (
|
||||
<p className="mt-1.5 text-xs text-[var(--r-sub)]">
|
||||
{pasted} word{pasted === 1 ? '' : 's'} here were pasted, so they don't count as your own work.{' '}
|
||||
<button type="button" onClick={() => updateChunk(chunk.id, { aiPastes: clearPastes(chunk, field) })} className="text-[var(--r-accent)] hover:underline">That’s mine</button>
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-1.5"><DictateButton label={`Dictate ${label.toLowerCase()}`} onText={(said) => updateChunk(chunk.id, { [field]: appendSpoken(chunk[field], said) })} /></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user