Add companion kitten — reactive corner mascot
A cozy bottom-right mascot that reacts to the writing session: cheers on accepted suggestions and word-count milestones, drops Mandarin-first writing tips, suggests screen breaks after long stretches, and naps when idle. - useCompanion: library-agnostic behavior engine (priority + cooldown paced so it never nags); tips.ts holds all copy, bilingual zh-first. - LottiePlayer wraps lottie-web's light build (offline, no eval/CDN) so it bundles into the Go binary. Ships with an emoji-kitten placeholder per mood; dropping a Lottie cat JSON into animations/index.ts is the only swap needed. - Speech bubble uses the CJK-first font stack (spec Note #17). Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
This commit is contained in:
@@ -5,6 +5,7 @@ import { useCheckpoint } from './hooks/useCheckpoint'
|
||||
import { DocList } from './components/DocList/DocList'
|
||||
import { EditorCore, type EditorChange } from './components/Editor/EditorCore'
|
||||
import { StatusBar } from './components/StatusBar/StatusBar'
|
||||
import { PetalCompanion } from './components/Companion/PetalCompanion'
|
||||
|
||||
export default function App() {
|
||||
const [docs, setDocs] = useState<DocSummary[]>([])
|
||||
@@ -16,6 +17,9 @@ export default function App() {
|
||||
// sidebar. Escape or a click outside the editor canvas restores it.
|
||||
const [focusMode, setFocusMode] = useState(false)
|
||||
const canvasRef = useRef<HTMLDivElement>(null)
|
||||
// Monotonic counters the companion watches to react to writing + accepts.
|
||||
const [editTick, setEditTick] = useState(0)
|
||||
const [acceptTick, setAcceptTick] = useState(0)
|
||||
|
||||
const { status, schedule, saveNow } = useAutoSave(currentDoc?.id ?? null)
|
||||
const {
|
||||
@@ -116,6 +120,7 @@ export default function App() {
|
||||
const handleEditorChange = useCallback(
|
||||
(change: EditorChange) => {
|
||||
setWordCount(change.word_count)
|
||||
setEditTick((n) => n + 1)
|
||||
if (currentDoc) {
|
||||
patchSummary(currentDoc.id, { word_count: change.word_count })
|
||||
schedule(change)
|
||||
@@ -130,6 +135,7 @@ export default function App() {
|
||||
const handleAccept = useCallback(
|
||||
async (s: Suggestion) => {
|
||||
removeSuggestion(s.id)
|
||||
setAcceptTick((n) => n + 1)
|
||||
try {
|
||||
await api.acceptSuggestion(s.id)
|
||||
} catch (err) {
|
||||
@@ -238,6 +244,13 @@ export default function App() {
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<PetalCompanion
|
||||
wordCount={wordCount}
|
||||
saveStatus={status}
|
||||
editTick={editTick}
|
||||
acceptTick={acceptTick}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user