From 2d0ca98ae6d7ea1a9e60ebfcff375c3eb3fb01d3 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 9 Apr 2026 20:35:58 -0400 Subject: [PATCH] fix(canvas): disabled module gating + toolbar reorganization - Remove MODULE_BADGES fallback in tab-bar add-menu (show "Loading rApps..." instead of leaking all modules) - Block ?tool= URL param from spawning shapes for disabled modules - Add disabled-module overlay on existing canvas shapes (grayed out + "Module disabled" badge) - Guard newShape() against creating shapes for disabled modules - Reorganize toolbar from 10 groups to 9 category-aligned groups: Write, Embed (core 4), Communicate, Plan, Decide, Spend, Create, AI, Record - Eliminate duplicate "Connect" groups, slim mega "Embed" group, merge "Travel" into "Plan" Co-Authored-By: Claude Opus 4.6 --- shared/components/rstack-tab-bar.ts | 10 +- website/canvas.html | 275 ++++++++++++++++++---------- 2 files changed, 177 insertions(+), 108 deletions(-) diff --git a/shared/components/rstack-tab-bar.ts b/shared/components/rstack-tab-bar.ts index 7d83216e..376a5487 100644 --- a/shared/components/rstack-tab-bar.ts +++ b/shared/components/rstack-tab-bar.ts @@ -476,15 +476,11 @@ export class RStackTabBar extends HTMLElement { #renderAddMenu(): string { const existingModuleIds = new Set(this.#layers.map(l => l.moduleId)); - // Use server module list if available, fall back to MODULE_BADGES keys - const allModules: Array<{ id: string; name: string; icon: string; description: string }> = - this.#modules.length > 0 - ? this.#modules - : Object.keys(MODULE_BADGES) - .map(id => ({ id, name: id, icon: "", description: "" })); + // Only use server module list โ€” never fall back to MODULE_BADGES (leaks disabled modules) + const allModules = this.#modules; if (allModules.length === 0) { - return `
No rApps available
`; + return `
Loading rApps\u2026
`; } let html = ""; diff --git a/website/canvas.html b/website/canvas.html index 4fc3b156..346dc859 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -153,6 +153,26 @@ cursor: default; } + /* Disabled-module overlay on canvas shapes */ + [data-module-disabled] { + position: relative; + opacity: 0.4; + pointer-events: none !important; + filter: grayscale(0.6); + } + [data-module-disabled]::after { + content: "Module disabled"; + position: absolute; + top: 4px; right: 4px; + background: rgba(0,0,0,0.7); + color: #fff; + font-size: 11px; + padding: 2px 6px; + border-radius: 4px; + pointer-events: none; + z-index: 9999; + } + /* Picker modal โ€” inline replacement for browser prompt() */ .picker-modal-overlay { position: fixed; inset: 0; z-index: 9999; @@ -1985,42 +2005,18 @@
- +
- +
-
Note
+
Write
- +
- -
- -
-
Connect
- - - -
-
- - -
- -
-
Media
- - - - - -
-
- - +
@@ -2029,32 +2025,92 @@ - - - - +
+
+ + +
+ +
+
Communicate
+ + + + + +
+
+ + +
+ +
+
Plan
+ + + + + + + +
+
+ + +
+ +
+
Decide
+ + + + + + + + + +
+
+ + +
+ +
+
Spend
+ + + + +
+
+ + +
+ +
+
Create
+ + + + + + + + + + + +
- -
- -
-
Connect
- - - - - -
-
- - +
@@ -2068,61 +2124,17 @@
- +
- +
-
Create
- - - - - - - - -
-
- - -
- -
-
Decide
- - - - - - - - - - -
-
- - -
- -
-
Spend
- - - -
-
- - -
- -
-
Travel
- - - - - +
Record
+ + + + + +
@@ -2587,6 +2599,8 @@ const cls = customElements.get("folk-task-request"); if (cls?.setEnabledModules) cls.setEnabledModules(enabledIds); }); + // Overlay disabled-module shapes on canvas + syncCanvasShapeModuleStates(enabledIds); }).catch(() => {}); // React to runtime module toggling from app switcher @@ -2609,6 +2623,9 @@ const FolkTaskRequest = customElements.get("folk-task-request"); if (FolkTaskRequest?.setEnabledModules) FolkTaskRequest.setEnabledModules(enabledModules); + // Update disabled-module overlays on canvas shapes + syncCanvasShapeModuleStates(enabledModules); + document.querySelectorAll("[data-requires-module]").forEach(el => { el.style.display = enabledSet.has(el.dataset.requiresModule) ? "" : "none"; }); @@ -3963,6 +3980,52 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest }); } + // Map shape tag โ†’ required module (for gating disabled-module shapes) + const SHAPE_TO_MODULE = { + "folk-map": "rmaps", + "folk-calendar": "rcal", + "folk-social-post": "rsocials", + "folk-splat": "rsplat", + "folk-design-agent": "rdesign", + "folk-choice-vote": "rchoices", + "folk-choice-rank": "rchoices", + "folk-choice-spider": "rchoices", + "folk-spider-3d": "rchoices", + "folk-choice-conviction": "rchoices", + "folk-token-mint": "rwallet", + "folk-token-ledger": "rwallet", + "folk-transaction-builder": "rwallet", + "folk-commitment-pool": "rtime", + "folk-task-request": "rtime", + "folk-gov-binary": "rvote", + "folk-gov-threshold": "rvote", + "folk-itinerary": "rtrips", + "folk-destination": "rtrips", + "folk-budget": "rtrips", + "folk-packing-list": "rtrips", + "folk-booking": "rtrips", + "folk-video-chat": "rmeets", + // folk-rapp shapes are gated separately via FolkRApp.setEnabledModules + }; + + /** Toggle data-module-disabled on existing canvas shapes based on enabled module list */ + function syncCanvasShapeModuleStates(enabledIds) { + if (!enabledIds) return; + const enabledSet = new Set(enabledIds); + const canvasEl = document.getElementById("canvas-content") || document.getElementById("canvas"); + if (!canvasEl) return; + for (const shape of canvasEl.children) { + const tag = shape.tagName?.toLowerCase(); + const reqMod = SHAPE_TO_MODULE[tag]; + if (!reqMod) continue; + if (enabledSet.has(reqMod)) { + shape.removeAttribute("data-module-disabled"); + } else { + shape.setAttribute("data-module-disabled", reqMod); + } + } + } + // Default dimensions for each shape type const SHAPE_DEFAULTS = { "folk-markdown": { width: 300, height: 200 }, @@ -4226,6 +4289,13 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest // atPosition: optional { x, y } in canvas coordinates โ€” places shape centered there exactly. // Without atPosition, uses findFreePosition to auto-place without overlapping. function newShape(tagName, props = {}, atPosition) { + // Block creating shapes for disabled modules + const _reqMod = SHAPE_TO_MODULE[tagName]; + if (_reqMod && window.__rspaceEnabledModules && + !new Set(window.__rspaceEnabledModules).has(_reqMod)) { + console.warn(`[Canvas] Blocked newShape("${tagName}") โ€” module "${_reqMod}" is disabled`); + return null; + } const id = `shape-${Date.now()}-${++shapeCounter}`; const defaults = SHAPE_DEFAULTS[tagName] || { width: 300, height: 200 }; @@ -5071,7 +5141,10 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest // Auto-spawn shape from ?tool= URL param (e.g. ?tool=folk-zine-gen) const toolParam = urlParams.get("tool"); - if (toolParam && shapeRegistry.has(toolParam)) { + const _toolMod = toolParam && SHAPE_TO_MODULE[toolParam]; + const _toolAllowed = !_toolMod || !window.__rspaceEnabledModules || + new Set(window.__rspaceEnabledModules).has(_toolMod); + if (toolParam && shapeRegistry.has(toolParam) && _toolAllowed) { const spawnToolShape = () => { newShape(toolParam); const cleanUrl = new URL(window.location.href);