Merge branch 'dev'
CI/CD / deploy (push) Waiting to run Details

This commit is contained in:
Jeff Emmett 2026-04-01 15:19:33 -07:00
commit 007df8a3bd
19 changed files with 228 additions and 42 deletions

View File

@ -14,6 +14,8 @@ export interface CanvasToolDefinition {
}; };
}; };
tagName: string; tagName: string;
/** Module that owns this tool (omit for core/always-available tools) */
moduleId?: string;
buildProps: (args: Record<string, any>) => Record<string, any>; buildProps: (args: Record<string, any>) => Record<string, any>;
actionLabel: (args: Record<string, any>) => string; actionLabel: (args: Record<string, any>) => string;
} }
@ -35,6 +37,7 @@ const registry: CanvasToolDefinition[] = [
}, },
}, },
tagName: "folk-map", tagName: "folk-map",
moduleId: "rmaps",
buildProps: (args) => ({ buildProps: (args) => ({
center: [args.longitude, args.latitude], center: [args.longitude, args.latitude],
zoom: args.zoom || 12, zoom: args.zoom || 12,
@ -161,6 +164,7 @@ const registry: CanvasToolDefinition[] = [
}, },
}, },
tagName: "folk-destination", tagName: "folk-destination",
moduleId: "rtrips",
buildProps: (args) => ({ buildProps: (args) => ({
destName: args.destName, destName: args.destName,
...(args.country ? { country: args.country } : {}), ...(args.country ? { country: args.country } : {}),
@ -186,6 +190,7 @@ const registry: CanvasToolDefinition[] = [
}, },
}, },
tagName: "folk-itinerary", tagName: "folk-itinerary",
moduleId: "rtrips",
buildProps: (args) => { buildProps: (args) => {
let items: any[] = []; let items: any[] = [];
try { items = JSON.parse(args.itemsJson); } catch { items = []; } try { items = JSON.parse(args.itemsJson); } catch { items = []; }
@ -217,6 +222,7 @@ const registry: CanvasToolDefinition[] = [
}, },
}, },
tagName: "folk-booking", tagName: "folk-booking",
moduleId: "rtrips",
buildProps: (args) => ({ buildProps: (args) => ({
bookingType: args.bookingType, bookingType: args.bookingType,
provider: args.provider, provider: args.provider,
@ -244,6 +250,7 @@ const registry: CanvasToolDefinition[] = [
}, },
}, },
tagName: "folk-budget", tagName: "folk-budget",
moduleId: "rtrips",
buildProps: (args) => { buildProps: (args) => {
let expenses: any[] = []; let expenses: any[] = [];
try { expenses = JSON.parse(args.expensesJson); } catch { expenses = []; } try { expenses = JSON.parse(args.expensesJson); } catch { expenses = []; }
@ -268,6 +275,7 @@ const registry: CanvasToolDefinition[] = [
}, },
}, },
tagName: "folk-packing-list", tagName: "folk-packing-list",
moduleId: "rtrips",
buildProps: (args) => { buildProps: (args) => {
let items: any[] = []; let items: any[] = [];
try { items = JSON.parse(args.itemsJson); } catch { items = []; } try { items = JSON.parse(args.itemsJson); } catch { items = []; }
@ -320,6 +328,7 @@ registry.push(
}, },
}, },
tagName: "folk-social-post", tagName: "folk-social-post",
moduleId: "rsocials",
buildProps: (args) => ({ buildProps: (args) => ({
platform: args.platform || "x", platform: args.platform || "x",
content: args.content, content: args.content,
@ -346,6 +355,7 @@ registry.push(
}, },
}, },
tagName: "folk-social-thread", tagName: "folk-social-thread",
moduleId: "rsocials",
buildProps: (args) => { buildProps: (args) => {
let tweets: string[] = []; let tweets: string[] = [];
try { tweets = JSON.parse(args.tweetsJson || "[]"); } catch { tweets = []; } try { tweets = JSON.parse(args.tweetsJson || "[]"); } catch { tweets = []; }
@ -374,6 +384,7 @@ registry.push(
}, },
}, },
tagName: "folk-social-campaign", tagName: "folk-social-campaign",
moduleId: "rsocials",
buildProps: (args) => ({ buildProps: (args) => ({
title: args.title, title: args.title,
description: args.description || "", description: args.description || "",
@ -398,6 +409,7 @@ registry.push(
}, },
}, },
tagName: "folk-social-newsletter", tagName: "folk-social-newsletter",
moduleId: "rsocials",
buildProps: (args) => ({ buildProps: (args) => ({
subject: args.subject, subject: args.subject,
listName: args.listName || "", listName: args.listName || "",
@ -423,6 +435,7 @@ registry.push(
}, },
}, },
tagName: "folk-commitment-pool", tagName: "folk-commitment-pool",
moduleId: "rtime",
buildProps: (args) => ({ buildProps: (args) => ({
spaceSlug: args.spaceSlug || "demo", spaceSlug: args.spaceSlug || "demo",
}), }),
@ -443,6 +456,7 @@ registry.push(
}, },
}, },
tagName: "folk-task-request", tagName: "folk-task-request",
moduleId: "rtime",
buildProps: (args) => { buildProps: (args) => {
let needs: Record<string, number> = {}; let needs: Record<string, number> = {};
try { needs = JSON.parse(args.needsJson || "{}"); } catch { needs = {}; } try { needs = JSON.parse(args.needsJson || "{}"); } catch { needs = {}; }
@ -470,6 +484,7 @@ registry.push({
}, },
}, },
tagName: "folk-design-agent", tagName: "folk-design-agent",
moduleId: "rdesign",
buildProps: (args) => ({ brief: args.brief || "" }), buildProps: (args) => ({ brief: args.brief || "" }),
actionLabel: (args) => `Opened design agent${args.brief ? `: ${args.brief.slice(0, 50)}` : ""}`, 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 { export function registerCanvasTool(def: CanvasToolDefinition): void {
CANVAS_TOOLS.push(def); 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));
}

View File

@ -198,6 +198,48 @@ export class FolkCommitmentPool extends FolkShape {
this.styles = sheet; this.styles = sheet;
} }
// ── Module-disabled gating ──
static #enabledModules: Set<string> | null = null;
static #instances = new Set<FolkCommitmentPool>();
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 = '<span style="font-size:24px">🔒</span><span>rTime is disabled</span>';
this.#wrapper.appendChild(overlay);
}
}
#spaceSlug = "demo"; #spaceSlug = "demo";
#canvas!: HTMLCanvasElement; #canvas!: HTMLCanvasElement;
#ctx!: CanvasRenderingContext2D; #ctx!: CanvasRenderingContext2D;
@ -234,13 +276,20 @@ export class FolkCommitmentPool extends FolkShape {
this.#canvas.addEventListener("pointerdown", this.#onPointerDown); this.#canvas.addEventListener("pointerdown", this.#onPointerDown);
this.#canvas.addEventListener("pointerleave", () => { this.#hoveredOrb = null; }); this.#canvas.addEventListener("pointerleave", () => { this.#hoveredOrb = null; });
this.#fetchCommitments(); FolkCommitmentPool.#instances.add(this);
this.#startAnimation();
if (this.#isModuleDisabled()) {
this.#showDisabledOverlay();
} else {
this.#fetchCommitments();
this.#startAnimation();
}
return root; return root;
} }
disconnectedCallback() { disconnectedCallback() {
FolkCommitmentPool.#instances.delete(this);
if (this.#animFrame) cancelAnimationFrame(this.#animFrame); if (this.#animFrame) cancelAnimationFrame(this.#animFrame);
this.#animFrame = 0; this.#animFrame = 0;
this.#removeGhost(); this.#removeGhost();

View File

@ -648,6 +648,7 @@ export class FolkPrompt extends FolkShape {
})), })),
model: this.#model, model: this.#model,
...(useTools ? { useTools: true, systemPrompt: this.#systemPrompt || undefined } : {}), ...(useTools ? { useTools: true, systemPrompt: this.#systemPrompt || undefined } : {}),
...(useTools && (window as any).__rspaceEnabledModules ? { enabledModules: (window as any).__rspaceEnabledModules } : {}),
}), }),
}); });

View File

@ -1,6 +1,7 @@
import { FolkShape } from "./folk-shape"; import { FolkShape } from "./folk-shape";
import { css, html } from "./tags"; import { css, html } from "./tags";
import { rspaceNavUrl } from "../shared/url-helpers"; import { rspaceNavUrl } from "../shared/url-helpers";
import { MODULE_META } from "./module-display";
import type { PortDescriptor } from "./data-types"; import type { PortDescriptor } from "./data-types";
/** /**
@ -16,35 +17,6 @@ import type { PortDescriptor } from "./data-types";
* iframe parent: { source: "rspace-rapp", type: "navigate", moduleId } * iframe parent: { source: "rspace-rapp", type: "navigate", moduleId }
*/ */
// Module metadata for header display (subset of rstack-app-switcher badges)
const MODULE_META: Record<string, { badge: string; color: string; name: string; icon: string }> = {
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` const styles = css`
:host { :host {
display: flex; display: flex;

View File

@ -139,6 +139,45 @@ export class FolkTaskRequest extends FolkShape {
this.styles = sheet; this.styles = sheet;
} }
// ── Module-disabled gating ──
static #enabledModules: Set<string> | null = null;
static #instances = new Set<FolkTaskRequest>();
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 = '<span style="font-size:24px">🔒</span><span>rTime is disabled</span>';
this.#wrapper.appendChild(overlay);
}
}
#taskId = ""; #taskId = "";
#spaceSlug = "demo"; #spaceSlug = "demo";
#taskName = "New Task"; #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-move", this.#onDragMove as EventListener);
document.addEventListener("commitment-drag-end", this.#onDragEnd as EventListener); document.addEventListener("commitment-drag-end", this.#onDragEnd as EventListener);
FolkTaskRequest.#instances.add(this);
if (this.#isModuleDisabled()) this.#showDisabledOverlay();
return root; return root;
} }
disconnectedCallback() { disconnectedCallback() {
FolkTaskRequest.#instances.delete(this);
document.removeEventListener("commitment-drag-start", this.#onDragStart as EventListener); document.removeEventListener("commitment-drag-start", this.#onDragStart as EventListener);
document.removeEventListener("commitment-drag-move", this.#onDragMove as EventListener); document.removeEventListener("commitment-drag-move", this.#onDragMove as EventListener);
document.removeEventListener("commitment-drag-end", this.#onDragEnd as EventListener); document.removeEventListener("commitment-drag-end", this.#onDragEnd as EventListener);

41
lib/module-display.ts Normal file
View File

@ -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<string, ModuleDisplayMeta> = {
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: "⏳" },
};

View File

@ -19,12 +19,23 @@ export interface ShapeRegistration {
class ShapeRegistry { class ShapeRegistry {
#registrations = new Map<string, ShapeRegistration>(); #registrations = new Map<string, ShapeRegistration>();
#moduleMap = new Map<string, string>();
/** Register a shape type. */ /** Register a shape type. */
register(tagName: string, elementClass: ShapeRegistration["elementClass"]): void { register(tagName: string, elementClass: ShapeRegistration["elementClass"]): void {
this.#registrations.set(tagName, { tagName, elementClass }); 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. */ /** Get registration for a tag name. */
getRegistration(tagName: string): ShapeRegistration | undefined { getRegistration(tagName: string): ShapeRegistration | undefined {
return this.#registrations.get(tagName); return this.#registrations.get(tagName);

View File

@ -1082,6 +1082,7 @@ export const calModule: RSpaceModule = {
name: "rCal", name: "rCal",
icon: "📅", icon: "📅",
description: "Temporal coordination calendar with lunar, solar, and seasonal systems", description: "Temporal coordination calendar with lunar, solar, and seasonal systems",
canvasShapes: ["folk-calendar"],
scoping: { defaultScope: 'global', userConfigurable: true }, scoping: { defaultScope: 'global', userConfigurable: true },
docSchemas: [{ pattern: '{space}:cal:events', description: 'Calendar events and sources', init: calendarSchema.init }], docSchemas: [{ pattern: '{space}:cal:events', description: 'Calendar events and sources', init: calendarSchema.init }],
routes, routes,

View File

@ -142,6 +142,7 @@ export const choicesModule: RSpaceModule = {
name: "rChoices", name: "rChoices",
icon: "☑", icon: "☑",
description: "Polls, rankings, and multi-criteria scoring", 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 }, scoping: { defaultScope: 'space', userConfigurable: false },
routes, routes,
standaloneDomain: "rchoices.online", standaloneDomain: "rchoices.online",

View File

@ -673,6 +673,8 @@ export const designModule: RSpaceModule = {
name: "rDesign", name: "rDesign",
icon: "🎨", icon: "🎨",
description: "AI-powered DTP workspace — text in, design out", description: "AI-powered DTP workspace — text in, design out",
canvasShapes: ["folk-design-agent"],
canvasToolIds: ["create_design_agent"],
scoping: { defaultScope: 'global', userConfigurable: false }, scoping: { defaultScope: 'global', userConfigurable: false },
publicWrite: true, publicWrite: true,
routes, routes,

View File

@ -304,6 +304,8 @@ export const mapsModule: RSpaceModule = {
name: "rMaps", name: "rMaps",
icon: "🗺", icon: "🗺",
description: "Real-time collaborative location sharing and indoor/outdoor maps", description: "Real-time collaborative location sharing and indoor/outdoor maps",
canvasShapes: ["folk-map"],
canvasToolIds: ["create_map"],
scoping: { defaultScope: 'global', userConfigurable: false }, scoping: { defaultScope: 'global', userConfigurable: false },
routes, routes,
landingPage: renderLanding, landingPage: renderLanding,

View File

@ -2270,6 +2270,8 @@ export const socialsModule: RSpaceModule = {
name: "rSocials", name: "rSocials",
icon: "📢", icon: "📢",
description: "Federated social feed aggregator for communities", 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 }, scoping: { defaultScope: "space", userConfigurable: true },
docSchemas: [{ pattern: "{space}:socials:data", description: "Threads and campaigns", init: socialsSchema.init }], docSchemas: [{ pattern: "{space}:socials:data", description: "Threads and campaigns", init: socialsSchema.init }],
routes, routes,

View File

@ -863,6 +863,7 @@ export const splatModule: RSpaceModule = {
name: "rSplat", name: "rSplat",
icon: "🔮", icon: "🔮",
description: "3D Gaussian splat viewer", description: "3D Gaussian splat viewer",
canvasShapes: ["folk-splat"],
publicWrite: true, publicWrite: true,
scoping: { defaultScope: 'global', userConfigurable: true }, scoping: { defaultScope: 'global', userConfigurable: true },
docSchemas: [{ pattern: '{space}:splat:scenes', description: 'Splat scene metadata', init: splatScenesSchema.init }], docSchemas: [{ pattern: '{space}:splat:scenes', description: 'Splat scene metadata', init: splatScenesSchema.init }],

View File

@ -439,6 +439,8 @@ export const timeModule: RSpaceModule = {
name: "rTime", name: "rTime",
icon: "⏳", icon: "⏳",
description: "Timebank commitment pool & weaving dashboard", 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 }, scoping: { defaultScope: 'space', userConfigurable: false },
docSchemas: [ docSchemas: [
{ pattern: '{space}:rtime:commitments', description: 'Commitment pool', init: commitmentsSchema.init }, { pattern: '{space}:rtime:commitments', description: 'Commitment pool', init: commitmentsSchema.init },

View File

@ -754,6 +754,8 @@ export const tripsModule: RSpaceModule = {
name: "rTrips", name: "rTrips",
icon: "✈️", icon: "✈️",
description: "Collaborative trip planner with itinerary, bookings, and expense splitting", 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 }, scoping: { defaultScope: 'global', userConfigurable: true },
docSchemas: [{ pattern: '{space}:trips:trips:{tripId}', description: 'Trip with destinations and itinerary', init: tripSchema.init }], docSchemas: [{ pattern: '{space}:trips:trips:{tripId}', description: 'Trip with destinations and itinerary', init: tripSchema.init }],
routes, routes,

View File

@ -1294,6 +1294,7 @@ export const walletModule: RSpaceModule = {
name: "rWallet", name: "rWallet",
icon: "💰", icon: "💰",
description: "Multichain Safe wallet visualization and treasury management", description: "Multichain Safe wallet visualization and treasury management",
canvasShapes: ["folk-token-mint", "folk-token-ledger", "folk-transaction-builder"],
scoping: { defaultScope: 'global', userConfigurable: false }, scoping: { defaultScope: 'global', userConfigurable: false },
routes, routes,
standaloneDomain: "rwallet.online", standaloneDomain: "rwallet.online",

View File

@ -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.`; For text-only questions (explanations, coding help, math), respond with text don't create shapes unless asked.`;
app.post("/api/prompt", async (c) => { 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); if (!messages?.length) return c.json({ error: "messages required" }, 400);
// Determine provider // Determine provider
@ -1912,8 +1912,9 @@ app.post("/api/prompt", async (c) => {
// Build model config with optional tools // Build model config with optional tools
const modelConfig: any = { model: GEMINI_MODELS[model] }; const modelConfig: any = { model: GEMINI_MODELS[model] };
if (useTools) { if (useTools) {
const { CANVAS_TOOL_DECLARATIONS } = await import("../lib/canvas-tools"); const { getToolsForModules } = await import("../lib/canvas-tools");
modelConfig.tools = [{ functionDeclarations: CANVAS_TOOL_DECLARATIONS }]; const tools = getToolsForModules(enabledModules ?? null);
modelConfig.tools = [{ functionDeclarations: tools.map(t => t.declaration) }];
modelConfig.systemInstruction = systemPrompt || CANVAS_TOOLS_SYSTEM_PROMPT; modelConfig.systemInstruction = systemPrompt || CANVAS_TOOLS_SYSTEM_PROMPT;
} }
const geminiModel = genAI.getGenerativeModel(modelConfig); const geminiModel = genAI.getGenerativeModel(modelConfig);

View File

@ -173,6 +173,11 @@ export interface RSpaceModule {
/** Per-module settings schema for space-level configuration */ /** Per-module settings schema for space-level configuration */
settingsSchema?: ModuleSettingField[]; 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 */ /** Registry of all loaded modules */
@ -210,6 +215,8 @@ export interface ModuleInfo {
subPageInfos?: Array<{ path: string; title: string }>; subPageInfos?: Array<{ path: string; title: string }>;
settingsSchema?: ModuleSettingField[]; settingsSchema?: ModuleSettingField[];
onboardingActions?: OnboardingAction[]; onboardingActions?: OnboardingAction[];
canvasShapes?: string[];
canvasToolIds?: string[];
} }
export function getModuleInfoList(): ModuleInfo[] { 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.subPageInfos ? { subPageInfos: m.subPageInfos.map(s => ({ path: s.path, title: s.title })) } : {}),
...(m.settingsSchema ? { settingsSchema: m.settingsSchema } : {}), ...(m.settingsSchema ? { settingsSchema: m.settingsSchema } : {}),
...(m.onboardingActions ? { onboardingActions: m.onboardingActions } : {}), ...(m.onboardingActions ? { onboardingActions: m.onboardingActions } : {}),
...(m.canvasShapes ? { canvasShapes: m.canvasShapes } : {}),
...(m.canvasToolIds ? { canvasToolIds: m.canvasToolIds } : {}),
})); }));
} }

View File

@ -2032,7 +2032,7 @@
<button id="new-bookmark" title="Bookmark">🔖 Bookmark</button> <button id="new-bookmark" title="Bookmark">🔖 Bookmark</button>
<button id="new-google-item" title="Google">📎 Google</button> <button id="new-google-item" title="Google">📎 Google</button>
<button id="new-map" title="Map" data-requires-module="rmaps">🗺️ Map</button> <button id="new-map" title="Map" data-requires-module="rmaps">🗺️ Map</button>
<button id="new-social-post" title="Social Post">📱 Social Post</button> <button id="new-social-post" title="Social Post" data-requires-module="rsocials">📱 Social Post</button>
<button id="embed-notes" title="Embed rNotes" data-requires-module="rnotes">📝 rNotes</button> <button id="embed-notes" title="Embed rNotes" data-requires-module="rnotes">📝 rNotes</button>
<button id="embed-books" title="Embed rBooks" data-requires-module="rbooks">📚 rBooks</button> <button id="embed-books" title="Embed rBooks" data-requires-module="rbooks">📚 rBooks</button>
<button id="embed-forum" title="Embed rForum" data-requires-module="rforum">💬 rForum</button> <button id="embed-forum" title="Embed rForum" data-requires-module="rforum">💬 rForum</button>
@ -2091,12 +2091,12 @@
<button class="toolbar-group-toggle" title="Decide"><span class="tg-icon">📊</span><span class="tg-label">Decide</span></button> <button class="toolbar-group-toggle" title="Decide"><span class="tg-icon">📊</span><span class="tg-label">Decide</span></button>
<div class="toolbar-dropdown"> <div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">Decide</div> <div class="toolbar-dropdown-header">Decide</div>
<button id="new-choice-vote" title="Poll">☑ Poll</button> <button id="new-choice-vote" title="Poll" data-requires-module="rchoices">☑ Poll</button>
<button id="new-choice-rank" title="Ranking">📊 Ranking</button> <button id="new-choice-rank" title="Ranking" data-requires-module="rchoices">📊 Ranking</button>
<button id="new-choice-spider" title="Scoring">🕸 Scoring</button> <button id="new-choice-spider" title="Scoring" data-requires-module="rchoices">🕸 Scoring</button>
<button id="new-spider-3d" title="3D Spider">📊 3D Spider</button> <button id="new-spider-3d" title="3D Spider" data-requires-module="rchoices">📊 3D Spider</button>
<button id="new-conviction" title="Conviction">⏳ Conviction</button> <button id="new-conviction" title="Conviction" data-requires-module="rchoices">⏳ Conviction</button>
<button id="new-token" title="Token">🪙 Token</button> <button id="new-token" title="Token" data-requires-module="rwallet">🪙 Token</button>
<button id="new-commitment-pool" title="Commitment Pool" data-requires-module="rtime">🧺 Commitments</button> <button id="new-commitment-pool" title="Commitment Pool" data-requires-module="rtime">🧺 Commitments</button>
<button id="new-task-request" title="Task Request" data-requires-module="rtime">📋 Task Request</button> <button id="new-task-request" title="Task Request" data-requires-module="rtime">📋 Task Request</button>
<button id="new-multisig-email" title="Multi-Sig Email">✉️ Multi-Sig Email</button> <button id="new-multisig-email" title="Multi-Sig Email">✉️ Multi-Sig Email</button>
@ -2111,7 +2111,7 @@
<div class="toolbar-dropdown-header">Spend</div> <div class="toolbar-dropdown-header">Spend</div>
<button id="embed-wallet" title="rWallet" data-requires-module="rwallet">💰 rWallet</button> <button id="embed-wallet" title="rWallet" data-requires-module="rwallet">💰 rWallet</button>
<button id="embed-flows" title="rFlows" data-requires-module="rflows">🌊 rFlows</button> <button id="embed-flows" title="rFlows" data-requires-module="rflows">🌊 rFlows</button>
<button id="new-tx-builder" title="Transaction Builder">🔐 Tx Builder</button> <button id="new-tx-builder" title="Transaction Builder" data-requires-module="rwallet">🔐 Tx Builder</button>
</div> </div>
</div> </div>
@ -2575,6 +2575,15 @@
const FolkRApp = customElements.get("folk-rapp"); const FolkRApp = customElements.get("folk-rapp");
if (FolkRApp?.setEnabledModules) FolkRApp.setEnabledModules(enabledIds); 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(() => {}); }).catch(() => {});
// React to runtime module toggling from app switcher // React to runtime module toggling from app switcher
@ -2591,6 +2600,12 @@
const FolkRApp = customElements.get("folk-rapp"); const FolkRApp = customElements.get("folk-rapp");
if (FolkRApp?.setEnabledModules) FolkRApp.setEnabledModules(enabledModules); 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 => { document.querySelectorAll("[data-requires-module]").forEach(el => {
el.style.display = enabledSet.has(el.dataset.requiresModule) ? "" : "none"; el.style.display = enabledSet.has(el.dataset.requiresModule) ? "" : "none";
}); });
@ -2790,6 +2805,13 @@
shapeRegistry.register("folk-holon", FolkHolon); shapeRegistry.register("folk-holon", FolkHolon);
shapeRegistry.register("folk-holon-browser", FolkHolonBrowser); 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 // Zoom and pan state — declared early to avoid TDZ errors
// (event handlers reference these before awaits yield execution) // (event handlers reference these before awaits yield execution)
let scale = 1; let scale = 1;