diff --git a/apps/server/src/db/migrations/0030_whiteboards.sql b/apps/server/src/db/migrations/0030_whiteboards.sql new file mode 100644 index 00000000..3927b8f7 --- /dev/null +++ b/apps/server/src/db/migrations/0030_whiteboards.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS whiteboards ( + agent_id TEXT PRIMARY KEY, + scene JSONB NOT NULL DEFAULT '{"elements":[]}', + version INTEGER NOT NULL DEFAULT 1, + updated_by TEXT NOT NULL DEFAULT 'user', + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/apps/server/src/routes/mcp.ts b/apps/server/src/routes/mcp.ts index da6fdfa1..b471a5a1 100644 --- a/apps/server/src/routes/mcp.ts +++ b/apps/server/src/routes/mcp.ts @@ -49,6 +49,11 @@ type McpRouteDeps = { mcpRenameSession: unknown; mcpShareMedia: unknown; mcpListMedia: unknown; + mcpGetWhiteboard: unknown; + mcpUpdateWhiteboard: unknown; + mcpClearWhiteboard: unknown; + mcpLibraryList: unknown; + mcpInsertLibraryItem: unknown; mcpSubmitFeedback: unknown; mcpListPersonas: unknown; mcpLaunchPersona: unknown; @@ -207,6 +212,11 @@ export async function registerMcpRoutes( renameSession: deps.mcpRenameSession, shareMedia: deps.mcpShareMedia, listMedia: deps.mcpListMedia, + getWhiteboard: deps.mcpGetWhiteboard, + updateWhiteboard: deps.mcpUpdateWhiteboard, + clearWhiteboard: deps.mcpClearWhiteboard, + libraryList: deps.mcpLibraryList, + insertLibraryItem: deps.mcpInsertLibraryItem, submitFeedback: deps.mcpSubmitFeedback, upsertPin: deps.mcpUpsertPin, deletePin: deps.mcpDeletePin, @@ -295,6 +305,11 @@ export async function registerMcpRoutes( renameSession: deps.mcpRenameSession, shareMedia: deps.mcpShareMedia, listMedia: deps.mcpListMedia, + getWhiteboard: deps.mcpGetWhiteboard, + updateWhiteboard: deps.mcpUpdateWhiteboard, + clearWhiteboard: deps.mcpClearWhiteboard, + libraryList: deps.mcpLibraryList, + insertLibraryItem: deps.mcpInsertLibraryItem, submitFeedback: deps.mcpSubmitFeedback, listPersonas: deps.mcpListPersonas, launchPersona: deps.mcpLaunchPersona, diff --git a/apps/server/src/routes/whiteboard.ts b/apps/server/src/routes/whiteboard.ts new file mode 100644 index 00000000..74e50d47 --- /dev/null +++ b/apps/server/src/routes/whiteboard.ts @@ -0,0 +1,143 @@ +import path from "node:path"; +import { mkdir, rm, writeFile } from "node:fs/promises"; + +import type { FastifyInstance } from "fastify"; +import type { Pool } from "pg"; + +import type { AgentManager } from "../agents/manager.js"; +import { resolveMediaDir } from "../shared/media.js"; +import { + EMPTY_SCENE, + isValidScene, + loadWhiteboard, + MAX_ELEMENTS, + saveWhiteboard, + WHITEBOARD_SNAPSHOT_FILENAME, +} from "../shared/whiteboard-store.js"; + +const SCENE_BODY_LIMIT = 8 * 1024 * 1024; + +type WhiteboardRouteDeps = { + pool: Pool; + mediaRoot: string; + agentManager: AgentManager; + publishUiEvent: (event: unknown) => void; +}; + +export async function registerWhiteboardRoutes( + app: FastifyInstance, + deps: WhiteboardRouteDeps +): Promise { + app.get("/api/v1/agents/:id/whiteboard", async (request, reply) => { + const params = request.params as { id?: string }; + const id = params.id ?? ""; + const agent = await deps.agentManager.getAgent(id); + if (!agent) { + return reply.code(404).send({ error: "Agent not found." }); + } + + const row = await loadWhiteboard(deps.pool, id); + if (!row) { + return { scene: EMPTY_SCENE, version: 0, updatedAt: null }; + } + return { + scene: row.scene, + version: Number(row.version), + updatedAt: row.updated_at.toISOString(), + }; + }); + + app.put( + "/api/v1/agents/:id/whiteboard", + { bodyLimit: SCENE_BODY_LIMIT }, + async (request, reply) => { + const params = request.params as { id?: string }; + const id = params.id ?? ""; + const agent = await deps.agentManager.getAgent(id); + if (!agent) { + return reply.code(404).send({ error: "Agent not found." }); + } + + const body = request.body as + | { scene?: unknown; baseVersion?: unknown } + | undefined; + if (!isValidScene(body?.scene)) { + return reply.code(400).send({ + error: `scene must be an object with an elements array (max ${MAX_ELEMENTS}).`, + }); + } + const baseVersion = + typeof body?.baseVersion === "number" && + Number.isInteger(body.baseVersion) && + body.baseVersion >= 0 + ? body.baseVersion + : null; + if (baseVersion === null) { + return reply + .code(400) + .send({ error: "baseVersion must be a non-negative integer." }); + } + + const saved = await saveWhiteboard( + deps.pool, + id, + body.scene, + baseVersion, + "user" + ); + if (!saved) { + const current = await loadWhiteboard(deps.pool, id); + return reply.code(409).send({ + error: "Whiteboard was modified by someone else.", + scene: current?.scene ?? EMPTY_SCENE, + version: current ? Number(current.version) : 0, + }); + } + + deps.publishUiEvent({ + type: "whiteboard.changed", + agentId: id, + version: saved.version, + source: "user", + }); + return { ok: true, version: saved.version }; + } + ); + + app.post("/api/v1/agents/:id/whiteboard/snapshot", async (request, reply) => { + const params = request.params as { id?: string }; + const id = params.id ?? ""; + const agent = await deps.agentManager.getAgent(id); + if (!agent) { + return reply.code(404).send({ error: "Agent not found." }); + } + + const data = await request.file(); + if (!data || data.mimetype !== "image/png") { + return reply.code(400).send({ error: "A PNG file field is required." }); + } + + const mediaDir = resolveMediaDir(id, agent.mediaDir, deps.mediaRoot); + await mkdir(mediaDir, { recursive: true }); + const buffer = await data.toBuffer(); + await writeFile(path.join(mediaDir, WHITEBOARD_SNAPSHOT_FILENAME), buffer); + return { ok: true, sizeBytes: buffer.length }; + }); + + app.delete( + "/api/v1/agents/:id/whiteboard/snapshot", + async (request, reply) => { + const params = request.params as { id?: string }; + const id = params.id ?? ""; + const agent = await deps.agentManager.getAgent(id); + if (!agent) { + return reply.code(404).send({ error: "Agent not found." }); + } + const mediaDir = resolveMediaDir(id, agent.mediaDir, deps.mediaRoot); + await rm(path.join(mediaDir, WHITEBOARD_SNAPSHOT_FILENAME), { + force: true, + }); + return { ok: true }; + } + ); +} diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index 34f75103..97ab3ce3 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -1,5 +1,6 @@ import path from "node:path"; import os from "node:os"; +import { fileURLToPath } from "node:url"; import { mkdir, readFile, @@ -36,6 +37,7 @@ import { deleteSetting, getSetting, setSetting } from "./db/settings.js"; import { runCommand } from "./shared/lib/run-command.js"; import { shouldSkipAutomaticMacPathProbe } from "./shared/mac-path-privacy.js"; import { mimeType, resolveMediaDir } from "./shared/media.js"; +import { LibraryCatalog } from "./shared/whiteboard-library.js"; import { handleMcpRequest } from "./shared/mcp/server.js"; import { readReleaseStore, writeReleaseStore } from "./release-store.js"; import { @@ -105,6 +107,7 @@ import { registerFeedbackRoutes } from "./routes/feedback.js"; import { registerJobRoutes } from "./routes/jobs.js"; import { registerTemplateRoutes } from "./routes/templates.js"; import { registerMediaRoutes } from "./routes/media.js"; +import { registerWhiteboardRoutes } from "./routes/whiteboard.js"; import { registerMcpRoutes } from "./routes/mcp.js"; import { registerPersonaReviewRoutes } from "./routes/persona-reviews.js"; import { registerPersonalityRoutes } from "./routes/personalities.js"; @@ -146,7 +149,29 @@ import { createActivityMonitor } from "./agents/activity-monitor.js"; import { createAutoRenamePrompter } from "./agents/auto-rename-prompter.js"; import { DiffStatsRefresher } from "./agents/diff-stats-refresher.js"; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const config = loadConfig(); +let libraryCatalog: LibraryCatalog | undefined; +try { + libraryCatalog = await LibraryCatalog.fromDirectory( + path.resolve( + __dirname, + "..", + "..", + "web", + "public", + "excalidraw", + "libraries" + ) + ); +} catch (err) { + console.warn( + `Failed to load whiteboard library catalog, continuing without library support: ${ + err instanceof Error ? err.message : String(err) + }` + ); + libraryCatalog = undefined; +} const app = Fastify({ logger: true, ...(config.tls && { https: { cert: config.tls.cert, key: config.tls.key } }), @@ -319,6 +344,7 @@ const mcpHandlers = createMcpHandlers({ withStreamFlag, sendAgentPrompt: injectAgentPrompt, appLog: app.log, + libraryCatalog, }); const jobTerminalStatuses = new Set([ "completed", @@ -476,6 +502,11 @@ async function registerRoutes() { mcpRenameSession: mcpHandlers.renameSession, mcpShareMedia: mcpHandlers.shareMedia, mcpListMedia: mcpHandlers.listMedia, + mcpGetWhiteboard: mcpHandlers.getWhiteboard, + mcpUpdateWhiteboard: mcpHandlers.updateWhiteboard, + mcpClearWhiteboard: mcpHandlers.clearWhiteboard, + mcpLibraryList: mcpHandlers.libraryList, + mcpInsertLibraryItem: mcpHandlers.insertLibraryItem, mcpSubmitFeedback: mcpHandlers.submitFeedback, mcpListPersonas: mcpHandlers.listPersonas, mcpLaunchPersona: mcpHandlers.launchPersona, @@ -581,6 +612,13 @@ async function registerRoutes() { publishUiEvent: (event) => uiEventBroker.publish(event as UiEvent), }); + await registerWhiteboardRoutes(app, { + pool, + mediaRoot: config.mediaRoot, + agentManager, + publishUiEvent: (event) => uiEventBroker.publish(event as UiEvent), + }); + await registerAgentRoutes(app, { pool, appLog: app.log, diff --git a/apps/server/src/server/mcp-handlers.ts b/apps/server/src/server/mcp-handlers.ts index 7afcfc73..16ad5b4c 100644 --- a/apps/server/src/server/mcp-handlers.ts +++ b/apps/server/src/server/mcp-handlers.ts @@ -28,6 +28,8 @@ import { resolveHeadSha } from "../shared/git/worktree.js"; import { isMediaFile, isTextFile, resolveMediaDir } from "../shared/media.js"; import type { PublishUiEvent, SendAgentPrompt } from "./mcp-handler-types.js"; import { createReviewHandlers } from "./mcp-review-handlers.js"; +import { createWhiteboardHandlers } from "./mcp-whiteboard-handlers.js"; +import type { LibraryCatalog } from "../shared/whiteboard-library.js"; const AGENT_LATEST_EVENT_TYPES = [ "working", @@ -63,6 +65,7 @@ type CreateMcpHandlersDeps = { ) => T & { hasStream: boolean }; sendAgentPrompt: SendAgentPrompt; appLog: FastifyBaseLogger; + libraryCatalog?: LibraryCatalog; }; function isAgentLatestEventType(value: unknown): value is AgentLatestEventType { @@ -141,6 +144,7 @@ export function createMcpHandlers(deps: CreateMcpHandlersDeps) { withStreamFlag, sendAgentPrompt, appLog, + libraryCatalog, } = deps; const reviewHandlers = createReviewHandlers({ @@ -151,8 +155,17 @@ export function createMcpHandlers(deps: CreateMcpHandlersDeps) { sendAgentPrompt, }); + const whiteboardHandlers = createWhiteboardHandlers({ + pool, + mediaRoot, + agentManager, + publishUiEvent, + libraryCatalog, + }); + return { ...reviewHandlers, + ...whiteboardHandlers, async upsertEvent( agentId: string, diff --git a/apps/server/src/server/mcp-whiteboard-handlers.ts b/apps/server/src/server/mcp-whiteboard-handlers.ts new file mode 100644 index 00000000..2cf55307 --- /dev/null +++ b/apps/server/src/server/mcp-whiteboard-handlers.ts @@ -0,0 +1,269 @@ +import path from "node:path"; +import { rm, stat } from "node:fs/promises"; + +import type { Pool } from "pg"; + +import type { AgentManager } from "../agents/manager.js"; +import { resolveMediaDir } from "../shared/media.js"; +import { + simplifyElements, + type WhiteboardGetResult, + type WhiteboardUpdateResult, +} from "../shared/whiteboard.js"; +import { + EMPTY_SCENE, + loadWhiteboard, + MAX_ELEMENTS, + saveWhiteboard, + WHITEBOARD_SNAPSHOT_FILENAME, +} from "../shared/whiteboard-store.js"; +import type { + CompactLibraryItem, + DetailedLibraryItem, + LibraryCatalog, +} from "../shared/whiteboard-library.js"; +import type { PublishUiEvent } from "./mcp-handler-types.js"; + +type CreateWhiteboardHandlersDeps = { + pool: Pool; + mediaRoot: string; + agentManager: AgentManager; + publishUiEvent: PublishUiEvent; + libraryCatalog?: LibraryCatalog; +}; + +type RawElement = Record; + +function hasRequiredFields( + el: unknown +): el is RawElement & { id: string; type: string } { + if (typeof el !== "object" || el === null) return false; + const obj = el as RawElement; + return typeof obj.id === "string" && typeof obj.type === "string"; +} + +function mergeElements( + existing: unknown[], + incoming: unknown[], + deleteIds: string[] +): { elements: unknown[]; addedIds: string[]; updatedIds: string[] } { + const deleteSet = new Set(deleteIds); + const existingMap = new Map(); + for (const el of existing) { + if (typeof el === "object" && el !== null) { + const id = (el as RawElement).id; + if (typeof id === "string") { + existingMap.set(id, el); + } + } + } + + const addedIds: string[] = []; + const updatedIds: string[] = []; + + for (const el of incoming) { + if (!hasRequiredFields(el)) continue; + if (existingMap.has(el.id)) { + updatedIds.push(el.id); + } else { + addedIds.push(el.id); + } + existingMap.set(el.id, el); + } + + for (const id of deleteSet) { + if (existingMap.has(id)) { + existingMap.delete(id); + } + } + + return { + elements: Array.from(existingMap.values()), + addedIds, + updatedIds, + }; +} + +export function createWhiteboardHandlers(deps: CreateWhiteboardHandlersDeps) { + const { pool, mediaRoot, agentManager, publishUiEvent, libraryCatalog } = + deps; + + return { + async getWhiteboard(agentId: string): Promise { + const agent = await agentManager.getAgent(agentId); + if (!agent) throw new Error("Agent not found."); + + const row = await loadWhiteboard(pool, agentId); + const snapshotFile = path.join( + resolveMediaDir(agentId, agent.mediaDir, mediaRoot), + WHITEBOARD_SNAPSHOT_FILENAME + ); + const snapshotStat = await stat(snapshotFile).catch(() => null); + const snapshotPath = snapshotStat?.isFile() ? snapshotFile : null; + return { + elements: row ? simplifyElements(row.scene.elements) : [], + version: row ? Number(row.version) : 0, + updatedAt: row ? row.updated_at.toISOString() : null, + updatedBy: row ? row.updated_by : null, + snapshotPath, + snapshotStale: + snapshotPath !== null && + row !== null && + snapshotStat !== null && + row.updated_at.getTime() > snapshotStat.mtime.getTime(), + }; + }, + + async updateWhiteboard( + agentId: string, + elements: unknown[], + deleteIds: string[] + ): Promise { + const agent = await agentManager.getAgent(agentId); + if (!agent) throw new Error("Agent not found."); + + for (let attempt = 0; attempt < 3; attempt++) { + const row = await loadWhiteboard(pool, agentId); + const baseVersion = row ? Number(row.version) : 0; + const existing = row ? row.scene.elements : []; + + const merged = mergeElements(existing, elements, deleteIds); + + if (merged.elements.length > MAX_ELEMENTS) { + throw new Error(`Board is full (max ${MAX_ELEMENTS} elements).`); + } + + const saved = await saveWhiteboard( + pool, + agentId, + { elements: merged.elements }, + baseVersion, + "agent" + ); + if (saved) { + publishUiEvent({ + type: "whiteboard.changed", + agentId, + version: saved.version, + source: "agent", + }); + return { + version: saved.version, + elementCount: merged.elements.length, + addedIds: merged.addedIds, + updatedIds: merged.updatedIds, + deletedIds: deleteIds.filter((id) => + existing.some( + (el) => + typeof el === "object" && + el !== null && + (el as RawElement).id === id + ) + ), + elements: simplifyElements(merged.elements), + }; + } + } + throw new Error( + "Whiteboard is being edited concurrently; try again in a moment." + ); + }, + + async clearWhiteboard(agentId: string): Promise { + const agent = await agentManager.getAgent(agentId); + if (!agent) throw new Error("Agent not found."); + + for (let attempt = 0; attempt < 3; attempt++) { + const row = await loadWhiteboard(pool, agentId); + const baseVersion = row ? Number(row.version) : 0; + const saved = await saveWhiteboard( + pool, + agentId, + EMPTY_SCENE, + baseVersion, + "agent" + ); + if (saved) { + publishUiEvent({ + type: "whiteboard.changed", + agentId, + version: saved.version, + source: "agent", + }); + const snapshotFile = path.join( + resolveMediaDir(agentId, agent.mediaDir, mediaRoot), + WHITEBOARD_SNAPSHOT_FILENAME + ); + await rm(snapshotFile, { force: true }); + return; + } + } + throw new Error( + "Whiteboard is being edited concurrently; try again in a moment." + ); + }, + + libraryList( + category?: string, + name?: string + ): CompactLibraryItem[] | DetailedLibraryItem | null { + if (!libraryCatalog) return []; + if (name) return libraryCatalog.get(name); + return libraryCatalog.list(category); + }, + + async insertLibraryItem( + agentId: string, + name: string, + x: number, + y: number, + scale: number, + label?: string + ): Promise { + const agent = await agentManager.getAgent(agentId); + if (!agent) throw new Error("Agent not found."); + if (!libraryCatalog) throw new Error("Library catalog not available."); + + const stamped = libraryCatalog.stamp(name, x, y, scale, label); + + for (let attempt = 0; attempt < 3; attempt++) { + const row = await loadWhiteboard(pool, agentId); + const baseVersion = row ? Number(row.version) : 0; + const existing = row ? row.scene.elements : []; + + const merged = mergeElements(existing, stamped.elements, []); + + if (merged.elements.length > MAX_ELEMENTS) { + throw new Error(`Board is full (max ${MAX_ELEMENTS} elements).`); + } + + const saved = await saveWhiteboard( + pool, + agentId, + { elements: merged.elements }, + baseVersion, + "agent" + ); + if (saved) { + publishUiEvent({ + type: "whiteboard.changed", + agentId, + version: saved.version, + source: "agent", + }); + return { + version: saved.version, + elementCount: merged.elements.length, + addedIds: stamped.addedIds, + updatedIds: [], + deletedIds: [], + elements: simplifyElements(merged.elements), + }; + } + } + throw new Error( + "Whiteboard is being edited concurrently; try again in a moment." + ); + }, + }; +} diff --git a/apps/server/src/server/ui-events.ts b/apps/server/src/server/ui-events.ts index f020a79f..415ce82b 100644 --- a/apps/server/src/server/ui-events.ts +++ b/apps/server/src/server/ui-events.ts @@ -19,6 +19,12 @@ export type UiEvent = } | { type: "agent.deleted"; agentId: string } | { type: "media.changed"; agentId: string } + | { + type: "whiteboard.changed"; + agentId: string; + version: number; + source: "user" | "agent"; + } | { type: "media.seen"; agentId: string; keys: string[] } | { type: "stream.started"; agentId: string } | { type: "stream.stopped"; agentId: string } diff --git a/apps/server/src/shared/mcp/server.ts b/apps/server/src/shared/mcp/server.ts index 319c6c5d..63ed8346 100644 --- a/apps/server/src/shared/mcp/server.ts +++ b/apps/server/src/shared/mcp/server.ts @@ -13,6 +13,15 @@ import { registerBrainTools } from "./brain-tools.js"; import { registerCrudTools, type CrudToolCallbacks } from "./crud-tools.js"; import { registerJobTools, type JobTools } from "./job-tools.js"; import { registerMessagingTools } from "./messaging-tools.js"; +import { registerWhiteboardTools } from "./whiteboard-tools.js"; +import type { + WhiteboardGetResult, + WhiteboardUpdateResult, +} from "../whiteboard.js"; +import type { + CompactLibraryItem, + DetailedLibraryItem, +} from "../whiteboard-library.js"; import { registerPersonaInteractionTools, type LaunchPersonaAgentType, @@ -104,6 +113,11 @@ const AGENT_TOOLS = new Set([ "get_activity_summary", "get_agent_history", "get_feedback_summary", + "whiteboard_get", + "whiteboard_update", + "whiteboard_clear", + "whiteboard_library", + "whiteboard_insert", "brain_get_object", "brain_store_object", "brain_list_objects", @@ -191,6 +205,7 @@ const PERSONA_TOOLS = new Set([ "dispatch_share", "dispatch_feedback", "get_parent_context", + "whiteboard_get", ]); type AgentType = "agent" | "job" | "persona"; @@ -405,6 +420,25 @@ export type McpRequestContext = { pin: { label: string; value: string; type: string } ) => Promise; deletePin?: (agentId: string, label: string) => Promise; + getWhiteboard?: (agentId: string) => Promise; + updateWhiteboard?: ( + agentId: string, + elements: unknown[], + deleteIds: string[] + ) => Promise; + clearWhiteboard?: (agentId: string) => Promise; + libraryList?: ( + category?: string, + name?: string + ) => CompactLibraryItem[] | DetailedLibraryItem | null; + insertLibraryItem?: ( + agentId: string, + name: string, + x: number, + y: number, + scale: number, + label?: string + ) => Promise; getParentContext?: (parentAgentId: string) => Promise; getRecheckContext?: (agentId: string) => Promise; updateReviewStatus?: ( @@ -561,6 +595,18 @@ async function createDispatchMcpServer( }); } + // ── Whiteboard tools ────────────────────────────────────────────── + if (context.agent) { + registerWhiteboardTools(server, allowed, { + agentId: context.agent.id, + getWhiteboard: context.getWhiteboard, + updateWhiteboard: context.updateWhiteboard, + clearWhiteboard: context.clearWhiteboard, + libraryList: context.libraryList, + insertLibraryItem: context.insertLibraryItem, + }); + } + // ── Inter-agent messaging tools ─────────────────────────────────── if (context.agent) { registerMessagingTools(server, allowed, { diff --git a/apps/server/src/shared/mcp/whiteboard-tools.ts b/apps/server/src/shared/mcp/whiteboard-tools.ts new file mode 100644 index 00000000..ff9595cf --- /dev/null +++ b/apps/server/src/shared/mcp/whiteboard-tools.ts @@ -0,0 +1,482 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import * as z from "zod/v4"; + +import { toToolError } from "./tool-error.js"; +import type { + WhiteboardGetResult, + WhiteboardUpdateResult, +} from "../whiteboard.js"; +import type { + CompactLibraryItem, + DetailedLibraryItem, +} from "../whiteboard-library.js"; + +export type WhiteboardToolsContext = { + agentId: string; + getWhiteboard?: (agentId: string) => Promise; + updateWhiteboard?: ( + agentId: string, + elements: unknown[], + deleteIds: string[] + ) => Promise; + clearWhiteboard?: (agentId: string) => Promise; + libraryList?: ( + category?: string, + name?: string + ) => CompactLibraryItem[] | DetailedLibraryItem | null; + insertLibraryItem?: ( + agentId: string, + name: string, + x: number, + y: number, + scale: number, + label?: string + ) => Promise; +}; + +// ── Excalidraw element format cheat sheet ────────────────────────────── +// Included in the whiteboard_update tool description so agents can +// construct valid Excalidraw element JSON without importing the library. +const EXCALIDRAW_CHEAT_SHEET = ` + +## Excalidraw Element Format Reference + +Every element is a JSON object. Fields marked (required) must be present; all others have sensible defaults the editor will apply if omitted. + +### Common fields (all element types) + +| Field | Type | Required | Default | Notes | +|-------|------|----------|---------|-------| +| id | string | YES | — | Unique id. Use readable slugs like "api-box", "db-node". | +| type | string | YES | — | One of: rectangle, ellipse, diamond, text, arrow, line, frame, freedraw, image | +| x | number | YES | — | Left edge, canvas pixels | +| y | number | YES | — | Top edge, canvas pixels | +| width | number | YES | — | Element width (use 0 for arrows/lines) | +| height | number | YES | — | Element height (use 0 for arrows/lines) | +| angle | number | no | 0 | Rotation in radians | +| strokeColor | string | no | "#1e1e1e" | Stroke/outline color (hex) | +| backgroundColor | string | no | "transparent" | Fill color (hex or "transparent") | +| fillStyle | string | no | "solid" | One of: solid, hachure, cross-hatch | +| strokeWidth | number | no | 2 | Stroke thickness in px | +| strokeStyle | string | no | "solid" | One of: solid, dashed, dotted | +| roughness | number | no | 1 | 0=smooth, 1=normal, 2=rough (hand-drawn look) | +| opacity | number | no | 100 | 0–100 | +| groupIds | string[] | no | [] | Group membership | +| frameId | string\\|null | no | null | Parent frame id | +| roundness | object\\|null | no | null | { type: 3 } for rounded corners on rectangles | +| seed | number | no | random | Random seed for roughness rendering | +| version | number | no | 1 | Bump on each edit | +| versionNonce | number | no | random | Random nonce, changes with version | +| isDeleted | boolean | no | false | Soft-delete flag | +| boundElements | array\\|null | no | null | Back-references: [{ id, type }] | +| updated | number | no | Date.now() | Timestamp ms | +| link | string\\|null | no | null | URL link | +| locked | boolean | no | false | Prevent editing | + +### Shape elements: rectangle, ellipse, diamond + +\`\`\`json +{ + "id": "api-box", + "type": "rectangle", + "x": 100, "y": 100, + "width": 160, "height": 70, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "roundness": { "type": 3 } +} +\`\`\` + +Ellipse and diamond use the same fields, just change \`type\`. + +### Text elements + +\`\`\`json +{ + "id": "title-text", + "type": "text", + "x": 100, "y": 50, + "width": 200, "height": 25, + "text": "API Server", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "originalText": "API Server" +} +\`\`\` + +**Bound text (label inside a shape):** Create a text element with \`containerId\` pointing to the shape, and add a back-reference in the shape's \`boundElements\`: + +\`\`\`json +[ + { + "id": "box1", + "type": "rectangle", + "x": 100, "y": 100, "width": 160, "height": 70, + "boundElements": [{ "id": "box1-label", "type": "text" }] + }, + { + "id": "box1-label", + "type": "text", + "x": 110, "y": 120, "width": 140, "height": 25, + "text": "API", + "originalText": "API", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "box1" + } +] +\`\`\` + +Text font families: 1=Virgil (handwritten), 3=Cascadia (monospace), 5=Excalifont (default). + +### Arrow and line elements + +\`\`\`json +{ + "id": "flow-arrow", + "type": "arrow", + "x": 260, "y": 135, + "width": 140, "height": 0, + "points": [[0, 0], [140, 0]], + "startArrowhead": null, + "endArrowhead": "arrow", + "startBinding": { + "elementId": "api-box", + "focus": 0, + "gap": 1, + "fixedPoint": [1, 0.5] + }, + "endBinding": { + "elementId": "db-node", + "focus": 0, + "gap": 1, + "fixedPoint": [0, 0.5] + }, + "elbowed": false +} +\`\`\` + +**Points:** Array of [x, y] offsets relative to the element's x, y. First point is always [0, 0]. Add intermediate points for bends. + +**Arrowheads:** \`startArrowhead\` and \`endArrowhead\` can be: null, "arrow", "bar", "dot", "triangle", "diamond". + +**Bindings:** Connect arrows to shapes. \`fixedPoint\` is [proportionX, proportionY] on the target shape (0-1 range): [0.5, 0] = top center, [1, 0.5] = right center, [0.5, 1] = bottom center, [0, 0.5] = left center. + +When binding an arrow, also add a back-reference in the target shape's \`boundElements\`: +\`\`\`json +{ "id": "flow-arrow", "type": "arrow" } +\`\`\` + +**Elbow routing:** Set \`elbowed: true\` for right-angle connector routing (auto-computed path). The \`points\` array will be overridden by the editor. + +**Lines** use the same format but \`type: "line"\` and no arrowheads. + +### Frame elements + +\`\`\`json +{ + "id": "frame1", + "type": "frame", + "x": 50, "y": 50, + "width": 400, "height": 300, + "name": "Backend Services" +} +\`\`\` + +Children are assigned to frames by setting their \`frameId\` to the frame's id. + +### Color reference + +**Stroke colors:** #1e1e1e (black/default), #e03131 (red), #2f9e44 (green), #1971c2 (blue), #f08c00 (orange), #6741d9 (violet), #0c8599 (cyan), #e8590c (dark orange), #868e96 (gray) + +**Pastel fills (good for shape backgrounds):** #a5d8ff (light blue), #b2f2bb (light green), #ffd8a8 (light orange), #d0bfff (light purple), #ffc9c9 (light red), #fff3bf (light yellow), #c3fae8 (light teal), #eebefa (light pink), #e5dbff (light violet) + +### Layout tips + +- Typical box: width 160, height 70 +- Leave ~80px gaps between shapes +- Center labels inside shapes using \`containerId\` + \`boundElements\` binding +- Keep labels short (2–5 words) — text wraps to shape width +- For arrows: set x, y to the start point, compute width/height from the last point offset +- Arrow width = last point's x offset, height = last point's y offset (can be negative) + +### Important notes + +- The editor auto-heals many issues (null fields, missing indices). Don't over-validate. +- Always provide \`id\`, \`type\`, \`x\`, \`y\` at minimum. Width and height default to 0 if omitted. +- Use readable, descriptive ids — you'll reference them in bindings and future updates. +- Elements are merged by id: sending an element with an existing id replaces it entirely. +`; + +export function registerWhiteboardTools( + server: McpServer, + allowed: Set, + context: WhiteboardToolsContext +): void { + if (allowed.has("whiteboard_get") && context.getWhiteboard) { + const agentId = context.agentId; + const getWhiteboard = context.getWhiteboard; + + server.registerTool( + "whiteboard_get", + { + description: + "Get the current state of this agent's shared whiteboard — a canvas the user sketches on " + + "(architecture diagrams, flows, ideas). Returns a simplified element list (geometry, text, " + + "arrow connections via from/to element ids) plus snapshotPath: a PNG rendering of the board. " + + "Read the snapshot file to SEE the drawing — freehand sketches are hard to interpret from " + + "elements alone. Use this whenever the user refers to the whiteboard/board/drawing.", + inputSchema: {}, + }, + async () => { + try { + const board = await getWhiteboard(agentId); + const summary = { + elementCount: board.elements.length, + version: board.version, + updatedAt: board.updatedAt, + updatedBy: board.updatedBy, + snapshotPath: board.snapshotPath, + snapshotStale: board.snapshotStale, + elements: board.elements, + }; + const snapshotNote = board.snapshotPath + ? board.snapshotStale + ? `\n\nNote: ${board.snapshotPath} was rendered BEFORE the latest edits — trust the element list over the image until a browser re-exports it.` + : `\n\nTip: Read ${board.snapshotPath} to view the board visually.` + : "\n\nNo snapshot has been rendered yet (the board may be empty or never opened)."; + return { + content: [ + { + type: "text", + text: JSON.stringify(summary, null, 2) + snapshotNote, + }, + ], + structuredContent: summary, + }; + } catch (error) { + return toToolError(error); + } + } + ); + } + + if (allowed.has("whiteboard_update") && context.updateWhiteboard) { + const agentId = context.agentId; + const updateWhiteboard = context.updateWhiteboard; + + server.registerTool( + "whiteboard_update", + { + description: + "Draw on the shared whiteboard — the user sees your edits live. You construct raw " + + "Excalidraw element JSON directly. Elements are merged by id: if an element with that " + + "id already exists on the board, it is replaced entirely; otherwise it is added. Use " + + "`deleteIds` to remove elements." + + "\n\n" + + "**Workflow:** Call `whiteboard_get` first to see current elements, their ids, and where " + + "free space is. Then construct your elements and send them here. Give elements readable " + + "ids (e.g. 'api-box', 'db-node') so you can reference them in arrow bindings." + + "\n\n" + + "**Labels:** To put text inside a shape, create BOTH a shape element (with a `boundElements` " + + "back-reference to the text) AND a text element (with `containerId` pointing to the shape). " + + "See the format reference below." + + "\n\n" + + "**Arrows:** Use `startBinding`/`endBinding` with `elementId` and `fixedPoint` to connect " + + "arrows to shapes. Add back-references in each target shape's `boundElements` array. " + + "Set `elbowed: true` for right-angle routing." + + "\n\n" + + "**Layout:** Typical box w=160, h=70 with ~80px gaps. Keep labels short (2–5 words)." + + EXCALIDRAW_CHEAT_SHEET, + inputSchema: { + elements: z + .array(z.record(z.string(), z.any())) + .max(500) + .describe( + "Array of Excalidraw element objects to add or update. Each must have at least " + + "'id' and 'type'. Elements are merged by id (upsert). See the format reference in " + + "the tool description for the full element schema." + ), + deleteIds: z + .array(z.string()) + .max(500) + .optional() + .describe("Array of element ids to remove from the board."), + }, + }, + async (args) => { + try { + const result = await updateWhiteboard( + agentId, + args.elements, + args.deleteIds ?? [] + ); + const summary = { + ok: true, + version: result.version, + elementCount: result.elementCount, + addedIds: result.addedIds, + updatedIds: result.updatedIds, + deletedIds: result.deletedIds, + elements: result.elements, + }; + return { + content: [{ type: "text", text: JSON.stringify(summary, null, 2) }], + structuredContent: summary, + }; + } catch (error) { + return toToolError(error); + } + } + ); + } + + if (allowed.has("whiteboard_clear") && context.clearWhiteboard) { + const agentId = context.agentId; + const clearWhiteboard = context.clearWhiteboard; + + server.registerTool( + "whiteboard_clear", + { + description: "Clear the whiteboard entirely, removing all elements.", + inputSchema: {}, + }, + async () => { + try { + await clearWhiteboard(agentId); + return { + content: [ + { + type: "text", + text: '{"ok": true, "message": "Whiteboard cleared."}', + }, + ], + }; + } catch (error) { + return toToolError(error); + } + } + ); + } + + if (allowed.has("whiteboard_library") && context.libraryList) { + const libraryList = context.libraryList; + + server.registerTool( + "whiteboard_library", + { + description: + "Browse the library of pre-built shapes and icons available for the whiteboard. " + + "Call with no arguments for a compact list of all items (name, category, dimensions). " + + "Call with `name` to get full detail (anchors, element count) for layout planning. " + + "Call with `category` to filter: 'flowchart', 'architecture', 'wireframe', 'ui-input'.", + inputSchema: { + category: z + .string() + .optional() + .describe( + "Filter by category: 'flowchart', 'architecture', 'wireframe', 'ui-input'" + ), + name: z + .string() + .optional() + .describe( + "Get full detail for a specific item by name (case-insensitive). " + + "Takes priority over category." + ), + }, + }, + async (args) => { + try { + const result = libraryList(args.category, args.name); + if (args.name && result === null) { + return { + content: [ + { + type: "text", + text: `Library item "${args.name}" not found. Call whiteboard_library with no arguments to see all available items.`, + }, + ], + isError: true, + }; + } + return { + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], + }; + } catch (error) { + return toToolError(error); + } + } + ); + } + + if (allowed.has("whiteboard_insert") && context.insertLibraryItem) { + const agentId = context.agentId; + const insertLibraryItem = context.insertLibraryItem; + + server.registerTool( + "whiteboard_insert", + { + description: + "Place a pre-built library shape onto the whiteboard at a given position. " + + "Use `whiteboard_library` first to browse available items and get their dimensions " + + "for layout planning. Items are scaled and positioned, with optional text label replacement.", + inputSchema: { + name: z + .string() + .describe( + "Library item name (case-insensitive). Use whiteboard_library to see available names." + ), + x: z + .number() + .describe("X coordinate for the item's top-left corner."), + y: z + .number() + .describe("Y coordinate for the item's top-left corner."), + scale: z + .number() + .min(0.1) + .max(10) + .default(1) + .describe( + "Size multiplier. 1 = original size, 0.5 = half, 2 = double." + ), + label: z + .string() + .optional() + .describe( + "Replace the item's primary text label (e.g. place a 'Server' icon but label it 'Auth Service')." + ), + }, + }, + async (args) => { + try { + const result = await insertLibraryItem( + agentId, + args.name, + args.x, + args.y, + args.scale, + args.label + ); + const summary = { + ok: true, + version: result.version, + elementCount: result.elementCount, + addedIds: result.addedIds, + }; + return { + content: [{ type: "text", text: JSON.stringify(summary, null, 2) }], + }; + } catch (error) { + return toToolError(error); + } + } + ); + } +} diff --git a/apps/server/src/shared/whiteboard-library.ts b/apps/server/src/shared/whiteboard-library.ts new file mode 100644 index 00000000..c74fa477 --- /dev/null +++ b/apps/server/src/shared/whiteboard-library.ts @@ -0,0 +1,287 @@ +import { readdir, readFile } from "node:fs/promises"; +import path from "node:path"; +import { randomUUID } from "node:crypto"; + +type RawElement = Record; + +type LibraryFileItem = { + id: string; + name?: string; + status: string; + elements: RawElement[]; + created: number; +}; + +type Anchors = { + top: [number, number]; + bottom: [number, number]; + left: [number, number]; + right: [number, number]; +}; + +export type CompactLibraryItem = { + name: string; + category: string; + width: number; + height: number; +}; + +export type DetailedLibraryItem = { + name: string; + category: string; + description: string; + width: number; + height: number; + elementCount: number; + anchors: Anchors; +}; + +type CatalogEntry = DetailedLibraryItem & { + elements: RawElement[]; +}; + +export type StampResult = { + elements: RawElement[]; + addedIds: string[]; +}; + +const FILENAME_TO_CATEGORY: Record = { + "flow-chart-symbols": "flowchart", + "architecture-diagram-components": "architecture", + "universal-ui-kit": "wireframe", + "html-input-elements": "ui-input", +}; + +function computeBounds(elements: RawElement[]): { + minX: number; + minY: number; + maxX: number; + maxY: number; + width: number; + height: number; +} { + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + for (const el of elements) { + const x = typeof el.x === "number" ? el.x : 0; + const y = typeof el.y === "number" ? el.y : 0; + const w = typeof el.width === "number" ? el.width : 0; + const h = typeof el.height === "number" ? el.height : 0; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x + w); + maxY = Math.max(maxY, y + h); + } + return { + minX, + minY, + maxX, + maxY, + width: Math.round(maxX - minX), + height: Math.round(maxY - minY), + }; +} + +function computeAnchors(width: number, height: number): Anchors { + return { + top: [Math.round(width / 2), 0], + bottom: [Math.round(width / 2), height], + left: [0, Math.round(height / 2)], + right: [width, Math.round(height / 2)], + }; +} + +export class LibraryCatalog { + private items: Map; + + private constructor(items: Map) { + this.items = items; + } + + static async fromDirectory(dir: string): Promise { + const items = new Map(); + const files = await readdir(dir); + for (const file of files) { + if (!file.endsWith(".excalidrawlib")) continue; + const filePath = path.join(dir, file); + const raw = JSON.parse(await readFile(filePath, "utf8")) as { + version?: number; + libraryItems?: LibraryFileItem[]; + }; + if (raw.version !== 2 || !Array.isArray(raw.libraryItems)) continue; + const stem = file.replace(/\.excalidrawlib$/, ""); + const category = FILENAME_TO_CATEGORY[stem] ?? stem; + for (const libItem of raw.libraryItems) { + const name = libItem.name?.trim(); + if ( + !name || + !Array.isArray(libItem.elements) || + libItem.elements.length === 0 + ) + continue; + const bounds = computeBounds(libItem.elements); + const anchors = computeAnchors(bounds.width, bounds.height); + const key = `${category}:${name.toLowerCase()}`; + items.set(key, { + name, + category, + description: name, + width: bounds.width, + height: bounds.height, + elementCount: libItem.elements.length, + anchors, + elements: libItem.elements, + }); + } + } + return new LibraryCatalog(items); + } + + /** + * Resolve a lookup string to a catalog entry. + * Accepts either a bare name (first case-insensitive match across all + * categories, in insertion order) or a "category:name" pair for an + * exact, unambiguous lookup. + */ + private find(input: string): CatalogEntry | null { + const separatorIndex = input.indexOf(":"); + if (separatorIndex !== -1) { + const category = input.slice(0, separatorIndex).toLowerCase(); + const name = input.slice(separatorIndex + 1).toLowerCase(); + const exact = this.items.get(`${category}:${name}`); + if (exact) return exact; + } + const target = input.toLowerCase(); + for (const entry of this.items.values()) { + if (entry.name.toLowerCase() === target) return entry; + } + return null; + } + + list(category?: string): CompactLibraryItem[] { + const result: CompactLibraryItem[] = []; + for (const item of this.items.values()) { + if (category && item.category !== category) continue; + result.push({ + name: item.name, + category: item.category, + width: item.width, + height: item.height, + }); + } + return result; + } + + get(name: string): DetailedLibraryItem | null { + const entry = this.find(name); + if (!entry) return null; + const { elements: _, ...detail } = entry; + return detail; + } + + stamp( + name: string, + x: number, + y: number, + scale: number, + label?: string + ): StampResult { + const entry = this.find(name); + if (!entry) throw new Error(`Library item "${name}" not found.`); + + const bounds = computeBounds(entry.elements); + const idMap = new Map(); + const groupIdMap = new Map(); + const addedIds: string[] = []; + + // Build ID mapping first so we can remap bindings + for (const el of entry.elements) { + const oldId = el.id as string; + const newId = randomUUID().slice(0, 8); + idMap.set(oldId, newId); + addedIds.push(newId); + // Build a separate mapping for groupIds — they are a distinct + // namespace from element IDs, so each unique groupId needs its own + // fresh ID per stamp to avoid cross-linking separate stamps. + if (Array.isArray(el.groupIds)) { + for (const gid of el.groupIds as string[]) { + if (!groupIdMap.has(gid)) { + groupIdMap.set(gid, randomUUID().slice(0, 8)); + } + } + } + } + + let labelApplied = false; + const elements: RawElement[] = entry.elements.map((el) => { + const cloned: RawElement = { ...el }; + // Assign fresh ID + cloned.id = idMap.get(el.id as string)!; + // Scale and offset coordinates + const origX = typeof el.x === "number" ? el.x : 0; + const origY = typeof el.y === "number" ? el.y : 0; + cloned.x = x + (origX - bounds.minX) * scale; + cloned.y = y + (origY - bounds.minY) * scale; + if (typeof el.width === "number") cloned.width = el.width * scale; + if (typeof el.height === "number") cloned.height = el.height * scale; + + // Scale points for arrows/lines + if (Array.isArray(el.points)) { + cloned.points = (el.points as number[][]).map(([px, py]) => [ + px * scale, + py * scale, + ]); + } + + // Remap containerId + if (typeof el.containerId === "string" && idMap.has(el.containerId)) { + cloned.containerId = idMap.get(el.containerId)!; + } + // Remap frameId + if (typeof el.frameId === "string" && idMap.has(el.frameId)) { + cloned.frameId = idMap.get(el.frameId)!; + } + // Remap boundElements + if (Array.isArray(el.boundElements)) { + cloned.boundElements = ( + el.boundElements as Array<{ id: string; type: string }> + ).map((be) => ({ + ...be, + id: idMap.get(be.id) ?? be.id, + })); + } + // Remap arrow bindings + if (typeof el.startBinding === "object" && el.startBinding !== null) { + const sb = el.startBinding as Record; + if (typeof sb.elementId === "string" && idMap.has(sb.elementId)) { + cloned.startBinding = { ...sb, elementId: idMap.get(sb.elementId)! }; + } + } + if (typeof el.endBinding === "object" && el.endBinding !== null) { + const eb = el.endBinding as Record; + if (typeof eb.elementId === "string" && idMap.has(eb.elementId)) { + cloned.endBinding = { ...eb, elementId: idMap.get(eb.elementId)! }; + } + } + // Remap groupIds (separate namespace from element IDs) + if (Array.isArray(el.groupIds)) { + cloned.groupIds = (el.groupIds as string[]).map( + (gid) => groupIdMap.get(gid) ?? gid + ); + } + + // Replace label text + if (label && !labelApplied && el.type === "text") { + cloned.text = label; + cloned.originalText = label; + labelApplied = true; + } + + return cloned; + }); + + return { elements, addedIds }; + } +} diff --git a/apps/server/src/shared/whiteboard-store.ts b/apps/server/src/shared/whiteboard-store.ts new file mode 100644 index 00000000..742bb70d --- /dev/null +++ b/apps/server/src/shared/whiteboard-store.ts @@ -0,0 +1,59 @@ +import type { Pool } from "pg"; + +export const WHITEBOARD_SNAPSHOT_FILENAME = "whiteboard.png"; + +export const MAX_ELEMENTS = 20_000; + +export const EMPTY_SCENE = { elements: [] as unknown[] }; + +export type WhiteboardRow = { + scene: { elements: unknown[] }; + version: string; + updated_by: string; + updated_at: Date; +}; + +export async function loadWhiteboard( + pool: Pool, + agentId: string +): Promise { + const result = await pool.query( + "SELECT scene, version, updated_by, updated_at FROM whiteboards WHERE agent_id = $1", + [agentId] + ); + return result.rows[0] ?? null; +} + +export function isValidScene(scene: unknown): scene is { elements: unknown[] } { + return ( + typeof scene === "object" && + scene !== null && + Array.isArray((scene as { elements?: unknown }).elements) && + (scene as { elements: unknown[] }).elements.length <= MAX_ELEMENTS + ); +} + +export async function saveWhiteboard( + pool: Pool, + agentId: string, + scene: { elements: unknown[] }, + baseVersion: number, + updatedBy: "user" | "agent" +): Promise<{ version: number } | null> { + const result = await pool.query<{ version: string }>( + `INSERT INTO whiteboards (agent_id, scene, version, updated_by) + VALUES ($1, $2::jsonb, 1, $3) + ON CONFLICT (agent_id) DO UPDATE + SET scene = EXCLUDED.scene, + version = whiteboards.version + 1, + updated_by = EXCLUDED.updated_by, + updated_at = NOW() + WHERE whiteboards.version = $4 + RETURNING version`, + [agentId, JSON.stringify(scene), updatedBy, baseVersion] + ); + if (result.rows.length === 0) { + return null; + } + return { version: Number(result.rows[0].version) }; +} diff --git a/apps/server/src/shared/whiteboard.ts b/apps/server/src/shared/whiteboard.ts new file mode 100644 index 00000000..c59a9bc5 --- /dev/null +++ b/apps/server/src/shared/whiteboard.ts @@ -0,0 +1,87 @@ +type RawElement = Record; + +export type SimplifiedElement = { + id: string; + type: string; + x: number; + y: number; + width: number; + height: number; + angle?: number; + text?: string; + containerId?: string; + from?: string; + to?: string; + frameId?: string; + strokeColor?: string; + backgroundColor?: string; +}; + +export type WhiteboardGetResult = { + elements: SimplifiedElement[]; + version: number; + updatedAt: string | null; + updatedBy: string | null; + snapshotPath: string | null; + snapshotStale: boolean; +}; + +export type WhiteboardUpdateResult = { + version: number; + elementCount: number; + addedIds: string[]; + updatedIds: string[]; + deletedIds: string[]; + elements: SimplifiedElement[]; +}; + +function num(value: unknown): number { + return typeof value === "number" && Number.isFinite(value) + ? Math.round(value) + : 0; +} + +function str(value: unknown): string | undefined { + return typeof value === "string" && value ? value : undefined; +} + +export function simplifyElements(elements: unknown[]): SimplifiedElement[] { + const out: SimplifiedElement[] = []; + for (const raw of elements) { + if (typeof raw !== "object" || raw === null) continue; + const el = raw as RawElement; + if (el.isDeleted === true) continue; + + const simplified: SimplifiedElement = { + id: str(el.id) ?? "", + type: str(el.type) ?? "unknown", + x: num(el.x), + y: num(el.y), + width: num(el.width), + height: num(el.height), + }; + if (typeof el.angle === "number" && Math.abs(el.angle) > 0.01) { + simplified.angle = Number((el.angle as number).toFixed(2)); + } + const text = str(el.text) ?? str(el.originalText); + if (text) simplified.text = text; + const containerId = str((el as { containerId?: unknown }).containerId); + if (containerId) simplified.containerId = containerId; + const startBinding = el.startBinding as { elementId?: unknown } | null; + const endBinding = el.endBinding as { elementId?: unknown } | null; + const from = str(startBinding?.elementId); + const to = str(endBinding?.elementId); + if (from) simplified.from = from; + if (to) simplified.to = to; + const frameId = str(el.frameId); + if (frameId) simplified.frameId = frameId; + const strokeColor = str(el.strokeColor); + if (strokeColor) simplified.strokeColor = strokeColor; + const backgroundColor = str(el.backgroundColor); + if (backgroundColor && backgroundColor !== "transparent") { + simplified.backgroundColor = backgroundColor; + } + out.push(simplified); + } + return out; +} diff --git a/apps/server/test/whiteboard-library.test.ts b/apps/server/test/whiteboard-library.test.ts new file mode 100644 index 00000000..310b4a3f --- /dev/null +++ b/apps/server/test/whiteboard-library.test.ts @@ -0,0 +1,252 @@ +import { describe, expect, it } from "vitest"; +import path from "node:path"; +import { LibraryCatalog } from "../src/shared/whiteboard-library.js"; + +// Use the actual library files checked in during Task 1 +const LIBRARIES_DIR = path.resolve( + __dirname, + "../../web/public/excalidraw/libraries" +); + +describe("LibraryCatalog", () => { + describe("fromDirectory", () => { + it("loads all library files and builds catalog", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + expect(all.length).toBeGreaterThanOrEqual(40); + // Every item has required fields + for (const item of all) { + expect(item.name).toBeTruthy(); + expect(item.category).toBeTruthy(); + expect(item.width).toBeGreaterThan(0); + expect(item.height).toBeGreaterThan(0); + } + }); + + it("keeps items with duplicate names across different libraries", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + // "Button" and "Select" exist in both html-input-elements + // (category ui-input) and universal-ui-kit (category wireframe). + // Both copies must survive — the catalog must not collapse them + // into a single entry keyed only by lowercase name. + expect(all.length).toBeGreaterThanOrEqual(54); + const buttons = all.filter((i) => i.name.toLowerCase() === "button"); + const selects = all.filter((i) => i.name.toLowerCase() === "select"); + expect(buttons.length).toBe(2); + expect(selects.length).toBe(2); + expect(new Set(buttons.map((i) => i.category)).size).toBe(2); + expect(new Set(selects.map((i) => i.category)).size).toBe(2); + }); + + it("makes every duplicate-named item retrievable via category:name", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const wireframeButton = catalog.get("wireframe:button"); + const uiInputButton = catalog.get("ui-input:button"); + expect(wireframeButton).not.toBeNull(); + expect(uiInputButton).not.toBeNull(); + expect(wireframeButton!.category).toBe("wireframe"); + expect(uiInputButton!.category).toBe("ui-input"); + }); + + it("assigns correct categories from filenames", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const categories = new Set(catalog.list().map((i) => i.category)); + expect(categories).toContain("flowchart"); + expect(categories).toContain("architecture"); + expect(categories).toContain("wireframe"); + expect(categories).toContain("ui-input"); + }); + }); + + describe("list", () => { + it("filters by category", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const flowchart = catalog.list("flowchart"); + expect(flowchart.length).toBeGreaterThan(0); + expect(flowchart.every((i) => i.category === "flowchart")).toBe(true); + }); + + it("returns all items when no category specified", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const flowchart = catalog.list("flowchart"); + expect(all.length).toBeGreaterThan(flowchart.length); + }); + }); + + describe("get", () => { + it("returns detailed item with anchors but no raw elements", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const detail = catalog.get(all[0].name); + expect(detail).not.toBeNull(); + expect(detail!.anchors).toBeDefined(); + expect(detail!.anchors.top).toHaveLength(2); + expect(detail!.anchors.bottom).toHaveLength(2); + expect(detail!.anchors.left).toHaveLength(2); + expect(detail!.anchors.right).toHaveLength(2); + expect(detail!.elementCount).toBeGreaterThan(0); + expect(detail!.description).toBeTruthy(); + // Elements are internal — never leaked to callers + expect((detail as Record).elements).toBeUndefined(); + }); + + it("is case-insensitive", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const name = all[0].name; + const lower = catalog.get(name.toLowerCase()); + const upper = catalog.get(name.toUpperCase()); + expect(lower).not.toBeNull(); + expect(upper).not.toBeNull(); + expect(lower!.name).toBe(upper!.name); + }); + + it("returns null for unknown name", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + expect(catalog.get("nonexistent-item-xyz")).toBeNull(); + }); + }); + + describe("stamp", () => { + it("returns elements offset to target position", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const result = catalog.stamp(all[0].name, 200, 300, 1); + expect(result.elements.length).toBeGreaterThan(0); + // All elements should have fresh string IDs + const ids = result.elements.map( + (e) => (e as Record).id as string + ); + expect(new Set(ids).size).toBe(ids.length); // unique + // Bounding box top-left should be near (200, 300) + const xs = result.elements.map( + (e) => (e as Record).x as number + ); + const ys = result.elements.map( + (e) => (e as Record).y as number + ); + expect(Math.min(...xs)).toBeCloseTo(200, 0); + expect(Math.min(...ys)).toBeCloseTo(300, 0); + }); + + it("scales elements", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const result1x = catalog.stamp(all[0].name, 0, 0, 1); + const result2x = catalog.stamp(all[0].name, 0, 0, 2); + // At 2x, bounding box should be roughly double + const width1 = Math.max( + ...result1x.elements.map( + (e) => + ((e as Record).x as number) + + ((e as Record).width as number) + ) + ); + const width2 = Math.max( + ...result2x.elements.map( + (e) => + ((e as Record).x as number) + + ((e as Record).width as number) + ) + ); + expect(width2).toBeCloseTo(width1 * 2, -1); + }); + + it("replaces text when label is provided", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + // Use the first item — stamp will apply label to any text element it contains + const textItemName = all[0].name; + const result = catalog.stamp(textItemName, 0, 0, 1, "Custom Label"); + const textEls = result.elements.filter( + (e) => (e as Record).type === "text" + ); + expect(textEls.length).toBeGreaterThan(0); + expect((textEls[0] as Record).text).toBe("Custom Label"); + expect((textEls[0] as Record).originalText).toBe( + "Custom Label" + ); + }); + + it("throws for unknown item", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + expect(() => catalog.stamp("nonexistent", 0, 0, 1)).toThrow(); + }); + + it("assigns fresh, non-overlapping groupIds on each stamp", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + // Find an item whose elements actually carry groupIds so the + // assertion exercises the remapping logic. + let groupedItemName: string | null = null; + for (const item of all) { + const stamped = catalog.stamp(item.name, 0, 0, 1); + const hasGroups = stamped.elements.some( + (e) => + Array.isArray((e as Record).groupIds) && + ((e as Record).groupIds as unknown[]).length > 0 + ); + if (hasGroups) { + groupedItemName = item.name; + break; + } + } + if (!groupedItemName) return; + + const first = catalog.stamp(groupedItemName, 0, 0, 1); + const second = catalog.stamp(groupedItemName, 0, 0, 1); + + const firstGroupIds = new Set( + first.elements.flatMap( + (e) => ((e as Record).groupIds as string[]) ?? [] + ) + ); + const secondGroupIds = new Set( + second.elements.flatMap( + (e) => ((e as Record).groupIds as string[]) ?? [] + ) + ); + + expect(firstGroupIds.size).toBeGreaterThan(0); + expect(secondGroupIds.size).toBeGreaterThan(0); + // No groupId should be shared between the two independent stamps — + // otherwise selecting one copy in the UI would also select the other. + for (const gid of firstGroupIds) { + expect(secondGroupIds.has(gid)).toBe(false); + } + }); + + it("maintains internal bindings with remapped IDs", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + // Stamp each item until we find one that has containerId bindings + let boundItemName: string | null = null; + for (const item of all) { + const stamped = catalog.stamp(item.name, 0, 0, 1); + if ( + stamped.elements.some( + (e) => (e as Record).containerId + ) + ) { + boundItemName = item.name; + break; + } + } + if (!boundItemName) return; + const result = catalog.stamp(boundItemName, 0, 0, 1); + const idSet = new Set( + result.elements.map((e) => (e as Record).id as string) + ); + // Every containerId should reference an ID that exists in the stamped set + for (const el of result.elements) { + const containerId = (el as Record) + .containerId as string; + if (containerId) { + expect(idSet.has(containerId)).toBe(true); + } + } + }); + }); +}); diff --git a/apps/server/test/whiteboard.test.ts b/apps/server/test/whiteboard.test.ts new file mode 100644 index 00000000..c8cc3e04 --- /dev/null +++ b/apps/server/test/whiteboard.test.ts @@ -0,0 +1,459 @@ +import { describe, expect, it, vi } from "vitest"; + +import { simplifyElements } from "../src/shared/whiteboard.js"; +import { isValidScene, MAX_ELEMENTS } from "../src/shared/whiteboard-store.js"; + +// ── mergeElements is not exported, so we test it indirectly through +// createWhiteboardHandlers. Import the module and extract the merge +// logic by testing the handlers with mocked deps. ── + +import { createWhiteboardHandlers } from "../src/server/mcp-whiteboard-handlers.js"; + +function rect(id: string, x = 0, y = 0) { + return { id, type: "rectangle", x, y, width: 100, height: 50 }; +} + +function text(id: string, t: string) { + return { + id, + type: "text", + x: 0, + y: 0, + width: 80, + height: 20, + text: t, + originalText: t, + }; +} + +function arrow(id: string, from: string, to: string) { + return { + id, + type: "arrow", + x: 100, + y: 50, + width: 100, + height: 0, + points: [ + [0, 0], + [100, 0], + ], + startBinding: { elementId: from, focus: 0, gap: 1 }, + endBinding: { elementId: to, focus: 0, gap: 1 }, + }; +} + +// ── simplifyElements ── + +describe("simplifyElements", () => { + it("converts raw elements to simplified format", () => { + const result = simplifyElements([ + rect("box1", 10, 20), + text("label1", "Hello"), + ]); + expect(result).toEqual([ + { id: "box1", type: "rectangle", x: 10, y: 20, width: 100, height: 50 }, + { + id: "label1", + type: "text", + x: 0, + y: 0, + width: 80, + height: 20, + text: "Hello", + }, + ]); + }); + + it("skips deleted elements", () => { + const result = simplifyElements([ + { ...rect("a"), isDeleted: true }, + rect("b"), + ]); + expect(result).toHaveLength(1); + expect(result[0].id).toBe("b"); + }); + + it("skips non-object values", () => { + const result = simplifyElements([ + null, + undefined, + 42, + "str", + rect("ok"), + ] as unknown[]); + expect(result).toHaveLength(1); + expect(result[0].id).toBe("ok"); + }); + + it("extracts arrow bindings as from/to", () => { + const result = simplifyElements([arrow("a1", "box1", "box2")]); + expect(result[0].from).toBe("box1"); + expect(result[0].to).toBe("box2"); + }); + + it("extracts containerId for bound text", () => { + const el = { ...text("lbl", "Hi"), containerId: "box1" }; + const result = simplifyElements([el]); + expect(result[0].containerId).toBe("box1"); + }); + + it("extracts frameId", () => { + const el = { ...rect("child"), frameId: "frame1" }; + const result = simplifyElements([el]); + expect(result[0].frameId).toBe("frame1"); + }); + + it("rounds angle and includes when non-zero", () => { + const el = { ...rect("rotated"), angle: 1.5708 }; + const result = simplifyElements([el]); + expect(result[0].angle).toBe(1.57); + }); + + it("omits angle when near zero", () => { + const el = { ...rect("flat"), angle: 0.005 }; + const result = simplifyElements([el]); + expect(result[0].angle).toBeUndefined(); + }); + + it("extracts colors (skips transparent bg)", () => { + const el = { + ...rect("colored"), + strokeColor: "#e03131", + backgroundColor: "transparent", + }; + const result = simplifyElements([el]); + expect(result[0].strokeColor).toBe("#e03131"); + expect(result[0].backgroundColor).toBeUndefined(); + }); + + it("includes non-transparent backgroundColor", () => { + const el = { ...rect("filled"), backgroundColor: "#a5d8ff" }; + const result = simplifyElements([el]); + expect(result[0].backgroundColor).toBe("#a5d8ff"); + }); +}); + +// ── isValidScene ── + +describe("isValidScene", () => { + it("accepts valid scene", () => { + expect(isValidScene({ elements: [rect("a")] })).toBe(true); + }); + + it("accepts empty scene", () => { + expect(isValidScene({ elements: [] })).toBe(true); + }); + + it("rejects null", () => { + expect(isValidScene(null)).toBe(false); + }); + + it("rejects non-object", () => { + expect(isValidScene("string")).toBe(false); + }); + + it("rejects missing elements", () => { + expect(isValidScene({ foo: "bar" })).toBe(false); + }); + + it("rejects non-array elements", () => { + expect(isValidScene({ elements: "not-array" })).toBe(false); + }); + + it("rejects oversized elements array", () => { + const elements = Array.from({ length: MAX_ELEMENTS + 1 }, (_, i) => + rect(`e${i}`) + ); + expect(isValidScene({ elements })).toBe(false); + }); + + it("accepts exactly MAX_ELEMENTS", () => { + const elements = Array.from({ length: MAX_ELEMENTS }, (_, i) => + rect(`e${i}`) + ); + expect(isValidScene({ elements })).toBe(true); + }); +}); + +// ── createWhiteboardHandlers (mergeElements + handler logic) ── + +describe("createWhiteboardHandlers", () => { + function createMockDeps() { + const publishedEvents: unknown[] = []; + return { + pool: { + query: vi.fn(), + } as unknown as import("pg").Pool, + mediaRoot: "/tmp/test-media", + agentManager: { + getAgent: vi.fn().mockResolvedValue({ id: "agt_test", mediaDir: null }), + } as unknown as import("../src/agents/manager.js").AgentManager, + publishUiEvent: vi.fn((e: unknown) => publishedEvents.push(e)), + publishedEvents, + }; + } + + function mockLoadReturn( + pool: { query: ReturnType }, + scene: { elements: unknown[] }, + version = 1 + ) { + pool.query.mockResolvedValueOnce({ + rows: [ + { + scene, + version: String(version), + updated_by: "agent", + updated_at: new Date(), + }, + ], + }); + } + + function mockSaveReturn( + pool: { query: ReturnType }, + version: number + ) { + pool.query.mockResolvedValueOnce({ + rows: [{ version: String(version) }], + }); + } + + function mockEmptyLoad(pool: { query: ReturnType }) { + pool.query.mockResolvedValueOnce({ rows: [] }); + } + + describe("updateWhiteboard", () => { + it("adds new elements to an empty board", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + + mockEmptyLoad( + deps.pool as unknown as { query: ReturnType } + ); + mockSaveReturn( + deps.pool as unknown as { query: ReturnType }, + 1 + ); + + const result = await handlers.updateWhiteboard( + "agt_test", + [rect("a"), rect("b")], + [] + ); + expect(result.addedIds).toEqual(["a", "b"]); + expect(result.updatedIds).toEqual([]); + expect(result.elementCount).toBe(2); + expect(result.version).toBe(1); + }); + + it("upserts existing elements", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + mockLoadReturn(pool, { elements: [rect("a", 0, 0)] }, 1); + mockSaveReturn(pool, 2); + + const result = await handlers.updateWhiteboard( + "agt_test", + [rect("a", 50, 50), rect("b")], + [] + ); + expect(result.addedIds).toEqual(["b"]); + expect(result.updatedIds).toEqual(["a"]); + expect(result.elementCount).toBe(2); + }); + + it("deletes specified elements", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + mockLoadReturn(pool, { elements: [rect("a"), rect("b"), rect("c")] }, 1); + mockSaveReturn(pool, 2); + + const result = await handlers.updateWhiteboard( + "agt_test", + [], + ["a", "c"] + ); + expect(result.deletedIds).toEqual(["a", "c"]); + expect(result.elementCount).toBe(1); + }); + + it("publishes whiteboard.changed event", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + mockEmptyLoad(pool); + mockSaveReturn(pool, 1); + + await handlers.updateWhiteboard("agt_test", [rect("a")], []); + expect(deps.publishUiEvent).toHaveBeenCalledWith({ + type: "whiteboard.changed", + agentId: "agt_test", + version: 1, + source: "agent", + }); + }); + + it("retries on optimistic lock conflict", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + // First attempt: load version 1, save fails (conflict) + mockLoadReturn(pool, { elements: [] }, 1); + pool.query.mockResolvedValueOnce({ rows: [] }); // save fails + + // Second attempt: load version 2, save succeeds + mockLoadReturn(pool, { elements: [] }, 2); + mockSaveReturn(pool, 3); + + const result = await handlers.updateWhiteboard( + "agt_test", + [rect("a")], + [] + ); + expect(result.version).toBe(3); + }); + + it("throws after 3 failed attempts", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + for (let i = 0; i < 3; i++) { + mockLoadReturn(pool, { elements: [] }, i + 1); + pool.query.mockResolvedValueOnce({ rows: [] }); // save fails + } + + await expect( + handlers.updateWhiteboard("agt_test", [rect("a")], []) + ).rejects.toThrow("concurrently"); + }); + + it("throws for unknown agent", async () => { + const deps = createMockDeps(); + ( + deps.agentManager.getAgent as ReturnType + ).mockResolvedValueOnce(null); + const handlers = createWhiteboardHandlers(deps); + + await expect( + handlers.updateWhiteboard("agt_missing", [rect("a")], []) + ).rejects.toThrow("Agent not found"); + }); + + it("throws when merged result exceeds MAX_ELEMENTS", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + const hugeScene = { + elements: Array.from({ length: MAX_ELEMENTS }, (_, i) => rect(`e${i}`)), + }; + mockLoadReturn(pool, hugeScene, 1); + + await expect( + handlers.updateWhiteboard("agt_test", [rect("new-one")], []) + ).rejects.toThrow("full"); + }); + + it("ignores incoming elements without required fields", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + mockEmptyLoad(pool); + mockSaveReturn(pool, 1); + + const result = await handlers.updateWhiteboard( + "agt_test", + [ + { id: "valid", type: "rectangle", x: 0, y: 0 }, + { type: "rectangle" }, // missing id + { id: "no-type" }, // missing type + null as unknown as Record, + ], + [] + ); + expect(result.addedIds).toEqual(["valid"]); + expect(result.elementCount).toBe(1); + }); + }); + + describe("clearWhiteboard", () => { + it("clears board and publishes event", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + mockLoadReturn(pool, { elements: [rect("a")] }, 1); + mockSaveReturn(pool, 2); + + await handlers.clearWhiteboard("agt_test"); + expect(deps.publishUiEvent).toHaveBeenCalledWith({ + type: "whiteboard.changed", + agentId: "agt_test", + version: 2, + source: "agent", + }); + }); + + it("throws for unknown agent", async () => { + const deps = createMockDeps(); + ( + deps.agentManager.getAgent as ReturnType + ).mockResolvedValueOnce(null); + const handlers = createWhiteboardHandlers(deps); + + await expect(handlers.clearWhiteboard("agt_missing")).rejects.toThrow( + "Agent not found" + ); + }); + }); + + describe("getWhiteboard", () => { + it("returns empty state for nonexistent whiteboard", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + mockEmptyLoad(pool); + + const result = await handlers.getWhiteboard("agt_test"); + expect(result.elements).toEqual([]); + expect(result.version).toBe(0); + expect(result.updatedAt).toBeNull(); + }); + + it("returns simplified elements for existing board", async () => { + const deps = createMockDeps(); + const handlers = createWhiteboardHandlers(deps); + const pool = deps.pool as unknown as { query: ReturnType }; + + const now = new Date(); + pool.query.mockResolvedValueOnce({ + rows: [ + { + scene: { elements: [rect("a", 10, 20)] }, + version: "3", + updated_by: "agent", + updated_at: now, + }, + ], + }); + + const result = await handlers.getWhiteboard("agt_test"); + expect(result.elements).toEqual([ + { id: "a", type: "rectangle", x: 10, y: 20, width: 100, height: 50 }, + ]); + expect(result.version).toBe(3); + expect(result.updatedAt).toBe(now.toISOString()); + }); + }); +}); diff --git a/apps/web/package.json b/apps/web/package.json index 22a2fd3f..9c8eaa15 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -13,6 +13,7 @@ "test:coverage": "vitest run --coverage" }, "dependencies": { + "@excalidraw/excalidraw": "^0.18.1", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.16", diff --git a/apps/web/public/excalidraw/libraries/NOTICES b/apps/web/public/excalidraw/libraries/NOTICES new file mode 100644 index 00000000..520f6ac3 --- /dev/null +++ b/apps/web/public/excalidraw/libraries/NOTICES @@ -0,0 +1,36 @@ +Excalidraw Libraries +==================== + +The .excalidrawlib files in this directory are sourced from the +excalidraw-libraries project: + + https://github.com/excalidraw/excalidraw-libraries + +Licensed under the MIT License: + + Copyright (c) 2020 Excalidraw + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Individual libraries: + - flow-chart-symbols.excalidrawlib — by finfin + - architecture-diagram-components.excalidrawlib — by anna-pastushko + - universal-ui-kit.excalidrawlib — by manuelernestog + - html-input-elements.excalidrawlib — by marwinburesch diff --git a/apps/web/public/excalidraw/libraries/architecture-diagram-components.excalidrawlib b/apps/web/public/excalidraw/libraries/architecture-diagram-components.excalidrawlib new file mode 100644 index 00000000..528ea02e --- /dev/null +++ b/apps/web/public/excalidraw/libraries/architecture-diagram-components.excalidrawlib @@ -0,0 +1,3555 @@ +{ + "type": "excalidrawlib", + "version": 2, + "source": "https://excalidraw.com", + "libraryItems": [ + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 3035, + "versionNonce": 1280493655, + "isDeleted": false, + "id": "GUq90cfPCBI5_XleHcVf4", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1809.1495216187216, + "y": 1477.109035697224, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 64.46115236495699, + "height": 64.39942473426015, + "seed": 886726492, + "groupIds": [ + "6yKeiHwTZ9f1hENg1Rvln", + "T3ua4nOmCVl7yrAf5BCce", + "TCfHzaLQwx50O5BjXwJ-k", + "O-QUX1l0MwWQlYY-pidnL", + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "EUHZ_sUolb_30DI6OzWP4" + }, + { + "type": "arrow", + "id": "HjpGCBxUuBW46Wd5r_3cI" + }, + { + "type": "arrow", + "id": "qVpgRhAMH6njKSxkEUaob" + }, + { + "type": "arrow", + "id": "fXUqR5l9-mJxPJpngO-uK" + }, + { + "type": "arrow", + "id": "GST0HrCYDviCg-JDHslU7" + }, + { + "type": "arrow", + "id": "WwJfE-IGw-hz6uAt8MWQ6" + }, + { + "type": "arrow", + "id": "_xS_u95gyqYLnjH6Z_ekf" + } + ], + "updated": 1652788086502, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2667, + "versionNonce": 1834863769, + "isDeleted": false, + "id": "RyexOyb07MMvy6EvnZWEe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1829.0292782163615, + "y": 1525.8995920327388, + "strokeColor": "#e01e5a", + "backgroundColor": "#e01e5a", + "width": 9.812617686394493, + "height": 21.62230587269945, + "seed": 1677907044, + "groupIds": [ + "HCz7wqtztIfOhP5y_Go4g", + "O-QUX1l0MwWQlYY-pidnL", + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788086502, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.5493619039573798, + 7.856944476158105 + ], + [ + 9.210070077961463, + 7.039820663903555 + ], + [ + 9.812617686394493, + -13.576796391808415 + ], + [ + 0.7746982889836341, + -13.765361396541337 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3068, + "versionNonce": 2040898935, + "isDeleted": false, + "id": "LqIPxdOsv3S99kOaoHOiG", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1817.9597283311568, + "y": 1510.4826671595124, + "strokeColor": "#e01e5a", + "backgroundColor": "#e01e5a", + "width": 10.809935258890052, + "height": 10.400984872948921, + "seed": 1905136604, + "groupIds": [ + "HCz7wqtztIfOhP5y_Go4g", + "O-QUX1l0MwWQlYY-pidnL", + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788086502, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.818991814833378, + 2.324732734179281 + ], + [ + -3.8436806131559678, + 6.762634497124866 + ], + [ + 1.1650667382148967, + 10.071035010913667 + ], + [ + 6.770132861029776, + 7.667134320273243 + ], + [ + 6.946438793826424, + -0.3210484014135393 + ], + [ + 6.966254645734084, + -0.329949862035255 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2765, + "versionNonce": 420139385, + "isDeleted": false, + "id": "F6mCSafAu5tWLjM5CYatC", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 1.5707963267948957, + "x": 1823.1083833032508, + "y": 1505.83322408803, + "strokeColor": "#36c5f0", + "backgroundColor": "#36c5f0", + "width": 9.812617686394493, + "height": 21.62230587269945, + "seed": 1156753380, + "groupIds": [ + "HCz7wqtztIfOhP5y_Go4g", + "O-QUX1l0MwWQlYY-pidnL", + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788086502, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.5493619039573798, + 7.856944476158105 + ], + [ + 9.210070077961463, + 7.039820663903555 + ], + [ + 9.812617686394493, + -13.576796391808415 + ], + [ + 0.7746982889836341, + -13.765361396541337 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3150, + "versionNonce": 1860165271, + "isDeleted": false, + "id": "FDcmhAvzwShOEBdo_dw0l", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 1.5707963267948957, + "x": 1832.77271399571, + "y": 1483.2516879256468, + "strokeColor": "#36c5f0", + "backgroundColor": "#36c5f0", + "width": 10.809935258890052, + "height": 10.400984872948921, + "seed": 920074332, + "groupIds": [ + "HCz7wqtztIfOhP5y_Go4g", + "O-QUX1l0MwWQlYY-pidnL", + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788086502, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.818991814833378, + 2.324732734179281 + ], + [ + -3.8436806131559678, + 6.762634497124866 + ], + [ + 1.1650667382148967, + 10.071035010913667 + ], + [ + 6.770132861029776, + 7.667134320273243 + ], + [ + 6.946438793826424, + -0.3210484014135393 + ], + [ + 6.966254645734084, + -0.329949862035255 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2750, + "versionNonce": 1451506265, + "isDeleted": false, + "id": "nYFs-74rOPAlZ2nOvE97v", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 3.141592653589793, + "x": 1842.661901614898, + "y": 1498.7158578723952, + "strokeColor": "#2eb67d", + "backgroundColor": "#2eb67d", + "width": 9.812617686394493, + "height": 21.62230587269945, + "seed": 798755684, + "groupIds": [ + "HCz7wqtztIfOhP5y_Go4g", + "O-QUX1l0MwWQlYY-pidnL", + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788086502, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.5493619039573798, + 7.856944476158105 + ], + [ + 9.210070077961463, + 7.039820663903555 + ], + [ + 9.812617686394493, + -13.576796391808415 + ], + [ + 0.7746982889836341, + -13.765361396541337 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3134, + "versionNonce": 1857418167, + "isDeleted": false, + "id": "WmaJXyLyLpOOjJhw-UoJj", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 3.141592653589793, + "x": 1861.4724630741243, + "y": 1496.5456409197295, + "strokeColor": "#2eb67d", + "backgroundColor": "#2eb67d", + "width": 10.809935258890052, + "height": 10.400984872948921, + "seed": 814206172, + "groupIds": [ + "HCz7wqtztIfOhP5y_Go4g", + "O-QUX1l0MwWQlYY-pidnL", + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788086502, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.818991814833378, + 2.324732734179281 + ], + [ + -3.8436806131559678, + 6.762634497124866 + ], + [ + 1.1650667382148967, + 10.071035010913667 + ], + [ + 6.770132861029776, + 7.667134320273243 + ], + [ + 6.946438793826424, + -0.3210484014135393 + ], + [ + 6.966254645734084, + -0.329949862035255 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2819, + "versionNonce": 1990035257, + "isDeleted": false, + "id": "vkY1-p93k12hl8wGSNqf9", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 4.712388980384688, + "x": 1849.4061798638486, + "y": 1519.6810119128954, + "strokeColor": "#ecb22e", + "backgroundColor": "#ecb22e", + "width": 9.812617686394493, + "height": 21.62230587269945, + "seed": 1447886564, + "groupIds": [ + "HCz7wqtztIfOhP5y_Go4g", + "O-QUX1l0MwWQlYY-pidnL", + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788086502, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.5493619039573798, + 7.856944476158105 + ], + [ + 9.210070077961463, + 7.039820663903555 + ], + [ + 9.812617686394493, + -13.576796391808415 + ], + [ + 0.7746982889836341, + -13.765361396541337 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3219, + "versionNonce": 1900831959, + "isDeleted": false, + "id": "KSOt5gdgvmOrCDQ87AB18", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 4.712388980384688, + "x": 1845.5079975038857, + "y": 1525.079826667898, + "strokeColor": "#ecb22e", + "backgroundColor": "#ecb22e", + "width": 10.809935258890052, + "height": 10.400984872948921, + "seed": 1077006684, + "groupIds": [ + "HCz7wqtztIfOhP5y_Go4g", + "O-QUX1l0MwWQlYY-pidnL", + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788086502, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.818991814833378, + 2.324732734179281 + ], + [ + -3.8436806131559678, + 6.762634497124866 + ], + [ + 1.1650667382148967, + 10.071035010913667 + ], + [ + 6.770132861029776, + 7.667134320273243 + ], + [ + 6.946438793826424, + -0.3210484014135393 + ], + [ + 6.966254645734084, + -0.329949862035255 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 1858, + "versionNonce": 1673148441, + "isDeleted": false, + "id": "w5bRazofab7lU3wH7aSK_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1815.3800978012005, + "y": 1545.8807498280685, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 52, + "height": 25, + "seed": 1905492580, + "groupIds": [ + "32xgjoIfrxequxeS1yiKx" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086502, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Slack", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Slack" + } + ], + "id": "mb6aDt8lLpnjb9BXfkKzo", + "created": 1652788300698, + "name": "Slack" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 3159, + "versionNonce": 1890483031, + "isDeleted": false, + "id": "uanIHxCIrMYntFa5JEEBR", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2000.8934435647661, + "y": 1476.3079086177738, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 64.46115236495699, + "height": 64.39942473426015, + "seed": 323870300, + "groupIds": [ + "ACroLnLjF7CL0w1OeLMWU", + "nj3uI0loSvxxD4oKwuvTC", + "4xoG6DV7mIB8Ajo2D1P48", + "_aLjJPj6ubf6pav06ALqJ", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "EUHZ_sUolb_30DI6OzWP4" + }, + { + "type": "arrow", + "id": "HjpGCBxUuBW46Wd5r_3cI" + }, + { + "type": "arrow", + "id": "qVpgRhAMH6njKSxkEUaob" + }, + { + "type": "arrow", + "id": "fXUqR5l9-mJxPJpngO-uK" + }, + { + "type": "arrow", + "id": "GST0HrCYDviCg-JDHslU7" + }, + { + "type": "arrow", + "id": "WwJfE-IGw-hz6uAt8MWQ6" + }, + { + "type": "arrow", + "id": "_xS_u95gyqYLnjH6Z_ekf" + } + ], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2392, + "versionNonce": 336251289, + "isDeleted": false, + "id": "7_toODjCSHDISkrm2shFd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2016.0359076430884, + "y": 1511.661855278454, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 56.62135476067269, + "height": 27.781375401353056, + "seed": 841461348, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 26.49928798736996, + -0.5555399791810629 + ], + [ + 32.86897029257218, + -1.351746926303818 + ], + [ + 31.54797965057393, + -6.47281952059744 + ], + [ + 33.64708101980081, + -11.340553540806383 + ], + [ + 36.88620050527203, + -6.798543885447785 + ], + [ + 36.45191491873459, + -3.1432192393850187 + ], + [ + 41.67251763363534, + -6.7171136294920615 + ], + [ + 45.75309251293886, + -5.2242237812582175 + ], + [ + 43.32826283037451, + -1.9398610124836608 + ], + [ + 37.99003529362133, + 0.5844902862542796 + ], + [ + 24.0563885094017, + 14.925301714362618 + ], + [ + 0.1701084154921375, + 16.440821860546677 + ], + [ + -10.868262247733824, + 1.1146897259718145 + ], + [ + -3.582944626162501, + -0.5971552103421658 + ], + [ + -0.28719357028327863, + -0.019545320337599037 + ] + ] + }, + { + "type": "rectangle", + "version": 1298, + "versionNonce": 504559735, + "isDeleted": false, + "id": "6ldzdstTHWD2NSyZpjIOg", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2009.750948956059, + "y": 1503.8496173056888, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 5.566352494330056, + "height": 5.566352494330056, + "seed": 1835811300, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1353, + "versionNonce": 990916217, + "isDeleted": false, + "id": "ivs_QGHAG6lFu1mrc09qQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2016.7088876275266, + "y": 1503.5944970012197, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 5.566352494330056, + "height": 5.566352494330056, + "seed": 164783460, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1373, + "versionNonce": 1613418903, + "isDeleted": false, + "id": "1fSQ-3ayqjVqHJK8VuyFh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2023.991524160524, + "y": 1503.710450512912, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 5.566352494330056, + "height": 5.566352494330056, + "seed": 212343012, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1394, + "versionNonce": 701888345, + "isDeleted": false, + "id": "E8EWStWQBSOsaK4qU9Ft2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2031.2509668768762, + "y": 1503.3625666205105, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 5.566352494330056, + "height": 5.566352494330056, + "seed": 561732708, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1436, + "versionNonce": 2786999, + "isDeleted": false, + "id": "wZ7pAlvrm5QFNU_NtlYnW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2038.1393202055147, + "y": 1503.4553341013263, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 5.566352494330056, + "height": 5.566352494330056, + "seed": 1377447908, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1340, + "versionNonce": 1387242553, + "isDeleted": false, + "id": "lJi4w4P0QseZ4KgNEaXCX", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2016.5117382395738, + "y": 1496.8453065724698, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 5.566352494330056, + "height": 5.566352494330056, + "seed": 1049793380, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1364, + "versionNonce": 750163927, + "isDeleted": false, + "id": "-wnimA_jQMKWc_-g6eaOC", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2023.8175724821076, + "y": 1496.9380779461715, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 5.566352494330056, + "height": 5.566352494330056, + "seed": 1429374692, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1388, + "versionNonce": 293647641, + "isDeleted": false, + "id": "kbQ8u03t5Yo9KkMQKkAPk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2031.216178098346, + "y": 1496.9380701604036, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 5.566352494330056, + "height": 5.566352494330056, + "seed": 176687716, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1407, + "versionNonce": 1273201911, + "isDeleted": false, + "id": "-zI2AUQ5W8JbYNUCkTieB", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2030.9726410770982, + "y": 1489.8409763748195, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 5.566352494330056, + "height": 5.566352494330056, + "seed": 291577316, + "groupIds": [ + "eL4uAkeWwfpsKN2jOX7_g", + "544RHJd-Dhaeg2-NkUCY1", + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1917, + "versionNonce": 2042407417, + "isDeleted": false, + "id": "goGzbKzw9Qo8Immm60G38", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1999.6240197472443, + "y": 1546.0921712247273, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 67, + "height": 25, + "seed": 804287196, + "groupIds": [ + "0XWV8-iYC-gTs59lvQu5F" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788086506, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Docker", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Docker" + } + ], + "id": "Qsr_bCZKjzSUef9igg3F_", + "created": 1652788294057, + "name": "Docker" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 3143, + "versionNonce": 1591930393, + "isDeleted": false, + "id": "QtgIqDyZcHqpV8Ju_l857", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2131.4206261576473, + "y": 1482.2143172736266, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 64.46115236495699, + "height": 64.39942473426015, + "seed": 1157021412, + "groupIds": [ + "LrESEPdWDTzkPK50aCds2", + "h1Qh7Eu_XCbl35WTZ9Pq9", + "ZdJX-h95nCliReNZ8kLqG", + "I10Yt6AhGQErTZkizQcks", + "gR9JOi18bkX5JTHz6Te_0" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "EUHZ_sUolb_30DI6OzWP4" + }, + { + "type": "arrow", + "id": "HjpGCBxUuBW46Wd5r_3cI" + }, + { + "type": "arrow", + "id": "qVpgRhAMH6njKSxkEUaob" + }, + { + "type": "arrow", + "id": "fXUqR5l9-mJxPJpngO-uK" + }, + { + "type": "arrow", + "id": "GST0HrCYDviCg-JDHslU7" + }, + { + "type": "arrow", + "id": "WwJfE-IGw-hz6uAt8MWQ6" + }, + { + "type": "arrow", + "id": "_xS_u95gyqYLnjH6Z_ekf" + } + ], + "updated": 1652788096751, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1098, + "versionNonce": 664624119, + "isDeleted": false, + "id": "kJiWasVkq7EzcAp_yYMnF", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2137.7099509559657, + "y": 1488.5865456369318, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 51.88250276832002, + "height": 51.88250276832002, + "seed": 891096548, + "groupIds": [ + "YZQku5Chfcs1VbFqtc9Z-", + "gR9JOi18bkX5JTHz6Te_0" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788096752, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 4644, + "versionNonce": 812630777, + "isDeleted": false, + "id": "uygbaH3pY7f71kinGo1KC", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2153.457714886652, + "y": 1498.9945962464901, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 28.10264153823924, + "height": 32.45750960039449, + "seed": 965325156, + "groupIds": [ + "YZQku5Chfcs1VbFqtc9Z-", + "gR9JOi18bkX5JTHz6Te_0" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788096752, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.522383435627326, + 0.8876695614221044 + ], + [ + 4.222250533550099, + 2.710891079770457 + ], + [ + 6.53876438289814, + 1.9494891881139838 + ], + [ + 13.00273268947289, + 2.0374391115495714 + ], + [ + 15.018859724261578, + 2.2364052653721083 + ], + [ + 16.578273739956423, + 0.9941438562971401 + ], + [ + 18.744233429245106, + -0.5092823311506232 + ], + [ + 19.3382730064762, + 1.5588921397895759 + ], + [ + 19.283438583962543, + 3.566556278350808 + ], + [ + 21.26661686487244, + 5.46749782452002 + ], + [ + 22.153106695509578, + 10.909367329014938 + ], + [ + 21.45853734367018, + 16.608727547781946 + ], + [ + 19.438802781084537, + 18.8230883820351 + ], + [ + 16.687942584983702, + 20.009258415382803 + ], + [ + 13.71774469882833, + 20.664602961087855 + ], + [ + 14.50370475485716, + 21.40308436092641 + ], + [ + 15.390194585494283, + 23.040424434305827 + ], + [ + 15.572975993873055, + 28.176226896819482 + ], + [ + 15.536419712197318, + 31.28569415677526 + ], + [ + 14.567678247789736, + 31.823854903738514 + ], + [ + 7.240428539404906, + 31.948227269243866 + ], + [ + 6.324236729906197, + 31.05132655929166 + ], + [ + 6.068342758175913, + 27.392989048505843 + ], + [ + 4.377614730672068, + 27.862931757494 + ], + [ + 1.5810591824765403, + 28.041957551394276 + ], + [ + -1.0327149573401755, + 26.542616527479623 + ], + [ + -2.659469491911409, + 23.92436429168835 + ], + [ + -4.240528674387946, + 22.313132146586064 + ], + [ + -5.949534842729662, + 21.179302118551103 + ], + [ + -3.628210956319009, + 21.40308436092641 + ], + [ + -1.9831782809098755, + 22.671183734386556 + ], + [ + -0.11880791544621871, + 24.43906344915162 + ], + [ + 2.147681548450816, + 25.543055844869823 + ], + [ + 4.660925913659192, + 25.22976070554441 + ], + [ + 5.9038394906349865, + 24.252578247172192 + ], + [ + 6.022647406081191, + 22.71594018286161 + ], + [ + 6.689799546663803, + 21.380706136688904 + ], + [ + 7.558011236463052, + 20.776494082275484 + ], + [ + 4.277084956063728, + 19.93358096932851 + ], + [ + 1.1423838023674477, + 18.89984919335192 + ], + [ + -0.4569535209469784, + 16.571430507386058 + ], + [ + -1.6541717458280658, + 10.963038997107194 + ], + [ + -0.7037084222583516, + 5.6861115380996985 + ], + [ + 0.6671521405825978, + 4.240069736001822 + ], + [ + -0.11880791544621871, + 2.2772861946741387 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 1975, + "versionNonce": 563922199, + "isDeleted": false, + "id": "fdfeFdIFPqv8ob7vcD8RU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2131.1512023401265, + "y": 1552.2135661651414, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 65, + "height": 25, + "seed": 1520252004, + "groupIds": [ + "gR9JOi18bkX5JTHz6Te_0" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788096752, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "GitHub", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "GitHub" + } + ], + "id": "K39d22cEulRClkwYv67oo", + "created": 1652788286899, + "name": "GitHub" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 300, + "versionNonce": 421369913, + "isDeleted": false, + "id": "f_JKahZnSxJc4KAt7ZsW-", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1442.384234036135, + "y": 1636.2963678444105, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 269.51410950501486, + "height": 164.54545632937743, + "seed": 452493049, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2059, + "versionNonce": 1129440215, + "isDeleted": false, + "id": "LDxnjsBPCY93PCT2EGpUa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1491.4371102665593, + "y": 1645.2696725568244, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 37, + "height": 25, + "seed": 1032660985, + "groupIds": [ + "SD0C2qiCphyCRHCZftNUU" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "bVaqFXvJckS26nzM_m6zD", + "type": "arrow" + } + ], + "updated": 1652788094454, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "VPC", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "VPC" + }, + { + "type": "rectangle", + "version": 409, + "versionNonce": 1041246489, + "isDeleted": false, + "id": "Td3722QsARfBJtcZHOuFS", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1442.721971221976, + "y": 1636.5078306958162, + "strokeColor": "#2b8a3e", + "backgroundColor": "#40c05788", + "width": 39.6243416500605, + "height": 42.52368372201621, + "seed": 34349401, + "groupIds": [ + "zrX79N-B9zMuBpQmVDD9X", + "SD0C2qiCphyCRHCZftNUU" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 3514, + "versionNonce": 154489079, + "isDeleted": false, + "id": "GPDOaIWIdOnW9UIZfR-hn", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1475.6296262085905, + "y": 1666.1668186165264, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.27574323253868, + "height": 18.361713520282862, + "seed": 847652599, + "groupIds": [ + "LYSo7W3gyad40JoNnPoy2", + "zrX79N-B9zMuBpQmVDD9X", + "SD0C2qiCphyCRHCZftNUU" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 3.549984432244912, + -3.8949537333214295 + ], + [ + 2.3636841312957984, + -8.495303540186455 + ], + [ + -2.7289433979513547, + -10.111388238124647 + ], + [ + -3.027690717076837, + -10.609548106174614 + ], + [ + -3.567000789869894, + -12.789455475380407 + ], + [ + -6.056547102486405, + -13.810998302611608 + ], + [ + -8.328438953196809, + -12.373992295078466 + ], + [ + -8.57536552902286, + -12.601775211762344 + ], + [ + -12.065771512081795, + -16.306198129166546 + ], + [ + -17.183143313379162, + -17.9479861601084 + ], + [ + -22.002391699683074, + -15.86580108709353 + ], + [ + -24.284559618469785, + -10.65550025600254 + ], + [ + -24.644339441920692, + -9.79120847277541 + ], + [ + -28.1253575027867, + -7.581853163765089 + ], + [ + -28.72575880029376, + -3.0476054391618987 + ], + [ + -25.849736907374307, + 0.09177212746608632 + ], + [ + -22.43830984753089, + 0.3704975936309154 + ], + [ + -18.24098378130509, + 0.41372736017446243 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 504, + "versionNonce": 1279106553, + "isDeleted": false, + "id": "S9yi14_dAkxISVpNQePuS", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1458.147217704677, + "y": 1657.4286516397065, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 6.723014307274803, + "height": 6.1627631150019475, + "seed": 58972761, + "groupIds": [ + "qVX3fdAHxGWkYZqsWhS1z", + "LYSo7W3gyad40JoNnPoy2", + "zrX79N-B9zMuBpQmVDD9X", + "SD0C2qiCphyCRHCZftNUU" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 648, + "versionNonce": 1450056215, + "isDeleted": false, + "id": "H3JdgEwAIzhcdcvKskrMI", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1458.3447672988902, + "y": 1657.3456514630645, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.735007948486001, + "height": 3.735007948486001, + "seed": 537867865, + "groupIds": [ + "qVX3fdAHxGWkYZqsWhS1z", + "LYSo7W3gyad40JoNnPoy2", + "zrX79N-B9zMuBpQmVDD9X", + "SD0C2qiCphyCRHCZftNUU" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.1660003532660483, + -0.8300017663302128 + ], + [ + 0.5810012364311606, + -2.0750044158255605 + ], + [ + 1.4940031793944009, + -2.9880063587888444 + ], + [ + 2.407005122357648, + -3.652007771852979 + ], + [ + 3.735007948486001, + -3.7350079484860075 + ] + ] + }, + { + "type": "line", + "version": 727, + "versionNonce": 581531353, + "isDeleted": false, + "id": "uBYTxi2Fk94vTgj17Ahqt", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1464.6627315703688, + "y": 1657.3713769309054, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.735007948486001, + "height": 3.735007948486001, + "seed": 361767031, + "groupIds": [ + "qVX3fdAHxGWkYZqsWhS1z", + "LYSo7W3gyad40JoNnPoy2", + "zrX79N-B9zMuBpQmVDD9X", + "SD0C2qiCphyCRHCZftNUU" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.16600035326605678, + -0.8300017663301986 + ], + [ + -0.5810012364311561, + -2.0750044158255605 + ], + [ + -1.494003179394383, + -2.9880063587888444 + ], + [ + -2.407005122357667, + -3.652007771852979 + ], + [ + -3.7350079484860075, + -3.7350079484860075 + ] + ] + } + ], + "id": "iuZ_6K7EwMGSXXV81NDXm", + "created": 1652788276526, + "name": "VPC" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 344, + "versionNonce": 1341177655, + "isDeleted": false, + "id": "dPEYeIjNa1nluI_vQK-H9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 10, + "angle": 0, + "x": 1753.0347076234943, + "y": 1636.2963678444105, + "strokeColor": "#0091e2", + "backgroundColor": "#0091e2", + "width": 269.51410950501486, + "height": 164.54545632937743, + "seed": 1970567865, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2254, + "versionNonce": 1944531897, + "isDeleted": false, + "id": "WgyWtsyoLJ6f6YcSKYfC_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1797.9846599072503, + "y": 1645.2736234765187, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 147, + "height": 25, + "seed": 1311030615, + "groupIds": [ + "jojPcobUJp1FYQLYaB99r" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "bVaqFXvJckS26nzM_m6zD", + "type": "arrow" + } + ], + "updated": 1652788094454, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Private subnet", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Private subnet" + }, + { + "type": "rectangle", + "version": 589, + "versionNonce": 136429655, + "isDeleted": false, + "id": "dpalnXDPuZPWrt4ocUhsM", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1753.269520862667, + "y": 1636.5117816155105, + "strokeColor": "#0091e2", + "backgroundColor": "#4c6ef588", + "width": 39.6243416500605, + "height": 42.52368372201621, + "seed": 563193753, + "groupIds": [ + "z8fpnbqG9D5h5BJpjr8L7", + "jojPcobUJp1FYQLYaB99r" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 461, + "versionNonce": 1411011737, + "isDeleted": false, + "id": "ijAkpwJaRQ3Sm6tTVLxq_", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1762.93827098891, + "y": 1655.1338121646274, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 20.286841397574015, + "height": 16.448790322357453, + "seed": 1976429719, + "groupIds": [ + "-y-OIIssX5nNaTN-ohqqU", + "z8fpnbqG9D5h5BJpjr8L7", + "jojPcobUJp1FYQLYaB99r" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 522, + "versionNonce": 1356176759, + "isDeleted": false, + "id": "CBU6E53PAYyGidh-Cw0-j", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0.037253345120129566, + "x": 1767.2625911667262, + "y": 1654.8338126416122, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 12.858316444380746, + "height": 10.880113914476107, + "seed": 332567095, + "groupIds": [ + "-y-OIIssX5nNaTN-ohqqU", + "z8fpnbqG9D5h5BJpjr8L7", + "jojPcobUJp1FYQLYaB99r" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.329700421650787, + -3.9564050598095566 + ], + [ + 1.3188016866031462, + -8.90191138457145 + ], + [ + 6.2643080113649745, + -10.880113914476107 + ], + [ + 10.550413492825204, + -8.90191138457145 + ], + [ + 12.198915601079173, + -4.945506324761892 + ], + [ + 12.52861602272996, + -0.3297004216509265 + ] + ] + }, + { + "type": "ellipse", + "version": 442, + "versionNonce": 116808057, + "isDeleted": false, + "id": "nc52cTahwemUriesKHteX", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1770.3931057805657, + "y": 1659.2831289420449, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 5.275206746412619, + "height": 5.275206746412619, + "seed": 727656153, + "groupIds": [ + "-y-OIIssX5nNaTN-ohqqU", + "z8fpnbqG9D5h5BJpjr8L7", + "jojPcobUJp1FYQLYaB99r" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 420, + "versionNonce": 1297143447, + "isDeleted": false, + "id": "le0EGJ_WCnfaY7p2IsMOZ", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1773.0339879374565, + "y": 1664.7248252911374, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 4.945506324761884, + "seed": 888331287, + "groupIds": [ + "-y-OIIssX5nNaTN-ohqqU", + "z8fpnbqG9D5h5BJpjr8L7", + "jojPcobUJp1FYQLYaB99r" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 4.945506324761884 + ] + ] + } + ], + "id": "tFo9OYIShjpGSvTwOAS2f", + "created": 1652788267525, + "name": "Private subnet" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 432, + "versionNonce": 994005687, + "isDeleted": false, + "id": "nN_FFSvlJJflT8rpZVgCX", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 10, + "angle": 0, + "x": 2068.196153089959, + "y": 1636.2963678444105, + "strokeColor": "#2b8a3e", + "backgroundColor": "#2b8a3e", + "width": 269.51410950501486, + "height": 164.54545632937743, + "seed": 2043392407, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2349, + "versionNonce": 1806513719, + "isDeleted": false, + "id": "tc4qHpKR_vXNvf3-Pf1R4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2114.361939143105, + "y": 1645.9635823398605, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 25, + "seed": 807566071, + "groupIds": [ + "PV8Gn0PijRZzjX2hMKwgp" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "bVaqFXvJckS26nzM_m6zD", + "type": "arrow" + } + ], + "updated": 1652788239861, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Public subnet", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Public subnet" + }, + { + "type": "rectangle", + "version": 665, + "versionNonce": 1145564087, + "isDeleted": false, + "id": "zmrROgE716GaUWwB116de", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2078.0676224585577, + "y": 1655.823771027969, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 20.286841397574015, + "height": 16.448790322357453, + "seed": 975569943, + "groupIds": [ + "xCQT5sKyDemXqnqKwHTF9", + "6tYLHZ6RYpPuaKiv1UfQv", + "PV8Gn0PijRZzjX2hMKwgp" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 726, + "versionNonce": 1967591225, + "isDeleted": false, + "id": "08gY5fDDsjcoAZHTdjq8H", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0.037253345120129566, + "x": 2082.3919426363746, + "y": 1655.5237715049534, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 12.858316444380746, + "height": 10.880113914476107, + "seed": 1715285209, + "groupIds": [ + "xCQT5sKyDemXqnqKwHTF9", + "6tYLHZ6RYpPuaKiv1UfQv", + "PV8Gn0PijRZzjX2hMKwgp" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.329700421650787, + -3.9564050598095566 + ], + [ + 1.3188016866031462, + -8.90191138457145 + ], + [ + 6.2643080113649745, + -10.880113914476107 + ], + [ + 10.550413492825204, + -8.90191138457145 + ], + [ + 12.198915601079173, + -4.945506324761892 + ], + [ + 12.52861602272996, + -0.3297004216509265 + ] + ] + }, + { + "type": "ellipse", + "version": 646, + "versionNonce": 836721879, + "isDeleted": false, + "id": "NK5hWvP-BHfOMXbzjWyvQ", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2085.522457250214, + "y": 1659.9730878053863, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 5.275206746412619, + "height": 5.275206746412619, + "seed": 152791351, + "groupIds": [ + "xCQT5sKyDemXqnqKwHTF9", + "6tYLHZ6RYpPuaKiv1UfQv", + "PV8Gn0PijRZzjX2hMKwgp" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 624, + "versionNonce": 1676853273, + "isDeleted": false, + "id": "PO7G54ml-sCiqRZGCGtPE", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2088.163339407105, + "y": 1665.4147841544793, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 4.945506324761884, + "seed": 932456889, + "groupIds": [ + "xCQT5sKyDemXqnqKwHTF9", + "6tYLHZ6RYpPuaKiv1UfQv", + "PV8Gn0PijRZzjX2hMKwgp" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 4.945506324761884 + ] + ] + }, + { + "type": "rectangle", + "version": 596, + "versionNonce": 430680567, + "isDeleted": false, + "id": "YwX1Ni6wuwrdk5CtH0XKI", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2068.3988723323146, + "y": 1637.2017404788526, + "strokeColor": "#2b8a3e", + "backgroundColor": "#40c05788", + "width": 39.6243416500605, + "height": 42.52368372201621, + "seed": 962056087, + "groupIds": [ + "6tYLHZ6RYpPuaKiv1UfQv", + "PV8Gn0PijRZzjX2hMKwgp" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788094454, + "link": null, + "locked": false + } + ], + "id": "RMcbGqneckVAijgb0ksgq", + "created": 1652788258854, + "name": "Public subnet" + }, + { + "status": "published", + "elements": [ + { + "type": "text", + "version": 884, + "versionNonce": 1252295383, + "isDeleted": false, + "id": "QJfRdycBHBpPbSJ2qkaHR", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1482.749129546251, + "y": 2105.0869523931538, + "strokeColor": "#000000", + "backgroundColor": "white", + "width": 46, + "height": 25, + "seed": 14301209, + "groupIds": [ + "Viu2YdNB2na1uSBiKAFRr" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "User", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "User" + }, + { + "type": "line", + "version": 1193, + "versionNonce": 33225241, + "isDeleted": false, + "id": "ossF4GzkYCgGsHklL1hl0", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1479.9619088977436, + "y": 2095.434026383175, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 49.26942071813747, + "height": 43.87300421060919, + "seed": 506339289, + "groupIds": [ + "1qNPPmx9UHXcelOK9AVB0", + "Viu2YdNB2na1uSBiKAFRr" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.518175120431392, + -29.072472669680792 + ], + [ + 23.649321944705985, + -43.87300421060919 + ], + [ + 41.780468768980576, + -32.244015142736835 + ], + [ + 49.26942071813747, + -3.0188078795298896 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 930, + "versionNonce": 1792806903, + "isDeleted": false, + "id": "2zyw5TfId7xzj1osFRw3u", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1492.4157797340592, + "y": 2028.7429785973186, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 25.225943407686408, + "height": 22.072700481725683, + "seed": 19327833, + "groupIds": [ + "1qNPPmx9UHXcelOK9AVB0", + "Viu2YdNB2na1uSBiKAFRr" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false + } + ], + "id": "UFgHjkyx6W38-mAJ-BNVT", + "created": 1652788139616, + "name": "User" + }, + { + "status": "published", + "elements": [ + { + "type": "text", + "version": 917, + "versionNonce": 1604126201, + "isDeleted": false, + "id": "SPeAjZMYCx8EoDI65dPCE", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1648.6634829780694, + "y": 2107.713080534573, + "strokeColor": "#000000", + "backgroundColor": "white", + "width": 57, + "height": 25, + "seed": 769940439, + "groupIds": [ + "3Au8BUSkq1LVGtylUmgjQ" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Users", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Users" + }, + { + "type": "ellipse", + "version": 1846, + "versionNonce": 957952535, + "isDeleted": false, + "id": "NmoZVFoqQX1Vu5MZKFy1V", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1689.1062999172887, + "y": 2040.453237200236, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 19.75034363276957, + "height": 17.281550678673437, + "seed": 1178681657, + "groupIds": [ + "wDTsFXl-vMcxnOc1Vhb-n", + "2Je6Qe5C2ipP25gzhXag3", + "3Au8BUSkq1LVGtylUmgjQ" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1890, + "versionNonce": 1708908249, + "isDeleted": false, + "id": "h6PU4x9jiv-rRAUghe8Ai", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1645.9226257929668, + "y": 2041.03630498387, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 19.75034363276957, + "height": 17.281550678673437, + "seed": 987148825, + "groupIds": [ + "wDTsFXl-vMcxnOc1Vhb-n", + "2Je6Qe5C2ipP25gzhXag3", + "3Au8BUSkq1LVGtylUmgjQ" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 2073, + "versionNonce": 1568200503, + "isDeleted": false, + "id": "JAt8qoX76olLN6uxixJt7", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1656.3744458002311, + "y": 2099.9673407215196, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 41.80600213253467, + "height": 37.22704429756421, + "seed": 1632306937, + "groupIds": [ + "wDTsFXl-vMcxnOc1Vhb-n", + "2Je6Qe5C2ipP25gzhXag3", + "3Au8BUSkq1LVGtylUmgjQ" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.682272238843876, + -24.66852332971124 + ], + [ + 20.06688102361667, + -37.22704429756421 + ], + [ + 35.45148980838941, + -27.359634965679646 + ], + [ + 41.80600213253467, + -2.561513547547756 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1784, + "versionNonce": 1955371961, + "isDeleted": false, + "id": "q84Lz9t5dvdU-A-7bgTEI", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1666.860567951041, + "y": 2043.3928294016825, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 21.404673091857735, + "height": 18.729088955375612, + "seed": 1679933401, + "groupIds": [ + "wDTsFXl-vMcxnOc1Vhb-n", + "2Je6Qe5C2ipP25gzhXag3", + "3Au8BUSkq1LVGtylUmgjQ" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1186, + "versionNonce": 1558632535, + "isDeleted": false, + "id": "Vg-rpWJ3bCmlGFwLkz2RJ", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1685.525781254682, + "y": 2066.2982760364666, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 33.437317203044365, + "height": 37.41795020340682, + "seed": 808629655, + "groupIds": [ + "2Je6Qe5C2ipP25gzhXag3", + "3Au8BUSkq1LVGtylUmgjQ" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 5.174822900471103, + -5.57288620050744 + ], + [ + 9.553519200869804, + -8.3593293007612 + ], + [ + 17.116721901558428, + -8.757392600797402 + ], + [ + 22.291544802029577, + -7.563202700688624 + ], + [ + 25.874114502355752, + -3.5825697003262533 + ], + [ + 28.262494302573206, + 1.5922532001448921 + ], + [ + 30.650874102790677, + 7.961266000724821 + ], + [ + 32.64119060297189, + 14.33027880130475 + ], + [ + 33.437317203044365, + 19.50510170177589 + ], + [ + 32.64119060297189, + 24.67992460224705 + ], + [ + 29.45668420268195, + 27.068304402464427 + ], + [ + 25.077987902283255, + 28.26249430257321 + ], + [ + 18.708975101703373, + 28.660557602609412 + ], + [ + 14.728342101340951, + 27.86443100253699 + ], + [ + 14.330278801304704, + 22.291544802029662 + ], + [ + 12.738025601159729, + 18.70897510170331 + ], + [ + 9.951582500906056, + 13.136088901195967 + ], + [ + 5.970949500543595, + 5.572886200507347 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1481, + "versionNonce": 1808972953, + "isDeleted": false, + "id": "mPuYUIgb4Y_xezXZi9asr", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1667.606994801348, + "y": 2066.286400133062, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 32.24312730293567, + "height": 39.01020340355187, + "seed": 1685387257, + "groupIds": [ + "2Je6Qe5C2ipP25gzhXag3", + "3Au8BUSkq1LVGtylUmgjQ" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.980633000362431, + -4.378696300398655 + ], + [ + -8.359329300761118, + -7.165139400652425 + ], + [ + -15.922532001449774, + -7.56320270068861 + ], + [ + -21.097354901920916, + -6.369012800579835 + ], + [ + -24.679924602247063, + -2.388379800217473 + ], + [ + -27.06830440246457, + 2.7864431002536705 + ], + [ + -29.45668420268202, + 9.155455900833594 + ], + [ + -31.4470007028632, + 15.524468701413529 + ], + [ + -32.24312730293567, + 20.699291601884667 + ], + [ + -31.4470007028632, + 25.87411450235582 + ], + [ + -28.262494302573295, + 28.262494302573188 + ], + [ + -24.281861302210807, + 30.25281080275446 + ], + [ + -17.912848501630954, + 31.44700070286316 + ], + [ + -11.145772401014788, + 31.447000702863257 + ], + [ + -10.349645800942298, + 24.281861302210825 + ], + [ + -9.553519200869822, + 20.699291601884582 + ], + [ + -8.359329300761118, + 14.728342101341028 + ], + [ + -4.7767596004349215, + 6.767076100616125 + ], + [ + 0, + 0 + ] + ] + } + ], + "id": "2WIQDCyDP0rYd2UMhMFNE", + "created": 1652788132269, + "name": "Users" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 1797, + "versionNonce": 1285967609, + "isDeleted": false, + "id": "XY68X70pWBB_KTuFENlCA", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1872.6978641000337, + "y": 2023.9314163933077, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 44.89096291762419, + "height": 68.65473186014601, + "seed": 998136375, + "groupIds": [ + "Due5tD6SVG60wFltRvqxd", + "TDLQl-R1wAzxPa-KZXh88" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1959, + "versionNonce": 1927608599, + "isDeleted": false, + "id": "IXe55vQDHa7ParMVN5EnJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1877.8373858616685, + "y": 2029.1656926777612, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 35.37268888043181, + "height": 52.508196731676236, + "seed": 88341335, + "groupIds": [ + "Due5tD6SVG60wFltRvqxd", + "TDLQl-R1wAzxPa-KZXh88" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "Q3sXfBVz75bTl5TIhEB0s", + "type": "arrow" + } + ], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1995, + "versionNonce": 1423181785, + "isDeleted": false, + "id": "KRpxkVGxhpGc891OdiqcB", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1891.9016475570904, + "y": 2083.9210188706206, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 7.24416548958712, + "height": 7.24416548958712, + "seed": 66615415, + "groupIds": [ + "Due5tD6SVG60wFltRvqxd", + "TDLQl-R1wAzxPa-KZXh88" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1648, + "versionNonce": 406044215, + "isDeleted": false, + "id": "i9cMOQK3OV6uebTtpDAVC", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1883.0859307662918, + "y": 2035.8496961999592, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef588", + "width": 24.875599071184233, + "height": 12.608047084964703, + "seed": 1835028887, + "groupIds": [ + "Due5tD6SVG60wFltRvqxd", + "TDLQl-R1wAzxPa-KZXh88" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1692, + "versionNonce": 77046969, + "isDeleted": false, + "id": "WLPcX4j7cDcAXBAGiH37S", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1883.0859307662918, + "y": 2058.3899714141685, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef588", + "width": 24.875599071184233, + "height": 12.608047084964703, + "seed": 1253050039, + "groupIds": [ + "Due5tD6SVG60wFltRvqxd", + "TDLQl-R1wAzxPa-KZXh88" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "Q3sXfBVz75bTl5TIhEB0s", + "type": "arrow" + } + ], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1164, + "versionNonce": 1840045911, + "isDeleted": false, + "id": "otYDQpWPH4kWxm8LQuC1i", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1863.6433455588458, + "y": 2100.8780236517937, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 63, + "height": 25, + "seed": 227676793, + "groupIds": [ + "TDLQl-R1wAzxPa-KZXh88" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Device", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Device" + } + ], + "id": "9_9pebw1kmiJPt-x9timk", + "created": 1652788125156, + "name": "Device" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 750, + "versionNonce": 1637405529, + "isDeleted": false, + "id": "7Pk-TLxXtuWZkXpaK8tS4", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2067.820080211402, + "y": 2024.956526167033, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 37.26835999695436, + "height": 69.56760532764793, + "seed": 372949593, + "groupIds": [ + "76AB9cfQxi8EevCVG_nyp" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1296, + "versionNonce": 1066625719, + "isDeleted": false, + "id": "NiaPt1_volLg4xDhmhVPw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2054.9542602098786, + "y": 2104.416032826593, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 63, + "height": 25, + "seed": 81738841, + "groupIds": [ + "76AB9cfQxi8EevCVG_nyp" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Server", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Server" + }, + { + "type": "line", + "version": 595, + "versionNonce": 1169621049, + "isDeleted": false, + "id": "GDASN8Ar7kqbTnHNqRkxb", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2072.3046375445324, + "y": 2032.6524768329887, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 28.572409330998198, + "height": 2.2737367544323206e-13, + "seed": 627417527, + "groupIds": [ + "76AB9cfQxi8EevCVG_nyp" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 28.572409330998198, + 2.2737367544323206e-13 + ] + ] + }, + { + "type": "line", + "version": 627, + "versionNonce": 1432550359, + "isDeleted": false, + "id": "BBQgQPZgkKsxQ6zNRzIUr", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2072.1680555443795, + "y": 2039.056274610597, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 28.572409330998198, + "height": 2.2737367544323206e-13, + "seed": 204892599, + "groupIds": [ + "76AB9cfQxi8EevCVG_nyp" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 28.572409330998198, + 2.2737367544323206e-13 + ] + ] + }, + { + "type": "line", + "version": 609, + "versionNonce": 1664913689, + "isDeleted": false, + "id": "Usz5re68bOeeSKgyCLVmq", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2072.1371702106403, + "y": 2045.4600723882063, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 28.572409330998198, + "height": 2.2737367544323206e-13, + "seed": 987848505, + "groupIds": [ + "76AB9cfQxi8EevCVG_nyp" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 28.572409330998198, + 2.2737367544323206e-13 + ] + ] + }, + { + "type": "line", + "version": 613, + "versionNonce": 1768122615, + "isDeleted": false, + "id": "BbKD57FKlc-NgH63VqGFw", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2072.1371702106403, + "y": 2051.863870165815, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 28.572409330998198, + "height": 2.2737367544323206e-13, + "seed": 769157465, + "groupIds": [ + "76AB9cfQxi8EevCVG_nyp" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 28.572409330998198, + 2.2737367544323206e-13 + ] + ] + } + ], + "id": "RK7rWRm_bqsgMR-yyEact", + "created": 1652788117720, + "name": "Server" + }, + { + "status": "published", + "elements": [ + { + "type": "text", + "version": 937, + "versionNonce": 932488601, + "isDeleted": false, + "id": "BR44CilwTIbONCvGKgLhq", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2276.2302177969473, + "y": 2086.920421581179, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 50, + "height": 25, + "seed": 2109994009, + "groupIds": [ + "z8UUgMUPNsq5axJkMXtT8" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Email", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Email" + }, + { + "type": "rectangle", + "version": 999, + "versionNonce": 899996791, + "isDeleted": false, + "id": "UeFyWyJ1YbSMu7K0pkCpY", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2269.9867164774682, + "y": 2035.3562941088205, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 62.48700263895784, + "height": 41.463338199682276, + "seed": 1690575833, + "groupIds": [ + "UfI5AJ2N6H-8qqUQeyy3l", + "z8UUgMUPNsq5axJkMXtT8" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 826, + "versionNonce": 150899321, + "isDeleted": false, + "id": "e-Z3aRG8x2-hd5cKdX2t7", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2270.8896528022965, + "y": 2039.198838061454, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 30.732287023896305, + "height": 23.925868693893968, + "seed": 1478770873, + "groupIds": [ + "UfI5AJ2N6H-8qqUQeyy3l", + "z8UUgMUPNsq5axJkMXtT8" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 30.732287023896305, + 23.925868693893968 + ] + ] + }, + { + "type": "line", + "version": 912, + "versionNonce": 1944073623, + "isDeleted": false, + "id": "au4tcwg8T7lC7un-8nooJ", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2302.757305018425, + "y": 2064.267967494489, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 29.30307776200009, + "height": 29.23224875584021, + "seed": 657310105, + "groupIds": [ + "UfI5AJ2N6H-8qqUQeyy3l", + "z8UUgMUPNsq5axJkMXtT8" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1652788090905, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 29.30307776200009, + -29.23224875584021 + ] + ] + } + ], + "id": "yjClh1WmDx5TUhWugAREM", + "created": 1652788110401, + "name": "Email" + } + ] +} diff --git a/apps/web/public/excalidraw/libraries/flow-chart-symbols.excalidrawlib b/apps/web/public/excalidraw/libraries/flow-chart-symbols.excalidrawlib new file mode 100644 index 00000000..0acdcbd6 --- /dev/null +++ b/apps/web/public/excalidraw/libraries/flow-chart-symbols.excalidrawlib @@ -0,0 +1,2150 @@ +{ + "type": "excalidrawlib", + "version": 2, + "source": "https://app.excalidraw.com", + "libraryItems": [ + { + "status": "published", + "elements": [ + { + "id": "7lhy5sLQPzScfW-6lfBPd", + "type": "line", + "x": 1378.335226108522, + "y": 583.3618943263135, + "width": 102.95404895253068, + "height": 98.19215468747154, + "angle": 0, + "strokeColor": "#c2255c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [ + "z7Qf9wPKTcV9QOIwh5C8u" + ], + "frameId": null, + "index": "ay", + "roundness": null, + "seed": 407059785, + "version": 864, + "versionNonce": 1017959239, + "isDeleted": false, + "boundElements": null, + "updated": 1717805906559, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.3759156430037365, + 60.37295808721399 + ], + [ + 54.58954101281756, + 98.19215468747154 + ], + [ + 102.8267215907565, + 60.73117425017426 + ], + [ + 102.95404895253068, + 0.7385631877812102 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + 6.087115954179808, + -0.5751962248369864 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "LaNgWQrBqLmN39HuAoljg", + "type": "text", + "x": 1389.2954603618482, + "y": 599.4595828483938, + "width": 78.62739167691734, + "height": 41.557855548965705, + "angle": 0, + "strokeColor": "#c2255c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [ + "z7Qf9wPKTcV9QOIwh5C8u" + ], + "frameId": null, + "index": "az", + "roundness": null, + "seed": 404390247, + "version": 267, + "versionNonce": 1516294759, + "isDeleted": false, + "boundElements": null, + "updated": 1717805906559, + "link": null, + "locked": false, + "text": "Off-page\nConnector", + "fontSize": 16.623142219586274, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Off-page\nConnector", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "zB7s47_c8YdombAHNxSYv", + "created": 1717805908177, + "name": "Off-page Connector" + }, + { + "status": "published", + "elements": [ + { + "type": "ellipse", + "version": 591, + "versionNonce": 1421588297, + "index": "aU", + "isDeleted": false, + "id": "2Th0AKnbRgX9raD_PTxfu", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 1130.4422480585324, + "y": 572.0977341520572, + "strokeColor": "#c2255c", + "backgroundColor": "transparent", + "width": 127.3416397353642, + "height": 119.42128278810515, + "seed": 1122082343, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "s8ubTI_Ir7Aj5dRqJaXAW" + } + ], + "updated": 1717805270630, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 221, + "versionNonce": 1823991337, + "index": "aV", + "isDeleted": false, + "id": "s8ubTI_Ir7Aj5dRqJaXAW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 1168.1110265947138, + "y": 606.5865761073771, + "strokeColor": "#c2255c", + "backgroundColor": "#e9ecef", + "width": 51.95994567871094, + "height": 50, + "seed": 2113390601, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717805270630, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Conn-\nector", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "2Th0AKnbRgX9raD_PTxfu", + "originalText": "Conn-\nector", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "l1p_mCp6TTfiDjPwoaFVb", + "created": 1717805905850, + "name": "Connector" + }, + { + "status": "published", + "elements": [ + { + "id": "_mgqBsosbDKsaJPnV6GUm", + "type": "line", + "x": 859.977602555591, + "y": 576.6007020879847, + "width": 189.69431320997705, + "height": 92.14981705069717, + "angle": 0, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [ + "oPEowtQlLQ7W75BeCNWVA" + ], + "frameId": null, + "index": "b02", + "roundness": { + "type": 2 + }, + "seed": 328260681, + "version": 3351, + "versionNonce": 8291625, + "isDeleted": false, + "boundElements": null, + "updated": 1717805902145, + "link": null, + "locked": false, + "points": [ + [ + -0.4146249824936916, + -0.2553866241213836 + ], + [ + 71.9452762472197, + -2.7525736277341424 + ], + [ + 139.2031290073635, + -0.6084149661238962 + ], + [ + 172.18904871522625, + 16.492317293055287 + ], + [ + 182.21459657555823, + 42.44909898776862 + ], + [ + 171.9221877693061, + 69.91639259900991 + ], + [ + 140.95789965612278, + 86.00019617494236 + ], + [ + 75.1616541535643, + 89.39724342296302 + ], + [ + -0.3128306307790344, + 87.31240126458516 + ], + [ + -6.345759049713115, + 78.78690165941359 + ], + [ + -7.4797166344188515, + 40.74704621264733 + ], + [ + -6.438257100429156, + 4.4733548714151 + ], + [ + -0.4146249824936916, + -0.2553866241213836 + ] + ], + "lastCommittedPoint": [ + -3.916043340218039, + -1.0156321879867392 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "sfg_Xl9_3KcBVN47Kgn2q", + "type": "text", + "x": 909.6639788403473, + "y": 608.3927966420889, + "width": 54.51995849609375, + "height": 25, + "angle": 0, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [ + "oPEowtQlLQ7W75BeCNWVA" + ], + "frameId": null, + "index": "b03", + "roundness": null, + "seed": 1913210377, + "version": 62, + "versionNonce": 379975689, + "isDeleted": false, + "boundElements": null, + "updated": 1717805902145, + "link": null, + "locked": false, + "text": "Delay", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Delay", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "2ubxnY8d_WiH_BL5_tMbc", + "created": 1717805903780, + "name": "Delay" + }, + { + "status": "published", + "elements": [ + { + "id": "LY-AGR6tKvOVZsu08SLJA", + "type": "line", + "x": 618.729185415738, + "y": 574.4141895154723, + "width": 189.17322239287762, + "height": 88.20047222171354, + "angle": 0, + "strokeColor": "#c2255c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "6vM38QQLsTDACmn_9ejck" + ], + "frameId": null, + "index": "b00", + "roundness": null, + "seed": 54329543, + "version": 679, + "versionNonce": 1472253961, + "isDeleted": false, + "boundElements": null, + "updated": 1717805402857, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 126.05329447414965, + 0.5620759022698394 + ], + [ + 157.6228256829138, + 45.45400190100463 + ], + [ + 123.55145875596122, + 88.16937866116234 + ], + [ + 1.6025142745565972, + 88.20047222171354 + ], + [ + -31.55039670996382, + 43.4185695910827 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + -2.332017041332392, + 3.4131162235705688 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "ApXvjsobKnjz1PeaH9fbq", + "type": "text", + "x": 615.0594787913324, + "y": 597.0024653496696, + "width": 133.99989318847656, + "height": 50, + "angle": 0, + "strokeColor": "#c2255c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "6vM38QQLsTDACmn_9ejck" + ], + "frameId": null, + "index": "b01", + "roundness": null, + "seed": 1111487687, + "version": 127, + "versionNonce": 689694441, + "isDeleted": false, + "boundElements": null, + "updated": 1717805402857, + "link": null, + "locked": false, + "text": "Preparation /\nInitialization", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Preparation /\nInitialization", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "D0V6_gzucPNKaYo0rlADU", + "created": 1717805901232, + "name": "Preparation / Initialization" + }, + { + "status": "published", + "elements": [ + { + "id": "PcPdOASZmABbn4P_js_u1", + "type": "line", + "x": 325.8174914447077, + "y": 575.5990708861827, + "width": 186.07918384898863, + "height": 137.1689186538997, + "angle": 0, + "strokeColor": "#846358", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "pq1vHdsYRVS64hUBstKRe" + ], + "frameId": null, + "index": "av", + "roundness": { + "type": 2 + }, + "seed": 830482247, + "version": 5980, + "versionNonce": 1329646151, + "isDeleted": false, + "boundElements": null, + "updated": 1717805897881, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.4137231248057631, + 80.70410765856475 + ], + [ + 3.537687343585804, + 89.49067391476437 + ], + [ + 34.212999425215436, + 100.23859036851506 + ], + [ + 79.32037154121512, + 105.32451869855666 + ], + [ + 133.28864837634546, + 102.45862074388079 + ], + [ + 169.41599316026085, + 90.50205816741571 + ], + [ + 185.22460029580316, + 77.58061102964352 + ], + [ + 186.07918384898863, + 71.4655388066998 + ], + [ + 182.7938970961902, + 1.1510503641975167 + ], + [ + 181.65322356746285, + -8.904186819871667 + ], + [ + 160.80284252697302, + -21.48425765324725 + ], + [ + 124.1751653366708, + -31.844399955343047 + ], + [ + 66.85648538745887, + -30.430901039527544 + ], + [ + 25.802679767707165, + -19.23584450747441 + ], + [ + 0.990108737970345, + -3.480782822171966 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + 165.27542937901814, + 5.7908338673858 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "XeHrvd1hySngOROXOqsws", + "type": "line", + "x": 329.4014018301715, + "y": 573.7817555724162, + "width": 176.5728110046411, + "height": 20.583490956468065, + "angle": 0, + "strokeColor": "#846358", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "pq1vHdsYRVS64hUBstKRe" + ], + "frameId": null, + "index": "aw", + "roundness": { + "type": 2 + }, + "seed": 758439497, + "version": 1098, + "versionNonce": 90040679, + "isDeleted": false, + "boundElements": null, + "updated": 1717805897881, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 41.77495465263375, + 13.540818396300015 + ], + [ + 72.60534601315386, + 17.609537309811437 + ], + [ + 120.04879176491201, + 17.660943678863863 + ], + [ + 151.77978451501266, + 8.879974268889638 + ], + [ + 176.5728110046411, + -2.9225472776042034 + ] + ], + "lastCommittedPoint": [ + 168.21214375070542, + -2.869206603446287 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "k_kAmvLrH2am0NW-1IwXH", + "type": "text", + "x": 365.69673515659207, + "y": 619.7601563630714, + "width": 98.87992858886719, + "height": 25, + "angle": 0, + "strokeColor": "#846358", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "pq1vHdsYRVS64hUBstKRe" + ], + "frameId": null, + "index": "ax", + "roundness": null, + "seed": 1785268391, + "version": 148, + "versionNonce": 885662855, + "isDeleted": false, + "boundElements": null, + "updated": 1717805897881, + "link": null, + "locked": false, + "text": "Database", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Database", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "uaLlMcw6E6ejv52GWDpoo", + "created": 1717805899229, + "name": "Database" + }, + { + "status": "published", + "elements": [ + { + "type": "ellipse", + "version": 500, + "versionNonce": 958375049, + "index": "aa", + "isDeleted": false, + "id": "vwFOAbBg_p9rIPsYtAkX0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 1361.1874136976912, + "y": 362.3681099059932, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 127.93451698256467, + "height": 120.6590204032604, + "seed": 1823493767, + "groupIds": [ + "_1-JhdtfXiVOUwayr5yQq" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "6S3Ew6Wg_BCchkcublazQ", + "type": "arrow" + }, + { + "id": "wruXLRErwk37BqH1JAtDs", + "type": "arrow" + } + ], + "updated": 1717805209495, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 780, + "versionNonce": 1415328583, + "index": "ab", + "isDeleted": false, + "id": "6S3Ew6Wg_BCchkcublazQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 1424.091075703831, + "y": 361.5205435240584, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 4.640094860475074, + "height": 121.27151954645547, + "seed": 807862633, + "groupIds": [ + "_1-JhdtfXiVOUwayr5yQq" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1717805209593, + "link": null, + "locked": false, + "startBinding": { + "elementId": "vwFOAbBg_p9rIPsYtAkX0", + "focus": -0.019952896255160824, + "gap": 1 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.640094860475074, + 121.27151954645547 + ] + ] + }, + { + "type": "line", + "version": 836, + "versionNonce": 86258279, + "index": "ac", + "isDeleted": false, + "id": "wruXLRErwk37BqH1JAtDs", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 1490.025780767241, + "y": 423.8928211383988, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 127.36505831970203, + "height": 1.9484094365963074, + "seed": 181593511, + "groupIds": [ + "_1-JhdtfXiVOUwayr5yQq" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": null, + "updated": 1717805209593, + "link": null, + "locked": false, + "startBinding": { + "elementId": "vwFOAbBg_p9rIPsYtAkX0", + "focus": -0.03625590054573358, + "gap": 1 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -127.36505831970203, + 1.9484094365963074 + ] + ] + }, + { + "id": "_6y_RrnVDdjKXSGaCm1Mr", + "type": "text", + "x": 1387.708106915793, + "y": 440.34587244709144, + "width": 28.079986572265625, + "height": 25, + "angle": 0, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "_1-JhdtfXiVOUwayr5yQq" + ], + "frameId": null, + "index": "ad", + "roundness": null, + "seed": 1819601993, + "version": 481, + "versionNonce": 96120553, + "isDeleted": false, + "boundElements": [], + "updated": 1717805209495, + "link": null, + "locked": false, + "text": "OR", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "OR", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "n3Fuxs_iH1zKmej-e7dtU", + "created": 1717805896882, + "name": "Or" + }, + { + "status": "published", + "elements": [ + { + "type": "ellipse", + "version": 281, + "versionNonce": 1055977737, + "index": "aW", + "isDeleted": false, + "id": "3S1O6CX7i9203WEuE40eq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 1112.9146365430734, + "y": 358.98774814889, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 127.93451698256467, + "height": 120.6590204032604, + "seed": 25739399, + "groupIds": [ + "J7gGWiN0naYRqxU2D4LML" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1717805209495, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 202, + "versionNonce": 1470097385, + "index": "aX", + "isDeleted": false, + "id": "4_a6mJb9eCANog7sR_6ut", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 1128.9869451411314, + "y": 380.7745082126496, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 94.15767233998804, + "height": 78.09364075737852, + "seed": 2147450057, + "groupIds": [ + "J7gGWiN0naYRqxU2D4LML" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1717805209495, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 94.15767233998804, + 78.09364075737852 + ] + ] + }, + { + "type": "line", + "version": 166, + "versionNonce": 822936265, + "index": "aY", + "isDeleted": false, + "id": "z3NRRR-pDqZfCl_gMpDHG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 70, + "angle": 0, + "x": 1220.22117562468, + "y": 377.402452118897, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 85.4618399097069, + "height": 82.27849976818197, + "seed": 1424059111, + "groupIds": [ + "J7gGWiN0naYRqxU2D4LML" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1717805209495, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -85.4618399097069, + 82.27849976818197 + ] + ] + }, + { + "id": "LF0MxIcu9F1BvLad-RRp0", + "type": "text", + "x": 1156.5032073277193, + "y": 441.07091035248527, + "width": 41.5999755859375, + "height": 25, + "angle": 0, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "J7gGWiN0naYRqxU2D4LML" + ], + "frameId": null, + "index": "aZ", + "roundness": null, + "seed": 826740135, + "version": 198, + "versionNonce": 1473495465, + "isDeleted": false, + "boundElements": null, + "updated": 1717805209495, + "link": null, + "locked": false, + "text": "AND", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "AND", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "0sp_GmpRbdtlu1GZ7omMt", + "created": 1717805894879, + "name": "And" + }, + { + "status": "published", + "elements": [ + { + "type": "line", + "version": 2783, + "versionNonce": 1094955655, + "index": "aC", + "isDeleted": false, + "id": "c6tVey2vizAZfIpTpmyrn", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 863.8821959879904, + "y": 377.72227681403365, + "strokeColor": "#0c8599", + "backgroundColor": "transparent", + "width": 177.38720624737226, + "height": 68.61500430474813, + "seed": 298721961, + "groupIds": [ + "Swcr2QLDws17ylZpGabM-" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717805697298, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 141.1903133856958, + -0.8581778358546758 + ], + [ + 111.65315195251631, + 67.5555367658238 + ], + [ + -36.196892861676474, + 67.75682646889345 + ], + [ + -4.112523736907434, + 0.03207924491035474 + ] + ] + }, + { + "type": "text", + "version": 958, + "versionNonce": 736877929, + "index": "aD", + "isDeleted": false, + "id": "Osu8WLuVtjgbp_R-tvtGT", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 878.1033600594241, + "y": 385.57421875, + "strokeColor": "#0c8599", + "backgroundColor": "#eaddd7", + "width": 72.75994873046875, + "height": 50, + "seed": 1730536841, + "groupIds": [ + "Swcr2QLDws17ylZpGabM-" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717805697298, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Input /\nOutput", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Input /\nOutput", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "RQ9LMOAx9SsgXwRdUWp3m", + "created": 1717805892695, + "name": "Input / Output" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 1230, + "versionNonce": 1971044679, + "index": "a9", + "isDeleted": false, + "id": "0h-ZKB5IgZaejdvIPwoE8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 580.4717245132821, + "y": 378.1846500970294, + "strokeColor": "#12b886", + "backgroundColor": "transparent", + "width": 159.67236311735425, + "height": 78.82038654907808, + "seed": 879538921, + "groupIds": [ + "rFBS465VVdQ92cc9YMl_1" + ], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "RpFMi6YrYnAcn8MY877bl" + } + ], + "updated": 1717805885404, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1446, + "versionNonce": 924975207, + "index": "aA", + "isDeleted": false, + "id": "RpFMi6YrYnAcn8MY877bl", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 602.8039311574084, + "y": 389.08192138632666, + "strokeColor": "#12b886", + "backgroundColor": "transparent", + "width": 115.00794982910156, + "height": 57.025843970483656, + "seed": 268987849, + "groupIds": [ + "rFBS465VVdQ92cc9YMl_1" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717805885404, + "link": null, + "locked": false, + "fontSize": 22.810337588193462, + "fontFamily": 1, + "text": "Predefined\nProcess", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "0h-ZKB5IgZaejdvIPwoE8", + "originalText": "Predefined\nProcess", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 1395, + "versionNonce": 1676893063, + "index": "aB", + "isDeleted": false, + "id": "FgFiX0ABP0EDF6JlAkDxx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 568.8490542276033, + "y": 378.4058291500633, + "strokeColor": "#12b886", + "backgroundColor": "transparent", + "width": 185.46152548156357, + "height": 78.82038654907808, + "seed": 975817897, + "groupIds": [ + "rFBS465VVdQ92cc9YMl_1" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717805885404, + "link": null, + "locked": false + } + ], + "id": "3PmSGt-bzcvgwCEo6QQlC", + "created": 1717805887161, + "name": "Predefined Process" + }, + { + "status": "published", + "elements": [ + { + "type": "line", + "version": 941, + "versionNonce": 1356696073, + "index": "a0", + "isDeleted": false, + "id": "PEJ-QNcFs7f2x-SImP4Fv", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 315.3350889273492, + "y": 413.1976522799753, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 187.62872376742763, + "height": 99.60927275207422, + "seed": 1752890825, + "groupIds": [ + "kB67pLNC54wJH3yWPiTVB" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717805702754, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.6772038882485643, + 53.36744603397734 + ], + [ + 186.88413549416947, + 52.042222902854206 + ], + [ + 187.62872376742763, + -46.241826718096874 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 35, + "versionNonce": 1562294023, + "index": "a1", + "isDeleted": false, + "id": "DXjbJ3yXQCFLHJn6g-Zme", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 344.90625, + "y": 423.91015625, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 130.71990966796875, + "height": 25, + "seed": 257966985, + "groupIds": [ + "kB67pLNC54wJH3yWPiTVB" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717805702754, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Manual Input", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Manual Input", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "gHuOPWI1to2x4GZe-f8a6", + "created": 1717805884530, + "name": "Manaual Input" + }, + { + "status": "published", + "elements": [ + { + "type": "line", + "version": 5440, + "versionNonce": 1954191207, + "index": "aQ", + "isDeleted": false, + "id": "vHRbJ2BpfkCosy9NR5IEM", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1315.8953115682903, + "y": 274.03687989865256, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e9ecef", + "width": 169.06445083090944, + "height": 109.53810588080518, + "seed": 1680859433, + "groupIds": [ + "YmGzfEKNBNtMT7U_3Ik1q", + "fwNdaia9ClByvlnRwspJv" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1717803982727, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.210460059221532, + 1.8477748276831107 + ], + [ + 0.061875343815700035, + -69.08591209470663 + ], + [ + 0.385487287172964, + -82.52264461609295 + ], + [ + 19.12193512037811, + -84.7286753285594 + ], + [ + 149.93796237532024, + -86.31029269671342 + ], + [ + 166.81888186029096, + -85.57758694053635 + ], + [ + 167.88556319979034, + -70.81765155100794 + ], + [ + 168.8539907716879, + 2.7251734065049504 + ], + [ + 167.63134224522514, + 10.144835962980778 + ], + [ + 161.05653820691145, + 6.321368101000758 + ], + [ + 126.67162974648454, + -6.659572950867641 + ], + [ + 85.15472426677707, + 9.034466823305706 + ], + [ + 45.334502532349916, + 23.227813184091758 + ], + [ + 0.25980113762557266, + 6.572512990447478 + ] + ] + }, + { + "type": "line", + "version": 5652, + "versionNonce": 941691527, + "index": "aR", + "isDeleted": false, + "id": "FlxDB4fsEGl-MMNwUWJDA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1308.578191047576, + "y": 280.854699893971, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e9ecef", + "width": 169.06445083090944, + "height": 109.53810588080518, + "seed": 1855634665, + "groupIds": [ + "WMezY1oOBs-zKbPEOyf9H", + "fwNdaia9ClByvlnRwspJv" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1717803982727, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.210460059221532, + 1.8477748276831107 + ], + [ + 0.061875343815700035, + -69.08591209470663 + ], + [ + 0.385487287172964, + -82.52264461609295 + ], + [ + 19.12193512037811, + -84.7286753285594 + ], + [ + 149.93796237532024, + -86.31029269671342 + ], + [ + 166.81888186029096, + -85.57758694053635 + ], + [ + 167.88556319979034, + -70.81765155100794 + ], + [ + 168.8539907716879, + 2.7251734065049504 + ], + [ + 167.63134224522514, + 10.144835962980778 + ], + [ + 161.05653820691145, + 6.321368101000758 + ], + [ + 126.67162974648454, + -6.659572950867641 + ], + [ + 85.15472426677707, + 9.034466823305706 + ], + [ + 45.334502532349916, + 23.227813184091758 + ], + [ + 0.25980113762557266, + 6.572512990447478 + ] + ] + }, + { + "type": "line", + "version": 5779, + "versionNonce": 864758183, + "index": "aS", + "isDeleted": false, + "id": "7dLPYxBfyAwoZfHj-ESpk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1301.512959697645, + "y": 288.16753605061, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e9ecef", + "width": 169.06445083090944, + "height": 109.53810588080518, + "seed": 1659378345, + "groupIds": [ + "NZ75EPZcHVPixfpUW6YqW", + "fwNdaia9ClByvlnRwspJv" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1717803982727, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.210460059221532, + 1.8477748276831107 + ], + [ + 0.061875343815700035, + -69.08591209470663 + ], + [ + 0.385487287172964, + -82.52264461609295 + ], + [ + 19.12193512037811, + -84.7286753285594 + ], + [ + 149.93796237532024, + -86.31029269671342 + ], + [ + 166.81888186029096, + -85.57758694053635 + ], + [ + 167.88556319979034, + -70.81765155100794 + ], + [ + 168.8539907716879, + 2.7251734065049504 + ], + [ + 167.63134224522514, + 10.144835962980778 + ], + [ + 161.05653820691145, + 6.321368101000758 + ], + [ + 126.67162974648454, + -6.659572950867641 + ], + [ + 85.15472426677707, + 9.034466823305706 + ], + [ + 45.334502532349916, + 23.227813184091758 + ], + [ + 0.25980113762557266, + 6.572512990447478 + ] + ] + }, + { + "type": "text", + "version": 324, + "versionNonce": 1783405639, + "index": "aT", + "isDeleted": false, + "id": "RprONJByu_K2I-ck4qX2a", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1337.6528397819125, + "y": 226.30550509373967, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e9ecef", + "width": 92.31991577148438, + "height": 50, + "seed": 1987618505, + "groupIds": [ + "fwNdaia9ClByvlnRwspJv" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717804505382, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Multiple\nDocument", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Multiple\nDocument", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "ZXLnsU-Sx_2nZTLLHK4bm", + "created": 1717805862639, + "name": "Multiple Document" + }, + { + "status": "published", + "elements": [ + { + "type": "line", + "version": 5183, + "versionNonce": 891804871, + "index": "aK", + "isDeleted": false, + "id": "x72qFrvXbZWSSl97nrosm", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1082.1957019064055, + "y": 280.36608085527246, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 169.06445083090944, + "height": 109.53810588080518, + "seed": 1229771881, + "groupIds": [ + "E9X55ngSHFvvwCLu79kUv" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1717805859470, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.210460059221532, + 1.8477748276831107 + ], + [ + 0.061875343815700035, + -69.08591209470663 + ], + [ + 0.385487287172964, + -82.52264461609295 + ], + [ + 19.12193512037811, + -84.7286753285594 + ], + [ + 149.93796237532024, + -86.31029269671342 + ], + [ + 166.81888186029096, + -85.57758694053635 + ], + [ + 167.88556319979034, + -70.81765155100794 + ], + [ + 168.8539907716879, + 2.7251734065049504 + ], + [ + 167.63134224522514, + 10.144835962980778 + ], + [ + 161.05653820691145, + 6.321368101000758 + ], + [ + 126.67162974648454, + -6.659572950867641 + ], + [ + 85.15472426677707, + 9.034466823305706 + ], + [ + 45.334502532349916, + 23.227813184091758 + ], + [ + 0.25980113762557266, + 6.572512990447478 + ] + ] + }, + { + "type": "text", + "version": 83, + "versionNonce": 1908321255, + "index": "aL", + "isDeleted": false, + "id": "s6cEcwidKCnBX3gM0CNwM", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1123.1596518469416, + "y": 231.89641871848858, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 92.31991577148438, + "height": 25, + "seed": 1976372361, + "groupIds": [ + "E9X55ngSHFvvwCLu79kUv" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717805859470, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Document", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Document", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "EnBbCI8QaIPU6EmLx_POi", + "created": 1717805860607, + "name": "Document" + }, + { + "status": "published", + "elements": [ + { + "type": "diamond", + "version": 284, + "versionNonce": 494598825, + "index": "a7", + "isDeleted": false, + "id": "Q_oKTwLVNHjW9DQkiDUeo", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 809.5078125, + "y": 209.966604641718, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 199.9609375, + "height": 81.8671875, + "seed": 269057129, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "8dwg41NA4-0FK5_3BvgL6" + } + ], + "updated": 1717805713870, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 136, + "versionNonce": 393723273, + "index": "a8", + "isDeleted": false, + "id": "8dwg41NA4-0FK5_3BvgL6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 871.1880874633789, + "y": 238.433401516718, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 76.61991882324219, + "height": 25, + "seed": 2033436231, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717805713870, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Decision", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Q_oKTwLVNHjW9DQkiDUeo", + "originalText": "Decision", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "7drGFmKnMeVauqCwWXP1T", + "created": 1717805857819, + "name": "Decision" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 244, + "versionNonce": 359926087, + "index": "a5", + "isDeleted": false, + "id": "TpqBqrxZNxm6So_TyTxMG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 571.77734375, + "y": 214.21484375, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 167.30859374999997, + "height": 75.76562499999999, + "seed": 2040529417, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "VUPw28Em7aayRP1uWLDQd" + } + ], + "updated": 1717803160217, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 17, + "versionNonce": 427702215, + "index": "a6", + "isDeleted": false, + "id": "VUPw28Em7aayRP1uWLDQd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 617.6316833496094, + "y": 239.59765625, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 75.59991455078125, + "height": 25, + "seed": 913241927, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1717804505382, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Process", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "TpqBqrxZNxm6So_TyTxMG", + "originalText": "Process", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "349WwmMYPcxRe7MdAxOTo", + "created": 1717805855612, + "name": "Process" + }, + { + "status": "published", + "elements": [ + { + "id": "aOYKoBFtkBkWinkOTZSbx", + "type": "line", + "x": 341.79193029213565, + "y": 212.02306029032388, + "width": 215.3841423960141, + "height": 86.5470616870208, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [ + "iURYqzDDUS_HlukrruoXt" + ], + "frameId": null, + "index": "b04", + "roundness": { + "type": 2 + }, + "seed": 400689511, + "version": 4797, + "versionNonce": 106805319, + "isDeleted": false, + "boundElements": null, + "updated": 1717805847002, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 71.81544595290487, + -2.4136740813494826 + ], + [ + 138.82660765347347, + -1.2450510810007056 + ], + [ + 168.109388661726, + 16.07292491766025 + ], + [ + 177.73990742133736, + 41.00695112077255 + ], + [ + 167.85304263638204, + 67.39197167864695 + ], + [ + 138.9496417516002, + 83.97677862351297 + ], + [ + 74.80091837009296, + 84.13338760567132 + ], + [ + 2.404470760619684, + 84.10253853545282 + ], + [ + -25.363803827368663, + 71.44096570190817 + ], + [ + -37.64423497467675, + 43.52586048206696 + ], + [ + -27.748183191795828, + 13.57012300661859 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + -3.916043340218039, + -1.0156321879867392 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "bPfE_O7x6N99m2sVqdJ2A", + "type": "text", + "x": 351.0942276581047, + "y": 244.74988065652488, + "width": 116.13620399376578, + "height": 24.01494385587742, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 80, + "groupIds": [ + "iURYqzDDUS_HlukrruoXt" + ], + "frameId": null, + "index": "b05", + "roundness": null, + "seed": 1445651593, + "version": 379, + "versionNonce": 1722619751, + "isDeleted": false, + "boundElements": null, + "updated": 1717805847002, + "link": null, + "locked": false, + "text": "Start / End", + "fontSize": 19.211955084701934, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Start / End", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "id": "tVtn0WO3N-0HC-p2kIXOJ", + "created": 1717805851594, + "name": "Start / End" + } + ] +} diff --git a/apps/web/public/excalidraw/libraries/html-input-elements.excalidrawlib b/apps/web/public/excalidraw/libraries/html-input-elements.excalidrawlib new file mode 100644 index 00000000..4cea620b --- /dev/null +++ b/apps/web/public/excalidraw/libraries/html-input-elements.excalidrawlib @@ -0,0 +1,1165 @@ +{ + "type": "excalidrawlib", + "version": 2, + "source": "https://excalidraw.com", + "libraryItems": [ + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 204, + "versionNonce": 109672520, + "isDeleted": false, + "id": "k22PsWhRhSJGkuTxP5l9Y", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 461.9999999999999, + "y": 304, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef5", + "width": 209.00000000000017, + "height": 81, + "seed": 841086984, + "groupIds": [ + "J9pfMe7SYXD6thqgFLUGM" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839907, + "link": null + }, + { + "type": "text", + "version": 44, + "versionNonce": 455020600, + "isDeleted": false, + "id": "VJQw3xkenASRSundOHTVA", + "fillStyle": "cross-hatch", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 506, + "y": 323, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 124, + "height": 45, + "seed": 1647687432, + "groupIds": [ + "J9pfMe7SYXD6thqgFLUGM" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "fontSize": 36, + "fontFamily": 1, + "text": "Button", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Button" + } + ], + "id": "GAwmmQaDaHWr5CQpMgrSD", + "created": 1647962839907, + "name": "button" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 602, + "versionNonce": 195427656, + "isDeleted": false, + "id": "_SwsgMaRNIZhFwM2sk7dl", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 779.6666666666667, + "y": 297.3333333333333, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef5", + "width": 279.00000000000017, + "height": 81, + "seed": 1036899076, + "groupIds": [ + "Ni8RRCo1KHeV_Vffhq2zB" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "text", + "version": 277, + "versionNonce": 1740200760, + "isDeleted": false, + "id": "xdr_tGOm4BkYrJjnrsLCY", + "fillStyle": "cross-hatch", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 804.6666666666667, + "y": 316.3333333333333, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 120, + "height": 45, + "seed": 222662588, + "groupIds": [ + "Ni8RRCo1KHeV_Vffhq2zB" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "fontSize": 36, + "fontFamily": 1, + "text": "Number", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Number" + }, + { + "type": "rectangle", + "version": 286, + "versionNonce": 260510792, + "isDeleted": false, + "id": "okjTojr7z04fGOoogdW50", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 999.6666666666667, + "y": 297.3333333333333, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 59, + "height": 82.00000000000001, + "seed": 681693700, + "groupIds": [ + "Ni8RRCo1KHeV_Vffhq2zB" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "line", + "version": 252, + "versionNonce": 1599681592, + "isDeleted": false, + "id": "VbGG85jIJC_dkM8EoPEHV", + "fillStyle": "cross-hatch", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1019.6666666666667, + "y": 359.3333333333333, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 27.623260931670757, + "height": 1.1375199034810066, + "seed": 206606140, + "groupIds": [ + "Ni8RRCo1KHeV_Vffhq2zB" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 27.623260931670757, + 1.1375199034810066 + ] + ] + }, + { + "type": "line", + "version": 317, + "versionNonce": 1160596296, + "isDeleted": false, + "id": "N0REOwLI0xUza0eDwOufb", + "fillStyle": "cross-hatch", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1018.6666666666667, + "y": 322.51197164040497, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 23.988219098175673, + "height": 0, + "seed": 104368900, + "groupIds": [ + "yyp2PdgHtBtiPxRoZ01zn", + "Ni8RRCo1KHeV_Vffhq2zB" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 23.988219098175673, + 0 + ] + ] + }, + { + "type": "line", + "version": 361, + "versionNonce": 1737878840, + "isDeleted": false, + "id": "LqfOKkvxCE6dipUTr-0lF", + "fillStyle": "cross-hatch", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 4.71238898038469, + "x": 1016.9028222923077, + "y": 322.3893295528446, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 23.988219098175673, + "height": 0, + "seed": 836099644, + "groupIds": [ + "yyp2PdgHtBtiPxRoZ01zn", + "Ni8RRCo1KHeV_Vffhq2zB" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 23.988219098175673, + 0 + ] + ] + } + ], + "id": "TckAMjJwM4-6hr7cyVcnU", + "created": 1647962839907, + "name": "number" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 459, + "versionNonce": 1944770872, + "isDeleted": false, + "id": "I5gimYSn5ACZBi5Me3qos", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1168.6666666666665, + "y": 297.33333333333354, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef5", + "width": 389.0000000000001, + "height": 81, + "seed": 64666884, + "groupIds": [ + "gEnPYnMfu3qbbL98khJvF", + "ojKlDqNrXA99rdHd96oUr" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "text", + "version": 248, + "versionNonce": 934573640, + "isDeleted": false, + "id": "urfeovHdD3MElco_hPkyt", + "fillStyle": "cross-hatch", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1192.6666666666665, + "y": 314.6666666666668, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 190, + "height": 45, + "seed": 1057258628, + "groupIds": [ + "gEnPYnMfu3qbbL98khJvF", + "ojKlDqNrXA99rdHd96oUr" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "fontSize": 36, + "fontFamily": 1, + "text": "dd/mm/yyyy", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "dd/mm/yyyy" + }, + { + "type": "rectangle", + "version": 286, + "versionNonce": 615182904, + "isDeleted": false, + "id": "-8urCT5QIYTc-HGKdGio8", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1473.4070593841043, + "y": 318.4919723987164, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "width": 60.25647156923948, + "height": 46.31755141080755, + "seed": 1524189704, + "groupIds": [ + "Yy13x-nnF3ol_dv6cRiHU", + "ojKlDqNrXA99rdHd96oUr" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "rectangle", + "version": 343, + "versionNonce": 90074440, + "isDeleted": false, + "id": "OkK25Kmt2YYs0qERXCHQZ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1473.2516650016655, + "y": 317.8851919242895, + "strokeColor": "#343a40", + "backgroundColor": "#343a40", + "width": 60.46875082115287, + "height": 9.50335950515257, + "seed": 1079972872, + "groupIds": [ + "Yy13x-nnF3ol_dv6cRiHU", + "ojKlDqNrXA99rdHd96oUr" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "line", + "version": 319, + "versionNonce": 1476389688, + "isDeleted": false, + "id": "1l1UaOGfbA9pMj0jE5p1u", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1479.140610823897, + "y": 312.9308585962113, + "strokeColor": "#343a40", + "backgroundColor": "#343a40", + "width": 0, + "height": 8.767250802760055, + "seed": 807306360, + "groupIds": [ + "Yy13x-nnF3ol_dv6cRiHU", + "ojKlDqNrXA99rdHd96oUr" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 8.767250802760055 + ] + ] + }, + { + "type": "line", + "version": 398, + "versionNonce": 414286920, + "isDeleted": false, + "id": "3sNWHB0HaEu-IjlNBo95x", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1526.6834868861401, + "y": 312.65792540792614, + "strokeColor": "#343a40", + "backgroundColor": "#343a40", + "width": 0, + "height": 6.782212885153945, + "seed": 1054495864, + "groupIds": [ + "Yy13x-nnF3ol_dv6cRiHU", + "ojKlDqNrXA99rdHd96oUr" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 6.782212885153945 + ] + ] + } + ], + "id": "l2jzmGjj8xyTmiz_DgHDu", + "created": 1647962839907, + "name": "date" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 381, + "versionNonce": 1199582536, + "isDeleted": false, + "id": "-402Xjsn3gNeduUlZg2v3", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 471.13008971704664, + "y": 482.51725327812335, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 91.84807256235844, + "height": 91.84807256235844, + "seed": 1651987076, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "rectangle", + "version": 400, + "versionNonce": 797879096, + "isDeleted": false, + "id": "TQ8OBbwND54FxSCKhbXQw", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 624.1459627329187, + "y": 482.57394262052696, + "strokeColor": "#000000", + "backgroundColor": "#12b886", + "width": 96.84807256235842, + "height": 96.84807256235842, + "seed": 1902078852, + "groupIds": [ + "ssKI2tVi4gFqDjyI136lr" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "line", + "version": 501, + "versionNonce": 1450099784, + "isDeleted": false, + "id": "3MQgXEcewu0rf4Z7c7MYO", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 5.497787143782138, + "x": 651.3873436280293, + "y": 506.58664173169706, + "strokeColor": "#fff", + "backgroundColor": "#12b886", + "width": 48.536273629427996, + "height": 50.882468429031995, + "seed": 619666180, + "groupIds": [ + "ssKI2tVi4gFqDjyI136lr" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 48.536273629427996, + 50.882468429031995 + ] + ] + }, + { + "type": "rectangle", + "version": 420, + "versionNonce": 1760430152, + "isDeleted": false, + "id": "S2B2-2uaJBmW9t_tq6P0Z", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 782.0031055900616, + "y": 483.7644188110031, + "strokeColor": "#000000", + "backgroundColor": "#12b886", + "width": 98.51473922902507, + "height": 98.51473922902507, + "seed": 376259388, + "groupIds": [ + "jOcdqEWxTWwsfsRXK-7uU" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "line", + "version": 421, + "versionNonce": 1795915832, + "isDeleted": false, + "id": "0lk8cM1etWjomFj_dh7gO", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 802.4888362033943, + "y": 502.75090059325555, + "strokeColor": "#fff", + "backgroundColor": "#12b886", + "width": 60.38000146295094, + "height": 61.96894886987046, + "seed": 1359350020, + "groupIds": [ + "jOcdqEWxTWwsfsRXK-7uU" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 60.38000146295094, + 61.96894886987046 + ] + ] + }, + { + "type": "line", + "version": 465, + "versionNonce": 1033224008, + "isDeleted": false, + "id": "1YAABfniIJ9raJCUv2zJo", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 4.71238898038469, + "x": 804.082806226778, + "y": 501.3017811065811, + "strokeColor": "#fff", + "backgroundColor": "#12b886", + "width": 60.38000146295094, + "height": 61.96894886987046, + "seed": 2109675836, + "groupIds": [ + "jOcdqEWxTWwsfsRXK-7uU" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 60.38000146295094, + 61.96894886987046 + ] + ] + } + ], + "id": "5WS9ADBFgotstBrMOou5f", + "created": 1647962839907, + "name": "chekbox" + }, + { + "status": "published", + "elements": [ + { + "type": "line", + "version": 1348, + "versionNonce": 1876964152, + "isDeleted": false, + "id": "n3WYiFQfwBLt2YAdJ_MU9", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1083.071891717442, + "y": 486.924992676318, + "strokeColor": "#000000", + "backgroundColor": "#12b886", + "width": 185.52583445234677, + "height": 94.06284022471463, + "seed": 1512825988, + "groupIds": [ + "QnrtcvWK88gC267KzOUwo" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -54.0885741214574, + 3.26847156143346 + ], + [ + -89.67460071146283, + 23.45332181394217 + ], + [ + -92.22253205774396, + 63.0437880022644 + ], + [ + -60.144768972084975, + 91.86290744298051 + ], + [ + 2.5001724247550996, + 94.06284022471463 + ], + [ + 59.384541551166066, + 91.45434849780133 + ], + [ + 88.5062786635112, + 71.24639451701566 + ], + [ + 93.3033023946028, + 31.081907444785514 + ], + [ + 64.52116000805307, + 1.822801447722543 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 774, + "versionNonce": 601472072, + "isDeleted": false, + "id": "55VhtrODf-39BZeQmSumD", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1085.1854189506882, + "y": 486.1091852034925, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 93.04790406275514, + "height": 94.15561720635927, + "seed": 264549380, + "groupIds": [ + "QnrtcvWK88gC267KzOUwo" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + } + ], + "id": "BWDRsMWQdOyfQLv2O94Z-", + "created": 1647962839907, + "name": "toggle" + }, + { + "status": "published", + "elements": [ + { + "type": "ellipse", + "version": 799, + "versionNonce": 1616084792, + "isDeleted": false, + "id": "_bKueD4t26KAoK0x36dto", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1289.887799903069, + "y": 480.5734709177783, + "strokeColor": "#000000", + "backgroundColor": "#868e96", + "width": 93.04790406275514, + "height": 94.15561720635927, + "seed": 801562940, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "ellipse", + "version": 855, + "versionNonce": 323833400, + "isDeleted": false, + "id": "JywcmKH07KzwKJ7WrUY6L", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1447.0306570459259, + "y": 482.47823282254024, + "strokeColor": "#000000", + "backgroundColor": "#12b886", + "width": 93.04790406275514, + "height": 94.15561720635927, + "seed": 1129273788, + "groupIds": [ + "cmZUI6etnFxQxV4ffbPxo" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "ellipse", + "version": 898, + "versionNonce": 228577608, + "isDeleted": false, + "id": "IEmC0Lz-6-MWX4vTCRvLH", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1465.6020856173543, + "y": 501.0496613939688, + "strokeColor": "#fff", + "backgroundColor": "#fff", + "width": 56.342021709813956, + "height": 57.01276006350216, + "seed": 808763964, + "groupIds": [ + "cmZUI6etnFxQxV4ffbPxo" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + } + ], + "id": "B0oUjXnDcywKHZei6xk4i", + "created": 1647962839907, + "name": "switch" + }, + { + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 477, + "versionNonce": 1784890936, + "isDeleted": false, + "id": "rsDBcsbUrznjIROoJWVY-", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 466.33333333333326, + "y": 665.3333333333331, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef5", + "width": 280.0000000000002, + "height": 81, + "seed": 1791643836, + "groupIds": [ + "r-CGb7V61ePHRBq-msY10" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "text", + "version": 242, + "versionNonce": 1426413896, + "isDeleted": false, + "id": "Nzq7E6nU6lBMiQSl2OFNv", + "fillStyle": "cross-hatch", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 491.33333333333337, + "y": 684.3333333333331, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 109, + "height": 45, + "seed": 993581372, + "groupIds": [ + "r-CGb7V61ePHRBq-msY10" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "fontSize": 36, + "fontFamily": 1, + "text": "Select", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Select" + }, + { + "type": "line", + "version": 277, + "versionNonce": 1804451640, + "isDeleted": false, + "id": "XgNuRGYD3wVe9WRcTjGtr", + "fillStyle": "cross-hatch", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 691.3333333333336, + "y": 694.333333333333, + "strokeColor": "#fff", + "backgroundColor": "transparent", + "width": 38, + "height": 21, + "seed": 2109886908, + "groupIds": [ + "r-CGb7V61ePHRBq-msY10" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 16, + 21 + ], + [ + 38, + 1 + ] + ] + } + ], + "id": "bv6AOqfP6rvf3SMgBk4vS", + "created": 1647962839907, + "name": "select" + }, + { + "status": "published", + "elements": [ + { + "type": "ellipse", + "version": 949, + "versionNonce": 1524225848, + "isDeleted": false, + "id": "-XFi9OjODvOYo311gT6Nq", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 866.2806570459268, + "y": 641.1568042511118, + "strokeColor": "#000000", + "backgroundColor": "#4c6ef5", + "width": 121.61933263418368, + "height": 123.06718183220954, + "seed": 214961156, + "groupIds": [ + "tRqxGU5SyRtDdgEKm5CJx", + "oAsLckZqtdTyJ2geE6DUb" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null + }, + { + "type": "line", + "version": 542, + "versionNonce": 968644680, + "isDeleted": false, + "id": "lFJJLa_cebMd2km50s4xm", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 5.497787143782138, + "x": 901.340088985371, + "y": 675.7137286005344, + "strokeColor": "#fff", + "backgroundColor": "#12b886", + "width": 52.59124040853669, + "height": 53.84770758771799, + "seed": 1746541828, + "groupIds": [ + "oAsLckZqtdTyJ2geE6DUb" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 52.59124040853669, + 53.84770758771799 + ] + ] + }, + { + "type": "line", + "version": 585, + "versionNonce": 285880376, + "isDeleted": false, + "id": "jDM-ygnwOVf9BUNXN7z0j", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 3.9269908169872405, + "x": 899.3518713892238, + "y": 672.7988431503037, + "strokeColor": "#fff", + "backgroundColor": "#12b886", + "width": 55.26947763608382, + "height": 57.18171102206827, + "seed": 554732932, + "groupIds": [ + "oAsLckZqtdTyJ2geE6DUb" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1647962839908, + "link": null, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 55.26947763608382, + 57.18171102206827 + ] + ] + } + ], + "id": "ou1D2OZuKQ7llStNyaYRC", + "created": 1647962839907, + "name": "action button" + } + ] +} diff --git a/apps/web/public/excalidraw/libraries/universal-ui-kit.excalidrawlib b/apps/web/public/excalidraw/libraries/universal-ui-kit.excalidrawlib new file mode 100644 index 00000000..a1b77e69 --- /dev/null +++ b/apps/web/public/excalidraw/libraries/universal-ui-kit.excalidrawlib @@ -0,0 +1,5533 @@ +{ + "type": "excalidrawlib", + "version": 2, + "source": "https://excalidraw.com", + "libraryItems": [ + { + "id": "efwT0ETYP68OJzEX_E8IU", + "status": "published", + "elements": [ + { + "id": "KG8neKgjYI7jiqrqnoIQC", + "type": "line", + "x": 1232.0583558339324, + "y": 711.6815886369704, + "width": 91.16819396087658, + "height": 40.05388963584642, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1381540613, + "version": 654, + "versionNonce": 82428267, + "isDeleted": false, + "boundElements": null, + "updated": 1670481287442, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 79.15616840522046, + 0.5715874229521618 + ], + [ + 79.46416906049366, + 28.293577436133948 + ], + [ + 25.38405344658713, + 28.007783724657884 + ], + [ + 29.749396550933398, + 40.05388963584642 + ], + [ + 7.35734824195174, + 27.876909864399977 + ], + [ + -11.396024245109626, + 28.00778372465779 + ], + [ + -11.70402490038292, + 0 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + -2.5149846609897395, + -2.5149846609897395 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "IJw_SvUiIOp5t5Sx1srTe", + "type": "text", + "x": 1236.4888603357447, + "y": 716.4424527253498, + "width": 55.58329984584186, + "height": 19.571584452761225, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1755160261, + "version": 64, + "versionNonce": 717108907, + "isDeleted": false, + "boundElements": null, + "updated": 1670481297548, + "link": null, + "locked": false, + "text": "Tooltip", + "fontSize": 15.657267562208974, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 13.571584452761225, + "containerId": null, + "originalText": "Tooltip" + } + ], + "created": 1670481302096, + "name": "Tooltip" + }, + { + "id": "svD0u-QcyVOjF6OyDJ7qc", + "status": "published", + "elements": [ + { + "id": "Ayh5aB2Ig5Md0qkxp9sHo", + "type": "ellipse", + "x": 1225.0657409542778, + "y": 760.6889061008446, + "width": 111.25692343761082, + "height": 108.18776692898712, + "angle": 0, + "strokeColor": "#868e96", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "OMcN4WFF7QMYq0irrOX5m" + ], + "strokeSharpness": "sharp", + "seed": 96437541, + "version": 120, + "versionNonce": 1733177413, + "isDeleted": false, + "boundElements": null, + "updated": 1670481201060, + "link": null, + "locked": false + }, + { + "id": "qC-Ll8MOu6htDgJYKA4QE", + "type": "text", + "x": 1250.1893111120598, + "y": 798.9589646030448, + "width": 62, + "height": 34, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "OMcN4WFF7QMYq0irrOX5m" + ], + "strokeSharpness": "sharp", + "seed": 2010811941, + "version": 50, + "versionNonce": 538944139, + "isDeleted": false, + "boundElements": null, + "updated": 1670481201060, + "link": null, + "locked": false, + "text": "65%", + "fontSize": 27.36597562069708, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 24, + "containerId": null, + "originalText": "65%" + }, + { + "id": "5xv8rRuZBk1deML6msWc1", + "type": "line", + "x": 1289.6573643933048, + "y": 761.1937112170393, + "width": 65.04920335369343, + "height": 108.29620118774267, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "OMcN4WFF7QMYq0irrOX5m" + ], + "strokeSharpness": "round", + "seed": 828012139, + "version": 212, + "versionNonce": 755621797, + "isDeleted": false, + "boundElements": null, + "updated": 1670481201060, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -26.09116398252536, + 0.7148264104802138 + ], + [ + -55.04163360697157, + 23.231858340604845 + ], + [ + -65.04920335369343, + 45.034063860249375 + ], + [ + -63.61955053273323, + 67.19368258513407 + ], + [ + -53.611980786011145, + 89.35330131001865 + ], + [ + -39.67286578164817, + 100.07569746722095 + ], + [ + -24.66151116156516, + 106.5091351615423 + ], + [ + -7.863090515281556, + 108.29620118774267 + ] + ], + "lastCommittedPoint": [ + -7.863090515281556, + 108.29620118774267 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + } + ], + "created": 1670481202703, + "name": "Circle Progress Bar" + }, + { + "id": "EGtz9OOXb74RpkC0jHi9p", + "status": "published", + "elements": [ + { + "id": "gz0JGFkllMB9PVPBkSVkd", + "type": "ellipse", + "x": 1226.6003192085896, + "y": 732.2992083960748, + "width": 133.508308125133, + "height": 132.74101899797722, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "PMLwNXjxG2YXQX2Is_MbA" + ], + "strokeSharpness": "sharp", + "seed": 650245605, + "version": 45, + "versionNonce": 45659371, + "isDeleted": false, + "boundElements": null, + "updated": 1670480970116, + "link": null, + "locked": false + }, + { + "id": "z8kKi5XiJBhKFVrSN-d0p", + "type": "ellipse", + "x": 1272.6376668379457, + "y": 754.7141474478174, + "width": 37.597167230640935, + "height": 39.13174548495283, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "PMLwNXjxG2YXQX2Is_MbA" + ], + "strokeSharpness": "sharp", + "seed": 1628004715, + "version": 33, + "versionNonce": 705274693, + "isDeleted": false, + "boundElements": null, + "updated": 1670480970116, + "link": null, + "locked": false + }, + { + "id": "PHbQaPdvM5Afa80LxNsuy", + "type": "line", + "x": 1254.222727786203, + "y": 830.5122166720349, + "width": 77.49620184274954, + "height": 39.13174548495283, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "PMLwNXjxG2YXQX2Is_MbA" + ], + "strokeSharpness": "round", + "seed": 818769573, + "version": 148, + "versionNonce": 256434059, + "isDeleted": false, + "boundElements": null, + "updated": 1670480970116, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 37.597167230640935, + -23.7859629418341 + ], + [ + 77.49620184274954, + -0.7672891271560047 + ], + [ + 39.13174548495272, + 15.34578254311873 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + -3.0691565086237915, + 3.8364456357796826 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + } + ], + "created": 1670480971171, + "name": "User Icon" + }, + { + "id": "1TOJ67S86-pYl3x3LO-bV", + "status": "published", + "elements": [ + { + "type": "line", + "version": 276, + "versionNonce": 977636613, + "isDeleted": false, + "id": "iiZe9dq-kzAhxLKZTIXUM", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1366.9751567144779, + "y": 666.6293669043139, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 85.39136149270017, + "height": 42.07978670334516, + "seed": 1566526565, + "groupIds": [ + "mzJ43xI_t4SUAc24TnsK-" + ], + "strokeSharpness": "round", + "boundElements": null, + "updated": 1670480753825, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 72.81514702846026, + -0.9147779718117884 + ], + [ + 74.52844460560048, + 40.25023075972158 + ], + [ + -6.8531903085609756, + 41.16500873153337 + ], + [ + -10.862916887099686, + 5.783139948933467 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 134, + "versionNonce": 543781323, + "isDeleted": false, + "id": "vgIST6Us1mjPGwJozPW4I", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1351.6343079015057, + "y": 662.2653655628993, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 51.80388556015671, + "height": 49.10064195085418, + "seed": 609605739, + "groupIds": [ + "mzJ43xI_t4SUAc24TnsK-" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480753825, + "link": null, + "locked": false + } + ], + "created": 1670480761552, + "name": "Toggle Unchecked" + }, + { + "id": "WKn5Dggu6WveHBhW2xMQB", + "status": "published", + "elements": [ + { + "id": "V7FluXy8pgwwnZGK1kXtL", + "type": "ellipse", + "x": 1269.1667704967917, + "y": 459.86472125451473, + "width": 354.13953231335654, + "height": 354.13953231335654, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "N0C5Kbfe0nyicbFeH54gp" + ], + "strokeSharpness": "sharp", + "seed": 2015934763, + "version": 52, + "versionNonce": 1290905067, + "isDeleted": false, + "boundElements": null, + "updated": 1670480550159, + "link": null, + "locked": false + }, + { + "id": "TnuLxzt45uL0W5ZBDzYge", + "type": "line", + "x": 1387.6161703831704, + "y": 482.8294008243229, + "width": 195.80411001625862, + "height": 314.2535099026371, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#868e96", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "N0C5Kbfe0nyicbFeH54gp" + ], + "strokeSharpness": "sharp", + "seed": 1779669355, + "version": 249, + "versionNonce": 1892958789, + "isDeleted": false, + "boundElements": null, + "updated": 1670480550159, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 76.1460427841007, + 163.17009168021548 + ], + [ + -36.26002037338117, + 303.3755037906228 + ], + [ + -80.98071216721792, + 268.32415076302095 + ], + [ + -103.94539173702606, + 222.39479162340479 + ], + [ + -119.65806723215792, + 160.75275698865676 + ], + [ + -116.03206519481978, + 105.15405908280559 + ], + [ + -68.8940387094242, + 30.21668364448442 + ], + [ + -39.886022410719306, + 7.252004074676279 + ], + [ + -4.834669383117443, + -10.878006112014305 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + 0, + 6.043336728896861 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + } + ], + "created": 1670480551630, + "name": "Pie chart" + }, + { + "id": "QgODomeA5ZOdzyh1Us7Ta", + "status": "published", + "elements": [ + { + "id": "_pREWmz2mIL5hqJZb4eyn", + "type": "line", + "x": 1251.0367603101013, + "y": 509.4200824314691, + "width": 1.208667345779304, + "height": 361.39153638803276, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "cPFxXXqWyStXkMqXs51a2" + ], + "strokeSharpness": "round", + "seed": 1134180997, + "version": 135, + "versionNonce": 1086824331, + "isDeleted": false, + "boundElements": null, + "updated": 1670480509927, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.208667345779304, + 361.39153638803276 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "IOTqq0ireBufePLVtXFc5", + "type": "line", + "x": 1249.8280929643217, + "y": 865.9769494363844, + "width": 448.4155852841477, + "height": 0, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "cPFxXXqWyStXkMqXs51a2" + ], + "strokeSharpness": "round", + "seed": 740241131, + "version": 85, + "versionNonce": 1219244709, + "isDeleted": false, + "boundElements": null, + "updated": 1670480509927, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 448.4155852841477, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "OldHkNBURLU1VIyXCymne", + "type": "line", + "x": 1253.4540950016599, + "y": 823.6735923341064, + "width": 377.1042118831647, + "height": 270.7414854545798, + "angle": 0, + "strokeColor": "#364fc7", + "backgroundColor": "#868e96", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "cPFxXXqWyStXkMqXs51a2" + ], + "strokeSharpness": "sharp", + "seed": 1629572741, + "version": 115, + "versionNonce": 224491051, + "isDeleted": false, + "boundElements": null, + "updated": 1670480509927, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 113.61473050326117, + -154.70942025976 + ], + [ + 195.80411001625862, + -74.93737543832128 + ], + [ + 377.1042118831647, + -270.7414854545798 + ] + ], + "lastCommittedPoint": [ + 377.1042118831647, + -270.7414854545798 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "gZZh4i6_nKwSiqu8hIUi3", + "type": "line", + "x": 1249.828092964322, + "y": 765.6575597366964, + "width": 395.23422206985515, + "height": 166.79609371755362, + "angle": 0, + "strokeColor": "#e67700", + "backgroundColor": "#868e96", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "cPFxXXqWyStXkMqXs51a2" + ], + "strokeSharpness": "sharp", + "seed": 2015346629, + "version": 128, + "versionNonce": 205035013, + "isDeleted": false, + "boundElements": null, + "updated": 1670480509927, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 123.28406926949606, + -38.67735506494 + ], + [ + 229.64679569808095, + -166.79609371755362 + ], + [ + 395.23422206985515, + -107.57139377436431 + ] + ], + "lastCommittedPoint": [ + 395.23422206985515, + -107.57139377436431 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + } + ], + "created": 1670480511509, + "name": "Line chart" + }, + { + "id": "f4tLnOQbKZB5qr3bBDleA", + "status": "published", + "elements": [ + { + "id": "_pREWmz2mIL5hqJZb4eyn", + "type": "line", + "x": 1251.0367603101013, + "y": 509.4200824314691, + "width": 1.208667345779304, + "height": 361.39153638803276, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "W_JT33kCqmb3NrvX82yGi" + ], + "strokeSharpness": "round", + "seed": 1134180997, + "version": 133, + "versionNonce": 585081637, + "isDeleted": false, + "boundElements": null, + "updated": 1670480431166, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.208667345779304, + 361.39153638803276 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "IOTqq0ireBufePLVtXFc5", + "type": "line", + "x": 1249.8280929643217, + "y": 865.9769494363844, + "width": 448.4155852841477, + "height": 0, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "W_JT33kCqmb3NrvX82yGi" + ], + "strokeSharpness": "round", + "seed": 740241131, + "version": 83, + "versionNonce": 918244779, + "isDeleted": false, + "boundElements": null, + "updated": 1670480431166, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 448.4155852841477, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "_Ox-KDABPjBjqwKoXWxcD", + "type": "rectangle", + "x": 1252.2454276558806, + "y": 576.5581089168646, + "width": 183.71743655846467, + "height": 39.886022410719306, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#868e96", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "W_JT33kCqmb3NrvX82yGi" + ], + "strokeSharpness": "sharp", + "seed": 791085157, + "version": 147, + "versionNonce": 531232389, + "isDeleted": false, + "boundElements": null, + "updated": 1670480431166, + "link": null, + "locked": false + }, + { + "id": "jZ370aU59Lg8ITq_xSumM", + "type": "rectangle", + "x": 1253.4540950016599, + "y": 638.2001435516128, + "width": 258.65481199678595, + "height": 41.09468975649861, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "W_JT33kCqmb3NrvX82yGi" + ], + "strokeSharpness": "sharp", + "seed": 534368395, + "version": 202, + "versionNonce": 16408651, + "isDeleted": false, + "boundElements": null, + "updated": 1670480431166, + "link": null, + "locked": false + }, + { + "id": "QIeO1lL_xrQ-khl9k2Zhz", + "type": "rectangle", + "x": 1253.6627623474392, + "y": 727.3975393633151, + "width": 326.34018336043073, + "height": 41.09468975649861, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#868e96", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "W_JT33kCqmb3NrvX82yGi" + ], + "strokeSharpness": "sharp", + "seed": 815129739, + "version": 253, + "versionNonce": 1788009957, + "isDeleted": false, + "boundElements": null, + "updated": 1670480431166, + "link": null, + "locked": false + }, + { + "id": "-7EpOYY_SMHotwrlRvTUZ", + "type": "rectangle", + "x": 1253.2887643847773, + "y": 792.039573998063, + "width": 277.99348952925584, + "height": 37.468687719160464, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#ced4da", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "W_JT33kCqmb3NrvX82yGi" + ], + "strokeSharpness": "sharp", + "seed": 1767177419, + "version": 307, + "versionNonce": 1660821227, + "isDeleted": false, + "boundElements": null, + "updated": 1670480431166, + "link": null, + "locked": false + } + ], + "created": 1670480432680, + "name": "Bar Chart" + }, + { + "id": "84fkoxzqE6lYcScgkQyqT", + "status": "published", + "elements": [ + { + "id": "M6UQFq-DJ88ICC--Ay9uc", + "type": "rectangle", + "x": 1263.1746839620596, + "y": 522.6713486629604, + "width": 297.5749965150734, + "height": 292.3887759508462, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "seed": 259665643, + "version": 89, + "versionNonce": 2080883723, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203922, + "link": null, + "locked": false + }, + { + "id": "bGL6KammPQsdgPOWMRmOx", + "type": "line", + "x": 1264.916664669991, + "y": 564.350517806695, + "width": 292.0622768105809, + "height": 1.9677215053246755, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "seed": 1401779525, + "version": 105, + "versionNonce": 2085525029, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203922, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 292.0622768105809, + -1.9677215053246755 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "version": 370, + "versionNonce": 640223915, + "isDeleted": false, + "id": "zm60He4vBRj4iXyHOidon", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.5375575269711668, + "x": 1282.1296686979365, + "y": 535.8536476858105, + "strokeColor": "#000000", + "backgroundColor": "#000", + "width": 21.95411118135862, + "height": 15.781689674230238, + "seed": 806865637, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203922, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 10.977055590679315, + 13.80897846495146 + ], + [ + 21.95411118135862, + -1.9727112092787782 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 460, + "versionNonce": 1958906245, + "isDeleted": false, + "id": "EVTiFKS7UqBav4B3a-07M", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 4.68150589574342, + "x": 1522.0214444794117, + "y": 535.9219162165431, + "strokeColor": "#000000", + "backgroundColor": "#000", + "width": 21.95411118135862, + "height": 15.781689674230236, + "seed": 1035346245, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203922, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + -0.46089649349504347 + ], + [ + 10.977055590679315, + 13.348081971456416 + ], + [ + 21.95411118135862, + -2.4336077027738217 + ], + [ + 0, + -0.46089649349504347 + ] + ] + }, + { + "id": "3oLMRGYhNIYPjYMlcFfHM", + "type": "text", + "x": 1338.6382457183336, + "y": 530.721257836406, + "width": 158, + "height": 25, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "seed": 900358123, + "version": 34, + "versionNonce": 363363659, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203922, + "link": null, + "locked": false, + "text": "December 2022", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "December 2022" + }, + { + "id": "LvYW8Z0Ke5UyYRlBfEcpm", + "type": "text", + "x": 1290.0152503795039, + "y": 571.7315951113401, + "width": 13, + "height": 25, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "seed": 2126499275, + "version": 13, + "versionNonce": 1428230373, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203922, + "link": null, + "locked": false, + "text": "s", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "s" + }, + { + "id": "jMYOTvn8pEYKIw-6neFQp", + "type": "line", + "x": 1309.0365582643087, + "y": 563.6780087318599, + "width": 3.279535842207679, + "height": 248.58881683934385, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "seed": 1213881349, + "version": 49, + "versionNonce": 1440783339, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203922, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 3.279535842207679, + 248.58881683934385 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "A_E-YxfYQZKIvxCov45Hr", + "type": "line", + "x": 1349.0468955392425, + "y": 563.0221015634183, + "width": 1.3118143368833444, + "height": 247.9329096709023, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "seed": 897129733, + "version": 52, + "versionNonce": 1794616389, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.3118143368833444, + 247.9329096709023 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "3tnQs8W3vaOk8yJwdkgDb", + "type": "line", + "x": 1389.0572328141766, + "y": 564.3339159003015, + "width": 1.9677215053247892, + "height": 251.86835268155153, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "seed": 443113323, + "version": 53, + "versionNonce": 584836747, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.9677215053247892, + 251.86835268155153 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "ujfnIT1tYW3_O1svjjhrA", + "type": "line", + "x": 1427.788034246903, + "y": 562.0221015634183, + "width": 3.9354430106491236, + "height": 252.5242598499931, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "seed": 80801707, + "version": 60, + "versionNonce": 1982061477, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 3.9354430106491236, + 252.5242598499931 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "A2fk1HBI_qi_zk8gv0z7i", + "type": "line", + "x": 1468.1424643533953, + "y": 562.7102872265352, + "width": 3.935443010649351, + "height": 247.9329096709024, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "seed": 2003038507, + "version": 69, + "versionNonce": 1398070571, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 3.935443010649351, + 247.9329096709024 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "Xk-9MpexuDEwgHGAF4l2f", + "type": "line", + "x": 1514.0023797648232, + "y": 563.0221015634183, + "width": 3.279535842207679, + "height": 247.9329096709023, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "seed": 1767212331, + "version": 74, + "versionNonce": 1590894341, + "isDeleted": false, + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 3.279535842207679, + 247.9329096709023 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "type": "line", + "version": 234, + "versionNonce": 45481931, + "isDeleted": false, + "id": "PEo23-FIwpvF11iLGz1Jd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1558.5630005575767, + "y": 603.7106746548767, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 291.2227827880438, + "height": 2.623628673766234, + "seed": 719684965, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -291.2227827880438, + 2.623628673766234 + ] + ] + }, + { + "type": "line", + "version": 312, + "versionNonce": 730003045, + "isDeleted": false, + "id": "nYpR-qj49yimGBBd2tO46", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1559.8600918607117, + "y": 642.3965956531259, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 288.59915411427755, + "height": 2.623628673766234, + "seed": 1626187397, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -288.59915411427755, + 2.623628673766234 + ] + ] + }, + { + "type": "line", + "version": 361, + "versionNonce": 2065288811, + "isDeleted": false, + "id": "4vmT3vyGlYrdpBc_0ZmFV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1556.0392065122326, + "y": 682.6721459193508, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 288.5991541142778, + "height": 1.9677215053246755, + "seed": 332205003, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -288.5991541142778, + 1.9677215053246755 + ] + ] + }, + { + "type": "line", + "version": 415, + "versionNonce": 922804677, + "isDeleted": false, + "id": "eNnSYgc-DOFdeRTYY3Ajb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1558.4011967230454, + "y": 723.810309371515, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 289.2550612827192, + "height": 1.9677215053246755, + "seed": 1987857253, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -289.2550612827192, + 1.9677215053246755 + ] + ] + }, + { + "type": "line", + "version": 461, + "versionNonce": 1598736651, + "isDeleted": false, + "id": "8cJOm8S0GTX7rKIdCLrTS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1559.992018180589, + "y": 767.2620688861755, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 289.91096845116067, + "height": 2.623628673766234, + "seed": 1918413445, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "round", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -289.91096845116067, + 2.623628673766234 + ] + ] + }, + { + "type": "text", + "version": 38, + "versionNonce": 644505893, + "isDeleted": false, + "id": "Nrww6xcLvFoKhqaDCLPZy", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1324.1814948228796, + "y": 572.8229452904307, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 17, + "height": 25, + "seed": 1217802981, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "M", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "M" + }, + { + "type": "text", + "version": 63, + "versionNonce": 348352427, + "isDeleted": false, + "id": "i7LEI0-jY0-TF2-SoLr76", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1362.8477392662548, + "y": 573.4788524588724, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 18, + "height": 25, + "seed": 35082059, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "T", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "T" + }, + { + "type": "text", + "version": 89, + "versionNonce": 523039877, + "isDeleted": false, + "id": "EnMMMRd9y-L9CjEw5c8dl", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1402.2021693727472, + "y": 572.8229452904309, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 17, + "height": 25, + "seed": 191486283, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "W", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "W" + }, + { + "type": "text", + "version": 117, + "versionNonce": 1840136779, + "isDeleted": false, + "id": "PRV-lRA2ELRU7gyKn0B5Q", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1442.212506647681, + "y": 572.8229452904309, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 18, + "height": 25, + "seed": 47310405, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "T", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "T" + }, + { + "type": "text", + "version": 138, + "versionNonce": 1023810533, + "isDeleted": false, + "id": "V8jMROG3iez_gn4Zqd-dx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1482.8787510910565, + "y": 572.1670381219893, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 13, + "height": 25, + "seed": 1025604267, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "F", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "F" + }, + { + "type": "text", + "version": 168, + "versionNonce": 317047019, + "isDeleted": false, + "id": "f7wOS9NLYZcPGcf06WtZo", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1532.0717887241722, + "y": 571.5111309535478, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 14, + "height": 25, + "seed": 1970427371, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "S", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "S" + }, + { + "type": "text", + "version": 215, + "versionNonce": 1208178501, + "isDeleted": false, + "id": "34rXlSD9Lt5_d7YvUvSis", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1527.4804385450816, + "y": 777.4659818441916, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 21, + "height": 25, + "seed": 944687307, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203923, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "31", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "31" + }, + { + "type": "text", + "version": 242, + "versionNonce": 770993035, + "isDeleted": false, + "id": "Vva74DvoxIt8K4sujd3xL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1484.19056542794, + "y": 776.1541675073084, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 29, + "height": 25, + "seed": 10469739, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "30", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "30" + }, + { + "type": "text", + "version": 278, + "versionNonce": 713172645, + "isDeleted": false, + "id": "nKcmQwPTdL-nnjbcrk98s", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1439.5888779739153, + "y": 776.8100746757501, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 28, + "height": 25, + "seed": 1127213637, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "29", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "29" + }, + { + "type": "text", + "version": 297, + "versionNonce": 1393869355, + "isDeleted": false, + "id": "WMm3-BJ27tK-phf5Wk2i-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1398.266726362098, + "y": 779.4337033495161, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 32, + "height": 25, + "seed": 331902987, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "28", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "28" + }, + { + "type": "text", + "version": 332, + "versionNonce": 657459717, + "isDeleted": false, + "id": "f-00jcaGB-y_Y0NGVcPXd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1354.9768532449564, + "y": 779.4337033495161, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 27, + "height": 25, + "seed": 725845413, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "27", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "27" + }, + { + "type": "text", + "version": 356, + "versionNonce": 724649163, + "isDeleted": false, + "id": "w7VXTEKMvPHtvcMIDZnUh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1315.6224231384638, + "y": 779.4337033495161, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 29, + "height": 25, + "seed": 1149747557, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "26", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "26" + }, + { + "type": "text", + "version": 380, + "versionNonce": 1326088549, + "isDeleted": false, + "id": "zKnE6J4Y6dn1KT759lcdH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1274.9561786950883, + "y": 780.7455176863992, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 29, + "height": 25, + "seed": 111775531, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "25", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "25" + }, + { + "type": "text", + "version": 431, + "versionNonce": 1575252843, + "isDeleted": false, + "id": "ckAZn2av24IeIgYKMyK48", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1524.2009027028737, + "y": 734.1761087270498, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 29, + "height": 25, + "seed": 532718469, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "24", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "24" + }, + { + "type": "text", + "version": 456, + "versionNonce": 928976069, + "isDeleted": false, + "id": "QHga_G6e-SjJTpUtZnWFt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1482.2228439226153, + "y": 732.2083872217253, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 30, + "height": 25, + "seed": 1606294187, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "23", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "23" + }, + { + "type": "text", + "version": 481, + "versionNonce": 997772811, + "isDeleted": false, + "id": "ue6_w-EwSBdJYD2YwMaQ1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1438.2770636370321, + "y": 734.8320158954914, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 30, + "height": 25, + "seed": 1168972837, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "22", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "22" + }, + { + "type": "text", + "version": 500, + "versionNonce": 1875586085, + "isDeleted": false, + "id": "s2VVHFf7TVP1uEdRyIBTe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1396.9549120252152, + "y": 733.5202015586084, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 22, + "height": 25, + "seed": 437427243, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "21", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "21" + }, + { + "type": "text", + "version": 526, + "versionNonce": 652793003, + "isDeleted": false, + "id": "hYSTA7-dNehZoTx--_VJB", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1359.5682034240474, + "y": 733.5202015586084, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 30, + "height": 25, + "seed": 213870123, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "20", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "20" + }, + { + "type": "text", + "version": 555, + "versionNonce": 943480709, + "isDeleted": false, + "id": "0IxrNxGEforJSxSNYPTY2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1320.8696804859962, + "y": 734.1761087270501, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 20, + "height": 25, + "seed": 1055154539, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "19", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "19" + }, + { + "type": "text", + "version": 571, + "versionNonce": 1071581003, + "isDeleted": false, + "id": "nC_1QKdal6-LEm8aUYaBS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1279.5475288741793, + "y": 734.1761087270501, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 23, + "height": 25, + "seed": 1551393765, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "18", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "18" + }, + { + "type": "text", + "version": 616, + "versionNonce": 1995450085, + "isDeleted": false, + "id": "NRSV09eO2hnMRl7xiYe77", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1530.7599743872893, + "y": 692.1980499467915, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 18, + "height": 25, + "seed": 1242931269, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "17", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "17" + }, + { + "type": "text", + "version": 639, + "versionNonce": 1286989291, + "isDeleted": false, + "id": "N1Qhw_qoysAmAatRhzQKm", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1486.814194101706, + "y": 690.2303284414668, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 20, + "height": 25, + "seed": 1658147659, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "16", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "16" + }, + { + "type": "text", + "version": 668, + "versionNonce": 1853096517, + "isDeleted": false, + "id": "oX5nIa4tBUpOCaJSxqkNQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1442.868413816123, + "y": 690.8862356099085, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 20, + "height": 25, + "seed": 164777029, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "15", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "15" + }, + { + "type": "text", + "version": 687, + "versionNonce": 1685581963, + "isDeleted": false, + "id": "ydYVlG2H9DWIq6kFV50Cx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1401.5462622043058, + "y": 690.8862356099085, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 20, + "height": 25, + "seed": 487075339, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "14", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "14" + }, + { + "type": "text", + "version": 711, + "versionNonce": 866480549, + "isDeleted": false, + "id": "gxn0Pp0aRuXSmsv0vZHK9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1359.5682034240472, + "y": 692.1980499467916, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 21, + "height": 25, + "seed": 1268129131, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "13", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "13" + }, + { + "type": "text", + "version": 743, + "versionNonce": 1491052331, + "isDeleted": false, + "id": "3hS6WjpqNehLpAic25S_E", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1322.1814948228796, + "y": 691.54214277835, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 22, + "height": 25, + "seed": 2025386725, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "12", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "12" + }, + { + "type": "text", + "version": 771, + "versionNonce": 1672606981, + "isDeleted": false, + "id": "dEKzrCc04mq5G3R5R2-Mf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1282.1711575479458, + "y": 692.1980499467915, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 13, + "height": 25, + "seed": 641426731, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "11", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "11" + }, + { + "type": "text", + "version": 814, + "versionNonce": 1616349643, + "isDeleted": false, + "id": "_I8pKCXD4o6HyaBkFZf0q", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1529.415881555731, + "y": 650.5640839980914, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 21, + "height": 25, + "seed": 897848363, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "10", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "10" + }, + { + "type": "text", + "version": 842, + "versionNonce": 437718117, + "isDeleted": false, + "id": "jgtRwe0ipUNG0huyqz5Yw", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1482.8464725963815, + "y": 649.9081768296498, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 14, + "height": 25, + "seed": 779739339, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "9", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "9" + }, + { + "type": "text", + "version": 871, + "versionNonce": 2147096683, + "isDeleted": false, + "id": "tX83Ioy91Z87My66Y12GS", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1440.2125066476813, + "y": 649.2522696612083, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 17, + "height": 25, + "seed": 1419577029, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "8", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "8" + }, + { + "type": "text", + "version": 903, + "versionNonce": 648165317, + "isDeleted": false, + "id": "8_2XCYwPhc-Y4O19eiK51", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1402.8257980465135, + "y": 650.5640839980914, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 13, + "height": 25, + "seed": 1589727813, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "7", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "7" + }, + { + "type": "text", + "version": 924, + "versionNonce": 898749195, + "isDeleted": false, + "id": "PRDzudXZatQhLqQFQrcFw", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1364.1272751084628, + "y": 652.5318055034161, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 15, + "height": 25, + "seed": 276662283, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "6", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "6" + }, + { + "type": "text", + "version": 939, + "versionNonce": 57734949, + "isDeleted": false, + "id": "pJ9giTkCvuhJUftvV8hpe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1320.8696804859965, + "y": 652.5318055034161, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 14, + "height": 25, + "seed": 226579269, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "5", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "5" + }, + { + "type": "text", + "version": 958, + "versionNonce": 1637431723, + "isDeleted": false, + "id": "BTMcsNAl4pkKvotgKCSZO", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1282.8270647163872, + "y": 652.5318055034161, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 15, + "height": 25, + "seed": 1816943723, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "4", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "4" + }, + { + "type": "text", + "version": 991, + "versionNonce": 183552645, + "isDeleted": false, + "id": "eO5Xkd6JYPHhFHmCh1MFv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1531.4158815557312, + "y": 611.209653891599, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 16, + "height": 25, + "seed": 1516880869, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "3", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "3" + }, + { + "type": "text", + "version": 1015, + "versionNonce": 1899678795, + "isDeleted": false, + "id": "-8DWEofL7OTPEdV4vvS0Z", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1484.8464725963818, + "y": 610.5537467231575, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 16, + "height": 25, + "seed": 1532588677, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "2", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "2" + }, + { + "type": "text", + "version": 1044, + "versionNonce": 1843343845, + "isDeleted": false, + "id": "C4hxI0WzneK8JwnC4pKz0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1443.8684138161234, + "y": 610.5537467231575, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "width": 7, + "height": 25, + "seed": 1256278245, + "groupIds": [ + "0snuNBP8KeYVmBmPMDkqI" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670480203925, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "1", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "1" + } + ], + "created": 1670480206794, + "name": "Calendar" + }, + { + "id": "WUvBairpZgzaZXlDs58W_", + "status": "published", + "elements": [ + { + "id": "xzf0xwxUnotMwGSMVN_io", + "type": "rectangle", + "x": 894.8717153091703, + "y": 482.79845583554464, + "width": 276.21447426482905, + "height": 43.80493864992883, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "jbt3pCldqMa0CL-tNQv3o" + ], + "strokeSharpness": "sharp", + "seed": 462208869, + "version": 183, + "versionNonce": 1207655851, + "isDeleted": false, + "boundElements": null, + "updated": 1670479764317, + "link": null, + "locked": false + }, + { + "id": "TG3OnRzco97bW4sNWUZ2v", + "type": "ellipse", + "x": 904.5744231954941, + "y": 491.01289227698146, + "width": 15.717345693056908, + "height": 16.431770497286855, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "gUHsObtaNlEaACaFm6G1L", + "jbt3pCldqMa0CL-tNQv3o" + ], + "strokeSharpness": "round", + "seed": 444728299, + "version": 84, + "versionNonce": 1311958661, + "isDeleted": false, + "boundElements": null, + "updated": 1670479764317, + "link": null, + "locked": false + }, + { + "id": "ooS_lNUdcCMCm69oJ6_RN", + "type": "line", + "x": 917.7377965821889, + "y": 504.3816146105126, + "width": 10.001947259218033, + "height": 11.43079686767781, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "gUHsObtaNlEaACaFm6G1L", + "jbt3pCldqMa0CL-tNQv3o" + ], + "strokeSharpness": "round", + "seed": 521162379, + "version": 114, + "versionNonce": 263745611, + "isDeleted": false, + "boundElements": null, + "updated": 1670479764317, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 10.001947259218033, + 11.43079686767781 + ] + ], + "lastCommittedPoint": [ + 19.647263004038223, + 22.454014861758083 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "OPtaxkNlYGVVJtIoyxX52", + "type": "text", + "x": 948.8816500788305, + "y": 490.592470931165, + "width": 76.22680427945444, + "height": 28.442837417706876, + "angle": 0, + "strokeColor": "#343a40", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "jbt3pCldqMa0CL-tNQv3o" + ], + "strokeSharpness": "round", + "seed": 1511444773, + "version": 61, + "versionNonce": 981323237, + "isDeleted": false, + "boundElements": null, + "updated": 1670479764317, + "link": null, + "locked": false, + "text": "Search", + "fontSize": 22.754269934165507, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 19.442837417706876, + "containerId": null, + "originalText": "Search" + } + ], + "created": 1670479766386, + "name": "Search Input" + }, + { + "id": "Vpd0rxn52ZVDTFz_xQvkP", + "status": "published", + "elements": [ + { + "id": "sJ__J9GvN3kcu-x9t5U_o", + "type": "line", + "x": 877.3050175258194, + "y": 482.40406025749184, + "width": 341.020350712951, + "height": 0, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "KgOL4t2n6YYJeY21s4hID" + ], + "strokeSharpness": "round", + "seed": 1861141701, + "version": 61, + "versionNonce": 1835191595, + "isDeleted": false, + "boundElements": null, + "updated": 1670479609043, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 341.020350712951, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "ruBTrJAfkJE8N_Nb_gEUO", + "type": "ellipse", + "x": 893.1455286721379, + "y": 466.56354911117324, + "width": 28.06751857719769, + "height": 30.874270434917378, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "KgOL4t2n6YYJeY21s4hID" + ], + "strokeSharpness": "round", + "seed": 752053765, + "version": 19, + "versionNonce": 685518597, + "isDeleted": false, + "boundElements": null, + "updated": 1670479609043, + "link": null, + "locked": false + } + ], + "created": 1670479611431, + "name": "Slider" + }, + { + "id": "vRIqhXFjKIH7LHgPDdpss", + "status": "published", + "elements": [ + { + "id": "ita4BRJkzhHkyRhm7ziVw", + "type": "rectangle", + "x": 926.4231750359153, + "y": 458.5466694668739, + "width": 405.57564344050536, + "height": 266.6414264833773, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "_vaF9n9a8IjJOUBBNtgNu" + ], + "strokeSharpness": "round", + "seed": 50566693, + "version": 47, + "versionNonce": 1758022411, + "isDeleted": false, + "boundElements": null, + "updated": 1670479569627, + "link": null, + "locked": false + }, + { + "id": "ALMjLk0tbnCuPj1YQLgRh", + "type": "line", + "x": 925.0197991070553, + "y": 653.6159235783972, + "width": 402.76889158278584, + "height": 0, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "_vaF9n9a8IjJOUBBNtgNu" + ], + "strokeSharpness": "round", + "seed": 655151883, + "version": 61, + "versionNonce": 1535967013, + "isDeleted": false, + "boundElements": null, + "updated": 1670479569628, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 402.76889158278584, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "0iAlFKtVrTxyeq81SrhhS", + "type": "line", + "x": 1131.1059328628785, + "y": 653.6159235783972, + "width": 4.547473508864641e-13, + "height": 72.97554830071385, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "_vaF9n9a8IjJOUBBNtgNu" + ], + "strokeSharpness": "round", + "seed": 1876532683, + "version": 37, + "versionNonce": 790737323, + "isDeleted": false, + "boundElements": null, + "updated": 1670479569628, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -4.547473508864641e-13, + 72.97554830071385 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "AFGnrApC1IPrXBrL4kCF1", + "type": "text", + "x": 1019.0459863406672, + "y": 675.4901940133146, + "width": 34, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "_vaF9n9a8IjJOUBBNtgNu" + ], + "strokeSharpness": "round", + "seed": 2665125, + "version": 19, + "versionNonce": 306958981, + "isDeleted": false, + "boundElements": null, + "updated": 1670479569628, + "link": null, + "locked": false, + "text": "Yes", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Yes" + }, + { + "id": "pzqyKmexAqQShchKBWncc", + "type": "text", + "x": 1198.678105234732, + "y": 676.8935699421745, + "width": 26, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "_vaF9n9a8IjJOUBBNtgNu" + ], + "strokeSharpness": "round", + "seed": 1966970795, + "version": 62, + "versionNonce": 1727284299, + "isDeleted": false, + "boundElements": null, + "updated": 1670479569628, + "link": null, + "locked": false, + "text": "No", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "No" + }, + { + "id": "T26gkhfc4w7KHdxqFrQEs", + "type": "text", + "x": 1087.8114068548016, + "y": 502.379645612187, + "width": 72.05063893289801, + "height": 35.31894065338138, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "_vaF9n9a8IjJOUBBNtgNu" + ], + "strokeSharpness": "round", + "seed": 981253221, + "version": 93, + "versionNonce": 1687763429, + "isDeleted": false, + "boundElements": null, + "updated": 1670479569628, + "link": null, + "locked": false, + "text": "Alert", + "fontSize": 28.2551525227051, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 24.318940653381382, + "containerId": null, + "originalText": "Alert" + }, + { + "id": "3AI-TEKsYncfvUoD42C70", + "type": "text", + "x": 1000.2223548386487, + "y": 559.6704041197611, + "width": 248.46158691674464, + "height": 29.23077493138172, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "_vaF9n9a8IjJOUBBNtgNu" + ], + "strokeSharpness": "round", + "seed": 959197003, + "version": 187, + "versionNonce": 941327083, + "isDeleted": false, + "boundElements": null, + "updated": 1670479569628, + "link": null, + "locked": false, + "text": "Alert Text Desription", + "fontSize": 22.942222334529312, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 20.23077493138172, + "containerId": null, + "originalText": "Alert Text Desription" + } + ], + "created": 1670479571783, + "name": "Alert" + }, + { + "id": "8LCQ4rGWlSL2qVUMaaEYj", + "status": "published", + "elements": [ + { + "id": "wljCrPpmqV6UF3lcEEKI0", + "type": "rectangle", + "x": 983.5570594593066, + "y": 335.2130938779404, + "width": 37.45162074812538, + "height": 545.1291464449341, + "angle": 1.5647488869799497, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "seed": 1263338187, + "version": 151, + "versionNonce": 453190187, + "isDeleted": false, + "boundElements": null, + "updated": 1670479436411, + "link": null, + "locked": false + }, + { + "id": "2JMjCJ8PGT-6s-g-fFM-a", + "type": "rectangle", + "x": 1235.821968454621, + "y": 586.1391842094348, + "width": 37.45162074812538, + "height": 40.22581487761602, + "angle": 1.5647488869799497, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "seed": 990766827, + "version": 128, + "versionNonce": 1675078149, + "isDeleted": false, + "boundElements": null, + "updated": 1670479436411, + "link": null, + "locked": false + }, + { + "id": "GkPu0bgsXCEQzYJP5RABf", + "type": "rectangle", + "x": 731.2508241315762, + "y": 589.2764453692502, + "width": 37.45162074812538, + "height": 40.22581487761602, + "angle": 1.5647488869799497, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "seed": 1576092299, + "version": 199, + "versionNonce": 643015883, + "isDeleted": false, + "boundElements": null, + "updated": 1670479436411, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 397, + "versionNonce": 1438523749, + "isDeleted": false, + "id": "raYQ1Dv3FmPZ72nvaqu7u", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.5965879461109562, + "x": 737.9446121577234, + "y": 603.5934510213455, + "strokeColor": "#000000", + "backgroundColor": "#000", + "width": 22.41597955723904, + "height": 18.025237460407283, + "seed": 1530985515, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670479436411, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 11.207989778619526, + 15.772082777856372 + ], + [ + 22.41597955723904, + -2.25315468255091 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 485, + "versionNonce": 96129899, + "isDeleted": false, + "id": "SBIxytnSwWxKJRXSzvpZt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 4.717781918763523, + "x": 1241.2257481387528, + "y": 598.9343653655113, + "strokeColor": "#000000", + "backgroundColor": "#000", + "width": 22.41597955723904, + "height": 18.025237460407283, + "seed": 430759525, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670479436411, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 11.207989778619526, + 15.772082777856372 + ], + [ + 22.41597955723904, + -2.25315468255091 + ], + [ + 0, + 0 + ] + ] + }, + { + "id": "IIxh7sIcKoWg7w-UKnsXC", + "type": "rectangle", + "x": 1190.615892677032, + "y": 582.0985421841827, + "width": 18.459598986547917, + "height": 48.8636443761564, + "angle": 1.5647488869799497, + "strokeColor": "#000000", + "backgroundColor": "#000", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "seed": 1394705419, + "version": 101, + "versionNonce": 1721674949, + "isDeleted": false, + "boundElements": null, + "updated": 1670479436411, + "link": null, + "locked": false + } + ], + "created": 1670479439613, + "name": "Horizontal Scroll " + }, + { + "id": "T5384nxhOHMeLE5Xj2Ko1", + "status": "published", + "elements": [ + { + "id": "wljCrPpmqV6UF3lcEEKI0", + "type": "rectangle", + "x": 983.5143892065894, + "y": 335.25602277039843, + "width": 37.45162074812538, + "height": 545.1291464449341, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "seed": 1263338187, + "version": 74, + "versionNonce": 2139278059, + "isDeleted": false, + "boundElements": null, + "updated": 1670479428098, + "link": null, + "locked": false + }, + { + "id": "2JMjCJ8PGT-6s-g-fFM-a", + "type": "rectangle", + "x": 983.5143892065894, + "y": 335.43816663182065, + "width": 37.45162074812538, + "height": 40.22581487761602, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "seed": 990766827, + "version": 46, + "versionNonce": 1192488261, + "isDeleted": false, + "boundElements": null, + "updated": 1670479428098, + "link": null, + "locked": false + }, + { + "id": "GkPu0bgsXCEQzYJP5RABf", + "type": "rectangle", + "x": 983.6002479705039, + "y": 840.0190567964446, + "width": 37.45162074812538, + "height": 40.22581487761602, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "seed": 1576092299, + "version": 117, + "versionNonce": 1449145739, + "isDeleted": false, + "boundElements": null, + "updated": 1670479428098, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 315, + "versionNonce": 1729100965, + "isDeleted": false, + "id": "raYQ1Dv3FmPZ72nvaqu7u", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0.031839059131006486, + "x": 990.5501491836435, + "y": 854.5078422783215, + "strokeColor": "#000000", + "backgroundColor": "#000", + "width": 22.41597955723904, + "height": 18.025237460407283, + "seed": 1530985515, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670479428098, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 11.207989778619526, + 15.772082777856372 + ], + [ + 22.41597955723904, + -2.25315468255091 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 403, + "versionNonce": 1333218347, + "isDeleted": false, + "id": "SBIxytnSwWxKJRXSzvpZt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 3.153033031783572, + "x": 989.522820465324, + "y": 350.4585261179879, + "strokeColor": "#000000", + "backgroundColor": "#000", + "width": 22.41597955723904, + "height": 18.025237460407283, + "seed": 430759525, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670479428098, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 11.207989778619526, + 15.772082777856372 + ], + [ + 22.41597955723904, + -2.25315468255091 + ], + [ + 0, + 0 + ] + ] + }, + { + "id": "IIxh7sIcKoWg7w-UKnsXC", + "type": "rectangle", + "x": 992.9578621625155, + "y": 385.822021101814, + "width": 18.459598986547917, + "height": 48.8636443761564, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#000", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "76wYew9Fk1lQcYPo7XPBV" + ], + "strokeSharpness": "sharp", + "seed": 1394705419, + "version": 24, + "versionNonce": 2140348421, + "isDeleted": false, + "boundElements": null, + "updated": 1670479428098, + "link": null, + "locked": false + } + ], + "created": 1670479430230, + "name": "Vertical Scroll " + }, + { + "id": "IeSfUn1cG-oopAVVjTTPp", + "status": "published", + "elements": [ + { + "id": "JlfnB_ykNz7U-31f8mYkV", + "type": "rectangle", + "x": 814.2413905406195, + "y": 382.29551320972166, + "width": 367.8754217789495, + "height": 43.852699284907885, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "J-ABAu-U7dMd1uMSZljel" + ], + "strokeSharpness": "sharp", + "seed": 2033517477, + "version": 93, + "versionNonce": 1771172837, + "isDeleted": false, + "boundElements": null, + "updated": 1670479231416, + "link": null, + "locked": false + }, + { + "id": "y3Jv3INyRiSypvR3O4gTU", + "type": "rectangle", + "x": 815.4595210763114, + "y": 383.51364374541356, + "width": 218.04536588884753, + "height": 42.63456874921599, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "J-ABAu-U7dMd1uMSZljel" + ], + "strokeSharpness": "sharp", + "seed": 418179979, + "version": 94, + "versionNonce": 665020651, + "isDeleted": false, + "boundElements": null, + "updated": 1670479231416, + "link": null, + "locked": false + } + ], + "created": 1670479234343, + "name": "Linear Progress Bar" + }, + { + "id": "HGT3BGKkvXclK_eGY7wk2", + "status": "published", + "elements": [ + { + "id": "XnLJFolWBXRZ5n0Vn_q86", + "type": "line", + "x": 643.9288771818381, + "y": 718.600969515237, + "width": 85.39136149270017, + "height": 42.07978670334516, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "CP3ilmVf2tvU7qwwWNS2a" + ], + "strokeSharpness": "round", + "seed": 686565029, + "version": 256, + "versionNonce": 1380207371, + "isDeleted": false, + "boundElements": null, + "updated": 1670479073442, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 72.81514702846026, + -0.9147779718117884 + ], + [ + 74.52844460560048, + 40.25023075972158 + ], + [ + -6.8531903085609756, + 41.16500873153337 + ], + [ + -10.862916887099686, + 5.783139948933467 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + 1.0075701977275457, + -1.007570197727432 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "mZfNM81gkv1okLD9vOVOa", + "type": "ellipse", + "x": 677.1858131826609, + "y": 714.2369681738224, + "width": 51.80388556015671, + "height": 49.10064195085418, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "CP3ilmVf2tvU7qwwWNS2a" + ], + "strokeSharpness": "sharp", + "seed": 842074635, + "version": 74, + "versionNonce": 457309989, + "isDeleted": false, + "boundElements": null, + "updated": 1670479073442, + "link": null, + "locked": false + } + ], + "created": 1670479075002, + "name": "Toggle Checked" + }, + { + "id": "LMWqZquwvmqB27V3qXni0", + "status": "published", + "elements": [ + { + "type": "rectangle", + "version": 172, + "versionNonce": 1148237643, + "isDeleted": false, + "id": "rPzND8xY4cQY3HQczHhO_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 289.4324454072679, + "y": 719.1647310325625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 276.21447426482905, + "height": 43.80493864992883, + "seed": 2098720043, + "groupIds": [ + "XxPSIkHWUaiHFi74GCD6L" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670478955253, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 166, + "versionNonce": 26310373, + "isDeleted": false, + "id": "A2Jr_MzrxW0YGKkYAHkl6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 515.7579617652335, + "y": 719.8948133433946, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1.216803851386912, + "height": 43.80493864992883, + "seed": 1921584901, + "groupIds": [ + "XxPSIkHWUaiHFi74GCD6L" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670478955254, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.216803851386912, + 43.80493864992883 + ] + ] + }, + { + "type": "line", + "version": 332, + "versionNonce": 865179115, + "isDeleted": false, + "id": "ROSlH08hvHAQeo5wKsqrM", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0.07698597171669253, + "x": 527.329954841138, + "y": 744.2110033369908, + "strokeColor": "#000000", + "backgroundColor": "#000", + "width": 23.416075715097236, + "height": 10.901181258666599, + "seed": 563548107, + "groupIds": [ + "XxPSIkHWUaiHFi74GCD6L" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670478955254, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 11.708037857548625, + 9.538533601333274 + ], + [ + 23.416075715097236, + -1.3626476573333246 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 178, + "versionNonce": 946557509, + "isDeleted": false, + "id": "N_nv7-dWgsRokJUaPfDE_", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 308.07388041051536, + "y": 731.0894087761542, + "strokeColor": "#000000", + "backgroundColor": "#000", + "width": 42, + "height": 26, + "seed": 855303781, + "groupIds": [ + "XxPSIkHWUaiHFi74GCD6L" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670478955254, + "link": null, + "locked": false, + "fontSize": 20.44230470330012, + "fontFamily": 1, + "text": "256", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "256" + }, + { + "type": "line", + "version": 397, + "versionNonce": 1415422091, + "isDeleted": false, + "id": "N246kVCkR0OrjtJs2Pj2t", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 3.189410396262412, + "x": 526.7889205936648, + "y": 727.8544498670191, + "strokeColor": "#000000", + "backgroundColor": "#000", + "width": 23.416075715097236, + "height": 10.901181258666599, + "seed": 702367083, + "groupIds": [ + "XxPSIkHWUaiHFi74GCD6L" + ], + "strokeSharpness": "sharp", + "boundElements": null, + "updated": 1670478955254, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 11.708037857548625, + 9.538533601333274 + ], + [ + 23.416075715097236, + -1.3626476573333246 + ], + [ + 0, + 0 + ] + ] + } + ], + "created": 1670478959901, + "name": "Number Input" + }, + { + "id": "QiWTB9BNS7HcNaCElgbRn", + "status": "published", + "elements": [ + { + "id": "gTYfHz-fziFrtU5NiU7-O", + "type": "rectangle", + "x": 281.2896825396824, + "y": 613.741703977825, + "width": 276.21447426482905, + "height": 43.80493864992883, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 1500793547, + "version": 154, + "versionNonce": 1520329195, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871412, + "link": null, + "locked": false + }, + { + "id": "x4w66v57VxBxVlUUS9QZP", + "type": "line", + "x": 507.61519889764804, + "y": 614.4717862886571, + "width": 1.216803851386912, + "height": 43.80493864992883, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 1709494859, + "version": 148, + "versionNonce": 1851868741, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871412, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.216803851386912, + 43.80493864992883 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "PdUAOplOr8heuGNdcVAEL", + "type": "text", + "x": 299.9311175429299, + "y": 625.6663817214167, + "width": 91, + "height": 26, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#000", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 48560299, + "version": 165, + "versionNonce": 401665163, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871412, + "link": null, + "locked": false, + "text": "dd/mm/yy", + "fontSize": 20.44230470330012, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "dd/mm/yy" + }, + { + "id": "0oeT8t9x6Bej5TiYyZxxf", + "type": "line", + "x": 519.597152452057, + "y": 631.7084976087002, + "width": 24.13075290980819, + "height": 22.466563053959348, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "h9RGNA8jYBoc5XlOdhNqE", + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 1630041611, + "version": 173, + "versionNonce": 1296709029, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871412, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 20.386325734148294 + ], + [ + 24.13075290980819, + 19.970278270186085 + ], + [ + 20.802373198110512, + -2.0802373198110504 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + 2.5, + -1.2500000000000002 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "ZERXq57LcimcN41mtsTgy", + "type": "line", + "x": 525.421816947528, + "y": 630.0443077528515, + "width": 1.6641898558488406, + "height": 22.466563053959348, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "h9RGNA8jYBoc5XlOdhNqE", + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 717108709, + "version": 124, + "versionNonce": 735650603, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871412, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.6641898558488406, + 22.466563053959348 + ] + ], + "lastCommittedPoint": [ + 5, + 67.5 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "-IXZIKWtiaf_9Bvbwi1l5", + "type": "line", + "x": 532.9106712988478, + "y": 630.0443077528515, + "width": 2.9123322477354714, + "height": 19.970278270186085, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "h9RGNA8jYBoc5XlOdhNqE", + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 184190789, + "version": 119, + "versionNonce": 1948137733, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871412, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 2.9123322477354714, + 19.970278270186085 + ] + ], + "lastCommittedPoint": [ + 8.75, + 60 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "ckMd844TPkwF2TF2WN9NV", + "type": "line", + "x": 519.1811049880948, + "y": 637.9492095681334, + "width": 21.634468126034925, + "height": 1.2481423918866303, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "h9RGNA8jYBoc5XlOdhNqE", + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 1386388555, + "version": 117, + "versionNonce": 247563723, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871412, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 21.634468126034925, + -1.2481423918866303 + ] + ], + "lastCommittedPoint": [ + 65, + -3.75 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "CVWkEqiJZVsoYNAkZiXS5", + "type": "line", + "x": 520.0131999160193, + "y": 645.8541113834154, + "width": 22.466563053959348, + "height": 1.2481423918866303, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "h9RGNA8jYBoc5XlOdhNqE", + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 556187077, + "version": 121, + "versionNonce": 1549455461, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871412, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 22.466563053959348, + -1.2481423918866303 + ] + ], + "lastCommittedPoint": [ + 67.5, + -3.75 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "j1yekEMqebUwuzxJhz6bJ", + "type": "line", + "x": 518.3490100601703, + "y": 632.5405925366248, + "width": 22.88261051792156, + "height": 8.320949279244203, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#000", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "h9RGNA8jYBoc5XlOdhNqE", + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 257195115, + "version": 162, + "versionNonce": 1204065387, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871412, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 22.88261051792156, + -0.41604746396221015 + ], + [ + 21.21842066207272, + -8.320949279244203 + ], + [ + 0, + -6.656759423395362 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + 1.25, + -2.5 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "Y9PxD1L9QYHpSPW2391mu", + "type": "line", + "x": 522.9255321637547, + "y": 628.7961653609648, + "width": 15.809803630563986, + "height": 8.736996743206413, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#000", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "h9RGNA8jYBoc5XlOdhNqE", + "qRGYopWpK7rCA79_Dg9oV" + ], + "strokeSharpness": "sharp", + "seed": 1260651243, + "version": 169, + "versionNonce": 1942846405, + "isDeleted": false, + "boundElements": null, + "updated": 1670478871413, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -1.2481423918866303, + -7.488854351319783 + ], + [ + 3.328379711697681, + -7.904901815281993 + ], + [ + 2.9123322477354714, + -2.080237319811051 + ], + [ + 11.649328990941886, + -2.080237319811051 + ], + [ + 9.985139135093043, + -7.488854351319783 + ], + [ + 14.561661238677354, + -7.904901815281993 + ], + [ + 13.729566310752935, + 0.8320949279244203 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + 5, + 1.25 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + } + ], + "created": 1670478873756, + "name": "Date Input" + }, + { + "id": "V9vO3UBdck1KzojtHfuVc", + "status": "published", + "elements": [ + { + "id": "gTYfHz-fziFrtU5NiU7-O", + "type": "rectangle", + "x": 853.7896825396824, + "y": 391.265873015873, + "width": 283.75, + "height": 45, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "aveT88Ye3lzf7dEboBleK" + ], + "strokeSharpness": "sharp", + "seed": 1500793547, + "version": 25, + "versionNonce": 89689259, + "isDeleted": false, + "boundElements": null, + "updated": 1670478726539, + "link": null, + "locked": false + }, + { + "id": "x4w66v57VxBxVlUUS9QZP", + "type": "line", + "x": 1086.2896825396824, + "y": 392.015873015873, + "width": 1.25, + "height": 45, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "aveT88Ye3lzf7dEboBleK" + ], + "strokeSharpness": "sharp", + "seed": 1709494859, + "version": 19, + "versionNonce": 63186821, + "isDeleted": false, + "boundElements": null, + "updated": 1670478726539, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1.25, + 45 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "NN-0X2zfwnM_EPeRAOW4B", + "type": "line", + "x": 1098.5396825396824, + "y": 409.015873015873, + "width": 27.5, + "height": 20, + "angle": 0.07698597171669253, + "strokeColor": "#000000", + "backgroundColor": "#000", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "aveT88Ye3lzf7dEboBleK" + ], + "strokeSharpness": "sharp", + "seed": 616202379, + "version": 55, + "versionNonce": 664256331, + "isDeleted": false, + "boundElements": null, + "updated": 1670478726540, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 13.750000000000004, + 17.5 + ], + [ + 27.499999999999996, + -2.5 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": [ + 0, + -2.5 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "PdUAOplOr8heuGNdcVAEL", + "type": "text", + "x": 872.9396825396824, + "y": 403.515873015873, + "width": 112.35000000000002, + "height": 26.25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#000", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "aveT88Ye3lzf7dEboBleK" + ], + "strokeSharpness": "sharp", + "seed": 48560299, + "version": 27, + "versionNonce": 239005413, + "isDeleted": false, + "boundElements": null, + "updated": 1670478726540, + "link": null, + "locked": false, + "text": "Combo Box", + "fontSize": 21, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18.25, + "containerId": null, + "originalText": "Combo Box" + } + ], + "created": 1670478728317, + "name": "Select" + }, + { + "id": "19HvoN8DWeO_nAfgUrSYn", + "status": "published", + "elements": [ + { + "id": "8mo6Pwf8BSHQ8k-EC1zFA", + "type": "rectangle", + "x": 943.7896825396824, + "y": 508.765873015873, + "width": 32.05562659846547, + "height": 30.32289002557545, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "UvB-9lHd6seH-I9U3ajJV", + "T2IvLH5v1vMrfnxydSyy6" + ], + "strokeSharpness": "sharp", + "seed": 1356230539, + "version": 64, + "versionNonce": 688279179, + "isDeleted": false, + "boundElements": [], + "updated": 1670478596899, + "link": null, + "locked": false + }, + { + "id": "pHKMYdrdmOtjranBmLlpL", + "type": "text", + "x": 991.4399382941581, + "y": 511.35907081870954, + "width": 87.84974424552435, + "height": 24.957313706114874, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "UvB-9lHd6seH-I9U3ajJV", + "T2IvLH5v1vMrfnxydSyy6" + ], + "strokeSharpness": "sharp", + "seed": 773611109, + "version": 85, + "versionNonce": 204731301, + "isDeleted": false, + "boundElements": null, + "updated": 1670478596899, + "link": null, + "locked": false, + "text": "Checkbox", + "fontSize": 19.9658509648919, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 17.957313706114874, + "containerId": null, + "originalText": "Checkbox" + }, + { + "id": "-hiTHjdB3f3E6-AfN3P-J", + "type": "line", + "x": 971.0396825396824, + "y": 516.265873015873, + "width": 21.25, + "height": 13.75, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "T2IvLH5v1vMrfnxydSyy6" + ], + "strokeSharpness": "sharp", + "seed": 357849195, + "version": 45, + "versionNonce": 2012366123, + "isDeleted": false, + "boundElements": null, + "updated": 1670478596899, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -15, + 13.75 + ], + [ + -21.25, + 3.75 + ] + ], + "lastCommittedPoint": [ + -21.25, + 3.75 + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + } + ], + "created": 1670478599229, + "name": "Checkbox Checked" + }, + { + "id": "jQambTGjMlteML0gOjjX1", + "status": "published", + "elements": [ + { + "id": "8mo6Pwf8BSHQ8k-EC1zFA", + "type": "rectangle", + "x": 943.7896825396824, + "y": 508.765873015873, + "width": 32.05562659846547, + "height": 30.32289002557545, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "UvB-9lHd6seH-I9U3ajJV" + ], + "strokeSharpness": "sharp", + "seed": 1356230539, + "version": 63, + "versionNonce": 288368107, + "isDeleted": false, + "boundElements": [], + "updated": 1670478544787, + "link": null, + "locked": false + }, + { + "id": "pHKMYdrdmOtjranBmLlpL", + "type": "text", + "x": 991.4399382941581, + "y": 511.35907081870954, + "width": 87.84974424552435, + "height": 24.957313706114874, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "UvB-9lHd6seH-I9U3ajJV" + ], + "strokeSharpness": "sharp", + "seed": 773611109, + "version": 84, + "versionNonce": 1975370309, + "isDeleted": false, + "boundElements": null, + "updated": 1670478544787, + "link": null, + "locked": false, + "text": "Checkbox", + "fontSize": 19.9658509648919, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 17.957313706114874, + "containerId": null, + "originalText": "Checkbox" + } + ], + "created": 1670478548817, + "name": "Checkbox Unchecked" + }, + { + "status": "published", + "elements": [ + { + "id": "tIYxcqQGVsF2qlCIk5nqi", + "type": "rectangle", + "x": 818.7896825396824, + "y": 420.265873015873, + "width": 170, + "height": 45, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "FFiwayr2Q6y0DQrS0jxii" + ], + "strokeSharpness": "sharp", + "seed": 1085546123, + "version": 237, + "versionNonce": 1420898405, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "gYswTrtA5p1yIFUu6RTnD" + } + ], + "updated": 1670478468183, + "link": null, + "locked": false + }, + { + "id": "gYswTrtA5p1yIFUu6RTnD", + "type": "text", + "x": 885.2896825396824, + "y": 430.265873015873, + "width": 37, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "FFiwayr2Q6y0DQrS0jxii" + ], + "strokeSharpness": "sharp", + "seed": 2036733227, + "version": 190, + "versionNonce": 1854157931, + "isDeleted": false, + "boundElements": null, + "updated": 1670478468183, + "link": null, + "locked": false, + "text": "One", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "tIYxcqQGVsF2qlCIk5nqi", + "originalText": "One" + }, + { + "id": "BsC5EY4e099Ds4QDqKyn6", + "type": "rectangle", + "x": 988.7896825396824, + "y": 420.015873015873, + "width": 170, + "height": 45, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "FFiwayr2Q6y0DQrS0jxii" + ], + "strokeSharpness": "sharp", + "seed": 1498805611, + "version": 251, + "versionNonce": 1381489605, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "7jWyZUB0DUYFx6cujKPBr" + } + ], + "updated": 1670478468183, + "link": null, + "locked": false + }, + { + "id": "7jWyZUB0DUYFx6cujKPBr", + "type": "text", + "x": 1053.2896825396824, + "y": 430.015873015873, + "width": 41, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "FFiwayr2Q6y0DQrS0jxii" + ], + "strokeSharpness": "sharp", + "seed": 292163595, + "version": 205, + "versionNonce": 75001611, + "isDeleted": false, + "boundElements": null, + "updated": 1670478468183, + "link": null, + "locked": false, + "text": "Two", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "BsC5EY4e099Ds4QDqKyn6", + "originalText": "Two" + }, + { + "id": "3oWe5FzOseeI-yx_IywN_", + "type": "rectangle", + "x": 1160.0396825396824, + "y": 421.015873015873, + "width": 170, + "height": 45, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "FFiwayr2Q6y0DQrS0jxii" + ], + "strokeSharpness": "sharp", + "seed": 1158957573, + "version": 306, + "versionNonce": 1892817701, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "OLDt2vOCh3lNgAN1OJqUR" + } + ], + "updated": 1670478468183, + "link": null, + "locked": false + }, + { + "id": "OLDt2vOCh3lNgAN1OJqUR", + "type": "text", + "x": 1216.0396825396824, + "y": 431.015873015873, + "width": 58, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "FFiwayr2Q6y0DQrS0jxii" + ], + "strokeSharpness": "sharp", + "seed": 351756037, + "version": 263, + "versionNonce": 1363957163, + "isDeleted": false, + "boundElements": null, + "updated": 1670478468183, + "link": null, + "locked": false, + "text": "Three", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "3oWe5FzOseeI-yx_IywN_", + "originalText": "Three" + } + ], + "id": "1XCaoH5Ruq4V_TZYeqGx3", + "created": 1670478472137, + "name": "Button Group" + }, + { + "id": "AyVIJKUO5bYIZHegnhncd", + "status": "published", + "elements": [ + { + "id": "3oWe5FzOseeI-yx_IywN_", + "type": "rectangle", + "x": 921.2896825396824, + "y": 380.015873015873, + "width": 170, + "height": 45, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1158957573, + "version": 98, + "versionNonce": 1851678155, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "OLDt2vOCh3lNgAN1OJqUR" + } + ], + "updated": 1670478355798, + "link": null, + "locked": false + }, + { + "id": "OLDt2vOCh3lNgAN1OJqUR", + "type": "text", + "x": 970.7896825396824, + "y": 390.015873015873, + "width": 71, + "height": 25, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 351756037, + "version": 46, + "versionNonce": 1939306597, + "isDeleted": false, + "boundElements": null, + "updated": 1670478355799, + "link": null, + "locked": false, + "text": "Button", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "3oWe5FzOseeI-yx_IywN_", + "originalText": "Button" + } + ], + "created": 1670478361141, + "name": "Button" + } + ] +} diff --git a/apps/web/src/components/app/agents-view.tsx b/apps/web/src/components/app/agents-view.tsx index b5f9ba8d..624b049c 100644 --- a/apps/web/src/components/app/agents-view.tsx +++ b/apps/web/src/components/app/agents-view.tsx @@ -9,8 +9,12 @@ import { import { createPortal } from "react-dom"; import { Routes, Route, useNavigate, useParams } from "react-router-dom"; import { PanelLeftOpen, PanelRightOpen, Split } from "lucide-react"; +import { useAtomValue } from "jotai"; + +import { whiteboardAgentDrewAtomFamily } from "@/lib/store"; import { ChangesTab } from "@/components/app/changes-tab"; +import { WhiteboardPane } from "@/components/app/whiteboard-pane"; import { ChangesSettingsPopover } from "@/components/app/changes-settings-popover"; import { CenterPaneTabBar, @@ -136,6 +140,7 @@ export function AgentsView({ const { changesMatch, + whiteboardMatch, feedbackDetail, feedbackDetailRendered, handleFeedbackTransitionEnd, @@ -238,6 +243,10 @@ export function AgentsView({ ? (agents.find((agent) => agent.id === focusedAgentId) ?? null) : null; + const whiteboardAgentDrew = useAtomValue( + whiteboardAgentDrewAtomFamily(focusedAgentId ?? "") + ); + const { splitState, isSplit, exitSplit, updateSizes, handleTabDrop } = useSplitPane(focusedAgentId, isMobile); @@ -678,7 +687,13 @@ export function AgentsView({ {focusedAgent.name} { if (isSplit) { exitSplit(); @@ -686,6 +701,7 @@ export function AgentsView({ onTabChange(tab); }} diffStats={focusedDiffStats} + whiteboardAgentDrew={whiteboardAgentDrew} isSplit={isSplit} splitState={splitState} isMobile={isMobile} @@ -811,11 +827,18 @@ export function AgentsView({ <>
+ )} {stableTerminalContainerRef.current diff --git a/apps/web/src/components/app/center-pane-tab-bar.tsx b/apps/web/src/components/app/center-pane-tab-bar.tsx index e8ba9393..357c01aa 100644 --- a/apps/web/src/components/app/center-pane-tab-bar.tsx +++ b/apps/web/src/components/app/center-pane-tab-bar.tsx @@ -15,12 +15,14 @@ type TabDef = { const TABS: TabDef[] = [ { id: "terminal", label: "Terminal" }, { id: "changes", label: "Changes" }, + { id: "whiteboard", label: "Whiteboard" }, ]; type CenterPaneTabBarProps = { activeTab: CenterTab; onTabChange: (tab: CenterTab) => void; diffStats: DiffStats | null | undefined; + whiteboardAgentDrew?: boolean; isSplit: boolean; splitState: SplitPaneState; isMobile: boolean; @@ -30,6 +32,7 @@ export const CenterPaneTabBar = memo(function CenterPaneTabBar({ activeTab, onTabChange, diffStats, + whiteboardAgentDrew = false, isSplit, splitState, isMobile, @@ -81,6 +84,14 @@ export const CenterPaneTabBar = memo(function CenterPaneTabBar({ > {tab.label} + {tab.id === "whiteboard" && + whiteboardAgentDrew && + activeTab !== "whiteboard" ? ( + + ) : null} {activeTab === tab.id && !isSplit ? ( ) : null} diff --git a/apps/web/src/components/app/whiteboard-pane.tsx b/apps/web/src/components/app/whiteboard-pane.tsx new file mode 100644 index 00000000..3996cc16 --- /dev/null +++ b/apps/web/src/components/app/whiteboard-pane.tsx @@ -0,0 +1,44 @@ +import { lazy, Suspense, useEffect, useState } from "react"; +import { useAtom } from "jotai"; + +import { whiteboardAgentDrewAtomFamily } from "@/lib/store"; +import { cn } from "@/lib/utils"; + +const WhiteboardTab = lazy(() => import("@/components/app/whiteboard-tab")); + +type WhiteboardPaneProps = { + agentId: string | null; + active: boolean; +}; + +export function WhiteboardPane({ + agentId, + active, +}: WhiteboardPaneProps): JSX.Element | null { + const [opened, setOpened] = useState(false); + useEffect(() => { + if (active) setOpened(true); + }, [active]); + + const [agentDrew, setAgentDrew] = useAtom( + whiteboardAgentDrewAtomFamily(agentId ?? "") + ); + useEffect(() => { + if (active && agentId && agentDrew) setAgentDrew(false); + }, [active, agentId, agentDrew, setAgentDrew]); + + if (!opened || !agentId) return null; + return ( +
+ + Loading whiteboard… +
+ } + > + + +
+ ); +} diff --git a/apps/web/src/components/app/whiteboard-tab.tsx b/apps/web/src/components/app/whiteboard-tab.tsx new file mode 100644 index 00000000..bd96d29d --- /dev/null +++ b/apps/web/src/components/app/whiteboard-tab.tsx @@ -0,0 +1,331 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { + Excalidraw, + exportToBlob, + getSceneVersion, + restoreElements, +} from "@excalidraw/excalidraw"; +import type { + ExcalidrawImperativeAPI, + ExcalidrawInitialDataState, +} from "@excalidraw/excalidraw/types"; +import type { ExcalidrawElement } from "@excalidraw/excalidraw/element/types"; +import "@excalidraw/excalidraw/index.css"; + +import { + useWhiteboard, + whiteboardQueryKey, + type WhiteboardData, +} from "@/hooks/use-whiteboard"; +import { getThemeMode, useTheme } from "@/hooks/use-theme"; +import { api } from "@/lib/api"; + +declare global { + interface Window { + EXCALIDRAW_ASSET_PATH?: string; + } +} +window.EXCALIDRAW_ASSET_PATH = "/excalidraw/"; + +const SAVE_DEBOUNCE_MS = 1000; +const SNAPSHOT_DEBOUNCE_MS = 4000; + +const LIBRARY_FILES = [ + "/excalidraw/libraries/flow-chart-symbols.excalidrawlib", + "/excalidraw/libraries/architecture-diagram-components.excalidrawlib", + "/excalidraw/libraries/universal-ui-kit.excalidrawlib", + "/excalidraw/libraries/html-input-elements.excalidrawlib", +]; + +type WhiteboardTabProps = { + agentId: string; + visible: boolean; +}; + +export default function WhiteboardTab({ + agentId, + visible, +}: WhiteboardTabProps): JSX.Element { + const { data, isLoading, isError } = useWhiteboard(agentId); + + if (isLoading || (!data && !isError)) { + return ( +
+ Loading whiteboard… +
+ ); + } + if (isError || !data) { + return ( +
+ Could not load the whiteboard. +
+ ); + } + return ( + + ); +} + +function WhiteboardCanvas({ + agentId, + initial, + visible, +}: { + agentId: string; + initial: WhiteboardData; + visible: boolean; +}): JSX.Element { + const queryClient = useQueryClient(); + const { theme } = useTheme(); + const [excalidrawAPI, setExcalidrawAPI] = + useState(null); + + useEffect(() => { + if (!excalidrawAPI) return; + let cancelled = false; + void (async () => { + try { + const responses = await Promise.all( + LIBRARY_FILES.map((url) => fetch(url)) + ); + const blobs = await Promise.all(responses.map((r) => r.blob())); + if (cancelled) return; + for (const blob of blobs) { + await excalidrawAPI.updateLibrary({ + libraryItems: blob, + merge: true, + defaultStatus: "published", + openLibraryMenu: false, + }); + } + } catch { + // Library loading is best-effort — whiteboard works fine without them + } + })(); + return () => { + cancelled = true; + }; + }, [excalidrawAPI]); + + const [boardEmpty, setBoardEmpty] = useState( + initial.scene.elements.length === 0 + ); + const { data } = useWhiteboard(agentId); + + const versionRef = useRef(initial.version); + const sceneVersionRef = useRef( + getSceneVersion(initial.scene.elements as readonly ExcalidrawElement[]) + ); + const saveTimerRef = useRef(undefined); + const snapshotTimerRef = useRef(undefined); + const savingRef = useRef(false); + const pointerDownRef = useRef(false); + const pendingRemoteRef = useRef(null); + + const initialData = useMemo( + () => ({ + elements: initial.scene.elements as readonly ExcalidrawElement[], + scrollToContent: true, + }), + [initial] + ); + + const persistSnapshot = useCallback(async () => { + if (!excalidrawAPI) return; + const elements = excalidrawAPI.getSceneElements(); + if (elements.length === 0) { + try { + await api(`/api/v1/agents/${agentId}/whiteboard/snapshot`, { + method: "DELETE", + }); + } catch {} + return; + } + try { + const blob = await exportToBlob({ + elements, + appState: { + ...excalidrawAPI.getAppState(), + exportBackground: true, + }, + files: excalidrawAPI.getFiles(), + mimeType: "image/png", + }); + const form = new FormData(); + form.append("file", blob, "whiteboard.png"); + await api(`/api/v1/agents/${agentId}/whiteboard/snapshot`, { + method: "POST", + body: form, + }); + } catch { + // Snapshot is best-effort; the scene itself is already persisted. + } + }, [agentId, excalidrawAPI]); + + const persistScene = useCallback(async () => { + if (!excalidrawAPI || savingRef.current) return; + const elements = excalidrawAPI.getSceneElements(); + const sceneVersion = getSceneVersion(elements); + if (sceneVersion === sceneVersionRef.current) return; + savingRef.current = true; + try { + const res = await api<{ version: number }>( + `/api/v1/agents/${agentId}/whiteboard`, + { + method: "PUT", + body: JSON.stringify({ + scene: { elements }, + baseVersion: versionRef.current, + }), + } + ); + versionRef.current = res.version; + sceneVersionRef.current = sceneVersion; + queryClient.setQueryData( + whiteboardQueryKey(agentId), + (old) => + old + ? { + ...old, + scene: { elements: [...elements] }, + version: res.version, + } + : old + ); + if (snapshotTimerRef.current !== undefined) { + window.clearTimeout(snapshotTimerRef.current); + } + snapshotTimerRef.current = window.setTimeout(() => { + void persistSnapshot(); + }, SNAPSHOT_DEBOUNCE_MS); + } catch { + void queryClient.invalidateQueries({ + queryKey: whiteboardQueryKey(agentId), + exact: true, + }); + } finally { + savingRef.current = false; + } + }, [agentId, excalidrawAPI, persistSnapshot, queryClient]); + + const scheduleSave = useCallback(() => { + if (excalidrawAPI) { + setBoardEmpty(excalidrawAPI.getSceneElements().length === 0); + } + if (saveTimerRef.current !== undefined) { + window.clearTimeout(saveTimerRef.current); + } + saveTimerRef.current = window.setTimeout(() => { + saveTimerRef.current = undefined; + void persistScene(); + }, SAVE_DEBOUNCE_MS); + }, [excalidrawAPI, persistScene]); + + const applyRemote = useCallback( + (remote: WhiteboardData) => { + if (!excalidrawAPI) return; + const hydrated = restoreElements( + remote.scene.elements as ExcalidrawElement[], + excalidrawAPI.getSceneElements(), + { repairBindings: true } + ); + versionRef.current = remote.version; + sceneVersionRef.current = getSceneVersion(hydrated); + excalidrawAPI.updateScene({ elements: hydrated }); + setBoardEmpty(hydrated.length === 0); + if (snapshotTimerRef.current !== undefined) { + window.clearTimeout(snapshotTimerRef.current); + } + snapshotTimerRef.current = window.setTimeout(() => { + void persistSnapshot(); + }, SNAPSHOT_DEBOUNCE_MS); + }, + [excalidrawAPI, persistSnapshot] + ); + + useEffect(() => { + if (!data || data.version <= versionRef.current) return; + if (pointerDownRef.current) { + pendingRemoteRef.current = data; + return; + } + applyRemote(data); + }, [data, applyRemote]); + + useEffect(() => { + const onPointerUp = () => { + pointerDownRef.current = false; + const pending = pendingRemoteRef.current; + if (pending) { + pendingRemoteRef.current = null; + applyRemote(pending); + } + }; + window.addEventListener("pointerup", onPointerUp); + return () => window.removeEventListener("pointerup", onPointerUp); + }, [applyRemote]); + + useEffect(() => { + if (visible && excalidrawAPI) { + excalidrawAPI.refresh(); + } + }, [visible, excalidrawAPI]); + + useEffect(() => { + if (visible) return; + if (saveTimerRef.current !== undefined) { + window.clearTimeout(saveTimerRef.current); + saveTimerRef.current = undefined; + void persistScene(); + } + }, [visible, persistScene]); + + useEffect(() => { + return () => { + if (saveTimerRef.current !== undefined) { + window.clearTimeout(saveTimerRef.current); + void persistScene(); + } + if (snapshotTimerRef.current !== undefined) { + window.clearTimeout(snapshotTimerRef.current); + void persistSnapshot(); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( +
{ + pointerDownRef.current = true; + }} + > + {boardEmpty ? ( +
+

+ Sketch here — your agent can see this board. Ask it to “look + at the whiteboard” in the terminal. +

+
+ ) : null} + +
+ ); +} diff --git a/apps/web/src/hooks/use-agents-view-routing.ts b/apps/web/src/hooks/use-agents-view-routing.ts index 6fc17d42..d934e898 100644 --- a/apps/web/src/hooks/use-agents-view-routing.ts +++ b/apps/web/src/hooks/use-agents-view-routing.ts @@ -8,6 +8,7 @@ import { agentFeedbackRoute, agentReviewRoute, agentRoute, + agentWhiteboardRoute, } from "@/lib/agent-routes"; type UseAgentsViewRoutingOptions = { @@ -27,6 +28,7 @@ export function useAgentsViewRouting({ const feedbackMatch = useMatch("/agents/:agentId/feedback/:itemId"); const reviewMatch = useMatch("/agents/:agentId/review/:summaryAgentId"); const changesMatch = useMatch("/agents/:agentId/changes"); + const whiteboardMatch = useMatch("/agents/:agentId/whiteboard"); const itemId = feedbackMatch?.params.itemId; const summaryAgentId = reviewMatch?.params.summaryAgentId; @@ -74,12 +76,14 @@ export function useAgentsViewRouting({ }, [agents, agentsLoaded, navigate, routeAgentId, summaryAgentId]); const onTabChange = useCallback( - (tab: "terminal" | "changes") => { + (tab: "terminal" | "changes" | "whiteboard") => { if (!routeAgentId) return; navigate( tab === "changes" ? agentChangesRoute(routeAgentId) - : agentRoute(routeAgentId), + : tab === "whiteboard" + ? agentWhiteboardRoute(routeAgentId) + : agentRoute(routeAgentId), { replace: true } ); }, @@ -128,6 +132,7 @@ export function useAgentsViewRouting({ return { changesMatch: !!changesMatch, + whiteboardMatch: !!whiteboardMatch, feedbackDetail, feedbackDetailRendered, handleFeedbackTransitionEnd, diff --git a/apps/web/src/hooks/use-sse.ts b/apps/web/src/hooks/use-sse.ts index 1dce706a..a19a3a4a 100644 --- a/apps/web/src/hooks/use-sse.ts +++ b/apps/web/src/hooks/use-sse.ts @@ -1,5 +1,6 @@ import { useEffect, useRef } from "react"; import { useQueryClient } from "@tanstack/react-query"; +import { useStore } from "jotai"; import { type Agent, type AuthState, @@ -11,6 +12,7 @@ import { agentDiffQueryKey } from "@/hooks/use-agent-diff"; import { diffStatsQueryKey } from "@/hooks/use-agent-diff-stats"; import { sortAgentsByCreatedAtDesc } from "@/lib/agent-sort"; import { recordSSEEvent, recordSSEReconnect } from "@/lib/energy-metrics"; +import { whiteboardAgentDrewAtomFamily } from "@/lib/store"; import { showWebNotification } from "@/lib/web-notifications"; import { CACHED_RELEASE_INFO_QUERY_KEY, @@ -32,6 +34,12 @@ type UiEvent = } | { type: "agent.deleted"; agentId: string } | { type: "media.changed"; agentId: string } + | { + type: "whiteboard.changed"; + agentId: string; + version: number; + source: "user" | "agent"; + } | { type: "media.seen"; agentId: string; keys: string[] } | { type: "stream.started"; agentId: string } | { type: "stream.stopped"; agentId: string } @@ -70,6 +78,7 @@ function patchAgentHasStream( export function useSSE(authState: AuthState): void { const queryClient = useQueryClient(); + const jotaiStore = useStore(); const eventSourceRef = useRef(null); useEffect(() => { @@ -92,6 +101,7 @@ export function useSSE(authState: AuthState): void { void queryClient.invalidateQueries({ queryKey: ["jobs"] }); void queryClient.invalidateQueries({ queryKey: ["templates"] }); void queryClient.invalidateQueries({ queryKey: ["brain"] }); + void queryClient.invalidateQueries({ queryKey: ["whiteboard"] }); void queryClient.invalidateQueries({ queryKey: CACHED_RELEASE_INFO_QUERY_KEY, }); @@ -157,6 +167,20 @@ export function useSSE(authState: AuthState): void { return; } + if (payload.type === "whiteboard.changed") { + if (payload.source === "agent") { + void queryClient.invalidateQueries({ + queryKey: ["whiteboard", payload.agentId], + exact: true, + }); + jotaiStore.set( + whiteboardAgentDrewAtomFamily(payload.agentId), + true + ); + } + return; + } + if (payload.type === "media.seen") { const seen = new Set(payload.keys); queryClient.setQueryData( @@ -273,5 +297,5 @@ export function useSSE(authState: AuthState): void { document.removeEventListener("visibilitychange", onVisChange); closeSSE(); }; - }, [authState, queryClient]); + }, [authState, queryClient, jotaiStore]); } diff --git a/apps/web/src/hooks/use-theme.ts b/apps/web/src/hooks/use-theme.ts index df5085a2..36399a56 100644 --- a/apps/web/src/hooks/use-theme.ts +++ b/apps/web/src/hooks/use-theme.ts @@ -417,6 +417,10 @@ function applyTheme(themeId: ThemeId): void { } } +export function getThemeMode(themeId: ThemeId): "light" | "dark" { + return THEMES.find((t) => t.id === themeId)?.mode ?? "dark"; +} + export function useTheme(): { theme: ThemeId; setTheme: (id: ThemeId) => void; diff --git a/apps/web/src/hooks/use-whiteboard.ts b/apps/web/src/hooks/use-whiteboard.ts new file mode 100644 index 00000000..2150cc5f --- /dev/null +++ b/apps/web/src/hooks/use-whiteboard.ts @@ -0,0 +1,22 @@ +import { useQuery } from "@tanstack/react-query"; + +import { api } from "@/lib/api"; + +export type WhiteboardData = { + scene: { elements: unknown[] }; + version: number; + updatedAt: string | null; +}; + +export function whiteboardQueryKey(agentId: string): [string, string] { + return ["whiteboard", agentId]; +} + +export function useWhiteboard(agentId: string | null) { + return useQuery({ + queryKey: whiteboardQueryKey(agentId ?? ""), + queryFn: () => api(`/api/v1/agents/${agentId}/whiteboard`), + enabled: !!agentId, + staleTime: Infinity, + }); +} diff --git a/apps/web/src/lib/agent-routes.ts b/apps/web/src/lib/agent-routes.ts index 341813e7..914fb519 100644 --- a/apps/web/src/lib/agent-routes.ts +++ b/apps/web/src/lib/agent-routes.ts @@ -6,6 +6,10 @@ export function agentChangesRoute(agentId: string): string { return `/agents/${agentId}/changes`; } +export function agentWhiteboardRoute(agentId: string): string { + return `/agents/${agentId}/whiteboard`; +} + export function agentFeedbackRoute(agentId: string, itemId: number): string { return `/agents/${agentId}/feedback/${itemId}`; } diff --git a/apps/web/src/lib/store.ts b/apps/web/src/lib/store.ts index 55020051..19422f1a 100644 --- a/apps/web/src/lib/store.ts +++ b/apps/web/src/lib/store.ts @@ -92,6 +92,10 @@ export const dismissedReleaseToastAtomFamily = atomFamily((tag: string) => atomWithLocalStorage(`dispatch:dismissedReleaseToast:${tag}`, false) ); +export const whiteboardAgentDrewAtomFamily = atomFamily((_agentId: string) => + atom(false) +); + export type DiffViewType = "unified" | "split"; export const diffViewTypeAtom = atomWithLocalStorage( @@ -279,7 +283,7 @@ export function reconcileDiffViewStateStorage( // Split pane state — per-agent split/single mode and pane sizes // --------------------------------------------------------------------------- -export type CenterTab = "terminal" | "changes"; +export type CenterTab = "terminal" | "changes" | "whiteboard"; export type SplitPaneState = { mode: "single" | "split"; diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index debbf27f..bf90a2e5 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,11 +1,49 @@ -import { defineConfig } from "vite"; +import { defineConfig, type Plugin } from "vite"; import react from "@vitejs/plugin-react"; import { VitePWA } from "vite-plugin-pwa"; import path from "node:path"; -import { readFileSync } from "node:fs"; +import { readFileSync, cpSync, existsSync } from "node:fs"; const isProd = process.env.NODE_ENV === "production"; +function excalidrawAssets(): Plugin { + const fontsDir = path.resolve( + __dirname, + "node_modules/@excalidraw/excalidraw/dist/prod/fonts" + ); + const publicPrefix = "/excalidraw/fonts/"; + const skipFamilies = new Set(["Xiaolai"]); + return { + name: "excalidraw-assets", + configureServer(server) { + server.middlewares.use((req, res, next) => { + const url = (req.url ?? "").split("?")[0]; + if (!url.startsWith(publicPrefix)) return next(); + const rel = decodeURIComponent(url.slice(publicPrefix.length)); + const file = path.join(fontsDir, rel); + if (!file.startsWith(fontsDir) || !existsSync(file)) { + res.statusCode = 404; + return res.end(); + } + res.setHeader( + "Content-Type", + file.endsWith(".woff2") ? "font/woff2" : "font/woff" + ); + return res.end(readFileSync(file)); + }); + }, + writeBundle(options) { + const outDir = options.dir ?? path.resolve(__dirname, "dist"); + cpSync(fontsDir, path.join(outDir, "excalidraw/fonts"), { + recursive: true, + filter: (src) => + !skipFamilies.has(path.basename(path.dirname(src))) && + !skipFamilies.has(path.basename(src)), + }); + }, + }; +} + // Bake the workspace version into the bundle. The web client compares // this against the `X-Dispatch-Version` response header to detect a // stale bundle after a server self-update. @@ -35,6 +73,7 @@ export default defineConfig({ }, plugins: [ react(), + excalidrawAssets(), isProd && VitePWA({ registerType: "prompt", diff --git a/docs/superpowers/plans/2026-07-11-whiteboard-libraries.md b/docs/superpowers/plans/2026-07-11-whiteboard-libraries.md new file mode 100644 index 00000000..7e186e66 --- /dev/null +++ b/docs/superpowers/plans/2026-07-11-whiteboard-libraries.md @@ -0,0 +1,1262 @@ +# Whiteboard Libraries Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add pre-loaded Excalidraw shape libraries (flowchart, architecture, wireframe, UI inputs) with agent MCP tools to browse and insert library items onto the whiteboard. + +**Architecture:** Four `.excalidrawlib` v2 files are checked into `apps/web/public/excalidraw/libraries/` as static assets. At server startup, a `LibraryCatalog` class parses these files, computes bounding boxes and anchor points for each item, and caches the catalog in memory. Two new MCP tools (`whiteboard_library`, `whiteboard_insert`) let agents browse and place library items. The client lazy-fetches the files on whiteboard mount and loads them into Excalidraw's built-in library panel via `updateLibrary()`. + +**Tech Stack:** Excalidraw library format v2 (JSON), Zod v4 schemas, Fastify, React Query, `@excalidraw/excalidraw` API. + +## Global Constraints + +- Library files are MIT licensed (Copyright 2020 Excalidraw) — include attribution. +- All `.excalidrawlib` files must be v2 format (containing `libraryItems` array with named items). +- Library items are static — no runtime fetching from external CDNs. +- The catalog is computed once at startup and cached in memory. +- `whiteboard_library` compact mode must be token-efficient (~30 tokens per item). +- `whiteboard_insert` reuses the existing `mergeElements` + `saveWhiteboard` persistence path. + +## File Structure + +### New files + +| File | Responsibility | +| ------------------------------------------------------------------------------------ | ---------------------------------------------------- | +| `apps/web/public/excalidraw/libraries/flow-chart-symbols.excalidrawlib` | Flowchart library (15 items) | +| `apps/web/public/excalidraw/libraries/architecture-diagram-components.excalidrawlib` | Architecture library (11 items) | +| `apps/web/public/excalidraw/libraries/universal-ui-kit.excalidrawlib` | UI wireframe library (22 items) | +| `apps/web/public/excalidraw/libraries/html-input-elements.excalidrawlib` | HTML input library (8 items) | +| `apps/web/public/excalidraw/libraries/NOTICES` | MIT attribution for library files | +| `apps/server/src/shared/whiteboard-library.ts` | `LibraryCatalog` class: parse, compute bounds, stamp | +| `apps/server/test/whiteboard-library.test.ts` | Unit tests for catalog parsing, bounds, stamp | + +### Modified files + +| File | Change | +| --------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `apps/server/src/shared/mcp/whiteboard-tools.ts` | Register `whiteboard_library` and `whiteboard_insert` tools; extend `WhiteboardToolsContext` | +| `apps/server/src/server/mcp-whiteboard-handlers.ts` | Add `libraryList`, `libraryGet`, `insertLibraryItem` handler methods | +| `apps/server/src/shared/mcp/server.ts` | Wire new handlers into MCP context object | +| `apps/web/src/components/app/whiteboard-tab.tsx` | Lazy-load libraries on mount via `updateLibrary()` | +| `e2e/whiteboard.spec.ts` | E2E tests for `whiteboard_library` and `whiteboard_insert` MCP tools | + +--- + +### Task 1: Download Library Files and Add Attribution + +**Files:** + +- Create: `apps/web/public/excalidraw/libraries/flow-chart-symbols.excalidrawlib` +- Create: `apps/web/public/excalidraw/libraries/architecture-diagram-components.excalidrawlib` +- Create: `apps/web/public/excalidraw/libraries/universal-ui-kit.excalidrawlib` +- Create: `apps/web/public/excalidraw/libraries/html-input-elements.excalidrawlib` +- Create: `apps/web/public/excalidraw/libraries/NOTICES` + +**Interfaces:** + +- Consumes: Nothing +- Produces: Static `.excalidrawlib` v2 JSON files at the paths above, served by Vite at `/excalidraw/libraries/` + +- [ ] **Step 1: Create the libraries directory** + +```bash +mkdir -p apps/web/public/excalidraw/libraries +``` + +- [ ] **Step 2: Download the four library files** + +Download each file from the excalidraw-libraries GitHub repo: + +```bash +curl -L -o apps/web/public/excalidraw/libraries/flow-chart-symbols.excalidrawlib \ + "https://raw.githubusercontent.com/excalidraw/excalidraw-libraries/main/libraries/finfin/flow-chart-symbols.excalidrawlib" + +curl -L -o apps/web/public/excalidraw/libraries/architecture-diagram-components.excalidrawlib \ + "https://raw.githubusercontent.com/excalidraw/excalidraw-libraries/main/libraries/anna-pastushko/architecture-diagram-components.excalidrawlib" + +curl -L -o apps/web/public/excalidraw/libraries/universal-ui-kit.excalidrawlib \ + "https://raw.githubusercontent.com/excalidraw/excalidraw-libraries/main/libraries/manuelernestog/universal-ui-kit.excalidrawlib" + +curl -L -o apps/web/public/excalidraw/libraries/html-input-elements.excalidrawlib \ + "https://raw.githubusercontent.com/excalidraw/excalidraw-libraries/main/libraries/marwinburesch/html-input-elements.excalidrawlib" +``` + +- [ ] **Step 3: Validate each file is v2 format** + +Each file must be valid JSON with `libraryItems` array where each item has a `name` field: + +```bash +for f in apps/web/public/excalidraw/libraries/*.excalidrawlib; do + echo "=== $(basename "$f") ===" + node -e " + const lib = JSON.parse(require('fs').readFileSync('$f', 'utf8')); + if (lib.version !== 2) throw new Error('Not v2'); + if (!Array.isArray(lib.libraryItems)) throw new Error('No libraryItems'); + const named = lib.libraryItems.filter(i => i.name); + console.log('Items: ' + lib.libraryItems.length + ', Named: ' + named.length); + named.forEach(i => console.log(' - ' + i.name)); + " +done +``` + +Expected: All files parse successfully, all items have names. If any file is v1 format (has `library` instead of `libraryItems`), it cannot be used — find an alternative v2 source or manually convert it. + +- [ ] **Step 4: Create NOTICES file** + +Create `apps/web/public/excalidraw/libraries/NOTICES`: + +``` +Excalidraw Libraries +==================== + +The .excalidrawlib files in this directory are sourced from the +excalidraw-libraries project: + + https://github.com/excalidraw/excalidraw-libraries + +Licensed under the MIT License: + + Copyright (c) 2020 Excalidraw + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Individual libraries: + - flow-chart-symbols.excalidrawlib — by finfin + - architecture-diagram-components.excalidrawlib — by anna-pastushko + - universal-ui-kit.excalidrawlib — by manuelernestog + - html-input-elements.excalidrawlib — by marwinburesch +``` + +- [ ] **Step 5: Commit** + +```bash +git add apps/web/public/excalidraw/libraries/ +git commit -m "feat: add excalidraw library files for whiteboard (flowchart, architecture, wireframe, UI inputs)" +``` + +--- + +### Task 2: LibraryCatalog Server-Side Class + +**Files:** + +- Create: `apps/server/src/shared/whiteboard-library.ts` +- Create: `apps/server/test/whiteboard-library.test.ts` + +**Interfaces:** + +- Consumes: `.excalidrawlib` v2 JSON files on disk +- Produces: + - `LibraryCatalog` class with methods: + - `static async fromDirectory(dir: string): Promise` — parse all `.excalidrawlib` files in a directory + - `list(category?: string): CompactLibraryItem[]` — compact list for browse mode + - `get(name: string): DetailedLibraryItem | null` — full detail for one item (case-insensitive) + - `stamp(name: string, x: number, y: number, scale: number): StampResult` — clone elements with fresh IDs, scale, and offset + - Exported types: `CompactLibraryItem`, `DetailedLibraryItem`, `StampResult` + +- [ ] **Step 1: Write the failing tests** + +Create `apps/server/test/whiteboard-library.test.ts`: + +```typescript +import { describe, expect, it } from "vitest"; +import path from "node:path"; +import { LibraryCatalog } from "../src/shared/whiteboard-library.js"; + +// Use the actual library files checked in during Task 1 +const LIBRARIES_DIR = path.resolve( + __dirname, + "../../web/public/excalidraw/libraries" +); + +describe("LibraryCatalog", () => { + describe("fromDirectory", () => { + it("loads all library files and builds catalog", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + expect(all.length).toBeGreaterThanOrEqual(40); + // Every item has required fields + for (const item of all) { + expect(item.name).toBeTruthy(); + expect(item.category).toBeTruthy(); + expect(item.width).toBeGreaterThan(0); + expect(item.height).toBeGreaterThan(0); + } + }); + + it("assigns correct categories from filenames", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const categories = new Set(catalog.list().map((i) => i.category)); + expect(categories).toContain("flowchart"); + expect(categories).toContain("architecture"); + expect(categories).toContain("wireframe"); + expect(categories).toContain("ui-input"); + }); + }); + + describe("list", () => { + it("filters by category", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const flowchart = catalog.list("flowchart"); + expect(flowchart.length).toBeGreaterThan(0); + expect(flowchart.every((i) => i.category === "flowchart")).toBe(true); + }); + + it("returns all items when no category specified", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const flowchart = catalog.list("flowchart"); + expect(all.length).toBeGreaterThan(flowchart.length); + }); + }); + + describe("get", () => { + it("returns detailed item with anchors but no raw elements", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const detail = catalog.get(all[0].name); + expect(detail).not.toBeNull(); + expect(detail!.anchors).toBeDefined(); + expect(detail!.anchors.top).toHaveLength(2); + expect(detail!.anchors.bottom).toHaveLength(2); + expect(detail!.anchors.left).toHaveLength(2); + expect(detail!.anchors.right).toHaveLength(2); + expect(detail!.elementCount).toBeGreaterThan(0); + expect(detail!.description).toBeTruthy(); + // Elements are internal — never leaked to callers + expect((detail as Record).elements).toBeUndefined(); + }); + + it("is case-insensitive", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const name = all[0].name; + const lower = catalog.get(name.toLowerCase()); + const upper = catalog.get(name.toUpperCase()); + expect(lower).not.toBeNull(); + expect(upper).not.toBeNull(); + expect(lower!.name).toBe(upper!.name); + }); + + it("returns null for unknown name", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + expect(catalog.get("nonexistent-item-xyz")).toBeNull(); + }); + }); + + describe("stamp", () => { + it("returns elements offset to target position", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const result = catalog.stamp(all[0].name, 200, 300, 1); + expect(result.elements.length).toBeGreaterThan(0); + // All elements should have fresh string IDs + const ids = result.elements.map( + (e) => (e as Record).id as string + ); + expect(new Set(ids).size).toBe(ids.length); // unique + // Bounding box top-left should be near (200, 300) + const xs = result.elements.map( + (e) => (e as Record).x as number + ); + const ys = result.elements.map( + (e) => (e as Record).y as number + ); + expect(Math.min(...xs)).toBeCloseTo(200, 0); + expect(Math.min(...ys)).toBeCloseTo(300, 0); + }); + + it("scales elements", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + const result1x = catalog.stamp(all[0].name, 0, 0, 1); + const result2x = catalog.stamp(all[0].name, 0, 0, 2); + // At 2x, bounding box should be roughly double + const width1 = Math.max( + ...result1x.elements.map( + (e) => + ((e as Record).x as number) + + ((e as Record).width as number) + ) + ); + const width2 = Math.max( + ...result2x.elements.map( + (e) => + ((e as Record).x as number) + + ((e as Record).width as number) + ) + ); + expect(width2).toBeCloseTo(width1 * 2, -1); + }); + + it("replaces text when label is provided", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + // Use the first item — stamp will apply label to any text element it contains + const textItemName = all[0].name; + const result = catalog.stamp(textItemName, 0, 0, 1, "Custom Label"); + const textEls = result.elements.filter( + (e) => (e as Record).type === "text" + ); + expect(textEls.length).toBeGreaterThan(0); + expect((textEls[0] as Record).text).toBe("Custom Label"); + expect((textEls[0] as Record).originalText).toBe( + "Custom Label" + ); + }); + + it("throws for unknown item", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + expect(() => catalog.stamp("nonexistent", 0, 0, 1)).toThrow(); + }); + + it("maintains internal bindings with remapped IDs", async () => { + const catalog = await LibraryCatalog.fromDirectory(LIBRARIES_DIR); + const all = catalog.list(); + // Stamp each item until we find one that has containerId bindings + let boundItemName: string | null = null; + for (const item of all) { + const stamped = catalog.stamp(item.name, 0, 0, 1); + if ( + stamped.elements.some( + (e) => (e as Record).containerId + ) + ) { + boundItemName = item.name; + break; + } + } + if (!boundItemName) return; + const result = catalog.stamp(boundItemName, 0, 0, 1); + const idSet = new Set( + result.elements.map((e) => (e as Record).id as string) + ); + // Every containerId should reference an ID that exists in the stamped set + for (const el of result.elements) { + const containerId = (el as Record) + .containerId as string; + if (containerId) { + expect(idSet.has(containerId)).toBe(true); + } + } + }); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +```bash +pnpm run test -- apps/server/test/whiteboard-library.test.ts +``` + +Expected: FAIL — module `../src/shared/whiteboard-library.js` does not exist. + +- [ ] **Step 3: Implement LibraryCatalog** + +Create `apps/server/src/shared/whiteboard-library.ts`: + +```typescript +import { readdir, readFile } from "node:fs/promises"; +import path from "node:path"; +import { randomUUID } from "node:crypto"; + +type RawElement = Record; + +type LibraryFileItem = { + id: string; + name?: string; + status: string; + elements: RawElement[]; + created: number; +}; + +type Anchors = { + top: [number, number]; + bottom: [number, number]; + left: [number, number]; + right: [number, number]; +}; + +export type CompactLibraryItem = { + name: string; + category: string; + width: number; + height: number; +}; + +export type DetailedLibraryItem = { + name: string; + category: string; + description: string; + width: number; + height: number; + elementCount: number; + anchors: Anchors; +}; + +type CatalogEntry = DetailedLibraryItem & { + elements: RawElement[]; +}; + +export type StampResult = { + elements: RawElement[]; + addedIds: string[]; +}; + +const FILENAME_TO_CATEGORY: Record = { + "flow-chart-symbols": "flowchart", + "architecture-diagram-components": "architecture", + "universal-ui-kit": "wireframe", + "html-input-elements": "ui-input", +}; + +function computeBounds(elements: RawElement[]): { + minX: number; + minY: number; + maxX: number; + maxY: number; + width: number; + height: number; +} { + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + for (const el of elements) { + const x = typeof el.x === "number" ? el.x : 0; + const y = typeof el.y === "number" ? el.y : 0; + const w = typeof el.width === "number" ? el.width : 0; + const h = typeof el.height === "number" ? el.height : 0; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x + w); + maxY = Math.max(maxY, y + h); + } + return { + minX, + minY, + maxX, + maxY, + width: Math.round(maxX - minX), + height: Math.round(maxY - minY), + }; +} + +function computeAnchors(width: number, height: number): Anchors { + return { + top: [Math.round(width / 2), 0], + bottom: [Math.round(width / 2), height], + left: [0, Math.round(height / 2)], + right: [width, Math.round(height / 2)], + }; +} + +export class LibraryCatalog { + private items: Map; + + private constructor(items: Map) { + this.items = items; + } + + static async fromDirectory(dir: string): Promise { + const items = new Map(); + const files = await readdir(dir); + for (const file of files) { + if (!file.endsWith(".excalidrawlib")) continue; + const filePath = path.join(dir, file); + const raw = JSON.parse(await readFile(filePath, "utf8")) as { + version?: number; + libraryItems?: LibraryFileItem[]; + }; + if (raw.version !== 2 || !Array.isArray(raw.libraryItems)) continue; + const stem = file.replace(/\.excalidrawlib$/, ""); + const category = FILENAME_TO_CATEGORY[stem] ?? stem; + for (const libItem of raw.libraryItems) { + const name = libItem.name?.trim(); + if ( + !name || + !Array.isArray(libItem.elements) || + libItem.elements.length === 0 + ) + continue; + const bounds = computeBounds(libItem.elements); + const anchors = computeAnchors(bounds.width, bounds.height); + items.set(name.toLowerCase(), { + name, + category, + description: name, + width: bounds.width, + height: bounds.height, + elementCount: libItem.elements.length, + anchors, + elements: libItem.elements, + }); + } + } + return new LibraryCatalog(items); + } + + list(category?: string): CompactLibraryItem[] { + const result: CompactLibraryItem[] = []; + for (const item of this.items.values()) { + if (category && item.category !== category) continue; + result.push({ + name: item.name, + category: item.category, + width: item.width, + height: item.height, + }); + } + return result; + } + + get(name: string): DetailedLibraryItem | null { + const entry = this.items.get(name.toLowerCase()); + if (!entry) return null; + const { elements: _, ...detail } = entry; + return detail; + } + + stamp( + name: string, + x: number, + y: number, + scale: number, + label?: string + ): StampResult { + const entry = this.items.get(name.toLowerCase()); + if (!entry) throw new Error(`Library item "${name}" not found.`); + + const bounds = computeBounds(entry.elements); + const idMap = new Map(); + const addedIds: string[] = []; + + // Build ID mapping first so we can remap bindings + for (const el of entry.elements) { + const oldId = el.id as string; + const newId = randomUUID().slice(0, 8); + idMap.set(oldId, newId); + addedIds.push(newId); + } + + let labelApplied = false; + const elements: RawElement[] = entry.elements.map((el) => { + const cloned: RawElement = { ...el }; + // Assign fresh ID + cloned.id = idMap.get(el.id as string)!; + // Scale and offset coordinates + const origX = typeof el.x === "number" ? el.x : 0; + const origY = typeof el.y === "number" ? el.y : 0; + cloned.x = x + (origX - bounds.minX) * scale; + cloned.y = y + (origY - bounds.minY) * scale; + if (typeof el.width === "number") cloned.width = el.width * scale; + if (typeof el.height === "number") cloned.height = el.height * scale; + + // Scale points for arrows/lines + if (Array.isArray(el.points)) { + cloned.points = (el.points as number[][]).map(([px, py]) => [ + px * scale, + py * scale, + ]); + } + + // Remap containerId + if (typeof el.containerId === "string" && idMap.has(el.containerId)) { + cloned.containerId = idMap.get(el.containerId)!; + } + // Remap frameId + if (typeof el.frameId === "string" && idMap.has(el.frameId)) { + cloned.frameId = idMap.get(el.frameId)!; + } + // Remap boundElements + if (Array.isArray(el.boundElements)) { + cloned.boundElements = ( + el.boundElements as Array<{ id: string; type: string }> + ).map((be) => ({ + ...be, + id: idMap.get(be.id) ?? be.id, + })); + } + // Remap arrow bindings + if (typeof el.startBinding === "object" && el.startBinding !== null) { + const sb = el.startBinding as Record; + if (typeof sb.elementId === "string" && idMap.has(sb.elementId)) { + cloned.startBinding = { ...sb, elementId: idMap.get(sb.elementId)! }; + } + } + if (typeof el.endBinding === "object" && el.endBinding !== null) { + const eb = el.endBinding as Record; + if (typeof eb.elementId === "string" && idMap.has(eb.elementId)) { + cloned.endBinding = { ...eb, elementId: idMap.get(eb.elementId)! }; + } + } + // Remap groupIds + if (Array.isArray(el.groupIds)) { + cloned.groupIds = (el.groupIds as string[]).map( + (gid) => idMap.get(gid) ?? gid + ); + } + + // Replace label text + if (label && !labelApplied && el.type === "text") { + cloned.text = label; + cloned.originalText = label; + labelApplied = true; + } + + return cloned; + }); + + return { elements, addedIds }; + } +} +``` + +- [ ] **Step 4: Run tests to verify they pass** + +```bash +pnpm run test -- apps/server/test/whiteboard-library.test.ts +``` + +Expected: All tests PASS. + +- [ ] **Step 5: Commit** + +```bash +git add apps/server/src/shared/whiteboard-library.ts apps/server/test/whiteboard-library.test.ts +git commit -m "feat: add LibraryCatalog class for parsing excalidraw libraries and stamping items" +``` + +--- + +### Task 3: MCP Tools — whiteboard_library and whiteboard_insert + +**Files:** + +- Modify: `apps/server/src/shared/mcp/whiteboard-tools.ts` — register new tools +- Modify: `apps/server/src/server/mcp-whiteboard-handlers.ts` — add handler methods +- Modify: `apps/server/src/shared/mcp/server.ts` — wire new handlers into context +- Modify: `e2e/whiteboard.spec.ts` — E2E tests for the new MCP tools + +**Interfaces:** + +- Consumes: + - `LibraryCatalog` from `apps/server/src/shared/whiteboard-library.ts`: `list(category?)`, `get(name)`, `stamp(name, x, y, scale, label?)` + - Existing whiteboard persistence: `loadWhiteboard`, `saveWhiteboard`, `mergeElements` from handlers + - `WhiteboardToolsContext` type and `registerWhiteboardTools` function +- Produces: + - `whiteboard_library` MCP tool (browse/detail) + - `whiteboard_insert` MCP tool (place library item) + +- [ ] **Step 1: Add handler methods to mcp-whiteboard-handlers.ts** + +Add imports at the top of `apps/server/src/server/mcp-whiteboard-handlers.ts`: + +```typescript +import type { LibraryCatalog } from "../shared/whiteboard-library.js"; +``` + +Extend `CreateWhiteboardHandlersDeps` to include an optional `libraryCatalog`: + +```typescript +type CreateWhiteboardHandlersDeps = { + pool: Pool; + mediaRoot: string; + agentManager: AgentManager; + publishUiEvent: PublishUiEvent; + libraryCatalog?: LibraryCatalog; +}; +``` + +Add two new methods to the returned object from `createWhiteboardHandlers`: + +```typescript + libraryList( + category?: string, + name?: string + ): CompactLibraryItem[] | DetailedLibraryItem | null { + if (!libraryCatalog) return []; + if (name) return libraryCatalog.get(name); + return libraryCatalog.list(category); + }, + + async insertLibraryItem( + agentId: string, + name: string, + x: number, + y: number, + scale: number, + label?: string + ): Promise { + const agent = await agentManager.getAgent(agentId); + if (!agent) throw new Error("Agent not found."); + if (!libraryCatalog) throw new Error("Library catalog not available."); + + const stamped = libraryCatalog.stamp(name, x, y, scale, label); + + for (let attempt = 0; attempt < 3; attempt++) { + const row = await loadWhiteboard(pool, agentId); + const baseVersion = row ? Number(row.version) : 0; + const existing = row ? row.scene.elements : []; + + const merged = mergeElements(existing, stamped.elements, []); + + if (merged.elements.length > MAX_ELEMENTS) { + throw new Error(`Board is full (max ${MAX_ELEMENTS} elements).`); + } + + const saved = await saveWhiteboard( + pool, + agentId, + { elements: merged.elements }, + baseVersion, + "agent" + ); + if (saved) { + publishUiEvent({ + type: "whiteboard.changed", + agentId, + version: saved.version, + source: "agent", + }); + return { + version: saved.version, + elementCount: merged.elements.length, + addedIds: stamped.addedIds, + updatedIds: [], + deletedIds: [], + elements: simplifyElements(merged.elements), + }; + } + } + throw new Error( + "Whiteboard is being edited concurrently; try again in a moment." + ); + }, +``` + +Note: `mergeElements` is a module-private function in this file. The `insertLibraryItem` method has access to it because it lives in the same closure returned by `createWhiteboardHandlers`. + +- [ ] **Step 2: Extend WhiteboardToolsContext and register new tools** + +In `apps/server/src/shared/mcp/whiteboard-tools.ts`, add to the imports: + +```typescript +import type { + WhiteboardGetResult, + WhiteboardUpdateResult, +} from "../whiteboard.js"; +import type { + CompactLibraryItem, + DetailedLibraryItem, +} from "../whiteboard-library.js"; +``` + +Extend `WhiteboardToolsContext`: + +```typescript +export type WhiteboardToolsContext = { + agentId: string; + getWhiteboard?: (agentId: string) => Promise; + updateWhiteboard?: ( + agentId: string, + elements: unknown[], + deleteIds: string[] + ) => Promise; + clearWhiteboard?: (agentId: string) => Promise; + libraryList?: ( + category?: string, + name?: string + ) => CompactLibraryItem[] | DetailedLibraryItem | null; + insertLibraryItem?: ( + agentId: string, + name: string, + x: number, + y: number, + scale: number, + label?: string + ) => Promise; +}; +``` + +Add tool registrations at the end of `registerWhiteboardTools`, before the closing `}`: + +```typescript +if (allowed.has("whiteboard_library") && context.libraryList) { + const libraryList = context.libraryList; + + server.registerTool( + "whiteboard_library", + { + description: + "Browse the library of pre-built shapes and icons available for the whiteboard. " + + "Call with no arguments for a compact list of all items (name, category, dimensions). " + + "Call with `name` to get full detail (anchors, element count) for layout planning. " + + "Call with `category` to filter: 'flowchart', 'architecture', 'wireframe', 'ui-input'.", + inputSchema: { + category: z + .string() + .optional() + .describe( + "Filter by category: 'flowchart', 'architecture', 'wireframe', 'ui-input'" + ), + name: z + .string() + .optional() + .describe( + "Get full detail for a specific item by name (case-insensitive). " + + "Takes priority over category." + ), + }, + }, + async (args) => { + try { + const result = libraryList(args.category, args.name); + if (args.name && result === null) { + return { + content: [ + { + type: "text", + text: `Library item "${args.name}" not found. Call whiteboard_library with no arguments to see all available items.`, + }, + ], + isError: true, + }; + } + return { + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], + }; + } catch (error) { + return toToolError(error); + } + } + ); +} + +if (allowed.has("whiteboard_insert") && context.insertLibraryItem) { + const agentId = context.agentId; + const insertLibraryItem = context.insertLibraryItem; + + server.registerTool( + "whiteboard_insert", + { + description: + "Place a pre-built library shape onto the whiteboard at a given position. " + + "Use `whiteboard_library` first to browse available items and get their dimensions " + + "for layout planning. Items are scaled and positioned, with optional text label replacement.", + inputSchema: { + name: z + .string() + .describe( + "Library item name (case-insensitive). Use whiteboard_library to see available names." + ), + x: z.number().describe("X coordinate for the item's top-left corner."), + y: z.number().describe("Y coordinate for the item's top-left corner."), + scale: z + .number() + .min(0.1) + .max(10) + .default(1) + .describe( + "Size multiplier. 1 = original size, 0.5 = half, 2 = double." + ), + label: z + .string() + .optional() + .describe( + "Replace the item's primary text label (e.g. place a 'Server' icon but label it 'Auth Service')." + ), + }, + }, + async (args) => { + try { + const result = await insertLibraryItem( + agentId, + args.name, + args.x, + args.y, + args.scale, + args.label + ); + const summary = { + ok: true, + version: result.version, + elementCount: result.elementCount, + addedIds: result.addedIds, + }; + return { + content: [{ type: "text", text: JSON.stringify(summary, null, 2) }], + }; + } catch (error) { + return toToolError(error); + } + } + ); +} +``` + +- [ ] **Step 3: Wire into MCP server context** + +In `apps/server/src/shared/mcp/server.ts`: + +1. Add to the context type (around line 423, after `clearWhiteboard`): + +```typescript + libraryList?: ( + category?: string, + name?: string + ) => CompactLibraryItem[] | DetailedLibraryItem | null; + insertLibraryItem?: ( + agentId: string, + name: string, + x: number, + y: number, + scale: number, + label?: string + ) => Promise; +``` + +2. Add the import for types at the top: + +```typescript +import type { + CompactLibraryItem, + DetailedLibraryItem, +} from "../whiteboard-library.js"; +``` + +3. In the whiteboard tools registration block (around line 582), add the new context fields: + +```typescript +registerWhiteboardTools(server, allowed, { + agentId: context.agent.id, + getWhiteboard: context.getWhiteboard, + updateWhiteboard: context.updateWhiteboard, + clearWhiteboard: context.clearWhiteboard, + libraryList: context.libraryList, + insertLibraryItem: context.insertLibraryItem, +}); +``` + +- [ ] **Step 4: Initialize LibraryCatalog at server startup and pass to handlers** + +Find where `createWhiteboardHandlers` is called in `apps/server/src/server/mcp-handlers.ts` (line ~155). The catalog needs to be initialized before this call. + +Add import at the top: + +```typescript +import { LibraryCatalog } from "../shared/whiteboard-library.js"; +``` + +The function that calls `createWhiteboardHandlers` receives `deps`. Add `libraryCatalog` to those deps. Initialize it near the top of the function: + +```typescript +const whiteboardHandlers = createWhiteboardHandlers({ + pool, + mediaRoot, + agentManager, + publishUiEvent, + libraryCatalog: deps.libraryCatalog, +}); +``` + +The `libraryCatalog` must be created once at app startup (before the MCP handlers factory is called) and passed through. Find the server bootstrap code that creates the deps object and add: + +```typescript +import path from "node:path"; + +// In the bootstrap/startup function: +const libraryCatalog = await LibraryCatalog.fromDirectory( + path.resolve(__dirname, "../../../web/public/excalidraw/libraries") +); +``` + +Pass it through the deps chain to `createWhiteboardHandlers`. The exact wiring depends on the server bootstrap structure — follow the existing pattern for how `pool`, `mediaRoot`, and `agentManager` are threaded through. + +- [ ] **Step 5: Add E2E tests** + +Add to `e2e/whiteboard.spec.ts`: + +```typescript +test("whiteboard MCP tool: whiteboard_library returns catalog", async ({ + request, +}) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-lib-${Date.now()}`, + }); + + // List all items + const listJson = await callMcpTool( + request, + agent.id, + "whiteboard_library", + {} + ); + const listResult = listJson.result as { + content?: Array<{ text?: string }>; + }; + const items = JSON.parse(listResult?.content?.[0]?.text ?? "[]") as Array<{ + name: string; + category: string; + width: number; + height: number; + }>; + expect(items.length).toBeGreaterThanOrEqual(40); + expect(items[0].name).toBeTruthy(); + expect(items[0].width).toBeGreaterThan(0); + + // Filter by category + const archJson = await callMcpTool(request, agent.id, "whiteboard_library", { + category: "architecture", + }); + const archResult = archJson.result as { + content?: Array<{ text?: string }>; + }; + const archItems = JSON.parse( + archResult?.content?.[0]?.text ?? "[]" + ) as Array<{ category: string }>; + expect(archItems.length).toBeGreaterThan(0); + expect(archItems.every((i) => i.category === "architecture")).toBe(true); + + // Get detail for a specific item + const detailJson = await callMcpTool( + request, + agent.id, + "whiteboard_library", + { name: items[0].name } + ); + const detailResult = detailJson.result as { + content?: Array<{ text?: string }>; + }; + const detail = JSON.parse(detailResult?.content?.[0]?.text ?? "{}") as { + name: string; + anchors: { + top: number[]; + bottom: number[]; + left: number[]; + right: number[]; + }; + elementCount: number; + }; + expect(detail.name).toBe(items[0].name); + expect(detail.anchors.top).toHaveLength(2); + expect(detail.elementCount).toBeGreaterThan(0); +}); + +test("whiteboard MCP tool: whiteboard_insert places library item", async ({ + request, +}) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-insert-${Date.now()}`, + }); + + // First get available items + const listJson = await callMcpTool( + request, + agent.id, + "whiteboard_library", + {} + ); + const listResult = listJson.result as { + content?: Array<{ text?: string }>; + }; + const items = JSON.parse(listResult?.content?.[0]?.text ?? "[]") as Array<{ + name: string; + }>; + const itemName = items[0].name; + + // Insert the item + const insertJson = await callMcpTool(request, agent.id, "whiteboard_insert", { + name: itemName, + x: 100, + y: 200, + scale: 1, + }); + const insertResult = insertJson.result as { + content?: Array<{ text?: string }>; + }; + const insertData = JSON.parse(insertResult?.content?.[0]?.text ?? "{}") as { + ok: boolean; + addedIds: string[]; + elementCount: number; + }; + expect(insertData.ok).toBe(true); + expect(insertData.addedIds.length).toBeGreaterThan(0); + expect(insertData.elementCount).toBeGreaterThan(0); + + // Verify the elements are on the board via REST + const getRes = await request.get(`/api/v1/agents/${agent.id}/whiteboard`, { + headers: AUTH_HEADER, + }); + const boardData = (await getRes.json()) as { + scene: { elements: Array<{ id: string }> }; + }; + expect(boardData.scene.elements.length).toBe(insertData.elementCount); +}); + +test("whiteboard MCP tool: whiteboard_insert with label and scale", async ({ + request, +}) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-insert-label-${Date.now()}`, + }); + + // Find an item with text elements via library detail + const listJson = await callMcpTool( + request, + agent.id, + "whiteboard_library", + {} + ); + const items = JSON.parse( + (listJson.result as { content?: Array<{ text?: string }> })?.content?.[0] + ?.text ?? "[]" + ) as Array<{ name: string }>; + + // Insert at 2x scale with a custom label + const insertJson = await callMcpTool(request, agent.id, "whiteboard_insert", { + name: items[0].name, + x: 50, + y: 50, + scale: 2, + label: "My Service", + }); + const insertData = JSON.parse( + (insertJson.result as { content?: Array<{ text?: string }> })?.content?.[0] + ?.text ?? "{}" + ) as { ok: boolean }; + expect(insertData.ok).toBe(true); +}); +``` + +- [ ] **Step 6: Run type check and tests** + +```bash +pnpm run check +pnpm run test -- apps/server/test/whiteboard-library.test.ts apps/server/test/whiteboard.test.ts +pnpm run test:e2e -- --grep "whiteboard" +``` + +Expected: All pass. + +- [ ] **Step 7: Commit** + +```bash +git add apps/server/src/shared/mcp/whiteboard-tools.ts \ + apps/server/src/server/mcp-whiteboard-handlers.ts \ + apps/server/src/shared/mcp/server.ts \ + apps/server/src/server/mcp-handlers.ts \ + e2e/whiteboard.spec.ts +git commit -m "feat: add whiteboard_library and whiteboard_insert MCP tools for agent access to shape libraries" +``` + +--- + +### Task 4: Client-Side Library Loading + +**Files:** + +- Modify: `apps/web/src/components/app/whiteboard-tab.tsx` + +**Interfaces:** + +- Consumes: `.excalidrawlib` files at `/excalidraw/libraries/` (served by Vite from `public/`) +- Consumes: `excalidrawAPI.updateLibrary()` from `@excalidraw/excalidraw` +- Produces: Libraries loaded into Excalidraw's built-in library panel on mount + +- [ ] **Step 1: Add library loading to WhiteboardCanvas** + +In `apps/web/src/components/app/whiteboard-tab.tsx`, add a new `useEffect` inside `WhiteboardCanvas` that fires when `excalidrawAPI` becomes available. Place it after the existing `excalidrawAPI` state declaration (around line 76): + +```typescript +const LIBRARY_FILES = [ + "/excalidraw/libraries/flow-chart-symbols.excalidrawlib", + "/excalidraw/libraries/architecture-diagram-components.excalidrawlib", + "/excalidraw/libraries/universal-ui-kit.excalidrawlib", + "/excalidraw/libraries/html-input-elements.excalidrawlib", +]; + +// Inside WhiteboardCanvas, after excalidrawAPI state: +useEffect(() => { + if (!excalidrawAPI) return; + let cancelled = false; + void (async () => { + try { + const responses = await Promise.all( + LIBRARY_FILES.map((url) => fetch(url)) + ); + const blobs = await Promise.all(responses.map((r) => r.blob())); + if (cancelled) return; + for (const blob of blobs) { + await excalidrawAPI.updateLibrary({ + libraryItems: blob, + merge: true, + defaultStatus: "published", + openLibraryMenu: false, + }); + } + } catch { + // Library loading is best-effort — whiteboard works fine without them + } + })(); + return () => { + cancelled = true; + }; +}, [excalidrawAPI]); +``` + +Note: `updateLibrary` accepts a `Blob` directly (the `.excalidrawlib` file content). Excalidraw handles parsing internally. The `defaultStatus: "published"` makes items appear immediately without the user having to "accept" them. `openLibraryMenu: false` prevents the library panel from opening automatically. + +- [ ] **Step 2: Run finalize:web to verify build** + +```bash +pnpm run finalize:web +``` + +Expected: Type check and production build pass. + +- [ ] **Step 3: Validate in browser** + +Start the dev server: + +```bash +# Use repo_dev_up MCP tool +``` + +Open the whiteboard tab for any agent. The library panel (book icon in the Excalidraw toolbar) should show the pre-loaded shapes organized by source. Drag a shape onto the canvas to verify it works. Take a screenshot and share via `dispatch_share`. + +- [ ] **Step 4: Commit** + +```bash +git add apps/web/src/components/app/whiteboard-tab.tsx +git commit -m "feat: lazy-load excalidraw shape libraries on whiteboard mount" +``` + +- [ ] **Step 5: Run all pre-completion checks** + +```bash +pnpm run finalize:web +pnpm run test +pnpm run test:e2e +``` + +Expected: All pass. diff --git a/docs/superpowers/specs/2026-07-11-whiteboard-libraries-design.md b/docs/superpowers/specs/2026-07-11-whiteboard-libraries-design.md new file mode 100644 index 00000000..ac13fab3 --- /dev/null +++ b/docs/superpowers/specs/2026-07-11-whiteboard-libraries-design.md @@ -0,0 +1,181 @@ +# Whiteboard Libraries: Pre-loaded Excalidraw Libraries with Agent Catalog & Insert + +## Overview + +Add pre-loaded Excalidraw shape libraries to the whiteboard (flowchart, architecture, UI wireframe, HTML inputs). Users get drag-and-drop access via Excalidraw's built-in library panel. Agents get two new MCP tools to browse available shapes with dimensions and place them on the board. + +## Library Assets + +Four `.excalidrawlib` v2 files checked into the repo at `apps/web/public/excalidraw/libraries/`: + +| File | Items | Size | Category slug | Coverage | +| ----------------------------------------------- | ----- | ------ | -------------- | ----------------------------------------------------------------- | +| `flow-chart-symbols.excalidrawlib` | 15 | ~56KB | `flowchart` | Start/End, Process, Decision, Database, Document, I/O, Connectors | +| `architecture-diagram-components.excalidrawlib` | 11 | ~96KB | `architecture` | Server, Docker, GitHub, Slack, VPC, Subnet, User, Device, Email | +| `universal-ui-kit.excalidrawlib` | 22 | ~151KB | `wireframe` | Button, Checkbox, Toggle, Select, Calendar, Charts, Alerts | +| `html-input-elements.excalidrawlib` | 8 | ~32KB | `ui-input` | Button, Number, Date, Checkbox, Toggle, Switch, Select | + +Total: 56 named items, ~335KB. Licensed MIT (Excalidraw, Copyright 2020) — include attribution in LICENSE or NOTICES. + +Source: https://github.com/excalidraw/excalidraw-libraries (v2-format libraries from community contributors). + +## Server-Side Catalog + +### Startup parsing + +At server startup, read the `.excalidrawlib` files from disk (`apps/web/public/excalidraw/libraries/`), parse the v2 JSON, and build an in-memory catalog: + +```typescript +type LibraryCatalogItem = { + name: string; // from libraryItem.name + category: string; // mapped from filename + description: string; // derived from name or manually curated + width: number; // bounding box: max(x+width) - min(x) + height: number; // bounding box: max(y+height) - min(y) + elementCount: number; // number of primitives in the item + anchors: { + top: [number, number]; + bottom: [number, number]; + left: [number, number]; + right: [number, number]; + }; + elements: unknown[]; // raw Excalidraw elements (not exposed to agents via browse) +}; +``` + +Bounding boxes are computed from `min(x)` / `max(x + width)` across all elements in each library item. Anchors are midpoints of the bounding box edges. + +### Catalog storage + +A `LibraryCatalog` class in `apps/server/src/shared/whiteboard-library.ts`: + +- Parses all `.excalidrawlib` files from a given directory +- Exposes `list(category?: string): CompactItem[]` for browse mode +- Exposes `get(name: string): LibraryCatalogItem | null` for detail/insert +- Exposes `stamp(name: string, x: number, y: number, scale: number): StampedElements` to clone elements with fresh IDs, scale, and offset + +## MCP Tools + +### `whiteboard_library` — Browse available library items + +Registered alongside existing whiteboard tools in `apps/server/src/shared/mcp/whiteboard-tools.ts`. + +**Input schema:** + +```typescript +{ + category?: string; // filter by category slug + name?: string; // get full detail for one item +} +``` + +**Behavior** (evaluated in priority order): + +- `name` provided: returns full detail for that item (ignores `category`) +- `category` provided: returns filtered compact list +- No args: returns compact list of all items + +**Compact response** (no args / category filter): + +```json +[ + { "name": "Server", "category": "architecture", "width": 80, "height": 120 }, + { "name": "Database", "category": "flowchart", "width": 186, "height": 139 } +] +``` + +**Detail response** (`name` provided): + +```json +{ + "name": "Server", + "category": "architecture", + "description": "Server/host icon", + "width": 80, + "height": 120, + "elementCount": 5, + "anchors": { + "top": [40, 0], + "bottom": [40, 120], + "left": [0, 60], + "right": [80, 60] + } +} +``` + +Element data is never returned to agents — only metadata for layout planning. + +### `whiteboard_insert` — Place a library item on the board + +**Input schema:** + +```typescript +{ + name: string; // library item name (required) + x: number; // target x coordinate (required) + y: number; // target y coordinate (required) + scale?: number; // size multiplier, default 1.0 + label?: string; // replace text content if item contains a text element +} +``` + +**Behavior:** + +1. Look up item by `name` in the catalog (case-insensitive match) +2. Clone all elements with fresh UUIDs +3. Scale all coordinates and dimensions by `scale` factor relative to the item's local origin +4. Offset all coordinates to place the item's top-left at `(x, y)` +5. If `label` is provided and the item contains text elements, replace the first/primary text element's content +6. Merge onto the board using the existing `mergeElements` + `saveWhiteboard` logic (same as `whiteboard_update`) +7. Publish `whiteboard.changed` SSE event + +**Response:** Same shape as `whiteboard_update`: + +```json +{ + "ok": true, + "version": 4, + "addedIds": ["fresh-id-1", "fresh-id-2", "fresh-id-3"], + "elementCount": 15 +} +``` + +## Client-Side Library Loading + +### Lazy loading on whiteboard mount + +In `whiteboard-tab.tsx`, after `excalidrawAPI` is available: + +1. Fetch all 4 `.excalidrawlib` files in parallel from `/excalidraw/libraries/` +2. Parse each as JSON (v2 format: `{ type: "excalidrawlib", version: 2, libraryItems: [...] }`) +3. Call `excalidrawAPI.updateLibrary({ libraryItems: allItems, merge: true, defaultStatus: "published" })` + +Items appear immediately in Excalidraw's built-in library panel, grouped by source. Users drag and drop as normal. No custom UI needed. + +Fetches are cached by the browser. The loading happens once per component mount (whiteboard tab stays mounted via CSS hidden, so effectively once per session). + +### No bundle impact + +Library files are static assets in `public/`, not imported into the JS bundle. They're only fetched when the whiteboard component mounts. + +## File Changes + +### New files + +- `apps/web/public/excalidraw/libraries/*.excalidrawlib` — 4 library files +- `apps/server/src/shared/whiteboard-library.ts` — `LibraryCatalog` class (parse, compute dimensions, stamp) + +### Modified files + +- `apps/server/src/shared/mcp/whiteboard-tools.ts` — register `whiteboard_library` and `whiteboard_insert` tools +- `apps/server/src/server/mcp-whiteboard-handlers.ts` — add handler methods for the new tools +- `apps/web/src/components/app/whiteboard-tab.tsx` — lazy-load libraries on mount + +### Test files + +- `apps/server/test/whiteboard.test.ts` — unit tests for catalog parsing, bounding box computation, stamp logic +- `e2e/whiteboard.spec.ts` — E2E tests for MCP library/insert tools + +## Attribution + +Include MIT license attribution for the Excalidraw libraries in the repo (e.g. a `NOTICES` comment in the libraries directory or the project LICENSE file). diff --git a/e2e/whiteboard.spec.ts b/e2e/whiteboard.spec.ts new file mode 100644 index 00000000..3a13f87f --- /dev/null +++ b/e2e/whiteboard.spec.ts @@ -0,0 +1,396 @@ +import { test, expect } from "@playwright/test"; +import { + cleanupE2EAgents, + clickAgentRow, + createAgentViaAPI, + loadApp, +} from "./helpers"; + +const AUTH_HEADER = { + Authorization: `Bearer ${process.env.AUTH_TOKEN ?? "dev-token"}`, +}; + +async function callMcpTool( + request: Parameters[1]>[0]["request"], + agentId: string, + toolName: string, + args: Record +): Promise> { + const res = await request.fetch(`/api/mcp/${agentId}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + }, + data: { + jsonrpc: "2.0", + id: 1, + method: "tools/call", + params: { name: toolName, arguments: args }, + }, + }); + const text = await res.text(); + const dataLine = text.split("\n").find((l) => l.startsWith("data: ")); + if (!dataLine) throw new Error(`No data line in MCP response: ${text}`); + return JSON.parse(dataLine.slice("data: ".length)) as Record; +} + +test.describe("Whiteboard", () => { + test.afterEach(async ({ request }) => { + await cleanupE2EAgents(request); + }); + + test("whiteboard tab is visible and navigates", async ({ page, request }) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-${Date.now()}`, + }); + await loadApp(page); + await clickAgentRow(page, agent.id); + + const wbTab = page.getByTestId("center-tab-whiteboard"); + await expect(wbTab).toBeVisible(); + await wbTab.click(); + + await page.waitForURL(/\/agents\/[^/]+\/whiteboard/); + }); + + test("whiteboard REST API: PUT persists and GET retrieves scene", async ({ + request, + }) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-api-${Date.now()}`, + }); + + // GET should return empty scene initially + const getRes1 = await request.get(`/api/v1/agents/${agent.id}/whiteboard`, { + headers: AUTH_HEADER, + }); + expect(getRes1.ok()).toBe(true); + const data1 = (await getRes1.json()) as { + scene: { elements: unknown[] }; + version: number; + }; + expect(data1.scene.elements).toEqual([]); + expect(data1.version).toBe(0); + + // PUT a scene + const scene = { + elements: [ + { + id: "test-box", + type: "rectangle", + x: 100, + y: 100, + width: 160, + height: 70, + }, + ], + }; + const putRes = await request.put(`/api/v1/agents/${agent.id}/whiteboard`, { + headers: { ...AUTH_HEADER, "content-type": "application/json" }, + data: { scene, baseVersion: 0 }, + }); + expect(putRes.ok()).toBe(true); + const putData = (await putRes.json()) as { ok: boolean; version: number }; + expect(putData.ok).toBe(true); + expect(putData.version).toBe(1); + + // GET should now return the scene + const getRes2 = await request.get(`/api/v1/agents/${agent.id}/whiteboard`, { + headers: AUTH_HEADER, + }); + const data2 = (await getRes2.json()) as { + scene: { elements: unknown[] }; + version: number; + }; + expect(data2.scene.elements).toHaveLength(1); + expect((data2.scene.elements[0] as { id: string }).id).toBe("test-box"); + expect(data2.version).toBe(1); + }); + + test("whiteboard REST API: PUT rejects stale baseVersion with 409", async ({ + request, + }) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-conflict-${Date.now()}`, + }); + + const scene = { + elements: [ + { id: "a", type: "rectangle", x: 0, y: 0, width: 100, height: 50 }, + ], + }; + + // First PUT succeeds + const putRes1 = await request.put(`/api/v1/agents/${agent.id}/whiteboard`, { + headers: { ...AUTH_HEADER, "content-type": "application/json" }, + data: { scene, baseVersion: 0 }, + }); + expect(putRes1.ok()).toBe(true); + + // Second PUT with stale baseVersion=0 should get 409 + const putRes2 = await request.put(`/api/v1/agents/${agent.id}/whiteboard`, { + headers: { ...AUTH_HEADER, "content-type": "application/json" }, + data: { scene, baseVersion: 0 }, + }); + expect(putRes2.status()).toBe(409); + const conflictData = (await putRes2.json()) as { + error: string; + version: number; + }; + expect(conflictData.error).toContain("modified"); + expect(conflictData.version).toBe(1); + }); + + test("whiteboard MCP tool: agent can update and read whiteboard", async ({ + request, + }) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-mcp-${Date.now()}`, + }); + + // Call whiteboard_update via MCP + const updateJson = await callMcpTool( + request, + agent.id, + "whiteboard_update", + { + elements: [ + { + id: "api-box", + type: "rectangle", + x: 100, + y: 100, + width: 160, + height: 70, + backgroundColor: "#a5d8ff", + }, + { + id: "api-label", + type: "text", + x: 110, + y: 120, + width: 140, + height: 25, + text: "API Server", + originalText: "API Server", + containerId: "api-box", + }, + ], + } + ); + + const updateResult = updateJson.result as { + content?: Array<{ text?: string }>; + }; + const updateContent = JSON.parse( + updateResult?.content?.[0]?.text ?? "{}" + ) as { + ok: boolean; + addedIds: string[]; + elementCount: number; + }; + expect(updateContent.ok).toBe(true); + expect(updateContent.addedIds).toEqual(["api-box", "api-label"]); + expect(updateContent.elementCount).toBe(2); + + // Call whiteboard_get via MCP + const getJson = await callMcpTool(request, agent.id, "whiteboard_get", {}); + const getResult = getJson.result as { + structuredContent?: { + elementCount: number; + elements: Array<{ id: string }>; + }; + }; + expect(getResult?.structuredContent?.elementCount).toBe(2); + expect( + getResult?.structuredContent?.elements?.map((e) => e.id).sort() + ).toEqual(["api-box", "api-label"]); + + // Call whiteboard_clear via MCP + const clearJson = await callMcpTool( + request, + agent.id, + "whiteboard_clear", + {} + ); + const clearResult = clearJson.result as { + content?: Array<{ text?: string }>; + }; + expect(clearResult?.content?.[0]?.text).toContain("ok"); + + // Verify board is empty via REST + const getRes2 = await request.get(`/api/v1/agents/${agent.id}/whiteboard`, { + headers: AUTH_HEADER, + }); + const data2 = (await getRes2.json()) as { + scene: { elements: unknown[] }; + }; + expect(data2.scene.elements).toEqual([]); + }); + + test("whiteboard MCP tool: whiteboard_library returns catalog", async ({ + request, + }) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-lib-${Date.now()}`, + }); + + // List all items + const listJson = await callMcpTool( + request, + agent.id, + "whiteboard_library", + {} + ); + const listResult = listJson.result as { + content?: Array<{ text?: string }>; + }; + const items = JSON.parse(listResult?.content?.[0]?.text ?? "[]") as Array<{ + name: string; + category: string; + width: number; + height: number; + }>; + expect(items.length).toBeGreaterThanOrEqual(40); + expect(items[0].name).toBeTruthy(); + expect(items[0].width).toBeGreaterThan(0); + + // Filter by category + const archJson = await callMcpTool( + request, + agent.id, + "whiteboard_library", + { + category: "architecture", + } + ); + const archResult = archJson.result as { + content?: Array<{ text?: string }>; + }; + const archItems = JSON.parse( + archResult?.content?.[0]?.text ?? "[]" + ) as Array<{ category: string }>; + expect(archItems.length).toBeGreaterThan(0); + expect(archItems.every((i) => i.category === "architecture")).toBe(true); + + // Get detail for a specific item + const detailJson = await callMcpTool( + request, + agent.id, + "whiteboard_library", + { name: items[0].name } + ); + const detailResult = detailJson.result as { + content?: Array<{ text?: string }>; + }; + const detail = JSON.parse(detailResult?.content?.[0]?.text ?? "{}") as { + name: string; + anchors: { + top: number[]; + bottom: number[]; + left: number[]; + right: number[]; + }; + elementCount: number; + }; + expect(detail.name).toBe(items[0].name); + expect(detail.anchors.top).toHaveLength(2); + expect(detail.elementCount).toBeGreaterThan(0); + }); + + test("whiteboard MCP tool: whiteboard_insert places library item", async ({ + request, + }) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-insert-${Date.now()}`, + }); + + // First get available items + const listJson = await callMcpTool( + request, + agent.id, + "whiteboard_library", + {} + ); + const listResult = listJson.result as { + content?: Array<{ text?: string }>; + }; + const items = JSON.parse(listResult?.content?.[0]?.text ?? "[]") as Array<{ + name: string; + }>; + const itemName = items[0].name; + + // Insert the item + const insertJson = await callMcpTool( + request, + agent.id, + "whiteboard_insert", + { + name: itemName, + x: 100, + y: 200, + scale: 1, + } + ); + const insertResult = insertJson.result as { + content?: Array<{ text?: string }>; + }; + const insertData = JSON.parse(insertResult?.content?.[0]?.text ?? "{}") as { + ok: boolean; + addedIds: string[]; + elementCount: number; + }; + expect(insertData.ok).toBe(true); + expect(insertData.addedIds.length).toBeGreaterThan(0); + expect(insertData.elementCount).toBeGreaterThan(0); + + // Verify the elements are on the board via REST + const getRes = await request.get(`/api/v1/agents/${agent.id}/whiteboard`, { + headers: AUTH_HEADER, + }); + const boardData = (await getRes.json()) as { + scene: { elements: Array<{ id: string }> }; + }; + expect(boardData.scene.elements.length).toBe(insertData.elementCount); + }); + + test("whiteboard MCP tool: whiteboard_insert with label and scale", async ({ + request, + }) => { + const agent = await createAgentViaAPI(request, { + name: `e2e-agent-wb-insert-label-${Date.now()}`, + }); + + // Find an item with text elements via library detail + const listJson = await callMcpTool( + request, + agent.id, + "whiteboard_library", + {} + ); + const items = JSON.parse( + (listJson.result as { content?: Array<{ text?: string }> })?.content?.[0] + ?.text ?? "[]" + ) as Array<{ name: string }>; + + // Insert at 2x scale with a custom label + const insertJson = await callMcpTool( + request, + agent.id, + "whiteboard_insert", + { + name: items[0].name, + x: 50, + y: 50, + scale: 2, + label: "My Service", + } + ); + const insertData = JSON.parse( + (insertJson.result as { content?: Array<{ text?: string }> }) + ?.content?.[0]?.text ?? "{}" + ) as { ok: boolean }; + expect(insertData.ok).toBe(true); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc467e22..1e2f099f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,16 +85,19 @@ importers: version: 8.18.1 "@vitest/coverage-v8": specifier: 4.1.2 - version: 4.1.2(vitest@4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) + version: 4.1.2(vitest@4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) vite: specifier: ^6.0.0 - version: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: ^4.0.18 - version: 4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) apps/web: dependencies: + "@excalidraw/excalidraw": + specifier: ^0.18.1 + version: 0.18.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(immer@11.1.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) "@radix-ui/react-checkbox": specifier: ^1.3.3 version: 1.3.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -221,10 +224,10 @@ importers: version: 18.3.7(@types/react@18.3.28) "@vitejs/plugin-react": specifier: ^4.7.0 - version: 4.7.0(vite@5.4.21(@types/node@24.12.0)(terser@5.46.1)) + version: 4.7.0(vite@5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1)) "@vitest/coverage-v8": specifier: 2.1.9 - version: 2.1.9(vitest@2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(terser@5.46.1)) + version: 2.1.9(vitest@2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(sass@1.51.0)(terser@5.46.1)) autoprefixer: specifier: ^10.4.20 version: 10.4.27(postcss@8.5.8) @@ -257,13 +260,13 @@ importers: version: 8.57.2(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3) vite: specifier: ^5.4.21 - version: 5.4.21(@types/node@24.12.0)(terser@5.46.1) + version: 5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1) vite-plugin-pwa: specifier: ^1.2.0 - version: 1.2.0(vite@5.4.21(@types/node@24.12.0)(terser@5.46.1))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) + version: 1.2.0(vite@5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) vitest: specifier: ^2.1.0 - version: 2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(terser@5.46.1) + version: 2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(sass@1.51.0)(terser@5.46.1) packages: "@alloc/quick-lru@5.2.0": @@ -1126,6 +1129,12 @@ packages: } engines: { node: ">=18" } + "@braintree/sanitize-url@6.0.2": + resolution: + { + integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==, + } + "@braintree/sanitize-url@7.1.2": resolution: { @@ -1139,12 +1148,42 @@ packages: } hasBin: true + "@chevrotain/cst-dts-gen@11.0.3": + resolution: + { + integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==, + } + + "@chevrotain/gast@11.0.3": + resolution: + { + integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==, + } + + "@chevrotain/regexp-to-ast@11.0.3": + resolution: + { + integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==, + } + + "@chevrotain/types@11.0.3": + resolution: + { + integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==, + } + "@chevrotain/types@11.1.2": resolution: { integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==, } + "@chevrotain/utils@11.0.3": + resolution: + { + integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==, + } + "@csstools/color-helpers@6.0.2": resolution: { @@ -1951,6 +1990,40 @@ packages: } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + "@excalidraw/excalidraw@0.18.1": + resolution: + { + integrity: sha512-6i5Gt7IDTOH//qa0Z315Ly5iVRhjWpu2whrlQFqkuwrkKUWgRsMk0P5qdE7bpyDpai7jeLeWYkyj1eVAfni1lw==, + } + peerDependencies: + react: ^17.0.2 || ^18.2.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.2.0 || ^19.0.0 + + "@excalidraw/laser-pointer@1.3.1": + resolution: + { + integrity: sha512-psA1z1N2qeAfsORdXc9JmD2y4CmDwmuMRxnNdJHZexIcPwaNEyIpNcelw+QkL9rz9tosaN9krXuKaRqYpRAR6g==, + } + + "@excalidraw/markdown-to-text@0.1.2": + resolution: + { + integrity: sha512-1nDXBNAojfi3oSFwJswKREkFm5wrSjqay81QlyRv2pkITG/XYB5v+oChENVBQLcxQwX4IUATWvXM5BcaNhPiIg==, + } + + "@excalidraw/mermaid-to-excalidraw@2.2.2": + resolution: + { + integrity: sha512-5VKQq5CdRocC82vOIUpQ5ufJOVV9FpBTdHGA+ULqazeIVV+cr299877omQCibsdS3Bpitz2fsnTwnIXEmLVDSg==, + } + + "@excalidraw/random-username@1.1.0": + resolution: + { + integrity: sha512-nULYsQxkWHnbmHvcs+efMkJ4/9TtvNyFeLyHdeGxW0zHs6P+jYVqcRff9A6Vq9w9JXeDRnRh2VKvTtS19GW2qA==, + } + engines: { node: ">=10" } + "@exodus/bytes@1.15.0": resolution: { @@ -2388,6 +2461,12 @@ packages: } engines: { node: ">=8" } + "@mermaid-js/parser@0.6.3": + resolution: + { + integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==, + } + "@mermaid-js/parser@1.1.1": resolution: { @@ -2455,12 +2534,40 @@ packages: integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==, } + "@radix-ui/primitive@1.0.0": + resolution: + { + integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==, + } + + "@radix-ui/primitive@1.1.1": + resolution: + { + integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==, + } + "@radix-ui/primitive@1.1.3": resolution: { integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==, } + "@radix-ui/react-arrow@1.1.2": + resolution: + { + integrity: sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-arrow@1.1.7": resolution: { @@ -2493,6 +2600,15 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-collection@1.0.1": + resolution: + { + integrity: sha512-uuiFbs+YCKjn3X1DTSx9G7BHApu4GHbi3kgiwsnFUbOKCrwejAJv4eE4Vc8C0Oaxt9T0aV4ox0WCOdx+39Xo+g==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + "@radix-ui/react-collection@1.1.7": resolution: { @@ -2509,6 +2625,26 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-compose-refs@1.0.0": + resolution: + { + integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + "@radix-ui/react-compose-refs@1.1.1": + resolution: + { + integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@radix-ui/react-compose-refs@1.1.2": resolution: { @@ -2521,6 +2657,26 @@ packages: "@types/react": optional: true + "@radix-ui/react-context@1.0.0": + resolution: + { + integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + "@radix-ui/react-context@1.1.1": + resolution: + { + integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@radix-ui/react-context@1.1.2": resolution: { @@ -2549,6 +2705,14 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-direction@1.0.0": + resolution: + { + integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + "@radix-ui/react-direction@1.1.1": resolution: { @@ -2577,6 +2741,22 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-dismissable-layer@1.1.5": + resolution: + { + integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-dropdown-menu@2.1.16": resolution: { @@ -2593,6 +2773,18 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-focus-guards@1.1.1": + resolution: + { + integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@radix-ui/react-focus-guards@1.1.3": resolution: { @@ -2605,6 +2797,22 @@ packages: "@types/react": optional: true + "@radix-ui/react-focus-scope@1.1.2": + resolution: + { + integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-focus-scope@1.1.7": resolution: { @@ -2621,6 +2829,26 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-id@1.0.0": + resolution: + { + integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + "@radix-ui/react-id@1.1.0": + resolution: + { + integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@radix-ui/react-id@1.1.1": resolution: { @@ -2665,6 +2893,38 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-popover@1.1.6": + resolution: + { + integrity: sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-popper@1.2.2": + resolution: + { + integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-popper@1.2.8": resolution: { @@ -2681,6 +2941,22 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-portal@1.1.4": + resolution: + { + integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-portal@1.1.9": resolution: { @@ -2697,6 +2973,31 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-presence@1.0.0": + resolution: + { + integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + "@radix-ui/react-presence@1.1.2": + resolution: + { + integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-presence@1.1.5": resolution: { @@ -2713,6 +3014,31 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-primitive@1.0.1": + resolution: + { + integrity: sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + "@radix-ui/react-primitive@2.0.2": + resolution: + { + integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-primitive@2.1.3": resolution: { @@ -2745,6 +3071,15 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-roving-focus@1.0.2": + resolution: + { + integrity: sha512-HLK+CqD/8pN6GfJm3U+cqpqhSKYAWiOJDe+A+8MfxBnOue39QEeMa43csUn2CXCHQT0/mewh1LrrG4tfkM9DMA==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + "@radix-ui/react-roving-focus@1.1.11": resolution: { @@ -2793,6 +3128,26 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-slot@1.0.1": + resolution: + { + integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + "@radix-ui/react-slot@1.1.2": + resolution: + { + integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@radix-ui/react-slot@1.2.3": resolution: { @@ -2817,6 +3172,15 @@ packages: "@types/react": optional: true + "@radix-ui/react-tabs@1.0.2": + resolution: + { + integrity: sha512-gOUwh+HbjCuL0UCo8kZ+kdUEG8QtpdO4sMQduJ34ZEz0r4922g9REOBM+vIsfwtGxSug4Yb1msJMJYN2Bk8TpQ==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + "@radix-ui/react-tooltip@1.2.8": resolution: { @@ -2824,19 +3188,107 @@ packages: } peerDependencies: "@types/react": "*" - "@types/react-dom": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-use-callback-ref@1.0.0": + resolution: + { + integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + "@radix-ui/react-use-callback-ref@1.1.0": + resolution: + { + integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-callback-ref@1.1.1": + resolution: + { + integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-controllable-state@1.0.0": + resolution: + { + integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + "@radix-ui/react-use-controllable-state@1.1.0": + resolution: + { + integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-controllable-state@1.2.2": + resolution: + { + integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-effect-event@0.0.2": + resolution: + { + integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-escape-keydown@1.1.0": + resolution: + { + integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==, + } + peerDependencies: + "@types/react": "*" react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - "@types/react-dom": - optional: true - "@radix-ui/react-use-callback-ref@1.1.1": + "@radix-ui/react-use-escape-keydown@1.1.1": resolution: { - integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==, + integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==, } peerDependencies: "@types/react": "*" @@ -2845,10 +3297,18 @@ packages: "@types/react": optional: true - "@radix-ui/react-use-controllable-state@1.2.2": + "@radix-ui/react-use-layout-effect@1.0.0": resolution: { - integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==, + integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + "@radix-ui/react-use-layout-effect@1.1.0": + resolution: + { + integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==, } peerDependencies: "@types/react": "*" @@ -2857,10 +3317,10 @@ packages: "@types/react": optional: true - "@radix-ui/react-use-effect-event@0.0.2": + "@radix-ui/react-use-layout-effect@1.1.1": resolution: { - integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==, + integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==, } peerDependencies: "@types/react": "*" @@ -2869,10 +3329,10 @@ packages: "@types/react": optional: true - "@radix-ui/react-use-escape-keydown@1.1.1": + "@radix-ui/react-use-previous@1.1.1": resolution: { - integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==, + integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==, } peerDependencies: "@types/react": "*" @@ -2881,10 +3341,10 @@ packages: "@types/react": optional: true - "@radix-ui/react-use-layout-effect@1.1.1": + "@radix-ui/react-use-rect@1.1.0": resolution: { - integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==, + integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==, } peerDependencies: "@types/react": "*" @@ -2893,10 +3353,10 @@ packages: "@types/react": optional: true - "@radix-ui/react-use-previous@1.1.1": + "@radix-ui/react-use-rect@1.1.1": resolution: { - integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==, + integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==, } peerDependencies: "@types/react": "*" @@ -2905,10 +3365,10 @@ packages: "@types/react": optional: true - "@radix-ui/react-use-rect@1.1.1": + "@radix-ui/react-use-size@1.1.0": resolution: { - integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==, + integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==, } peerDependencies: "@types/react": "*" @@ -2945,6 +3405,12 @@ packages: "@types/react-dom": optional: true + "@radix-ui/rect@1.1.0": + resolution: + { + integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==, + } + "@radix-ui/rect@1.1.1": resolution: { @@ -4290,6 +4756,12 @@ packages: } engines: { node: ">=8" } + browser-fs-access@0.29.1: + resolution: + { + integrity: sha512-LSvVX5e21LRrXqVMhqtAwj5xPgDb+fXAIH80NsnCQ9xuZPs2xWsOREi24RKgZa1XOiQRbcmVrv87+ulOKsgjxw==, + } + browserslist@4.28.1: resolution: { @@ -4359,6 +4831,12 @@ packages: integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==, } + canvas-roundrect-polyfill@0.0.1: + resolution: + { + integrity: sha512-yWq+R3U3jE+coOeEb3a3GgE2j/0MMiDKM/QpLb6h9ihf5fGY9UXtvK9o4vNqjWXoZz7/3EaSVU3IX53TvFFUOw==, + } + ccount@2.0.1: resolution: { @@ -4417,6 +4895,20 @@ packages: } engines: { node: ">= 16" } + chevrotain-allstar@0.3.1: + resolution: + { + integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==, + } + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: + { + integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==, + } + chokidar@3.6.0: resolution: { @@ -4457,6 +4949,13 @@ packages: } engines: { node: ">=12" } + clsx@1.1.1: + resolution: + { + integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==, + } + engines: { node: ">=6" } + clsx@2.1.1: resolution: { @@ -4611,6 +5110,13 @@ packages: integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==, } + crc-32@0.3.0: + resolution: + { + integrity: sha512-kucVIjOmMc1f0tv53BJ/5WIX+MGLcKuoBhnGqQrgKJNqLByb/sVMWfW/Aw6hw0jgcqjJ2pi9E5y32zOIpaUlsA==, + } + engines: { node: ">=0.8" } + croner@10.0.1: resolution: { @@ -4625,6 +5131,14 @@ packages: } hasBin: true + cross-env@7.0.3: + resolution: + { + integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==, + } + engines: { node: ">=10.14", npm: ">=6", yarn: ">=1" } + hasBin: true + cross-spawn@7.0.6: resolution: { @@ -5284,6 +5798,13 @@ packages: integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==, } + es6-promise-pool@2.5.0: + resolution: + { + integrity: sha512-VHErXfzR/6r/+yyzPKeBvO0lgjfC5cbDCQWjWwMZWSb6YU39TGIl51OUmCfWCq4ylMdJSB8zkz2vIuIeIxXApA==, + } + engines: { node: ">=0.10.0" } + esbuild@0.21.5: resolution: { @@ -5677,6 +6198,13 @@ packages: integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==, } + fractional-indexing@3.2.0: + resolution: + { + integrity: sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==, + } + engines: { node: ^14.13.1 || >=16.0.0 } + framer-motion@12.38.0: resolution: { @@ -5743,6 +6271,13 @@ packages: integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, } + fuzzy@0.1.3: + resolution: + { + integrity: sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w==, + } + engines: { node: ">= 0.6.0" } + generator-function@2.0.1: resolution: { @@ -5869,6 +6404,12 @@ packages: } engines: { node: ">= 0.4" } + glur@1.1.2: + resolution: + { + integrity: sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==, + } + gopd@1.2.0: resolution: { @@ -6049,6 +6590,12 @@ packages: } engines: { node: ">= 4" } + image-blob-reduce@3.0.1: + resolution: + { + integrity: sha512-/VmmWgIryG/wcn4TVrV7cC4mlfUC/oyiKIfSg5eVM3Ten/c1c34RJhMYKCWTnoSMHSqXLt3tsrBR4Q2HInvN+Q==, + } + immer@10.2.0: resolution: { @@ -6061,6 +6608,12 @@ packages: integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==, } + immutable@4.3.9: + resolution: + { + integrity: sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==, + } + import-fresh@3.3.1: resolution: { @@ -6481,6 +7034,30 @@ packages: integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==, } + jotai-scope@0.7.2: + resolution: + { + integrity: sha512-Gwed97f3dDObrO43++2lRcgOqw4O2sdr4JCjP/7eHK1oPACDJ7xKHGScpJX9XaflU+KBHXF+VhwECnzcaQiShg==, + } + peerDependencies: + jotai: ">=2.9.2" + react: ">=17.0.0" + + jotai@2.11.0: + resolution: + { + integrity: sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ==, + } + engines: { node: ">=12.20.0" } + peerDependencies: + "@types/react": ">=17.0.0" + react: ">=17.0.0" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + jotai@2.19.0: resolution: { @@ -6630,6 +7207,13 @@ packages: integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==, } + langium@3.3.1: + resolution: + { + integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==, + } + engines: { node: ">=16.0.0" } + layout-base@1.0.2: resolution: { @@ -6697,6 +7281,12 @@ packages: } engines: { node: ">=10" } + lodash-es@4.17.21: + resolution: + { + integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==, + } + lodash-es@4.18.1: resolution: { @@ -6721,6 +7311,12 @@ packages: integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==, } + lodash.throttle@4.1.1: + resolution: + { + integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==, + } + lodash@4.17.23: resolution: { @@ -7217,6 +7813,12 @@ packages: integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, } + multimath@2.0.0: + resolution: + { + integrity: sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g==, + } + mz@2.7.0: resolution: { @@ -7231,6 +7833,22 @@ packages: engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } hasBin: true + nanoid@3.3.3: + resolution: + { + integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + nanoid@4.0.2: + resolution: + { + integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==, + } + engines: { node: ^14 || ^16 || >=18 } + hasBin: true + natural-compare@1.4.0: resolution: { @@ -7367,6 +7985,12 @@ packages: } engines: { node: ">=18" } + open-color@1.9.1: + resolution: + { + integrity: sha512-vCseG/EQ6/RcvxhUcGJiHViOgrtz4x0XbZepXvKik66TMGkvbmjeJrKFyBEx6daG5rNyyd14zYXhz0hZVwQFOw==, + } + optionator@0.9.4: resolution: { @@ -7407,6 +8031,12 @@ packages: integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==, } + pako@2.0.3: + resolution: + { + integrity: sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw==, + } + parent-module@1.0.1: resolution: { @@ -7498,6 +8128,12 @@ packages: } engines: { node: ">= 14.16" } + perfect-freehand@1.2.0: + resolution: + { + integrity: sha512-h/0ikF1M3phW7CwpZ5MMvKnfpHficWoOEyr//KVNTxV4F6deRK1eYMtHyBKEAKFK0aXIEUK9oBvlF6PNXMDsAw==, + } + pg-cloudflare@1.3.0: resolution: { @@ -7556,6 +8192,12 @@ packages: integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==, } + pica@7.1.1: + resolution: + { + integrity: sha512-WY73tMvNzXWEld2LicT9Y260L43isrZ85tPuqRyvtkljSDLmnNFQmZICt4xUJMVulmcc6L9O7jbBrtx3DOz/YQ==, + } + picocolors@1.1.1: resolution: { @@ -7632,12 +8274,36 @@ packages: engines: { node: ">=18" } hasBin: true + png-chunk-text@1.0.0: + resolution: + { + integrity: sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw==, + } + + png-chunks-encode@1.0.0: + resolution: + { + integrity: sha512-J1jcHgbQRsIIgx5wxW9UmCymV3wwn4qCCJl6KYgEU/yHCh/L2Mwq/nMOkRPtmV79TLxRZj5w3tH69pvygFkDqA==, + } + + png-chunks-extract@1.0.0: + resolution: + { + integrity: sha512-ZiVwF5EJ0DNZyzAqld8BP1qyJBaGOFaq9zl579qfbkcmOwWLLO4I9L8i2O4j3HkI6/35i0nKG2n+dZplxiT89Q==, + } + points-on-curve@0.2.0: resolution: { integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==, } + points-on-curve@1.0.1: + resolution: + { + integrity: sha512-3nmX4/LIiyuwGLwuUrfhTlDeQFlAhi7lyK/zcRNGhalwapDWgAGR82bUpmn2mA03vII3fvNCG8jAONzKXwpxAg==, + } + points-on-path@0.2.1: resolution: { @@ -7828,6 +8494,12 @@ packages: } engines: { node: ">=6" } + pwacompat@2.0.17: + resolution: + { + integrity: sha512-6Du7IZdIy7cHiv7AhtDy4X2QRM8IAD5DII69mt5qWibC2d15ZU8DmBG1WdZKekG11cChSu4zkSUGPF9sweOl6w==, + } + qs@6.15.0: resolution: { @@ -8250,6 +8922,12 @@ packages: engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true + roughjs@4.6.4: + resolution: + { + integrity: sha512-s6EZ0BntezkFYMf/9mGn7M8XGIoaav9QQBCnJROWB3brUWQ683Q2LbRD/hq0Z3bAJ/9NVpU/5LpiTWvQMyLDhw==, + } + roughjs@4.6.6: resolution: { @@ -8322,6 +9000,14 @@ packages: integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, } + sass@1.51.0: + resolution: + { + integrity: sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==, + } + engines: { node: ">=12.0.0" } + hasBin: true + saxes@6.0.0: resolution: { @@ -8498,6 +9184,13 @@ packages: } engines: { node: ">=20" } + sliced@1.0.1: + resolution: + { + integrity: sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==, + } + deprecated: Unsupported + smob@1.6.1: resolution: { @@ -8992,6 +9685,12 @@ packages: engines: { node: ">=18.0.0" } hasBin: true + tunnel-rat@0.1.2: + resolution: + { + integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==, + } + type-check@0.4.0: resolution: { @@ -9430,6 +10129,44 @@ packages: jsdom: optional: true + vscode-jsonrpc@8.2.0: + resolution: + { + integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==, + } + engines: { node: ">=14.0.0" } + + vscode-languageserver-protocol@3.17.5: + resolution: + { + integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==, + } + + vscode-languageserver-textdocument@1.0.12: + resolution: + { + integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==, + } + + vscode-languageserver-types@3.17.5: + resolution: + { + integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==, + } + + vscode-languageserver@9.0.1: + resolution: + { + integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==, + } + hasBin: true + + vscode-uri@3.0.8: + resolution: + { + integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==, + } + w3c-xmlserializer@5.0.0: resolution: { @@ -9456,6 +10193,12 @@ packages: } engines: { node: ">=20" } + webworkify@1.5.0: + resolution: + { + integrity: sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g==, + } + whatwg-mimetype@3.0.0: resolution: { @@ -9749,6 +10492,24 @@ packages: integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==, } + zustand@4.5.7: + resolution: + { + integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==, + } + engines: { node: ">=12.7.0" } + peerDependencies: + "@types/react": ">=16.8" + immer: ">=9.0.6" + react: ">=16.8" + peerDependenciesMeta: + "@types/react": + optional: true + immer: + optional: true + react: + optional: true + zwitch@2.0.4: resolution: { @@ -10466,14 +11227,33 @@ snapshots: "@bcoe/v8-coverage@1.0.2": {} + "@braintree/sanitize-url@6.0.2": {} + "@braintree/sanitize-url@7.1.2": {} "@bramus/specificity@2.4.2": dependencies: css-tree: 3.2.1 + "@chevrotain/cst-dts-gen@11.0.3": + dependencies: + "@chevrotain/gast": 11.0.3 + "@chevrotain/types": 11.0.3 + lodash-es: 4.17.21 + + "@chevrotain/gast@11.0.3": + dependencies: + "@chevrotain/types": 11.0.3 + lodash-es: 4.17.21 + + "@chevrotain/regexp-to-ast@11.0.3": {} + + "@chevrotain/types@11.0.3": {} + "@chevrotain/types@11.1.2": {} + "@chevrotain/utils@11.0.3": {} + "@csstools/color-helpers@6.0.2": {} "@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)": @@ -10776,6 +11556,59 @@ snapshots: "@eslint/core": 0.17.0 levn: 0.4.1 + "@excalidraw/excalidraw@0.18.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(immer@11.1.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@braintree/sanitize-url": 6.0.2 + "@excalidraw/laser-pointer": 1.3.1 + "@excalidraw/mermaid-to-excalidraw": 2.2.2 + "@excalidraw/random-username": 1.1.0 + "@radix-ui/react-popover": 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-tabs": 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + browser-fs-access: 0.29.1 + canvas-roundrect-polyfill: 0.0.1 + clsx: 1.1.1 + cross-env: 7.0.3 + es6-promise-pool: 2.5.0 + fractional-indexing: 3.2.0 + fuzzy: 0.1.3 + image-blob-reduce: 3.0.1 + jotai: 2.11.0(@types/react@18.3.28)(react@18.3.1) + jotai-scope: 0.7.2(jotai@2.11.0(@types/react@18.3.28)(react@18.3.1))(react@18.3.1) + lodash.debounce: 4.0.8 + lodash.throttle: 4.1.1 + nanoid: 3.3.3 + open-color: 1.9.1 + pako: 2.0.3 + perfect-freehand: 1.2.0 + pica: 7.1.1 + png-chunk-text: 1.0.0 + png-chunks-encode: 1.0.0 + png-chunks-extract: 1.0.0 + points-on-curve: 1.0.1 + pwacompat: 2.0.17 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + roughjs: 4.6.4 + sass: 1.51.0 + tunnel-rat: 0.1.2(@types/react@18.3.28)(immer@11.1.4)(react@18.3.1) + transitivePeerDependencies: + - "@types/react" + - "@types/react-dom" + - immer + + "@excalidraw/laser-pointer@1.3.1": {} + + "@excalidraw/markdown-to-text@0.1.2": {} + + "@excalidraw/mermaid-to-excalidraw@2.2.2": + dependencies: + "@excalidraw/markdown-to-text": 0.1.2 + "@mermaid-js/parser": 0.6.3 + mermaid: 11.15.0 + nanoid: 4.0.2 + + "@excalidraw/random-username@1.1.0": {} + "@exodus/bytes@1.15.0": {} "@fastify/ajv-compiler@4.0.5": @@ -11008,6 +11841,10 @@ snapshots: "@lukeed/ms@2.0.2": {} + "@mermaid-js/parser@0.6.3": + dependencies: + langium: 3.3.1 + "@mermaid-js/parser@1.1.1": dependencies: "@chevrotain/types": 11.1.2 @@ -11057,8 +11894,23 @@ snapshots: "@radix-ui/number@1.1.1": {} + "@radix-ui/primitive@1.0.0": + dependencies: + "@babel/runtime": 7.29.2 + + "@radix-ui/primitive@1.1.1": {} + "@radix-ui/primitive@1.1.3": {} + "@radix-ui/react-arrow@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/react-primitive": 2.0.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.28 + "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@radix-ui/react-primitive": 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -11084,6 +11936,16 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-collection@1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + "@radix-ui/react-compose-refs": 1.0.0(react@18.3.1) + "@radix-ui/react-context": 1.0.0(react@18.3.1) + "@radix-ui/react-primitive": 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-slot": 1.0.1(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + "@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@radix-ui/react-compose-refs": 1.1.2(@types/react@18.3.28)(react@18.3.1) @@ -11096,12 +11958,34 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-compose-refs@1.0.0(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + react: 18.3.1 + + "@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.28)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.28)(react@18.3.1)": dependencies: react: 18.3.1 optionalDependencies: "@types/react": 18.3.28 + "@radix-ui/react-context@1.0.0(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + react: 18.3.1 + + "@radix-ui/react-context@1.1.1(@types/react@18.3.28)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@18.3.1)": dependencies: react: 18.3.1 @@ -11130,6 +12014,11 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-direction@1.0.0(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + react: 18.3.1 + "@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@18.3.1)": dependencies: react: 18.3.1 @@ -11149,6 +12038,19 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-dismissable-layer@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/primitive": 1.1.1 + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-primitive": 2.0.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-use-callback-ref": 1.1.0(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-use-escape-keydown": 1.1.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.28 + "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@radix-ui/primitive": 1.1.3 @@ -11164,12 +12066,29 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.28)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@18.3.1)": dependencies: react: 18.3.1 optionalDependencies: "@types/react": 18.3.28 + "@radix-ui/react-focus-scope@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-primitive": 2.0.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-use-callback-ref": 1.1.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.28 + "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@radix-ui/react-compose-refs": 1.1.2(@types/react@18.3.28)(react@18.3.1) @@ -11181,6 +12100,19 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-id@1.0.0(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + "@radix-ui/react-use-layout-effect": 1.0.0(react@18.3.1) + react: 18.3.1 + + "@radix-ui/react-id@1.1.0(@types/react@18.3.28)(react@18.3.1)": + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-id@1.1.1(@types/react@18.3.28)(react@18.3.1)": dependencies: "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@18.3.28)(react@18.3.1) @@ -11237,6 +12169,47 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-popover@1.1.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/primitive": 1.1.1 + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-context": 1.1.1(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-dismissable-layer": 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-focus-guards": 1.1.1(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-focus-scope": 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-id": 1.1.0(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-popper": 1.2.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-portal": 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-presence": 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-primitive": 2.0.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-slot": 1.1.2(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-use-controllable-state": 1.1.0(@types/react@18.3.28)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.28 + "@types/react-dom": 18.3.7(@types/react@18.3.28) + + "@radix-ui/react-popper@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@floating-ui/react-dom": 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-arrow": 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-context": 1.1.1(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-primitive": 2.0.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-use-callback-ref": 1.1.0(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-use-layout-effect": 1.1.0(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-use-rect": 1.1.0(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-use-size": 1.1.0(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/rect": 1.1.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.28 + "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@floating-ui/react-dom": 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -11255,6 +12228,16 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-portal@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/react-primitive": 2.0.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-use-layout-effect": 1.1.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.28 + "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@radix-ui/react-primitive": 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -11265,6 +12248,24 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-presence@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + "@radix-ui/react-compose-refs": 1.0.0(react@18.3.1) + "@radix-ui/react-use-layout-effect": 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + "@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.28)(react@18.3.1) + "@radix-ui/react-use-layout-effect": 1.1.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.28 + "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@radix-ui/react-compose-refs": 1.1.2(@types/react@18.3.28)(react@18.3.1) @@ -11275,6 +12276,22 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-primitive@1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + "@radix-ui/react-slot": 1.0.1(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + "@radix-ui/react-primitive@2.0.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/react-slot": 1.1.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.28 + "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@radix-ui/react-slot": 1.2.3(@types/react@18.3.28)(react@18.3.1) @@ -11293,6 +12310,21 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-roving-focus@1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + "@radix-ui/primitive": 1.0.0 + "@radix-ui/react-collection": 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-compose-refs": 1.0.0(react@18.3.1) + "@radix-ui/react-context": 1.0.0(react@18.3.1) + "@radix-ui/react-direction": 1.0.0(react@18.3.1) + "@radix-ui/react-id": 1.0.0(react@18.3.1) + "@radix-ui/react-primitive": 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-use-callback-ref": 1.0.0(react@18.3.1) + "@radix-ui/react-use-controllable-state": 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + "@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@radix-ui/primitive": 1.1.3 @@ -11356,6 +12388,19 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-slot@1.0.1(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + "@radix-ui/react-compose-refs": 1.0.0(react@18.3.1) + react: 18.3.1 + + "@radix-ui/react-slot@1.1.2(@types/react@18.3.28)(react@18.3.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-slot@1.2.3(@types/react@18.3.28)(react@18.3.1)": dependencies: "@radix-ui/react-compose-refs": 1.1.2(@types/react@18.3.28)(react@18.3.1) @@ -11370,6 +12415,20 @@ snapshots: optionalDependencies: "@types/react": 18.3.28 + "@radix-ui/react-tabs@1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + "@radix-ui/primitive": 1.0.0 + "@radix-ui/react-context": 1.0.0(react@18.3.1) + "@radix-ui/react-direction": 1.0.0(react@18.3.1) + "@radix-ui/react-id": 1.0.0(react@18.3.1) + "@radix-ui/react-presence": 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-primitive": 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-roving-focus": 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-use-controllable-state": 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + "@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@radix-ui/primitive": 1.1.3 @@ -11390,12 +12449,36 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/react-use-callback-ref@1.0.0(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + react: 18.3.1 + + "@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.28)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.28)(react@18.3.1)": dependencies: react: 18.3.1 optionalDependencies: "@types/react": 18.3.28 + "@radix-ui/react-use-controllable-state@1.0.0(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + "@radix-ui/react-use-callback-ref": 1.0.0(react@18.3.1) + react: 18.3.1 + + "@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.28)(react@18.3.1)": + dependencies: + "@radix-ui/react-use-callback-ref": 1.1.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.28)(react@18.3.1)": dependencies: "@radix-ui/react-use-effect-event": 0.0.2(@types/react@18.3.28)(react@18.3.1) @@ -11411,6 +12494,13 @@ snapshots: optionalDependencies: "@types/react": 18.3.28 + "@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.28)(react@18.3.1)": + dependencies: + "@radix-ui/react-use-callback-ref": 1.1.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.28)(react@18.3.1)": dependencies: "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@18.3.28)(react@18.3.1) @@ -11418,6 +12508,17 @@ snapshots: optionalDependencies: "@types/react": 18.3.28 + "@radix-ui/react-use-layout-effect@1.0.0(react@18.3.1)": + dependencies: + "@babel/runtime": 7.29.2 + react: 18.3.1 + + "@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.28)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.28)(react@18.3.1)": dependencies: react: 18.3.1 @@ -11430,6 +12531,13 @@ snapshots: optionalDependencies: "@types/react": 18.3.28 + "@radix-ui/react-use-rect@1.1.0(@types/react@18.3.28)(react@18.3.1)": + dependencies: + "@radix-ui/rect": 1.1.0 + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-use-rect@1.1.1(@types/react@18.3.28)(react@18.3.1)": dependencies: "@radix-ui/rect": 1.1.1 @@ -11437,6 +12545,13 @@ snapshots: optionalDependencies: "@types/react": 18.3.28 + "@radix-ui/react-use-size@1.1.0(@types/react@18.3.28)(react@18.3.1)": + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.28 + "@radix-ui/react-use-size@1.1.1(@types/react@18.3.28)(react@18.3.1)": dependencies: "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@18.3.28)(react@18.3.1) @@ -11453,6 +12568,8 @@ snapshots: "@types/react": 18.3.28 "@types/react-dom": 18.3.7(@types/react@18.3.28) + "@radix-ui/rect@1.1.0": {} + "@radix-ui/rect@1.1.1": {} "@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)": @@ -11958,7 +13075,7 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - "@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@24.12.0)(terser@5.46.1))": + "@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1))": dependencies: "@babel/core": 7.29.0 "@babel/plugin-transform-react-jsx-self": 7.27.1(@babel/core@7.29.0) @@ -11966,11 +13083,11 @@ snapshots: "@rolldown/pluginutils": 1.0.0-beta.27 "@types/babel__core": 7.20.5 react-refresh: 0.17.0 - vite: 5.4.21(@types/node@24.12.0)(terser@5.46.1) + vite: 5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1) transitivePeerDependencies: - supports-color - "@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(terser@5.46.1))": + "@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(sass@1.51.0)(terser@5.46.1))": dependencies: "@ampproject/remapping": 2.3.0 "@bcoe/v8-coverage": 0.2.3 @@ -11984,11 +13101,11 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(terser@5.46.1) + vitest: 2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(sass@1.51.0)(terser@5.46.1) transitivePeerDependencies: - supports-color - "@vitest/coverage-v8@4.1.2(vitest@4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))": + "@vitest/coverage-v8@4.1.2(vitest@4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))": dependencies: "@bcoe/v8-coverage": 1.0.2 "@vitest/utils": 4.1.2 @@ -12000,7 +13117,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) "@vitest/expect@2.1.9": dependencies: @@ -12018,21 +13135,21 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - "@vitest/mocker@2.1.9(vite@5.4.21(@types/node@24.12.0)(terser@5.46.1))": + "@vitest/mocker@2.1.9(vite@5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1))": dependencies: "@vitest/spy": 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 5.4.21(@types/node@24.12.0)(terser@5.46.1) + vite: 5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1) - "@vitest/mocker@4.1.2(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))": + "@vitest/mocker@4.1.2(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))": dependencies: "@vitest/spy": 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) "@vitest/pretty-format@2.1.9": dependencies: @@ -12321,6 +13438,8 @@ snapshots: dependencies: fill-range: 7.1.1 + browser-fs-access@0.29.1: {} + browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.10.12 @@ -12358,6 +13477,8 @@ snapshots: caniuse-lite@1.0.30001782: {} + canvas-roundrect-polyfill@0.0.1: {} + ccount@2.0.1: {} chai@5.3.3: @@ -12385,6 +13506,20 @@ snapshots: check-error@2.1.3: {} + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.18.1 + + chevrotain@11.0.3: + dependencies: + "@chevrotain/cst-dts-gen": 11.0.3 + "@chevrotain/gast": 11.0.3 + "@chevrotain/regexp-to-ast": 11.0.3 + "@chevrotain/types": 11.0.3 + "@chevrotain/utils": 11.0.3 + lodash-es: 4.17.21 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -12418,6 +13553,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@1.1.1: {} + clsx@2.1.1: {} cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -12485,10 +13622,16 @@ snapshots: dependencies: layout-base: 2.0.1 + crc-32@0.3.0: {} + croner@10.0.1: {} cronstrue@3.14.0: {} + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -12932,6 +14075,8 @@ snapshots: es-toolkit@1.45.1: {} + es6-promise-pool@2.5.0: {} + esbuild@0.21.5: optionalDependencies: "@esbuild/aix-ppc64": 0.21.5 @@ -13300,6 +14445,8 @@ snapshots: fraction.js@5.3.4: {} + fractional-indexing@3.2.0: {} + framer-motion@12.38.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: motion-dom: 12.38.0 @@ -13337,6 +14484,8 @@ snapshots: functions-have-names@1.2.3: {} + fuzzy@0.1.3: {} + generator-function@2.0.1: {} gensync@1.0.0-beta.2: {} @@ -13415,6 +14564,8 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 + glur@1.1.2: {} + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -13524,10 +14675,16 @@ snapshots: ignore@7.0.5: {} + image-blob-reduce@3.0.1: + dependencies: + pica: 7.1.1 + immer@10.2.0: {} immer@11.1.4: {} + immutable@4.3.9: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -13754,6 +14911,16 @@ snapshots: jose@6.2.2: {} + jotai-scope@0.7.2(jotai@2.11.0(@types/react@18.3.28)(react@18.3.1))(react@18.3.1): + dependencies: + jotai: 2.11.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + + jotai@2.11.0(@types/react@18.3.28)(react@18.3.1): + optionalDependencies: + "@types/react": 18.3.28 + react: 18.3.1 + jotai@2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1): optionalDependencies: "@babel/core": 7.29.0 @@ -13840,6 +15007,14 @@ snapshots: khroma@2.1.0: {} + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + layout-base@1.0.2: {} layout-base@2.0.1: {} @@ -13883,6 +15058,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.21: {} + lodash-es@4.18.1: {} lodash.debounce@4.0.8: {} @@ -13891,6 +15068,8 @@ snapshots: lodash.sortby@4.7.0: {} + lodash.throttle@4.1.1: {} + lodash@4.17.23: {} log-update@6.1.0: @@ -14370,6 +15549,11 @@ snapshots: ms@2.1.3: {} + multimath@2.0.0: + dependencies: + glur: 1.1.2 + object-assign: 4.1.1 + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -14378,6 +15562,10 @@ snapshots: nanoid@3.3.11: {} + nanoid@3.3.3: {} + + nanoid@4.0.2: {} + natural-compare@1.4.0: {} negotiator@1.0.0: {} @@ -14455,6 +15643,8 @@ snapshots: dependencies: mimic-function: 5.0.1 + open-color@1.9.1: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -14482,6 +15672,8 @@ snapshots: package-manager-detector@1.6.0: {} + pako@2.0.3: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -14528,6 +15720,8 @@ snapshots: pathval@2.0.1: {} + perfect-freehand@1.2.0: {} + pg-cloudflare@1.3.0: optional: true @@ -14563,6 +15757,14 @@ snapshots: dependencies: split2: 4.2.0 + pica@7.1.1: + dependencies: + glur: 1.1.2 + inherits: 2.0.4 + multimath: 2.0.0 + object-assign: 4.1.1 + webworkify: 1.5.0 + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -14603,8 +15805,21 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + png-chunk-text@1.0.0: {} + + png-chunks-encode@1.0.0: + dependencies: + crc-32: 0.3.0 + sliced: 1.0.1 + + png-chunks-extract@1.0.0: + dependencies: + crc-32: 0.3.0 + points-on-curve@0.2.0: {} + points-on-curve@1.0.1: {} + points-on-path@0.2.1: dependencies: path-data-parser: 0.1.0 @@ -14699,6 +15914,8 @@ snapshots: punycode@2.3.1: {} + pwacompat@2.0.17: {} + qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -15030,6 +16247,13 @@ snapshots: "@rollup/rollup-win32-x64-msvc": 4.60.1 fsevents: 2.3.3 + roughjs@4.6.4: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + roughjs@4.6.6: dependencies: hachure-fill: 0.5.2 @@ -15082,6 +16306,12 @@ snapshots: safer-buffer@2.1.2: {} + sass@1.51.0: + dependencies: + chokidar: 3.6.0 + immutable: 4.3.9 + source-map-js: 1.2.1 + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -15234,6 +16464,8 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + sliced@1.0.1: {} + smob@1.6.1: {} sonic-boom@4.2.1: @@ -15536,6 +16768,14 @@ snapshots: fsevents: 2.3.3 optional: true + tunnel-rat@0.1.2(@types/react@18.3.28)(immer@11.1.4)(react@18.3.1): + dependencies: + zustand: 4.5.7(@types/react@18.3.28)(immer@11.1.4)(react@18.3.1) + transitivePeerDependencies: + - "@types/react" + - immer + - react + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -15724,13 +16964,13 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-node@2.1.9(@types/node@24.12.0)(terser@5.46.1): + vite-node@2.1.9(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 1.1.2 - vite: 5.4.21(@types/node@24.12.0)(terser@5.46.1) + vite: 5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1) transitivePeerDependencies: - "@types/node" - less @@ -15742,18 +16982,18 @@ snapshots: - supports-color - terser - vite-plugin-pwa@1.2.0(vite@5.4.21(@types/node@24.12.0)(terser@5.46.1))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0): + vite-plugin-pwa@1.2.0(vite@5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0): dependencies: debug: 4.4.3 pretty-bytes: 6.1.1 tinyglobby: 0.2.15 - vite: 5.4.21(@types/node@24.12.0)(terser@5.46.1) + vite: 5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1) workbox-build: 7.4.0(@types/babel__core@7.20.5) workbox-window: 7.4.0 transitivePeerDependencies: - supports-color - vite@5.4.21(@types/node@24.12.0)(terser@5.46.1): + vite@5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1): dependencies: esbuild: 0.21.5 postcss: 8.5.8 @@ -15761,9 +17001,10 @@ snapshots: optionalDependencies: "@types/node": 24.12.0 fsevents: 2.3.3 + sass: 1.51.0 terser: 5.46.1 - vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) @@ -15775,14 +17016,15 @@ snapshots: "@types/node": 24.12.0 fsevents: 2.3.3 jiti: 1.21.7 + sass: 1.51.0 terser: 5.46.1 tsx: 4.21.0 yaml: 2.8.3 - vitest@2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(terser@5.46.1): + vitest@2.1.9(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(sass@1.51.0)(terser@5.46.1): dependencies: "@vitest/expect": 2.1.9 - "@vitest/mocker": 2.1.9(vite@5.4.21(@types/node@24.12.0)(terser@5.46.1)) + "@vitest/mocker": 2.1.9(vite@5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1)) "@vitest/pretty-format": 2.1.9 "@vitest/runner": 2.1.9 "@vitest/snapshot": 2.1.9 @@ -15798,8 +17040,8 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.1.1 tinyrainbow: 1.2.0 - vite: 5.4.21(@types/node@24.12.0)(terser@5.46.1) - vite-node: 2.1.9(@types/node@24.12.0)(terser@5.46.1) + vite: 5.4.21(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1) + vite-node: 2.1.9(@types/node@24.12.0)(sass@1.51.0)(terser@5.46.1) why-is-node-running: 2.3.0 optionalDependencies: "@types/node": 24.12.0 @@ -15816,10 +17058,10 @@ snapshots: - supports-color - terser - vitest@4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.2(@types/node@24.12.0)(happy-dom@18.0.1)(jsdom@29.1.1)(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: "@vitest/expect": 4.1.2 - "@vitest/mocker": 4.1.2(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + "@vitest/mocker": 4.1.2(vite@6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) "@vitest/pretty-format": 4.1.2 "@vitest/runner": 4.1.2 "@vitest/snapshot": 4.1.2 @@ -15836,7 +17078,7 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 6.4.1(@types/node@24.12.0)(jiti@1.21.7)(sass@1.51.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: "@types/node": 24.12.0 @@ -15845,6 +17087,23 @@ snapshots: transitivePeerDependencies: - msw + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -15857,6 +17116,8 @@ snapshots: webidl-conversions@8.0.1: {} + webworkify@1.5.0: {} + whatwg-mimetype@3.0.0: optional: true @@ -16095,4 +17356,12 @@ snapshots: zod@4.3.6: {} + zustand@4.5.7(@types/react@18.3.28)(immer@11.1.4)(react@18.3.1): + dependencies: + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.28 + immer: 11.1.4 + react: 18.3.1 + zwitch@2.0.4: {}