First Release of Claw3D (#11)

Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
Luke The Dev
2026-03-19 23:14:04 -05:00
committed by GitHub
parent 5ea96b2650
commit 4fa4f13558
431 changed files with 105438 additions and 14 deletions
+34
View File
@@ -0,0 +1,34 @@
import { expect, test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
await page.route("**/api/studio", async (route, request) => {
if (request.method() === "PUT") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
}),
});
return;
}
if (request.method() !== "GET") {
await route.fallback();
return;
}
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
}),
});
});
});
test("empty focused view shows zero agents when disconnected", async ({ page }) => {
await page.goto("/");
await expect(page.getByTestId("studio-menu-toggle")).toBeVisible();
await expect(page.getByRole("button", { name: "Connect" }).first()).toBeVisible();
});
+44
View File
@@ -0,0 +1,44 @@
import { expect, test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
await page.route("**/api/studio", async (route, request) => {
if (request.method() === "PUT") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
}),
});
return;
}
if (request.method() !== "GET") {
await route.fallback();
return;
}
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
}),
});
});
});
test("shows_connection_settings_control_in_header", async ({ page }) => {
await page.goto("/");
await expect(page.getByTestId("brain-files-toggle")).toHaveCount(0);
await page.getByTestId("studio-menu-toggle").click();
await expect(page.getByTestId("gateway-settings-toggle")).toBeVisible();
});
test("mobile_header_shows_connection_control", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/");
await expect(page.getByTestId("brain-files-toggle")).toHaveCount(0);
await page.getByTestId("studio-menu-toggle").click();
await expect(page.getByTestId("gateway-settings-toggle")).toBeVisible();
});
+17
View File
@@ -0,0 +1,17 @@
import { expect, test } from "@playwright/test";
import { stubStudioRoute } from "./helpers/studioRoute";
test.beforeEach(async ({ page }) => {
await stubStudioRoute(page);
});
test("connection panel reflects disconnected state", async ({ page }) => {
await page.goto("/");
await page.getByTestId("studio-menu-toggle").click();
await page.getByTestId("gateway-settings-toggle").click();
await expect(page.getByLabel("Upstream URL")).toBeVisible();
await expect(
page.getByRole("button", { name: /^(Connect|Disconnect)$/ })
).toBeVisible();
});
+31
View File
@@ -0,0 +1,31 @@
import { expect, test } from "@playwright/test";
import { stubStudioRoute } from "./helpers/studioRoute";
test("connection settings persist to the studio settings API", async ({ page }) => {
await stubStudioRoute(page);
await page.goto("/");
await page.getByTestId("studio-menu-toggle").click();
await page.getByTestId("gateway-settings-toggle").click();
await expect(page.getByLabel("Upstream URL")).toBeVisible();
await page.getByLabel("Upstream URL").fill("ws://gateway.example:18789");
await page.getByLabel("Upstream token").fill("token-123");
const request = await page.waitForRequest((req) => {
if (!req.url().includes("/api/studio") || req.method() !== "PUT") {
return false;
}
const payload = JSON.parse(req.postData() ?? "{}") as Record<string, unknown>;
const gateway = (payload.gateway ?? {}) as { url?: string; token?: string };
return gateway.url === "ws://gateway.example:18789" && gateway.token === "token-123";
});
const payload = JSON.parse(request.postData() ?? "{}") as Record<string, unknown>;
const gateway = (payload.gateway ?? {}) as { url?: string; token?: string };
expect(gateway.url).toBe("ws://gateway.example:18789");
expect(gateway.token).toBe("token-123");
await expect(
page.getByRole("button", { name: /^(Connect|Disconnect)$/ })
).toBeVisible();
});
+51
View File
@@ -0,0 +1,51 @@
import { expect, test } from "@playwright/test";
import { stubStudioRoute } from "./helpers/studioRoute";
test.beforeEach(async ({ page }) => {
await stubStudioRoute(page);
});
test("shows_disconnected_connect_surface", async ({ page }) => {
await page.goto("/");
await expect(page.getByLabel("Upstream URL")).toBeVisible();
await expect(page.getByRole("button", { name: /^(Connect|Connecting…)$/ })).toBeVisible();
});
test("persists_gateway_fields_to_studio_settings", async ({ page }) => {
await page.goto("/");
await page.getByLabel("Upstream URL").fill("ws://gateway.example:18789");
await page.getByLabel("Upstream token").fill("token-123");
const request = await page.waitForRequest((req) => {
if (!req.url().includes("/api/studio") || req.method() !== "PUT") {
return false;
}
const payload = JSON.parse(req.postData() ?? "{}") as Record<string, unknown>;
const gateway = (payload.gateway ?? {}) as { url?: string; token?: string };
return gateway.url === "ws://gateway.example:18789" && gateway.token === "token-123";
});
const payload = JSON.parse(request.postData() ?? "{}") as Record<string, unknown>;
const gateway = (payload.gateway ?? {}) as { url?: string; token?: string };
expect(gateway.url).toBe("ws://gateway.example:18789");
expect(gateway.token).toBe("token-123");
});
test("focused_preferences_persist_across_reload", async ({ page }) => {
await page.goto("/");
await page.getByTestId("studio-menu-toggle").click();
await expect(page.getByTestId("gateway-settings-toggle")).toBeVisible();
await page.reload();
await expect(page.getByTestId("studio-menu-toggle")).toBeVisible();
});
test("clears_unseen_indicator_on_focus", async ({ page }) => {
await page.goto("/");
await page.getByTestId("studio-menu-toggle").click();
await expect(page.getByTestId("gateway-settings-toggle")).toBeVisible();
});
+32
View File
@@ -0,0 +1,32 @@
import { expect, test } from "@playwright/test";
test("loads focused studio empty state", async ({ page }) => {
await page.route("**/api/studio", async (route, request) => {
if (request.method() === "PUT") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
}),
});
return;
}
if (request.method() !== "GET") {
await route.fallback();
return;
}
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
}),
});
});
await page.goto("/");
await expect(page.getByTestId("studio-menu-toggle")).toBeVisible();
await expect(page.getByRole("button", { name: "Connect" }).first()).toBeVisible();
});
+107
View File
@@ -0,0 +1,107 @@
import type { Page, Route, Request } from "@playwright/test";
export type StudioSettingsFixture = {
version: 1;
gateway: { url: string; token: string } | null;
focused: Record<string, { mode: "focused"; filter: string; selectedAgentId: string | null }>;
avatars: Record<string, Record<string, string>>;
};
const DEFAULT_SETTINGS: StudioSettingsFixture = {
version: 1,
gateway: null,
focused: {},
avatars: {},
};
const createStudioRoute = (initial: StudioSettingsFixture = DEFAULT_SETTINGS) => {
let settings: StudioSettingsFixture = {
version: 1,
gateway: initial.gateway ?? null,
focused: { ...(initial.focused ?? {}) },
avatars: { ...(initial.avatars ?? {}) },
};
return async (route: Route, request: Request) => {
if (request.method() === "GET") {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ settings }),
});
return;
}
if (request.method() !== "PUT") {
await route.fallback();
return;
}
const patch = JSON.parse(request.postData() ?? "{}") as Record<string, unknown>;
const next = { ...settings };
if ("gateway" in patch) {
next.gateway = (patch.gateway as StudioSettingsFixture["gateway"]) ?? null;
}
if (patch.focused && typeof patch.focused === "object") {
const focusedPatch = patch.focused as Record<string, Record<string, unknown>>;
const focusedNext = { ...next.focused };
for (const [key, value] of Object.entries(focusedPatch)) {
const existing = focusedNext[key] ?? {
mode: "focused" as const,
filter: "all",
selectedAgentId: null,
};
focusedNext[key] = {
mode: (value.mode as "focused") ?? existing.mode,
filter: (value.filter as string) ?? existing.filter,
selectedAgentId:
"selectedAgentId" in value
? ((value.selectedAgentId as string | null) ?? null)
: existing.selectedAgentId,
};
}
next.focused = focusedNext;
}
if (patch.avatars && typeof patch.avatars === "object") {
const avatarsPatch = patch.avatars as Record<string, Record<string, string | null> | null>;
const avatarsNext: StudioSettingsFixture["avatars"] = { ...next.avatars };
for (const [gatewayKey, gatewayPatch] of Object.entries(avatarsPatch)) {
if (gatewayPatch === null) {
delete avatarsNext[gatewayKey];
continue;
}
const existing = avatarsNext[gatewayKey] ? { ...avatarsNext[gatewayKey] } : {};
for (const [agentId, seedPatch] of Object.entries(gatewayPatch)) {
if (seedPatch === null) {
delete existing[agentId];
continue;
}
const seed = typeof seedPatch === "string" ? seedPatch.trim() : "";
if (!seed) {
delete existing[agentId];
continue;
}
existing[agentId] = seed;
}
avatarsNext[gatewayKey] = existing;
}
next.avatars = avatarsNext;
}
settings = next;
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ settings }),
});
};
};
export const stubStudioRoute = async (
page: Page,
initial: StudioSettingsFixture = DEFAULT_SETTINGS
) => {
await page.route("**/api/studio", createStudioRoute(initial));
};
+16
View File
@@ -0,0 +1,16 @@
import { expect, test } from "@playwright/test";
import { stubStudioRoute } from "./helpers/studioRoute";
test.beforeEach(async ({ page }) => {
await stubStudioRoute(page);
});
test("redirects unknown app routes to root", async ({ page }) => {
await page.goto("/not-a-real-route");
await expect
.poll(() => new URL(page.url()).pathname, {
message: "Expected invalid route to redirect to root path.",
})
.toBe("/");
await expect(page.getByTestId("studio-menu-toggle")).toBeVisible();
});
@@ -0,0 +1,20 @@
import { expect, test } from "@playwright/test";
import { stubStudioRoute } from "./helpers/studioRoute";
test.beforeEach(async ({ page }) => {
await stubStudioRoute(page);
});
test("settings route shows connect UI while disconnected and can return to chat", async ({ page }) => {
await page.goto("/agents/main/settings");
await expect(page.getByRole("button", { name: "Back to chat" })).toBeVisible();
await expect(page.getByLabel("Upstream URL")).toBeVisible();
await page.getByRole("button", { name: "Back to chat" }).click();
await expect
.poll(() => new URL(page.url()).pathname, {
message: "Expected back button to return to chat route.",
})
.toBe("/");
});