Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/cli/src/__tests__/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,18 @@ export function builtDiagram(
title,
document,
scene: {
kind: "canvas",
version: 1,
diagramId: id,
title,
width: 640,
height: 480,
accentColor: "#7c3aed",
backgroundColor: "#ffffff",
elements: [],
layers: [],
layouts: [],
zOrder: [],
},
excalidraw: {
type: "excalidraw",
Expand Down
5 changes: 5 additions & 0 deletions apps/cli/src/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,18 @@ export const decodePatchInput = Effect.fn("sketchi.cli.patch.decodeInput")(
requestId: "cli-patch-validation",
source: {
scene: {
kind: "canvas",
version: 1,
diagramId: "validation",
title: "Validation",
width: 1,
height: 1,
accentColor: "#000000",
backgroundColor: "#ffffff",
elements: [],
layers: [],
layouts: [],
zOrder: [],
},
},
});
Expand Down
2 changes: 2 additions & 0 deletions apps/playground/src/application-structure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const expectedPublicFullPaths = [
"/api/studio/projects/from-artifact",
"/api/v1/artifacts/$artifactId",
"/api/v1/artifacts/$artifactId/patch",
"/api/v1/canvases/create",
"/api/v1/flowcharts/build",
"/api/v1/generate",
"/api/v1/mindmaps/build",
Expand Down Expand Up @@ -77,6 +78,7 @@ describe("Playground application structure", () => {
"api/studio/projects_/$projectId.ts",
"api/v1/artifacts/$artifactId.ts",
"api/v1/artifacts/$artifactId/patch.ts",
"api/v1/canvases/create.ts",
"api/v1/flowcharts/build.ts",
"api/v1/generate.ts",
"api/v1/mindmaps/build.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export const DEPLOY_PIPELINE_SCENE = {
};
}

if (element.type !== "text") {
return element;
}

return {
...element,
fontSize: 15,
Expand Down
21 changes: 21 additions & 0 deletions apps/playground/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Route as ApiStudioProjectsRouteImport } from "./routes/api/studio/proje
import { Route as ApiV1SequencesBuildRouteImport } from "./routes/api/v1/sequences/build";
import { Route as ApiV1MindmapsBuildRouteImport } from "./routes/api/v1/mindmaps/build";
import { Route as ApiV1FlowchartsBuildRouteImport } from "./routes/api/v1/flowcharts/build";
import { Route as ApiV1CanvasesCreateRouteImport } from "./routes/api/v1/canvases/create";
import { Route as ApiV1ArtifactsArtifactIdRouteImport } from "./routes/api/v1/artifacts/$artifactId";
import { Route as ApiStudioProjectsProjectIdRouteImport } from "./routes/api/studio/projects_/$projectId";
import { Route as ApiStudioProjectsFromArtifactRouteImport } from "./routes/api/studio/projects/from-artifact";
Expand Down Expand Up @@ -111,6 +112,11 @@ const ApiV1FlowchartsBuildRoute = ApiV1FlowchartsBuildRouteImport.update({
path: "/api/v1/flowcharts/build",
getParentRoute: () => rootRouteImport,
} as any);
const ApiV1CanvasesCreateRoute = ApiV1CanvasesCreateRouteImport.update({
id: "/api/v1/canvases/create",
path: "/api/v1/canvases/create",
getParentRoute: () => rootRouteImport,
} as any);
const ApiV1ArtifactsArtifactIdRoute =
ApiV1ArtifactsArtifactIdRouteImport.update({
id: "/api/v1/artifacts/$artifactId",
Expand Down Expand Up @@ -160,6 +166,7 @@ export interface FileRoutesByFullPath {
"/api/studio/projects/from-artifact": typeof ApiStudioProjectsFromArtifactRoute;
"/api/studio/projects/$projectId": typeof ApiStudioProjectsProjectIdRoute;
"/api/v1/artifacts/$artifactId": typeof ApiV1ArtifactsArtifactIdRouteWithChildren;
"/api/v1/canvases/create": typeof ApiV1CanvasesCreateRoute;
"/api/v1/flowcharts/build": typeof ApiV1FlowchartsBuildRoute;
"/api/v1/mindmaps/build": typeof ApiV1MindmapsBuildRoute;
"/api/v1/sequences/build": typeof ApiV1SequencesBuildRoute;
Expand All @@ -183,6 +190,7 @@ export interface FileRoutesByTo {
"/api/studio/projects/from-artifact": typeof ApiStudioProjectsFromArtifactRoute;
"/api/studio/projects/$projectId": typeof ApiStudioProjectsProjectIdRoute;
"/api/v1/artifacts/$artifactId": typeof ApiV1ArtifactsArtifactIdRouteWithChildren;
"/api/v1/canvases/create": typeof ApiV1CanvasesCreateRoute;
"/api/v1/flowcharts/build": typeof ApiV1FlowchartsBuildRoute;
"/api/v1/mindmaps/build": typeof ApiV1MindmapsBuildRoute;
"/api/v1/sequences/build": typeof ApiV1SequencesBuildRoute;
Expand All @@ -207,6 +215,7 @@ export interface FileRoutesById {
"/api/studio/projects/from-artifact": typeof ApiStudioProjectsFromArtifactRoute;
"/api/studio/projects_/$projectId": typeof ApiStudioProjectsProjectIdRoute;
"/api/v1/artifacts/$artifactId": typeof ApiV1ArtifactsArtifactIdRouteWithChildren;
"/api/v1/canvases/create": typeof ApiV1CanvasesCreateRoute;
"/api/v1/flowcharts/build": typeof ApiV1FlowchartsBuildRoute;
"/api/v1/mindmaps/build": typeof ApiV1MindmapsBuildRoute;
"/api/v1/sequences/build": typeof ApiV1SequencesBuildRoute;
Expand All @@ -232,6 +241,7 @@ export interface FileRouteTypes {
| "/api/studio/projects/from-artifact"
| "/api/studio/projects/$projectId"
| "/api/v1/artifacts/$artifactId"
| "/api/v1/canvases/create"
| "/api/v1/flowcharts/build"
| "/api/v1/mindmaps/build"
| "/api/v1/sequences/build"
Expand All @@ -255,6 +265,7 @@ export interface FileRouteTypes {
| "/api/studio/projects/from-artifact"
| "/api/studio/projects/$projectId"
| "/api/v1/artifacts/$artifactId"
| "/api/v1/canvases/create"
| "/api/v1/flowcharts/build"
| "/api/v1/mindmaps/build"
| "/api/v1/sequences/build"
Expand All @@ -278,6 +289,7 @@ export interface FileRouteTypes {
| "/api/studio/projects/from-artifact"
| "/api/studio/projects_/$projectId"
| "/api/v1/artifacts/$artifactId"
| "/api/v1/canvases/create"
| "/api/v1/flowcharts/build"
| "/api/v1/mindmaps/build"
| "/api/v1/sequences/build"
Expand All @@ -301,6 +313,7 @@ export interface RootRouteChildren {
ApiStudioDiagramsDiagramIdRoute: typeof ApiStudioDiagramsDiagramIdRoute;
ApiStudioProjectsProjectIdRoute: typeof ApiStudioProjectsProjectIdRoute;
ApiV1ArtifactsArtifactIdRoute: typeof ApiV1ArtifactsArtifactIdRouteWithChildren;
ApiV1CanvasesCreateRoute: typeof ApiV1CanvasesCreateRoute;
ApiV1FlowchartsBuildRoute: typeof ApiV1FlowchartsBuildRoute;
ApiV1MindmapsBuildRoute: typeof ApiV1MindmapsBuildRoute;
ApiV1SequencesBuildRoute: typeof ApiV1SequencesBuildRoute;
Expand Down Expand Up @@ -420,6 +433,13 @@ declare module "@tanstack/react-router" {
preLoaderRoute: typeof ApiV1FlowchartsBuildRouteImport;
parentRoute: typeof rootRouteImport;
};
"/api/v1/canvases/create": {
id: "/api/v1/canvases/create";
path: "/api/v1/canvases/create";
fullPath: "/api/v1/canvases/create";
preLoaderRoute: typeof ApiV1CanvasesCreateRouteImport;
parentRoute: typeof rootRouteImport;
};
"/api/v1/artifacts/$artifactId": {
id: "/api/v1/artifacts/$artifactId";
path: "/api/v1/artifacts/$artifactId";
Expand Down Expand Up @@ -500,6 +520,7 @@ const rootRouteChildren: RootRouteChildren = {
ApiStudioDiagramsDiagramIdRoute: ApiStudioDiagramsDiagramIdRoute,
ApiStudioProjectsProjectIdRoute: ApiStudioProjectsProjectIdRoute,
ApiV1ArtifactsArtifactIdRoute: ApiV1ArtifactsArtifactIdRouteWithChildren,
ApiV1CanvasesCreateRoute: ApiV1CanvasesCreateRoute,
ApiV1FlowchartsBuildRoute: ApiV1FlowchartsBuildRoute,
ApiV1MindmapsBuildRoute: ApiV1MindmapsBuildRoute,
ApiV1SequencesBuildRoute: ApiV1SequencesBuildRoute,
Expand Down
24 changes: 24 additions & 0 deletions apps/playground/src/routes/api/v1/canvases/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/api/v1/canvases/create")({
server: {
handlers: {
POST: async ({ request }) => {
const [
{ getPlaygroundRequestBoundary },
{ handleCreateCanvasRequest },
{ runPlaygroundEffect },
] = await Promise.all([
import("@/server/bindings/cloudflare-bindings.server"),
import("@/server/codemode/api.server"),
import("@/server/runtime/runtime.server"),
]);

return runPlaygroundEffect(
handleCreateCanvasRequest(request),
getPlaygroundRequestBoundary(request),
);
},
},
},
});
93 changes: 93 additions & 0 deletions apps/playground/src/server/codemode/api.server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
handleBuildFlowchartRequest as handleBuildFlowchartRequestEffect,
handleBuildMindmapRequest as handleBuildMindmapRequestEffect,
handleBuildSequenceDiagramRequest as handleBuildSequenceDiagramRequestEffect,
handleCreateCanvasRequest as handleCreateCanvasRequestEffect,
handleGetArtifactRequest as handleGetArtifactRequestEffect,
handlePatchArtifactRequest as handlePatchArtifactRequestEffect,
} from "./api.server";
Expand Down Expand Up @@ -50,6 +51,13 @@ function handleBuildSequenceDiagramRequest(env: StudioEnv, request: Request) {
);
}

function handleCreateCanvasRequest(env: StudioEnv, request: Request) {
return runPlaygroundEffect(
handleCreateCanvasRequestEffect(request),
testBoundary(env, request),
);
}

function handleGetArtifactRequest(
env: StudioEnv,
request: Request,
Expand Down Expand Up @@ -300,6 +308,91 @@ function delay(ms: number): Promise<void> {
}

describe("Code Mode API handlers", () => {
it("creates a CanvasSpec through the normal Worker route", async () => {
const response = await handleCreateCanvasRequest(
{},
postRequest("https://studio.test/api/v1/canvases/create", {
requestId: "canvas-http",
spec: {
kind: "canvas",
version: 1,
diagramId: "http-canvas",
title: "HTTP canvas",
width: 480,
height: 320,
accentColor: "#111827",
backgroundColor: "#ffffff",
elements: [
{
type: "node",
id: "card",
nodeId: "card",
shape: "rectangle",
x: 40,
y: 40,
width: 240,
height: 120,
label: "Canvas",
},
],
layers: [],
layouts: [],
zOrder: ["card"],
},
options: {
artifactFormats: ["scene", "excalidraw"],
inlineArtifacts: ["scene"],
},
}),
);

expect(response.status).toBe(200);
await expect(response.json()).resolves.toMatchObject({
ok: true,
status: "accepted",
requestId: "canvas-http",
normalizedSpec: { kind: "canvas", version: 1 },
artifact: {
diagramId: "http-canvas",
formats: [{ format: "scene" }, { format: "excalidraw" }],
},
});
});

it("returns typed HTTP 422 for an empty CanvasSpec", async () => {
const response = await handleCreateCanvasRequest(
{},
postRequest("https://studio.test/api/v1/canvases/create", {
spec: {
kind: "canvas",
version: 1,
diagramId: "empty-http-canvas",
title: "Empty HTTP canvas",
width: 480,
height: 320,
accentColor: "#111827",
backgroundColor: "#ffffff",
elements: [],
layers: [],
layouts: [],
zOrder: [],
},
}),
);

expect(response.status).toBe(422);
await expect(response.json()).resolves.toMatchObject({
ok: false,
status: "invalid_canvas",
issues: [
expect.objectContaining({
code: "invalid_canvas_geometry",
stage: "canvas",
}),
],
});
});

it("builds a public sequence diagram through the no-auth HTTP handler", async () => {
const response = await handleBuildSequenceDiagramRequest(
{},
Expand Down
Loading
Loading