Build Release Installers / Release for the tag (push) Successful in 16s
Build Release Installers / Windows installer (NSIS x64) (push) Successful in 1m48s
Build Release Installers / Linux packages (AppImage + deb x64) (push) Successful in 2m21s
CI / Typecheck, lint, tests, e2e (push) Successful in 3m52s
Build Release Installers / macOS installers (dmg + zip, x64 + arm64) (push) Successful in 7m15s
- Home: the goal title box is two line boxes tall (2.8em), so a third line no longer peeks out. - Chat messages, reply fallbacks and assignment DMs follow the app display language; tasks and documents keep the working language. - OpenAI and GPT-account model lists match OpenAI's current line-up; default gpt-6.1-sol. - Onboarding connectors step takes the operator's Composio and logo.dev keys, then lists the toolkits the Composio project enables; without a key Connectors leaves the sidebar. - Google Antigravity removed end to end (migration 111), with the remote-revocation path only it used. - Office: 2x pixel people with outlines, rim light, walk, typing and blink; dithered real-clock lighting; detailed furniture and floors. - Per-agent model picker lists the provider's whole model list, live when connected. - Chat wake context breaks same-millisecond ties by insertion order; the mid-run inbox dedupes by message id. - The stored "clock" theme reads back instead of falling to "system". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
3.6 KiB
TypeScript
64 lines
3.6 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import { finishSprite, PixelGrid } from "../../src/renderer/src/pages/office/pixel-grid";
|
|
import { personArt, ANCHOR } from "../../src/renderer/src/pages/office/person-art";
|
|
import { appearanceFor, frameFor } from "../../src/renderer/src/pages/office/sprites";
|
|
import { ditherLevels, sunPatches } from "../../src/renderer/src/pages/office/lighting";
|
|
|
|
const pixel = (grid: PixelGrid, x: number, y: number) => Array.from(grid.data.subarray((y * grid.w + x) * 4, (y * grid.w + x) * 4 + 4));
|
|
|
|
describe("office pixel art", () => {
|
|
test("a finished sprite is rim-lit top-left, shaded bottom-right, and outlined in a dark tone of what it borders", () => {
|
|
const grid = new PixelGrid(6, 6);
|
|
grid.rect(1, 1, 4, 4, "#808080");
|
|
finishSprite(grid);
|
|
expect(pixel(grid, 1, 1)).toEqual([146, 146, 146, 255]);
|
|
expect(pixel(grid, 4, 4)).toEqual([108, 108, 108, 255]);
|
|
expect(pixel(grid, 2, 2)).toEqual([128, 128, 128, 255]);
|
|
expect(pixel(grid, 0, 2)).toEqual([41, 41, 41, 255]);
|
|
expect(pixel(grid, 0, 0)[3]).toBe(0);
|
|
});
|
|
|
|
test("people stand on the anchor in every facing, and west mirrors east", () => {
|
|
const look = appearanceFor("agent-7", false);
|
|
for (const facing of ["south", "north", "east", "west"] as const) {
|
|
const grid = personArt(look, { facing, step: -1, typing: -1, blink: false, carrying: null }, false);
|
|
const feetRow = Array.from({ length: grid.w }, (_, x) => grid.alpha(x, ANCHOR.y - 1));
|
|
expect(feetRow.some((alpha) => alpha > 0)).toBe(true);
|
|
expect(Array.from({ length: grid.w }, (_, x) => grid.alpha(x, ANCHOR.y + 2)).every((alpha) => alpha === 0)).toBe(true);
|
|
}
|
|
const east = personArt(look, { facing: "east", step: 0, typing: -1, blink: false, carrying: "coffee" }, false);
|
|
const west = personArt(look, { facing: "west", step: 0, typing: -1, blink: false, carrying: "coffee" }, false);
|
|
expect(pixel(west, 3, 40)).toEqual(pixel(east, east.w - 4, 40));
|
|
});
|
|
|
|
test("poses: four walk steps, typing beats only while working, a short blink", () => {
|
|
const pose = { facing: "south" as const, walkPhase: 0, working: false, carrying: null, timeMs: 0, animSeed: 3 };
|
|
expect(frameFor(pose).step).toBe(-1);
|
|
expect(new Set([0.1, 1.7, 3.3, 4.9].map((walkPhase) => frameFor({ ...pose, walkPhase }).step))).toEqual(new Set([0, 1, 2, 3]));
|
|
expect(frameFor(pose).typing).toBe(-1);
|
|
expect(new Set([0, 180].map((timeMs) => frameFor({ ...pose, working: true, timeMs }).typing))).toEqual(new Set([0, 1]));
|
|
const blinks = Array.from({ length: 400 }, (_, i) => frameFor({ ...pose, timeMs: i * 25 }).blink).filter(Boolean).length;
|
|
expect(blinks).toBeGreaterThan(0);
|
|
expect(blinks).toBeLessThan(20);
|
|
});
|
|
});
|
|
|
|
describe("office lighting", () => {
|
|
test("the light map bands into whole levels with an ordered dither", () => {
|
|
const data = new Uint8ClampedArray(4 * 4 * 4).fill(138);
|
|
ditherLevels(data, 4);
|
|
const levels = new Set(Array.from(data).filter((_, i) => i % 4 !== 3));
|
|
expect(levels.size).toBe(2);
|
|
for (const value of levels) expect(Math.round((value / 255) * 12) / 12).toBeCloseTo(value / 255, 2);
|
|
});
|
|
|
|
test("sun falls through the windows by day, slanting with the hour, and not at night", () => {
|
|
const glass = [{ x: 100, y: 8, w: 40, h: 20 }];
|
|
expect(sunPatches(glass, new Date(2026, 9, 9, 23, 0))).toEqual([]);
|
|
const morning = sunPatches(glass, new Date(2026, 9, 9, 9, 0))[0]!;
|
|
const evening = sunPatches(glass, new Date(2026, 9, 9, 17, 0))[0]!;
|
|
expect(morning[2]!.x).toBeGreaterThan(evening[2]!.x);
|
|
expect(morning[2]!.y).toBeGreaterThan(morning[1]!.y);
|
|
});
|
|
});
|