drexler 0.2.15 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +10 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/ui/App.tsx +44 -20
- package/src/ui/PetPanel.tsx +141 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.16
|
|
4
|
+
|
|
5
|
+
- Added an interactive pet system: feed, play, work, praise, rest, vibe, name, and profile commands; persistent stats with offline decay; intern→analyst→associate→VP→MD rank ladder driven by lifetime deal accumulation; 90-second cooldowns per action with in-character rejection copy.
|
|
6
|
+
- Adaptive pet UI: full animated panel on wide terminals (cols ≥ 112), bordered compact panel on medium terminals (≥ 48), one-line ticker surfacing the worst stat on tiny terminals.
|
|
7
|
+
- Compact panel routes stats through the existing satirical level ladder (peak/good/ok/low/critical) instead of bare percentages so it matches the Deal Desk surface.
|
|
8
|
+
- Pet save is now atomic (temp file + rename); dead-pet command guard prevents stat mutation during the death exit timer; frame interval pauses when the pet has died.
|
|
9
|
+
- Hardened launch flow: validate CLI flags and config before the first-run API key prompt with reason-specific errors. Fatal handlers moved off the interactive path so Ink's signal-exit can restore the terminal cleanly.
|
|
10
|
+
- Markdown link parser now balances parentheses, so URLs like `https://en.wikipedia.org/wiki/Foo_(bar)` parse correctly.
|
|
11
|
+
- New informational commands: `/setup` prints config + API key source without leaking the key; `/update` prints upgrade instructions and refuses to run installs.
|
|
12
|
+
- Transcript viewport enforces a hard row budget — oversized cards clip with an explicit `... N lines truncated — PageUp scrollback to read` hint; indicators report row counts in addition to item counts; scrollback keys work while a response is streaming.
|
|
13
|
+
- Command palette Enter on bare argument-parent commands (`/theme`, `/model`, `/startup`, `/retry`, `/export`) now reopens the chooser instead of executing the base form; history navigation preserves the unsent draft.
|
|
14
|
+
- Performance: collapsed duplicate width memos, hoisted divider/carpet constants, memoized StatBar, tightened the pet panel frame loop.
|
|
15
|
+
|
|
3
16
|
## 0.2.14
|
|
4
17
|
|
|
5
18
|
- Added a startup Mood panel with a stable boot gauge, percentage-only loading row, and rotating mood-specific posture/detail copy.
|
package/README.md
CHANGED
|
@@ -91,6 +91,8 @@ rm -rf ~/.config/drexler # optional: wipe stored key + settings
|
|
|
91
91
|
|
|
92
92
|
Drexler runs as an Ink terminal UI when both stdin and stdout are TTYs. The normal launch shows one integrated startup panel with the mascot, tips, a **Mood** readout, and the **Drexler Deal Desk**. Short terminals automatically suppress oversized startup chrome so the chat stays usable.
|
|
93
93
|
|
|
94
|
+
After startup, the pet UI adapts to the terminal instead of disappearing. Wide terminals show the animated **Drexler Pet Desk** as a right-side panel. Medium terminals show a compact pet panel below the Deal Desk. Tiny terminals show a one-line pet ticker so pet status remains visible without crushing the chat.
|
|
95
|
+
|
|
94
96
|
The startup panel is designed to stay stable while it boots: the mascot loading bar and Mood gauge animate without changing width, greeting copy is held in a fixed slot, and the Mood and Deal Desk boxes stay aligned when the greeting wraps. After boot, Mood resolves into a rotating Drexler-flavored posture with a short satirical subtext line.
|
|
95
97
|
|
|
96
98
|
The Deal Desk is intentionally not a frontier-model telemetry panel. It shows mood-shaped corporate nonsense like boardroom status, memo count, mandate, risk, fees, and counsel posture. Values rotate by mood and session so repeated moods still feel alive.
|
|
@@ -134,6 +136,14 @@ Keyboard notes:
|
|
|
134
136
|
| `/clear` | shred conversation history (system prompt pinned) |
|
|
135
137
|
| `/exit` | meeting adjourned |
|
|
136
138
|
| `/synergy` | run a rotating animated morale event |
|
|
139
|
+
| `/feed` | feed Drexler a deal memo |
|
|
140
|
+
| `/play` | corporate synergy game with Drexler |
|
|
141
|
+
| `/work` | Drexler grinds the deal pipeline |
|
|
142
|
+
| `/praise` | affirm Drexler's contributions |
|
|
143
|
+
| `/rest` | strategic nap |
|
|
144
|
+
| `/vibe` | let Drexler choose his own adventure |
|
|
145
|
+
| `/name [name]` | view or assign Drexler's pet name |
|
|
146
|
+
| `/profile` | print Drexler's personnel file |
|
|
137
147
|
| `/model` | show current model, or `/model 26b` to switch |
|
|
138
148
|
| `/theme` | show/switch theme; append `save` to persist, e.g. `/theme midnight save` |
|
|
139
149
|
| `/startup fast\|no-intro\|normal` | persist startup behavior for future launches |
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -63,6 +63,8 @@ Slash commands inside REPL:
|
|
|
63
63
|
/rest strategic nap (restores energy)
|
|
64
64
|
/praise affirm Drexler's contributions
|
|
65
65
|
/vibe let Drexler choose his own adventure
|
|
66
|
+
/name [name] view or assign Drexler's pet name
|
|
67
|
+
/profile print Drexler's personnel file
|
|
66
68
|
/model [id] show or switch model
|
|
67
69
|
/theme [name] show or switch theme; append save to persist
|
|
68
70
|
/startup [mode] persist startup mode: fast, no-intro, normal
|
package/src/ui/App.tsx
CHANGED
|
@@ -28,9 +28,12 @@ import {
|
|
|
28
28
|
} from "../pet/petState.ts";
|
|
29
29
|
import { DeathScreen } from "./DeathScreen.tsx";
|
|
30
30
|
import {
|
|
31
|
+
CompactPetPanel,
|
|
32
|
+
COMPACT_PET_PANEL_MIN_WIDTH,
|
|
33
|
+
COMPACT_PET_PANEL_ROWS,
|
|
31
34
|
PetPanel,
|
|
32
|
-
PET_PANEL_ROWS,
|
|
33
35
|
PET_PANEL_WIDTH,
|
|
36
|
+
TINY_PET_PANEL_ROWS,
|
|
34
37
|
type Environment,
|
|
35
38
|
} from "./PetPanel.tsx";
|
|
36
39
|
import {
|
|
@@ -90,11 +93,10 @@ import {
|
|
|
90
93
|
import { getActiveTheme } from "./themes.ts";
|
|
91
94
|
|
|
92
95
|
const TRANSCRIPT_CHROME_ROWS = 12;
|
|
93
|
-
const PET_PANEL_MIN_MAIN_COLUMNS =
|
|
96
|
+
const PET_PANEL_MIN_MAIN_COLUMNS = 75;
|
|
94
97
|
const PET_PANEL_GAP_COLUMNS = 1;
|
|
95
98
|
const PET_PANEL_MIN_COLUMNS =
|
|
96
99
|
PET_PANEL_WIDTH + PET_PANEL_GAP_COLUMNS + PET_PANEL_MIN_MAIN_COLUMNS;
|
|
97
|
-
const PET_PANEL_MIN_ROWS = TRANSCRIPT_CHROME_ROWS + PET_PANEL_ROWS;
|
|
98
100
|
|
|
99
101
|
export function transcriptRowsForTerminalRows(rows: number): number {
|
|
100
102
|
return Math.max(1, Math.min(24, rows - TRANSCRIPT_CHROME_ROWS));
|
|
@@ -226,14 +228,23 @@ export function App({
|
|
|
226
228
|
const mode = useMemo(() => pickLayout(cols), [cols]);
|
|
227
229
|
const chromeWidth = useMemo(() => Math.max(1, cols), [cols]);
|
|
228
230
|
const isCompact = mode === "very-narrow";
|
|
231
|
+
const [introDone, setIntroDone] = useState(false);
|
|
229
232
|
const integratedIntro =
|
|
230
|
-
showIntroChrome &&
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
showIntroChrome &&
|
|
234
|
+
typeof greeting === "string" &&
|
|
235
|
+
rows >= 32 &&
|
|
236
|
+
!introDone;
|
|
237
|
+
const showPetSidePanel = cols >= PET_PANEL_MIN_COLUMNS && !integratedIntro;
|
|
238
|
+
const showCompactPetPanel = !showPetSidePanel && !integratedIntro;
|
|
239
|
+
const compactPetRowBudget = showCompactPetPanel
|
|
240
|
+
? cols >= COMPACT_PET_PANEL_MIN_WIDTH
|
|
241
|
+
? COMPACT_PET_PANEL_ROWS
|
|
242
|
+
: TINY_PET_PANEL_ROWS
|
|
243
|
+
: 0;
|
|
244
|
+
const petPanelReservedWidth = showPetSidePanel
|
|
234
245
|
? PET_PANEL_WIDTH + PET_PANEL_GAP_COLUMNS
|
|
235
246
|
: 0;
|
|
236
|
-
const contentWidth =
|
|
247
|
+
const contentWidth = showPetSidePanel
|
|
237
248
|
? Math.max(1, cols - petPanelReservedWidth)
|
|
238
249
|
: chromeWidth;
|
|
239
250
|
const contentInputWidth = Math.max(1, contentWidth);
|
|
@@ -244,9 +255,9 @@ export function App({
|
|
|
244
255
|
() =>
|
|
245
256
|
Math.max(
|
|
246
257
|
1,
|
|
247
|
-
transcriptRowsForTerminalRows(rows) - introRowBudget,
|
|
258
|
+
transcriptRowsForTerminalRows(rows) - introRowBudget - compactPetRowBudget,
|
|
248
259
|
),
|
|
249
|
-
[introRowBudget, rows],
|
|
260
|
+
[compactPetRowBudget, introRowBudget, rows],
|
|
250
261
|
);
|
|
251
262
|
|
|
252
263
|
const [items, setItems] = useState<ChatItem[]>([]);
|
|
@@ -299,7 +310,14 @@ export function App({
|
|
|
299
310
|
const [historyIdx, setHistoryIdx] = useState<number | null>(null);
|
|
300
311
|
const [paletteIdx, setPaletteIdx] = useState(0);
|
|
301
312
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
302
|
-
const
|
|
313
|
+
const handleIntroComplete = useCallback(() => {
|
|
314
|
+
setIntroDone(true);
|
|
315
|
+
}, []);
|
|
316
|
+
const intro = useIntroAnimation(
|
|
317
|
+
chromeWidth,
|
|
318
|
+
integratedIntro,
|
|
319
|
+
handleIntroComplete,
|
|
320
|
+
);
|
|
303
321
|
|
|
304
322
|
const [petStats, setPetStats] = useState<PetStats>(() => loadPetState());
|
|
305
323
|
const [petActivity, setPetActivity] = useState<PetActivity>("idle");
|
|
@@ -405,11 +423,6 @@ export function App({
|
|
|
405
423
|
|
|
406
424
|
// Real-time stat decay matches the offline per-hour decay rate.
|
|
407
425
|
useEffect(() => {
|
|
408
|
-
if (!showPetPanel) {
|
|
409
|
-
return () => {
|
|
410
|
-
savePetState(petStatsRef.current);
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
426
|
petDecayTimerRef.current = setInterval(() => {
|
|
414
427
|
updatePetStats(applyMinuteDecay);
|
|
415
428
|
}, 60_000);
|
|
@@ -424,11 +437,11 @@ export function App({
|
|
|
424
437
|
}
|
|
425
438
|
savePetState(petStatsRef.current);
|
|
426
439
|
};
|
|
427
|
-
}, [
|
|
440
|
+
}, [updatePetStats]);
|
|
428
441
|
|
|
429
442
|
// Death detection
|
|
430
443
|
useEffect(() => {
|
|
431
|
-
if (
|
|
444
|
+
if (isDead || !isPetDead(petStats)) return;
|
|
432
445
|
const reason =
|
|
433
446
|
petStats.hunger <= 0 ? "hunger" :
|
|
434
447
|
petStats.happiness <= 0 ? "happiness" : "energy";
|
|
@@ -439,7 +452,7 @@ export function App({
|
|
|
439
452
|
petStatsRef.current = deadStats;
|
|
440
453
|
savePetState(deadStats);
|
|
441
454
|
exitTimerRef.current = setTimeout(() => exit(), 5000);
|
|
442
|
-
}, [petStats,
|
|
455
|
+
}, [petStats, isDead, exit]);
|
|
443
456
|
|
|
444
457
|
const paletteItems = useMemo(() => filterPaletteByPrefix(input), [input]);
|
|
445
458
|
const paletteOpen = paletteItems.length > 0;
|
|
@@ -1152,8 +1165,19 @@ export function App({
|
|
|
1152
1165
|
) : (
|
|
1153
1166
|
dealDeskHeader
|
|
1154
1167
|
)}
|
|
1168
|
+
{showCompactPetPanel && (
|
|
1169
|
+
<Box marginBottom={1}>
|
|
1170
|
+
<CompactPetPanel
|
|
1171
|
+
stats={petStats}
|
|
1172
|
+
activity={petActivity}
|
|
1173
|
+
env={petEnv}
|
|
1174
|
+
isPaused={isBusy}
|
|
1175
|
+
width={chromeWidth}
|
|
1176
|
+
/>
|
|
1177
|
+
</Box>
|
|
1178
|
+
)}
|
|
1155
1179
|
<Box flexDirection="row" alignItems="flex-start">
|
|
1156
|
-
{
|
|
1180
|
+
{showPetSidePanel && (
|
|
1157
1181
|
<Box marginRight={PET_PANEL_GAP_COLUMNS}>
|
|
1158
1182
|
<PetPanel
|
|
1159
1183
|
stats={petStats}
|
package/src/ui/PetPanel.tsx
CHANGED
|
@@ -7,12 +7,16 @@ import { type Theme } from "./themes.ts";
|
|
|
7
7
|
|
|
8
8
|
export const PET_PANEL_WIDTH = 36;
|
|
9
9
|
export const PET_PANEL_ROWS = 22;
|
|
10
|
+
export const COMPACT_PET_PANEL_ROWS = 5;
|
|
11
|
+
export const TINY_PET_PANEL_ROWS = 1;
|
|
12
|
+
export const COMPACT_PET_PANEL_MIN_WIDTH = 48;
|
|
10
13
|
export type Environment = "office" | "home" | "outdoors";
|
|
11
14
|
|
|
12
15
|
const PANEL_BORDER_COLUMNS = 2;
|
|
13
16
|
const PANEL_PADDING_COLUMNS = 2;
|
|
14
17
|
const CONTENT = PET_PANEL_WIDTH - PANEL_BORDER_COLUMNS - PANEL_PADDING_COLUMNS;
|
|
15
18
|
const SPRITE_W = 8;
|
|
19
|
+
const DIVIDER_LINE = "─".repeat(CONTENT);
|
|
16
20
|
|
|
17
21
|
const R_SKY = 0;
|
|
18
22
|
const R_BGA = 1;
|
|
@@ -454,6 +458,10 @@ interface PetPanelProps {
|
|
|
454
458
|
isPaused?: boolean;
|
|
455
459
|
}
|
|
456
460
|
|
|
461
|
+
interface CompactPetPanelProps extends PetPanelProps {
|
|
462
|
+
width: number;
|
|
463
|
+
}
|
|
464
|
+
|
|
457
465
|
function PetPanelView({ stats, activity, env = "office", isPaused = false }: PetPanelProps) {
|
|
458
466
|
const t = useTheme();
|
|
459
467
|
const [frame, setFrame] = useState(0);
|
|
@@ -463,12 +471,15 @@ function PetPanelView({ stats, activity, env = "office", isPaused = false }: Pet
|
|
|
463
471
|
}, [activity, env]);
|
|
464
472
|
|
|
465
473
|
useEffect(() => {
|
|
466
|
-
|
|
474
|
+
// Skip frame ticks when paused or when the pet has died — DeathScreen
|
|
475
|
+
// takes over the UI, no point burning a setInterval that mutates state
|
|
476
|
+
// nothing will read.
|
|
477
|
+
if (isPaused || stats.dead === true) return;
|
|
467
478
|
const id = setInterval(() => {
|
|
468
479
|
setFrame((f) => f + 1);
|
|
469
480
|
}, 800);
|
|
470
481
|
return () => clearInterval(id);
|
|
471
|
-
}, [isPaused]);
|
|
482
|
+
}, [isPaused, stats.dead]);
|
|
472
483
|
|
|
473
484
|
const scene = useMemo(
|
|
474
485
|
() => buildScene(activity, frame, stats, env),
|
|
@@ -479,7 +490,7 @@ function PetPanelView({ stats, activity, env = "office", isPaused = false }: Pet
|
|
|
479
490
|
() => pad(`memo ${getStatusMsg(stats, frame)}`, CONTENT),
|
|
480
491
|
[stats, frame],
|
|
481
492
|
);
|
|
482
|
-
const title = fitDisplayText(`DREXLER
|
|
493
|
+
const title = fitDisplayText(`DREXLER PET DESK [${env}]`, CONTENT);
|
|
483
494
|
const activityLabel = activity !== "idle" ? ` / ${activity}` : "";
|
|
484
495
|
const moodLabel = `mood ${mood}`;
|
|
485
496
|
const fittedMood = activityLabel
|
|
@@ -508,7 +519,7 @@ function PetPanelView({ stats, activity, env = "office", isPaused = false }: Pet
|
|
|
508
519
|
</Box>
|
|
509
520
|
|
|
510
521
|
<Box paddingX={1}>
|
|
511
|
-
<Text color={t.primaryDim}>{
|
|
522
|
+
<Text color={t.primaryDim}>{DIVIDER_LINE}</Text>
|
|
512
523
|
</Box>
|
|
513
524
|
|
|
514
525
|
<Box flexDirection="column" paddingX={1}>
|
|
@@ -519,7 +530,7 @@ function PetPanelView({ stats, activity, env = "office", isPaused = false }: Pet
|
|
|
519
530
|
</Box>
|
|
520
531
|
|
|
521
532
|
<Box paddingX={1}>
|
|
522
|
-
<Text color={t.primaryDim}>{
|
|
533
|
+
<Text color={t.primaryDim}>{DIVIDER_LINE}</Text>
|
|
523
534
|
</Box>
|
|
524
535
|
|
|
525
536
|
<Box paddingX={1}>
|
|
@@ -535,3 +546,128 @@ function PetPanelView({ stats, activity, env = "office", isPaused = false }: Pet
|
|
|
535
546
|
}
|
|
536
547
|
|
|
537
548
|
export const PetPanel = memo(PetPanelView);
|
|
549
|
+
|
|
550
|
+
function pct(value: number): string {
|
|
551
|
+
return `${Math.round(value)}%`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const STAT_LEVEL_LABEL: Record<MsgLevel, string> = {
|
|
555
|
+
critical: "critical",
|
|
556
|
+
low: "low",
|
|
557
|
+
ok: "ok",
|
|
558
|
+
good: "good",
|
|
559
|
+
great: "peak",
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
interface CompactStatProfile {
|
|
563
|
+
hunger: MsgLevel;
|
|
564
|
+
happiness: MsgLevel;
|
|
565
|
+
energy: MsgLevel;
|
|
566
|
+
deals: MsgLevel;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function compactStatProfile(stats: PetStats): CompactStatProfile {
|
|
570
|
+
return {
|
|
571
|
+
hunger: statLevel(stats.hunger),
|
|
572
|
+
happiness: statLevel(stats.happiness),
|
|
573
|
+
energy: statLevel(stats.energy),
|
|
574
|
+
deals: statLevel(stats.deals),
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
interface WorstStat {
|
|
579
|
+
key: "hunger" | "happiness" | "energy" | "deals";
|
|
580
|
+
value: number;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export function pickWorstStat(stats: PetStats): WorstStat {
|
|
584
|
+
const entries: WorstStat[] = [
|
|
585
|
+
{ key: "hunger", value: stats.hunger },
|
|
586
|
+
{ key: "happiness", value: stats.happiness },
|
|
587
|
+
{ key: "energy", value: stats.energy },
|
|
588
|
+
{ key: "deals", value: stats.deals },
|
|
589
|
+
];
|
|
590
|
+
return entries.reduce((best, cur) =>
|
|
591
|
+
cur.value < best.value ? cur : best,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function CompactPetPanelView({
|
|
596
|
+
stats,
|
|
597
|
+
activity,
|
|
598
|
+
env = "office",
|
|
599
|
+
isPaused = false,
|
|
600
|
+
width,
|
|
601
|
+
}: CompactPetPanelProps) {
|
|
602
|
+
const t = useTheme();
|
|
603
|
+
const safeWidth = Math.max(1, width);
|
|
604
|
+
|
|
605
|
+
// Rotate the memo every 10s so the compact panel doesn't feel static.
|
|
606
|
+
// Paused panels lock to a single message (no decay-tick to refresh anyway).
|
|
607
|
+
const [tick, setTick] = useState(() => Math.floor(Date.now() / 10_000));
|
|
608
|
+
useEffect(() => {
|
|
609
|
+
if (isPaused) return;
|
|
610
|
+
const id = setInterval(() => {
|
|
611
|
+
setTick(Math.floor(Date.now() / 10_000));
|
|
612
|
+
}, 10_000);
|
|
613
|
+
return () => clearInterval(id);
|
|
614
|
+
}, [isPaused]);
|
|
615
|
+
|
|
616
|
+
const mood = getPetMood(stats);
|
|
617
|
+
const profile = compactStatProfile(stats);
|
|
618
|
+
const activityCopy = activity === "idle" ? env : `${env} / ${activity}`;
|
|
619
|
+
const title = "Drexler Pet Desk";
|
|
620
|
+
const statLine = [
|
|
621
|
+
`happy ${STAT_LEVEL_LABEL[profile.happiness]}`,
|
|
622
|
+
`hungr ${STAT_LEVEL_LABEL[profile.hunger]}`,
|
|
623
|
+
`enrgy ${STAT_LEVEL_LABEL[profile.energy]}`,
|
|
624
|
+
`deals ${STAT_LEVEL_LABEL[profile.deals]}`,
|
|
625
|
+
].join(" · ");
|
|
626
|
+
const statusLine = `memo ${getStatusMsg(stats, tick)}`;
|
|
627
|
+
|
|
628
|
+
if (safeWidth < COMPACT_PET_PANEL_MIN_WIDTH) {
|
|
629
|
+
// Worst stat drives the ticker so an idle eye still catches a failing
|
|
630
|
+
// metric instead of a fixed happy/energy readout.
|
|
631
|
+
const worst = pickWorstStat(stats);
|
|
632
|
+
const worstLevel = statLevel(worst.value);
|
|
633
|
+
const accent = worstLevel === "critical" || worstLevel === "low"
|
|
634
|
+
? t.warning
|
|
635
|
+
: t.primary;
|
|
636
|
+
return (
|
|
637
|
+
<Box width={safeWidth} flexShrink={1}>
|
|
638
|
+
<Text color={accent}>
|
|
639
|
+
{fitDisplayText(
|
|
640
|
+
`pet ${mood} · ${worst.key} ${pct(worst.value)} (${worstLevel})`,
|
|
641
|
+
safeWidth,
|
|
642
|
+
)}
|
|
643
|
+
</Text>
|
|
644
|
+
</Box>
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const innerWidth = Math.max(1, safeWidth - PANEL_BORDER_COLUMNS - PANEL_PADDING_COLUMNS);
|
|
649
|
+
const header = `${title} [${activityCopy}]`;
|
|
650
|
+
|
|
651
|
+
return (
|
|
652
|
+
<Box
|
|
653
|
+
flexDirection="column"
|
|
654
|
+
width={safeWidth}
|
|
655
|
+
flexShrink={1}
|
|
656
|
+
borderStyle="round"
|
|
657
|
+
borderColor={t.primaryDim}
|
|
658
|
+
paddingX={1}
|
|
659
|
+
>
|
|
660
|
+
<Box>
|
|
661
|
+
<Text color={t.primary} bold>{fitDisplayText(header, innerWidth)}</Text>
|
|
662
|
+
</Box>
|
|
663
|
+
<Box>
|
|
664
|
+
<Text color={t.text}>{fitDisplayText(statLine, innerWidth)}</Text>
|
|
665
|
+
</Box>
|
|
666
|
+
<Box>
|
|
667
|
+
<Text color={t.dim}>{fitDisplayText(`${mood} · ${statusLine}`, innerWidth)}</Text>
|
|
668
|
+
</Box>
|
|
669
|
+
</Box>
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export const CompactPetPanel = memo(CompactPetPanelView);
|