diff --git a/lib/canvas-tools.ts b/lib/canvas-tools.ts index 054150c..f015b11 100644 --- a/lib/canvas-tools.ts +++ b/lib/canvas-tools.ts @@ -14,6 +14,8 @@ export interface CanvasToolDefinition { }; }; tagName: string; + /** Module that owns this tool (omit for core/always-available tools) */ + moduleId?: string; buildProps: (args: Record) => Record; actionLabel: (args: Record) => string; } @@ -35,6 +37,7 @@ const registry: CanvasToolDefinition[] = [ }, }, tagName: "folk-map", + moduleId: "rmaps", buildProps: (args) => ({ center: [args.longitude, args.latitude], zoom: args.zoom || 12, @@ -161,6 +164,7 @@ const registry: CanvasToolDefinition[] = [ }, }, tagName: "folk-destination", + moduleId: "rtrips", buildProps: (args) => ({ destName: args.destName, ...(args.country ? { country: args.country } : {}), @@ -186,6 +190,7 @@ const registry: CanvasToolDefinition[] = [ }, }, tagName: "folk-itinerary", + moduleId: "rtrips", buildProps: (args) => { let items: any[] = []; try { items = JSON.parse(args.itemsJson); } catch { items = []; } @@ -217,6 +222,7 @@ const registry: CanvasToolDefinition[] = [ }, }, tagName: "folk-booking", + moduleId: "rtrips", buildProps: (args) => ({ bookingType: args.bookingType, provider: args.provider, @@ -244,6 +250,7 @@ const registry: CanvasToolDefinition[] = [ }, }, tagName: "folk-budget", + moduleId: "rtrips", buildProps: (args) => { let expenses: any[] = []; try { expenses = JSON.parse(args.expensesJson); } catch { expenses = []; } @@ -268,6 +275,7 @@ const registry: CanvasToolDefinition[] = [ }, }, tagName: "folk-packing-list", + moduleId: "rtrips", buildProps: (args) => { let items: any[] = []; try { items = JSON.parse(args.itemsJson); } catch { items = []; } @@ -320,6 +328,7 @@ registry.push( }, }, tagName: "folk-social-post", + moduleId: "rsocials", buildProps: (args) => ({ platform: args.platform || "x", content: args.content, @@ -346,6 +355,7 @@ registry.push( }, }, tagName: "folk-social-thread", + moduleId: "rsocials", buildProps: (args) => { let tweets: string[] = []; try { tweets = JSON.parse(args.tweetsJson || "[]"); } catch { tweets = []; } @@ -374,6 +384,7 @@ registry.push( }, }, tagName: "folk-social-campaign", + moduleId: "rsocials", buildProps: (args) => ({ title: args.title, description: args.description || "", @@ -398,6 +409,7 @@ registry.push( }, }, tagName: "folk-social-newsletter", + moduleId: "rsocials", buildProps: (args) => ({ subject: args.subject, listName: args.listName || "", @@ -423,6 +435,7 @@ registry.push( }, }, tagName: "folk-commitment-pool", + moduleId: "rtime", buildProps: (args) => ({ spaceSlug: args.spaceSlug || "demo", }), @@ -443,6 +456,7 @@ registry.push( }, }, tagName: "folk-task-request", + moduleId: "rtime", buildProps: (args) => { let needs: Record = {}; try { needs = JSON.parse(args.needsJson || "{}"); } catch { needs = {}; } @@ -470,6 +484,7 @@ registry.push({ }, }, tagName: "folk-design-agent", + moduleId: "rdesign", buildProps: (args) => ({ brief: args.brief || "" }), actionLabel: (args) => `Opened design agent${args.brief ? `: ${args.brief.slice(0, 50)}` : ""}`, }); @@ -485,3 +500,11 @@ export function findTool(name: string): CanvasToolDefinition | undefined { export function registerCanvasTool(def: CanvasToolDefinition): void { CANVAS_TOOLS.push(def); } + +/** Return tools available for the given set of enabled modules. + * If enabledIds is null/undefined, all tools are returned (all modules enabled). */ +export function getToolsForModules(enabledIds: string[] | null | undefined): CanvasToolDefinition[] { + if (!enabledIds) return CANVAS_TOOLS; + const enabled = new Set(enabledIds); + return CANVAS_TOOLS.filter(t => !t.moduleId || enabled.has(t.moduleId)); +} diff --git a/lib/folk-commitment-pool.ts b/lib/folk-commitment-pool.ts index 3e8810e..1796a9c 100644 --- a/lib/folk-commitment-pool.ts +++ b/lib/folk-commitment-pool.ts @@ -198,6 +198,48 @@ export class FolkCommitmentPool extends FolkShape { this.styles = sheet; } + // โ”€โ”€ Module-disabled gating โ”€โ”€ + static #enabledModules: Set | null = null; + static #instances = new Set(); + + static setEnabledModules(ids: string[] | null) { + FolkCommitmentPool.#enabledModules = ids ? new Set(ids) : null; + for (const inst of FolkCommitmentPool.#instances) inst.#syncDisabledState(); + } + + #isModuleDisabled(): boolean { + const enabled = FolkCommitmentPool.#enabledModules; + if (!enabled) return false; + return !enabled.has("rtime"); + } + + #syncDisabledState() { + if (!this.#wrapper) return; + const disabled = this.#isModuleDisabled(); + const wasDisabled = this.hasAttribute("data-module-disabled"); + if (disabled && !wasDisabled) { + this.#showDisabledOverlay(); + } else if (!disabled && wasDisabled) { + this.removeAttribute("data-module-disabled"); + this.#wrapper.querySelector(".disabled-overlay")?.remove(); + this.#fetchCommitments(); + this.#startAnimation(); + } + } + + #showDisabledOverlay() { + this.setAttribute("data-module-disabled", ""); + if (this.#animFrame) { cancelAnimationFrame(this.#animFrame); this.#animFrame = 0; } + let overlay = this.#wrapper?.querySelector(".disabled-overlay") as HTMLElement; + if (!overlay && this.#wrapper) { + overlay = document.createElement("div"); + overlay.className = "disabled-overlay"; + overlay.style.cssText = "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:8px;background:rgba(15,23,42,0.85);border-radius:inherit;z-index:10;color:#94a3b8;font-size:13px;"; + overlay.innerHTML = '๐Ÿ”’rTime is disabled'; + this.#wrapper.appendChild(overlay); + } + } + #spaceSlug = "demo"; #canvas!: HTMLCanvasElement; #ctx!: CanvasRenderingContext2D; @@ -234,13 +276,20 @@ export class FolkCommitmentPool extends FolkShape { this.#canvas.addEventListener("pointerdown", this.#onPointerDown); this.#canvas.addEventListener("pointerleave", () => { this.#hoveredOrb = null; }); - this.#fetchCommitments(); - this.#startAnimation(); + FolkCommitmentPool.#instances.add(this); + + if (this.#isModuleDisabled()) { + this.#showDisabledOverlay(); + } else { + this.#fetchCommitments(); + this.#startAnimation(); + } return root; } disconnectedCallback() { + FolkCommitmentPool.#instances.delete(this); if (this.#animFrame) cancelAnimationFrame(this.#animFrame); this.#animFrame = 0; this.#removeGhost(); diff --git a/lib/folk-prompt.ts b/lib/folk-prompt.ts index d36c9c8..618c3e3 100644 --- a/lib/folk-prompt.ts +++ b/lib/folk-prompt.ts @@ -648,6 +648,7 @@ export class FolkPrompt extends FolkShape { })), model: this.#model, ...(useTools ? { useTools: true, systemPrompt: this.#systemPrompt || undefined } : {}), + ...(useTools && (window as any).__rspaceEnabledModules ? { enabledModules: (window as any).__rspaceEnabledModules } : {}), }), }); diff --git a/lib/folk-rapp.ts b/lib/folk-rapp.ts index 01d5239..59c48f1 100644 --- a/lib/folk-rapp.ts +++ b/lib/folk-rapp.ts @@ -1,6 +1,7 @@ import { FolkShape } from "./folk-shape"; import { css, html } from "./tags"; import { rspaceNavUrl } from "../shared/url-helpers"; +import { MODULE_META } from "./module-display"; import type { PortDescriptor } from "./data-types"; /** @@ -16,35 +17,6 @@ import type { PortDescriptor } from "./data-types"; * iframe โ†’ parent: { source: "rspace-rapp", type: "navigate", moduleId } */ -// Module metadata for header display (subset of rstack-app-switcher badges) -const MODULE_META: Record = { - rnotes: { badge: "rN", color: "#fcd34d", name: "rNotes", icon: "๐Ÿ“" }, - rphotos: { badge: "rPh", color: "#f9a8d4", name: "rPhotos", icon: "๐Ÿ“ธ" }, - rbooks: { badge: "rB", color: "#fda4af", name: "rBooks", icon: "๐Ÿ“š" }, - rpubs: { badge: "rP", color: "#fda4af", name: "rPubs", icon: "๐Ÿ“–" }, - rfiles: { badge: "rFi", color: "#67e8f9", name: "rFiles", icon: "๐Ÿ“" }, - rtasks: { badge: "rTa", color: "#cbd5e1", name: "rTasks", icon: "๐Ÿ“‹" }, - rforum: { badge: "rFo", color: "#fcd34d", name: "rForum", icon: "๐Ÿ’ฌ" }, - rinbox: { badge: "rI", color: "#a5b4fc", name: "rInbox", icon: "๐Ÿ“ง" }, - rtube: { badge: "rTu", color: "#f9a8d4", name: "rTube", icon: "๐ŸŽฌ" }, - rflows: { badge: "rFl", color: "#bef264", name: "rFlows", icon: "๐ŸŒŠ" }, - rwallet: { badge: "rW", color: "#fde047", name: "rWallet", icon: "๐Ÿ’ฐ" }, - rvote: { badge: "rV", color: "#c4b5fd", name: "rVote", icon: "๐Ÿ—ณ๏ธ" }, - rcart: { badge: "rCt", color: "#fdba74", name: "rCart", icon: "๐Ÿ›’" }, - rdata: { badge: "rD", color: "#d8b4fe", name: "rData", icon: "๐Ÿ“Š" }, - rnetwork: { badge: "rNe", color: "#93c5fd", name: "rNetwork", icon: "๐ŸŒ" }, - rsplat: { badge: "r3", color: "#d8b4fe", name: "rSplat", icon: "๐Ÿ”ฎ" }, - rswag: { badge: "rSw", color: "#fda4af", name: "rSwag", icon: "๐ŸŽจ" }, - rchoices: { badge: "rCo", color: "#f0abfc", name: "rChoices", icon: "๐Ÿค”" }, - rcal: { badge: "rC", color: "#7dd3fc", name: "rCal", icon: "๐Ÿ“…" }, - rtrips: { badge: "rT", color: "#6ee7b7", name: "rTrips", icon: "โœˆ๏ธ" }, - rmaps: { badge: "rM", color: "#86efac", name: "rMaps", icon: "๐Ÿ—บ๏ธ" }, - rmeets: { badge: "rMe", color: "#6ee7b7", name: "rMeets", icon: "๐Ÿ“น" }, - rschedule: { badge: "rSc", color: "#93c5fd", name: "rSchedule", icon: "โฐ" }, - rsocials: { badge: "rSo", color: "#f9a8d4", name: "rSocials", icon: "๐Ÿ“ฑ" }, - rdesign: { badge: "rDe", color: "#7c3aed", name: "rDesign", icon: "๐ŸŽจ" }, -}; - const styles = css` :host { display: flex; diff --git a/lib/folk-task-request.ts b/lib/folk-task-request.ts index b1bbc9a..a50e347 100644 --- a/lib/folk-task-request.ts +++ b/lib/folk-task-request.ts @@ -139,6 +139,45 @@ export class FolkTaskRequest extends FolkShape { this.styles = sheet; } + // โ”€โ”€ Module-disabled gating โ”€โ”€ + static #enabledModules: Set | null = null; + static #instances = new Set(); + + static setEnabledModules(ids: string[] | null) { + FolkTaskRequest.#enabledModules = ids ? new Set(ids) : null; + for (const inst of FolkTaskRequest.#instances) inst.#syncDisabledState(); + } + + #isModuleDisabled(): boolean { + const enabled = FolkTaskRequest.#enabledModules; + if (!enabled) return false; + return !enabled.has("rtime"); + } + + #syncDisabledState() { + if (!this.#wrapper) return; + const disabled = this.#isModuleDisabled(); + const wasDisabled = this.hasAttribute("data-module-disabled"); + if (disabled && !wasDisabled) { + this.#showDisabledOverlay(); + } else if (!disabled && wasDisabled) { + this.removeAttribute("data-module-disabled"); + this.#wrapper.querySelector(".disabled-overlay")?.remove(); + } + } + + #showDisabledOverlay() { + this.setAttribute("data-module-disabled", ""); + let overlay = this.#wrapper?.querySelector(".disabled-overlay") as HTMLElement; + if (!overlay && this.#wrapper) { + overlay = document.createElement("div"); + overlay.className = "disabled-overlay"; + overlay.style.cssText = "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:8px;background:rgba(15,23,42,0.85);border-radius:inherit;z-index:10;color:#94a3b8;font-size:13px;"; + overlay.innerHTML = '๐Ÿ”’rTime is disabled'; + this.#wrapper.appendChild(overlay); + } + } + #taskId = ""; #spaceSlug = "demo"; #taskName = "New Task"; @@ -234,10 +273,14 @@ export class FolkTaskRequest extends FolkShape { document.addEventListener("commitment-drag-move", this.#onDragMove as EventListener); document.addEventListener("commitment-drag-end", this.#onDragEnd as EventListener); + FolkTaskRequest.#instances.add(this); + if (this.#isModuleDisabled()) this.#showDisabledOverlay(); + return root; } disconnectedCallback() { + FolkTaskRequest.#instances.delete(this); document.removeEventListener("commitment-drag-start", this.#onDragStart as EventListener); document.removeEventListener("commitment-drag-move", this.#onDragMove as EventListener); document.removeEventListener("commitment-drag-end", this.#onDragEnd as EventListener); diff --git a/lib/module-display.ts b/lib/module-display.ts new file mode 100644 index 0000000..b9253fa --- /dev/null +++ b/lib/module-display.ts @@ -0,0 +1,41 @@ +/** + * Module display metadata โ€” badge colors, names, icons for canvas UI. + * Extracted from folk-rapp.ts so both folk-rapp and future display code + * can share a single source of truth. + */ + +export interface ModuleDisplayMeta { + badge: string; + color: string; + name: string; + icon: string; +} + +export const MODULE_META: Record = { + rnotes: { badge: "rN", color: "#fcd34d", name: "rNotes", icon: "๐Ÿ“" }, + rphotos: { badge: "rPh", color: "#f9a8d4", name: "rPhotos", icon: "๐Ÿ“ธ" }, + rbooks: { badge: "rB", color: "#fda4af", name: "rBooks", icon: "๐Ÿ“š" }, + rpubs: { badge: "rP", color: "#fda4af", name: "rPubs", icon: "๐Ÿ“–" }, + rfiles: { badge: "rFi", color: "#67e8f9", name: "rFiles", icon: "๐Ÿ“" }, + rtasks: { badge: "rTa", color: "#cbd5e1", name: "rTasks", icon: "๐Ÿ“‹" }, + rforum: { badge: "rFo", color: "#fcd34d", name: "rForum", icon: "๐Ÿ’ฌ" }, + rinbox: { badge: "rI", color: "#a5b4fc", name: "rInbox", icon: "๐Ÿ“ง" }, + rtube: { badge: "rTu", color: "#f9a8d4", name: "rTube", icon: "๐ŸŽฌ" }, + rflows: { badge: "rFl", color: "#bef264", name: "rFlows", icon: "๐ŸŒŠ" }, + rwallet: { badge: "rW", color: "#fde047", name: "rWallet", icon: "๐Ÿ’ฐ" }, + rvote: { badge: "rV", color: "#c4b5fd", name: "rVote", icon: "๐Ÿ—ณ๏ธ" }, + rcart: { badge: "rCt", color: "#fdba74", name: "rCart", icon: "๐Ÿ›’" }, + rdata: { badge: "rD", color: "#d8b4fe", name: "rData", icon: "๐Ÿ“Š" }, + rnetwork: { badge: "rNe", color: "#93c5fd", name: "rNetwork", icon: "๐ŸŒ" }, + rsplat: { badge: "r3", color: "#d8b4fe", name: "rSplat", icon: "๐Ÿ”ฎ" }, + rswag: { badge: "rSw", color: "#fda4af", name: "rSwag", icon: "๐ŸŽจ" }, + rchoices: { badge: "rCo", color: "#f0abfc", name: "rChoices", icon: "๐Ÿค”" }, + rcal: { badge: "rC", color: "#7dd3fc", name: "rCal", icon: "๐Ÿ“…" }, + rtrips: { badge: "rT", color: "#6ee7b7", name: "rTrips", icon: "โœˆ๏ธ" }, + rmaps: { badge: "rM", color: "#86efac", name: "rMaps", icon: "๐Ÿ—บ๏ธ" }, + rmeets: { badge: "rMe", color: "#6ee7b7", name: "rMeets", icon: "๐Ÿ“น" }, + rschedule: { badge: "rSc", color: "#93c5fd", name: "rSchedule", icon: "โฐ" }, + rsocials: { badge: "rSo", color: "#f9a8d4", name: "rSocials", icon: "๐Ÿ“ฑ" }, + rdesign: { badge: "rDe", color: "#7c3aed", name: "rDesign", icon: "๐ŸŽจ" }, + rtime: { badge: "rTi", color: "#a78bfa", name: "rTime", icon: "โณ" }, +}; diff --git a/lib/shape-registry.ts b/lib/shape-registry.ts index 097e865..0b05ef5 100644 --- a/lib/shape-registry.ts +++ b/lib/shape-registry.ts @@ -19,12 +19,23 @@ export interface ShapeRegistration { class ShapeRegistry { #registrations = new Map(); + #moduleMap = new Map(); /** Register a shape type. */ register(tagName: string, elementClass: ShapeRegistration["elementClass"]): void { this.#registrations.set(tagName, { tagName, elementClass }); } + /** Associate a shape tag with a module ID. */ + setModule(tagName: string, moduleId: string): void { + this.#moduleMap.set(tagName, moduleId); + } + + /** Get the owning module ID for a shape tag, or undefined if core/always-available. */ + moduleOf(tagName: string): string | undefined { + return this.#moduleMap.get(tagName); + } + /** Get registration for a tag name. */ getRegistration(tagName: string): ShapeRegistration | undefined { return this.#registrations.get(tagName); diff --git a/modules/rcal/mod.ts b/modules/rcal/mod.ts index e93e57b..fb627b0 100644 --- a/modules/rcal/mod.ts +++ b/modules/rcal/mod.ts @@ -1082,6 +1082,7 @@ export const calModule: RSpaceModule = { name: "rCal", icon: "๐Ÿ“…", description: "Temporal coordination calendar with lunar, solar, and seasonal systems", + canvasShapes: ["folk-calendar"], scoping: { defaultScope: 'global', userConfigurable: true }, docSchemas: [{ pattern: '{space}:cal:events', description: 'Calendar events and sources', init: calendarSchema.init }], routes, diff --git a/modules/rchoices/mod.ts b/modules/rchoices/mod.ts index 746568e..46d88ba 100644 --- a/modules/rchoices/mod.ts +++ b/modules/rchoices/mod.ts @@ -142,6 +142,7 @@ export const choicesModule: RSpaceModule = { name: "rChoices", icon: "โ˜‘", description: "Polls, rankings, and multi-criteria scoring", + canvasShapes: ["folk-choice-vote", "folk-choice-rank", "folk-choice-spider", "folk-spider-3d", "folk-choice-conviction"], scoping: { defaultScope: 'space', userConfigurable: false }, routes, standaloneDomain: "rchoices.online", diff --git a/modules/rdesign/mod.ts b/modules/rdesign/mod.ts index f82a1ca..93020c6 100644 --- a/modules/rdesign/mod.ts +++ b/modules/rdesign/mod.ts @@ -673,6 +673,8 @@ export const designModule: RSpaceModule = { name: "rDesign", icon: "๐ŸŽจ", description: "AI-powered DTP workspace โ€” text in, design out", + canvasShapes: ["folk-design-agent"], + canvasToolIds: ["create_design_agent"], scoping: { defaultScope: 'global', userConfigurable: false }, publicWrite: true, routes, diff --git a/modules/rmaps/mod.ts b/modules/rmaps/mod.ts index ffcb3c8..82f5907 100644 --- a/modules/rmaps/mod.ts +++ b/modules/rmaps/mod.ts @@ -304,6 +304,8 @@ export const mapsModule: RSpaceModule = { name: "rMaps", icon: "๐Ÿ—บ", description: "Real-time collaborative location sharing and indoor/outdoor maps", + canvasShapes: ["folk-map"], + canvasToolIds: ["create_map"], scoping: { defaultScope: 'global', userConfigurable: false }, routes, landingPage: renderLanding, diff --git a/modules/rsocials/mod.ts b/modules/rsocials/mod.ts index 07de488..da10bf2 100644 --- a/modules/rsocials/mod.ts +++ b/modules/rsocials/mod.ts @@ -2270,6 +2270,8 @@ export const socialsModule: RSpaceModule = { name: "rSocials", icon: "๐Ÿ“ข", description: "Federated social feed aggregator for communities", + canvasShapes: ["folk-social-post", "folk-social-thread", "folk-social-campaign", "folk-social-newsletter"], + canvasToolIds: ["create_social_post", "create_social_thread", "create_campaign_card", "create_newsletter_card"], scoping: { defaultScope: "space", userConfigurable: true }, docSchemas: [{ pattern: "{space}:socials:data", description: "Threads and campaigns", init: socialsSchema.init }], routes, diff --git a/modules/rsplat/mod.ts b/modules/rsplat/mod.ts index 5e31873..2ebf387 100644 --- a/modules/rsplat/mod.ts +++ b/modules/rsplat/mod.ts @@ -863,6 +863,7 @@ export const splatModule: RSpaceModule = { name: "rSplat", icon: "๐Ÿ”ฎ", description: "3D Gaussian splat viewer", + canvasShapes: ["folk-splat"], publicWrite: true, scoping: { defaultScope: 'global', userConfigurable: true }, docSchemas: [{ pattern: '{space}:splat:scenes', description: 'Splat scene metadata', init: splatScenesSchema.init }], diff --git a/modules/rtime/mod.ts b/modules/rtime/mod.ts index b5628e1..018b9c6 100644 --- a/modules/rtime/mod.ts +++ b/modules/rtime/mod.ts @@ -439,6 +439,8 @@ export const timeModule: RSpaceModule = { name: "rTime", icon: "โณ", description: "Timebank commitment pool & weaving dashboard", + canvasShapes: ["folk-commitment-pool", "folk-task-request"], + canvasToolIds: ["create_commitment_pool", "create_task_request"], scoping: { defaultScope: 'space', userConfigurable: false }, docSchemas: [ { pattern: '{space}:rtime:commitments', description: 'Commitment pool', init: commitmentsSchema.init }, diff --git a/modules/rtrips/mod.ts b/modules/rtrips/mod.ts index 16fd0c5..c6113a3 100644 --- a/modules/rtrips/mod.ts +++ b/modules/rtrips/mod.ts @@ -754,6 +754,8 @@ export const tripsModule: RSpaceModule = { name: "rTrips", icon: "โœˆ๏ธ", description: "Collaborative trip planner with itinerary, bookings, and expense splitting", + canvasShapes: ["folk-itinerary", "folk-destination", "folk-budget", "folk-packing-list", "folk-booking"], + canvasToolIds: ["create_destination", "create_itinerary", "create_booking", "create_budget", "create_packing_list"], scoping: { defaultScope: 'global', userConfigurable: true }, docSchemas: [{ pattern: '{space}:trips:trips:{tripId}', description: 'Trip with destinations and itinerary', init: tripSchema.init }], routes, diff --git a/modules/rwallet/mod.ts b/modules/rwallet/mod.ts index e1a3c18..ffde685 100644 --- a/modules/rwallet/mod.ts +++ b/modules/rwallet/mod.ts @@ -1294,6 +1294,7 @@ export const walletModule: RSpaceModule = { name: "rWallet", icon: "๐Ÿ’ฐ", description: "Multichain Safe wallet visualization and treasury management", + canvasShapes: ["folk-token-mint", "folk-token-ledger", "folk-transaction-builder"], scoping: { defaultScope: 'global', userConfigurable: false }, routes, standaloneDomain: "rwallet.online", diff --git a/server/index.ts b/server/index.ts index 5cbeeaf..d9eacfd 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1899,7 +1899,7 @@ After creating shapes, give a brief summary of what you placed. Only create shap For text-only questions (explanations, coding help, math), respond with text โ€” don't create shapes unless asked.`; app.post("/api/prompt", async (c) => { - const { messages, model = "gemini-flash", useTools = false, systemPrompt } = await c.req.json(); + const { messages, model = "gemini-flash", useTools = false, systemPrompt, enabledModules } = await c.req.json(); if (!messages?.length) return c.json({ error: "messages required" }, 400); // Determine provider @@ -1912,8 +1912,9 @@ app.post("/api/prompt", async (c) => { // Build model config with optional tools const modelConfig: any = { model: GEMINI_MODELS[model] }; if (useTools) { - const { CANVAS_TOOL_DECLARATIONS } = await import("../lib/canvas-tools"); - modelConfig.tools = [{ functionDeclarations: CANVAS_TOOL_DECLARATIONS }]; + const { getToolsForModules } = await import("../lib/canvas-tools"); + const tools = getToolsForModules(enabledModules ?? null); + modelConfig.tools = [{ functionDeclarations: tools.map(t => t.declaration) }]; modelConfig.systemInstruction = systemPrompt || CANVAS_TOOLS_SYSTEM_PROMPT; } const geminiModel = genAI.getGenerativeModel(modelConfig); diff --git a/shared/module.ts b/shared/module.ts index 63e7e46..d251c77 100644 --- a/shared/module.ts +++ b/shared/module.ts @@ -173,6 +173,11 @@ export interface RSpaceModule { /** Per-module settings schema for space-level configuration */ settingsSchema?: ModuleSettingField[]; + + /** Canvas shape tag names this module owns (e.g. ["folk-commitment-pool"]) */ + canvasShapes?: string[]; + /** Canvas AI tool IDs this module owns (e.g. ["create_commitment_pool"]) */ + canvasToolIds?: string[]; } /** Registry of all loaded modules */ @@ -210,6 +215,8 @@ export interface ModuleInfo { subPageInfos?: Array<{ path: string; title: string }>; settingsSchema?: ModuleSettingField[]; onboardingActions?: OnboardingAction[]; + canvasShapes?: string[]; + canvasToolIds?: string[]; } export function getModuleInfoList(): ModuleInfo[] { @@ -230,5 +237,7 @@ export function getModuleInfoList(): ModuleInfo[] { ...(m.subPageInfos ? { subPageInfos: m.subPageInfos.map(s => ({ path: s.path, title: s.title })) } : {}), ...(m.settingsSchema ? { settingsSchema: m.settingsSchema } : {}), ...(m.onboardingActions ? { onboardingActions: m.onboardingActions } : {}), + ...(m.canvasShapes ? { canvasShapes: m.canvasShapes } : {}), + ...(m.canvasToolIds ? { canvasToolIds: m.canvasToolIds } : {}), })); } diff --git a/website/canvas.html b/website/canvas.html index b8cdc7c..bf684db 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -2032,7 +2032,7 @@ - + @@ -2091,12 +2091,12 @@
Decide
- - - - - - + + + + + + @@ -2111,7 +2111,7 @@
Spend
- +
@@ -2575,6 +2575,15 @@ const FolkRApp = customElements.get("folk-rapp"); if (FolkRApp?.setEnabledModules) FolkRApp.setEnabledModules(enabledIds); }); + // Initialize rTime shape disabled states + customElements.whenDefined("folk-commitment-pool").then(() => { + const cls = customElements.get("folk-commitment-pool"); + if (cls?.setEnabledModules) cls.setEnabledModules(enabledIds); + }); + customElements.whenDefined("folk-task-request").then(() => { + const cls = customElements.get("folk-task-request"); + if (cls?.setEnabledModules) cls.setEnabledModules(enabledIds); + }); }).catch(() => {}); // React to runtime module toggling from app switcher @@ -2591,6 +2600,12 @@ const FolkRApp = customElements.get("folk-rapp"); if (FolkRApp?.setEnabledModules) FolkRApp.setEnabledModules(enabledModules); + // Update rTime shape disabled states + const FolkCommitmentPool = customElements.get("folk-commitment-pool"); + if (FolkCommitmentPool?.setEnabledModules) FolkCommitmentPool.setEnabledModules(enabledModules); + const FolkTaskRequest = customElements.get("folk-task-request"); + if (FolkTaskRequest?.setEnabledModules) FolkTaskRequest.setEnabledModules(enabledModules); + document.querySelectorAll("[data-requires-module]").forEach(el => { el.style.display = enabledSet.has(el.dataset.requiresModule) ? "" : "none"; }); @@ -2790,6 +2805,13 @@ shapeRegistry.register("folk-holon", FolkHolon); shapeRegistry.register("folk-holon-browser", FolkHolonBrowser); + // Wire shapeโ†’module affiliations from module declarations + for (const mod of window.__rspaceAllModules || []) { + for (const tag of mod.canvasShapes || []) { + shapeRegistry.setModule(tag, mod.id); + } + } + // Zoom and pan state โ€” declared early to avoid TDZ errors // (event handlers reference these before awaits yield execution) let scale = 1;