import { useEffect, useState } from 'react' import type { TelemetrySession } from '../../types/editor' import { useEditorStore } from '../../store/editorStore' import './HomeScreen.css' interface Stats { projectWordCount: number wordsToday: number streak: number avgWpm: number } interface RecentFile { path: string name: string lastSeen: number wordsAdded: number } interface Excerpt { text: string source: string } function countWords(text: string): number { return text.trim() === '' ? 0 : text.trim().split(/\s+/).length } function computeStats(sessions: TelemetrySession[], projectWordCount: number): Stats { const todayStart = new Date().setHours(0, 0, 0, 0) let wordsToday = 0 let totalWordsForWpm = 0 let totalMinutesForWpm = 0 const daySet = new Set() for (const s of sessions) { const sessionWords = Object.values(s.files).reduce((sum, f) => sum + Math.max(0, f.wordsAdded), 0) const sessionMinutes = (s.endedAt - s.startedAt) / 60000 if (s.startedAt >= todayStart) wordsToday += sessionWords if (sessionWords > 5 && sessionMinutes > 0.5) { totalWordsForWpm += sessionWords totalMinutesForWpm += sessionMinutes } if (sessionWords > 0) { daySet.add(new Date(s.startedAt).toDateString()) } } let streak = 0 const checkDate = new Date() while (daySet.has(checkDate.toDateString())) { streak++ checkDate.setDate(checkDate.getDate() - 1) } const avgWpm = totalMinutesForWpm > 0 ? Math.round(totalWordsForWpm / totalMinutesForWpm) : 0 return { projectWordCount, wordsToday, streak, avgWpm } } function getRecentFiles(sessions: TelemetrySession[]): RecentFile[] { const fileMap: Record = {} for (const s of sessions) { for (const [path, stats] of Object.entries(s.files)) { if (!fileMap[path] || s.endedAt > fileMap[path].lastSeen) { fileMap[path] = { lastSeen: s.endedAt, wordsAdded: (fileMap[path]?.wordsAdded ?? 0) + Math.max(0, stats.wordsAdded) } } } } return Object.entries(fileMap) .sort((a, b) => b[1].lastSeen - a[1].lastSeen) .slice(0, 6) .map(([path, data]) => ({ path, name: path.split('/').pop()?.replace(/\.md$/, '') ?? path, lastSeen: data.lastSeen, wordsAdded: data.wordsAdded })) } function pickExcerpt(files: { relativePath: string; content: string }[]): Excerpt | null { const candidates: { text: string; source: string }[] = [] for (const file of files) { const name = file.relativePath.split('/').pop()?.replace(/\.md$/, '') ?? file.relativePath const paragraphs = file.content.split(/\n\n+/).filter(p => { const clean = p.replace(/^#{1,6}\s.*/, '').trim() const words = countWords(clean) return words >= 20 && words <= 80 && !clean.startsWith('#') && !clean.startsWith('>') }) for (const p of paragraphs) { candidates.push({ text: p.trim(), source: name }) } } if (candidates.length === 0) return null return candidates[Math.floor(Math.random() * candidates.length)] } function relativeTime(ts: number): string { const diff = Date.now() - ts const mins = Math.floor(diff / 60000) const hours = Math.floor(mins / 60) const days = Math.floor(hours / 24) if (mins < 60) return `${mins}m ago` if (hours < 24) return `${hours}h ago` if (days === 1) return 'yesterday' return `${days}d ago` } export function HomeScreen(): JSX.Element { const { setActiveFile, activeFilePath, leaveHome } = useEditorStore() const [stats, setStats] = useState(null) const [recentFiles, setRecentFiles] = useState([]) const [excerpt, setExcerpt] = useState(null) const [projectTitle, setProjectTitle] = useState('Your Manuscript') useEffect(() => { async function load(): Promise { const [telemetry, projectWordCount, allFiles, cfg] = await Promise.all([ window.api.readTelemetry(), window.api.getProjectWordCount(), window.api.readAllDraftFiles(), window.api.readConfig() ]) setStats(computeStats(telemetry.sessions, projectWordCount)) setRecentFiles(getRecentFiles(telemetry.sessions)) setExcerpt(pickExcerpt(allFiles)) if (cfg.projectTitle) setProjectTitle(cfg.projectTitle) } load().catch(console.error) }, []) async function openFile(path: string): Promise { const content = await window.api.readFile(path) setActiveFile(path, content) } async function resumeWriting(): Promise { if (activeFilePath) { leaveHome() return } if (recentFiles.length > 0) { await openFile(recentFiles[0].path) } } return (

{projectTitle}

{stats && (
{stats.projectWordCount.toLocaleString()} total words
{stats.wordsToday > 0 ? `+${stats.wordsToday.toLocaleString()}` : '—'} today
{stats.streak > 0 ? stats.streak : '—'} {stats.streak === 1 ? 'day streak' : 'day streak'}
{stats.avgWpm > 0 ? stats.avgWpm : '—'} avg wpm
)} {excerpt && (

{excerpt.text}

— {excerpt.source}
)} {recentFiles.length > 0 && (

Recent

    {recentFiles.map(f => (
  • openFile(f.path)}> {f.name} {f.wordsAdded > 0 && +{f.wordsAdded.toLocaleString()} words} {relativeTime(f.lastSeen)}
  • ))}
)} {(activeFilePath || recentFiles.length > 0) && ( )}
) }