Let the garden keep what she was given, not only what she sought
Two halves of the same idea, both read out of work Petal already records. Planting: an accepted collocation is a learnable chunk, so it becomes a phrase card. The scheduler didn't need to know — a three-word chunk climbs the ladder exactly like a looked-up word. What needed care was deciding what *isn't* a chunk (single words are word choice; a six-word-plus "collocation" is a rewritten sentence, and sentences make miserable flashcards), and that the example must be the *corrected* sentence — the stored draft still holds the phrasing she just left behind. Re-accepting the same chunk leaves the existing card alone rather than resetting a schedule it has been climbing. The whole thing is best-effort: accepting an edit must never fail because a flashcard couldn't be made. The growth journal: kept this month beside kept the month before, the phrasing that stuck, the patterns that faded. The queries were the easy part; the honesty is the feature. "Stuck" needs the phrase in a *second* document, because one document is just the edit where she left it. "Faded" says nothing at all unless she has been writing lately — otherwise a month away from Petal comes back to her as progress, which is the one way this could lie. And a suggestion had to start recording when she *decided* it, not when the model proposed it, so 0012 adds resolved_at and backfills the old rows to their created_at. It lives as a second tab in the garden, and it feeds the kitten: after an accept she now sometimes hears something true of her alone, once per line, half the time, never waited for. Claude-Session: https://claude.ai/code/session_016y6gyuHkQXPiEuW8RGQyua
This commit is contained in:
@@ -3,6 +3,7 @@ import { api, type VocabGrade, type VocabWord } from '../../api/client'
|
||||
import { speak, speechSupported, stopSpeech } from '../../audio/speech'
|
||||
import { useFocusTrap } from '../../hooks/useFocusTrap'
|
||||
import { usePack, type Line } from '../../i18n'
|
||||
import { JournalView } from './JournalView'
|
||||
|
||||
// GardenPanel is the vocabulary garden: every word the writer has looked up,
|
||||
// grown into a blossom that opens further the more she remembers it, plus a
|
||||
@@ -51,6 +52,7 @@ export function GardenPanel({ onClose, onOpenDoc }: Props) {
|
||||
const [cursor, setCursor] = useState(0)
|
||||
const [revealed, setRevealed] = useState(false)
|
||||
const [expanded, setExpanded] = useState<string | null>(null)
|
||||
const [tab, setTab] = useState<'garden' | 'journal'>('garden')
|
||||
const panelRef = useFocusTrap<HTMLElement>()
|
||||
|
||||
// Read-aloud is fire-and-forget, so a word she tapped could still be speaking
|
||||
@@ -155,7 +157,7 @@ export function GardenPanel({ onClose, onOpenDoc }: Props) {
|
||||
<div>
|
||||
<div className="text-base font-extrabold text-plum">{t.garden.titleWithFlower}</div>
|
||||
<div className="text-xs" style={{ color: 'var(--color-muted)' }}>
|
||||
{queue ? t.garden.reviewing : t.garden.subtitle}
|
||||
{queue ? t.garden.reviewing : tab === 'journal' ? t.journal.subtitle : t.garden.subtitle}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -171,6 +173,31 @@ export function GardenPanel({ onClose, onOpenDoc }: Props) {
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{/* The two halves of learning: words and phrasing she has collected
|
||||
(garden), and how her writing has changed (journal). A review
|
||||
session takes over the panel entirely — mid-flashcard is no moment
|
||||
to be offered a different page. */}
|
||||
{!queue && (
|
||||
<div className="flex gap-1 px-4 pt-3">
|
||||
{(['garden', 'journal'] as const).map((id) => (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
onClick={() => setTab(id)}
|
||||
aria-pressed={tab === id}
|
||||
className="flex-1 rounded-full px-3 py-1.5 text-xs font-bold"
|
||||
style={{
|
||||
background: tab === id ? 'var(--color-surface-alt)' : 'transparent',
|
||||
border: `1px solid ${tab === id ? 'var(--color-border)' : 'transparent'}`,
|
||||
color: tab === id ? 'var(--color-plum)' : 'var(--color-muted)',
|
||||
}}
|
||||
>
|
||||
{id === 'garden' ? t.journal.tabGarden : t.journal.tabJournal}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{queue ? (
|
||||
<ReviewSession
|
||||
queue={queue}
|
||||
@@ -180,6 +207,8 @@ export function GardenPanel({ onClose, onOpenDoc }: Props) {
|
||||
onGrade={grade}
|
||||
onQuit={() => setQueue(null)}
|
||||
/>
|
||||
) : tab === 'journal' ? (
|
||||
<JournalView />
|
||||
) : (
|
||||
<GardenView
|
||||
words={words}
|
||||
|
||||
Reference in New Issue
Block a user