feat(companion): rename Tabby companion to Sagi, replace cat avatar with mascot

Full rename across files, identifiers, CSS classes, and user-facing strings
(en/vi locales, ARCHITECTURE.md). Replaces the hand-drawn animated cat
avatar with the pig-superhero mascot artwork, split into parts to keep
per-eye cursor tracking and blink animation working; whole-mascot mood
transforms (breathe/bob/shake/tilt) and new zzz/bang/sparkle overlays
carry the rest of the mood expression since the traced art has no shared
palette to key off of.
This commit is contained in:
2026-08-19 11:07:59 +07:00
parent dce54c8a3a
commit 4cc39f5069
25 changed files with 1390 additions and 1041 deletions
@@ -0,0 +1,32 @@
/**
* @file quips.test.ts
* @description Unit tests for Sagi's quip picker — full key coverage and stable selection behavior for the speech-bubble lines.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it, expect } from "vitest";
import { pickQuip, ALL_QUIP_KEYS } from "../quips";
describe("pickQuip", () => {
it("returns a non-empty string for every known key", () => {
for (const key of ALL_QUIP_KEYS) {
expect(pickQuip(key, () => 0).length).toBeGreaterThan(0);
}
});
it("is deterministic given an injected rand", () => {
expect(pickQuip("session_done", () => 0)).toBe(pickQuip("session_done", () => 0));
});
it("rand=0.999 stays within bounds (no out-of-range index)", () => {
for (const key of ALL_QUIP_KEYS) {
expect(typeof pickQuip(key, () => 0.999)).toBe("string");
expect(pickQuip(key, () => 0.999).length).toBeGreaterThan(0);
}
});
it("returns empty string for an unknown key without throwing", () => {
// @ts-expect-error intentionally passing an invalid key
expect(pickQuip("nope", () => 0)).toBe("");
});
});