/** * Canvas module — the collaborative infinite canvas. * * This is the original rSpace canvas restructured as an rSpace module. * Routes are relative to the mount point (/:space/canvas in unified mode, * / in standalone mode). */ import { Hono } from "hono"; import { resolve } from "node:path"; import { renderShell } from "../../server/shell"; import { getModuleInfoList } from "../../shared/module"; import type { RSpaceModule } from "../../shared/module"; const DIST_DIR = resolve(import.meta.dir, "../../dist"); const routes = new Hono(); // GET / — serve the canvas page wrapped in shell routes.get("/", async (c) => { const spaceSlug = c.req.param("space") || c.req.query("space") || "demo"; // Read the canvas page template from dist const canvasFile = Bun.file(resolve(DIST_DIR, "canvas-module.html")); let canvasBody = ""; if (await canvasFile.exists()) { canvasBody = await canvasFile.text(); } else { // Fallback: serve full canvas.html directly if module template not built yet const fallbackFile = Bun.file(resolve(DIST_DIR, "canvas.html")); if (await fallbackFile.exists()) { return new Response(fallbackFile, { headers: { "Content-Type": "text/html" }, }); } canvasBody = `
Canvas loading...
`; } const html = renderShell({ title: `${spaceSlug} — Canvas | rSpace`, moduleId: "canvas", spaceSlug, body: canvasBody, modules: getModuleInfoList(), theme: "dark", scripts: ``, }); return c.html(html); }); export const canvasModule: RSpaceModule = { id: "canvas", name: "rSpace", icon: "🎨", description: "Real-time collaborative canvas", routes, feeds: [ { id: "shapes", name: "Canvas Shapes", kind: "data", description: "All shapes on this canvas layer — notes, embeds, arrows, etc.", filterable: true, }, { id: "connections", name: "Shape Connections", kind: "data", description: "Arrow connections between shapes — the canvas graph", }, ], acceptsFeeds: ["economic", "trust", "data", "attention", "governance", "resource"], };