MCPcopy Create free account
hub / github.com/codeaashu/claude-code / CompanionSprite

Function CompanionSprite

src/buddy/CompanionSprite.tsx:176–290  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

174 return spriteColWidth(nameWidth) + SPRITE_PADDING_X + bubble;
175}
176export function CompanionSprite(): React.ReactNode {
177 const reaction = useAppState(s => s.companionReaction);
178 const petAt = useAppState(s => s.companionPetAt);
179 const focused = useAppState(s => s.footerSelection === 'companion');
180 const setAppState = useSetAppState();
181 const {
182 columns
183 } = useTerminalSize();
184 const [tick, setTick] = useState(0);
185 const lastSpokeTick = useRef(0);
186 // Sync-during-render (not useEffect) so the first post-pet render already
187 // has petStartTick=tick and petAge=0 — otherwise frame 0 is skipped.
188 const [{
189 petStartTick,
190 forPetAt
191 }, setPetStart] = useState({
192 petStartTick: 0,
193 forPetAt: petAt
194 });
195 if (petAt !== forPetAt) {
196 setPetStart({
197 petStartTick: tick,
198 forPetAt: petAt
199 });
200 }
201 useEffect(() => {
202 const timer = setInterval(setT => setT((t: number) => t + 1), TICK_MS, setTick);
203 return () => clearInterval(timer);
204 }, []);
205 useEffect(() => {
206 if (!reaction) return;
207 lastSpokeTick.current = tick;
208 const timer = setTimeout(setA => setA((prev: AppState) => prev.companionReaction === undefined ? prev : {
209 ...prev,
210 companionReaction: undefined
211 }), BUBBLE_SHOW * TICK_MS, setAppState);
212 return () => clearTimeout(timer);
213 // eslint-disable-next-line react-hooks/exhaustive-deps -- tick intentionally captured at reaction-change, not tracked
214 }, [reaction, setAppState]);
215 if (!feature('BUDDY')) return null;
216 const companion = getCompanion();
217 if (!companion || getGlobalConfig().companionMuted) return null;
218 const color = RARITY_COLORS[companion.rarity];
219 const colWidth = spriteColWidth(stringWidth(companion.name));
220 const bubbleAge = reaction ? tick - lastSpokeTick.current : 0;
221 const fading = reaction !== undefined && bubbleAge >= BUBBLE_SHOW - FADE_WINDOW;
222 const petAge = petAt ? tick - petStartTick : Infinity;
223 const petting = petAge * TICK_MS < PET_BURST_MS;
224
225 // Narrow terminals: collapse to one-line face. When speaking, the quip
226 // replaces the name beside the face (no room for a bubble).
227 if (columns < MIN_COLS_FOR_FULL_SPRITE) {
228 const quip = reaction && reaction.length > NARROW_QUIP_CAP ? reaction.slice(0, NARROW_QUIP_CAP - 1) + '…' : reaction;
229 const label = quip ? `"${quip}"` : focused ? ` ${companion.name} ` : companion.name;
230 return <Box paddingX={1} alignSelf="flex-end">
231 <Text>
232 {petting && <Text color="autoAccept">{figures.heart} </Text>}
233 <Text bold color={color}>

Callers

nothing calls this directly

Calls 11

useAppStateFunction · 0.85
useSetAppStateFunction · 0.85
useTerminalSizeFunction · 0.85
featureFunction · 0.85
getCompanionFunction · 0.85
getGlobalConfigFunction · 0.85
spriteColWidthFunction · 0.85
renderFaceFunction · 0.85
spriteFrameCountFunction · 0.85
renderSpriteFunction · 0.85
isFullscreenActiveFunction · 0.85

Tested by

no test coverage detected