Enforce module disabling on canvas folk-rapp shapes
CI/CD / deploy (push) Failing after 9s Details

Disabled rApp modules now show a grayed lock overlay on the canvas
instead of loading their iframe/widget. Uses a static instance registry
so setEnabledModules() broadcasts to all live shapes immediately.
Re-enabling a module reloads its content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-01 11:55:13 -07:00
parent 29cd6168f9
commit 7dc4e9b3e9
1 changed files with 77 additions and 0 deletions

View File

@ -195,6 +195,29 @@ const styles = css`
text-align: center;
}
.rapp-disabled {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 8px;
color: var(--rs-text-muted);
font-size: 13px;
padding: 16px;
text-align: center;
}
.rapp-disabled-icon {
font-size: 28px;
opacity: 0.4;
}
:host([data-module-disabled]) .rapp-header {
filter: grayscale(0.6);
opacity: 0.6;
}
/* Module picker (shown when no moduleId set) */
.rapp-picker {
display: flex;
@ -598,9 +621,13 @@ export class FolkRApp extends FolkShape {
/** Enabled module IDs for picker/switcher filtering (null = all enabled) */
static enabledModuleIds: Set<string> | null = null;
/** Live instance registry for broadcasting enabled-state changes */
static #instances = new Set<FolkRApp>();
/** Update which modules appear in the picker and switcher dropdowns */
static setEnabledModules(ids: string[] | null) {
FolkRApp.enabledModuleIds = ids ? new Set(ids) : null;
for (const inst of FolkRApp.#instances) inst.#syncDisabledState();
}
/** Port descriptors for data pipe integration (AC#3) */
@ -813,6 +840,9 @@ export class FolkRApp extends FolkShape {
} catch { /* iframe not ready */ }
}) as EventListener);
// Register for enabled-state broadcasts
FolkRApp.#instances.add(this);
// Load content
if (this.#moduleId) {
this.#renderContent();
@ -825,6 +855,7 @@ export class FolkRApp extends FolkShape {
disconnectedCallback() {
super.disconnectedCallback?.();
FolkRApp.#instances.delete(this);
if (this.#messageHandler) {
window.removeEventListener("message", this.#messageHandler);
this.#messageHandler = null;
@ -964,10 +995,56 @@ export class FolkRApp extends FolkShape {
return onSubdomain ? `/${this.#moduleId}` : `/${space}/${this.#moduleId}`;
}
/** Check if this shape's module is currently disabled */
#isModuleDisabled(): boolean {
const enabled = FolkRApp.enabledModuleIds;
if (!enabled) return false; // null = all enabled
return this.#moduleId !== "" && !enabled.has(this.#moduleId);
}
/** Show the disabled overlay and tear down any active iframe/timer */
#showDisabled() {
if (!this.#contentEl) return;
// Clear iframe and timer
this.#iframe = null;
if (this.#refreshTimer) {
clearInterval(this.#refreshTimer);
this.#refreshTimer = null;
}
this.setAttribute("data-module-disabled", "");
const meta = MODULE_META[this.#moduleId];
this.#contentEl.innerHTML = `
<div class="rapp-disabled">
<span class="rapp-disabled-icon">🔒</span>
<span>${meta?.name || this.#moduleId} is disabled</span>
</div>
`;
}
/** Sync this instance's visual state with the current enabled set */
#syncDisabledState() {
if (!this.#contentEl || !this.#moduleId) return;
const disabled = this.#isModuleDisabled();
const wasDisabled = this.hasAttribute("data-module-disabled");
if (disabled && !wasDisabled) {
this.#showDisabled();
} else if (!disabled && wasDisabled) {
this.removeAttribute("data-module-disabled");
this.#renderContent();
}
}
/** Route to the right render method based on current mode */
#renderContent() {
if (!this.#contentEl || !this.#moduleId) return;
// Block rendering if module is disabled
if (this.#isModuleDisabled()) { this.#showDisabled(); return; }
// Clear refresh timer
if (this.#refreshTimer) {
clearInterval(this.#refreshTimer);