From 805668b2b3a592b2522b72a9bcf790adacd0e7b3 Mon Sep 17 00:00:00 2001 From: Grace Kind Date: Mon, 27 Jul 2026 17:41:03 -0500 Subject: [PATCH] Revert to direct plugin context passing --- package.json | 2 +- src/css/style.css | 2 +- src/js/app.js | 6 +++++ src/js/components/plugin-posts-feed.js | 6 +++-- src/js/components/plugin-profiles-list.js | 7 +++--- src/js/context-provider.js | 24 ------------------- src/js/mainLayout.js | 17 +++---------- src/js/plugins/pluginRendering.js | 13 +++++++++- src/js/plugins/pluginService.js | 7 +++++- .../components/plugin-posts-feed.test.js | 16 +++++-------- .../components/plugin-profiles-list.test.js | 8 ++----- .../specs/plugins/pluginRendering.test.js | 18 ++++++++++++++ 12 files changed, 63 insertions(+), 63 deletions(-) delete mode 100644 src/js/context-provider.js diff --git a/package.json b/package.json index 34c5082e..0f764b53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "impro", - "version": "0.18.95", + "version": "0.18.96", "type": "module", "scripts": { "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve", diff --git a/src/css/style.css b/src/css/style.css index 321852fc..5a6b1734 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -331,7 +331,7 @@ video { display: none; } -context-provider { +.main-layout-wrapper { display: contents; } diff --git a/src/js/app.js b/src/js/app.js index 95c0f983..e713a7ae 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -133,6 +133,12 @@ export async function main() { postComposerService, reportService, }); + pluginService.setRenderContext({ + isAuthenticated: !!session, + dataLayer, + pluginService, + postInteractionHandler: interactionHandlers.postInteractionHandler, + }); // Precache author DIDs when data is set in the data store. // This will save us from needing to resolve handles when navigating between pages. diff --git a/src/js/components/plugin-posts-feed.js b/src/js/components/plugin-posts-feed.js index 67255d90..73d00407 100644 --- a/src/js/components/plugin-posts-feed.js +++ b/src/js/components/plugin-posts-feed.js @@ -2,7 +2,6 @@ import { html, render } from "/js/lib/lit-html.js"; import { Component } from "/js/components/component.js"; import { postFeedTemplate } from "/js/templates/postFeed.template.js"; import { Signal, ReactiveStore, effect } from "/js/signals.js"; -import { getContext } from "/js/context-provider.js"; class PluginPostsFeed extends Component { static get observedAttributes() { @@ -12,12 +11,15 @@ class PluginPostsFeed extends Component { connectedCallback() { if (this.initialized) return; this.initialized = true; + if (!this.renderContext) { + throw new Error("plugin-posts-feed requires a renderContext property"); + } const { dataLayer, isAuthenticated, pluginService, postInteractionHandler, - } = getContext(this, "plugin-component-context"); + } = this.renderContext; this.dataLayer = dataLayer; this.isAuthenticated = isAuthenticated; this.pluginService = pluginService; diff --git a/src/js/components/plugin-profiles-list.js b/src/js/components/plugin-profiles-list.js index d8678e8d..d408389c 100644 --- a/src/js/components/plugin-profiles-list.js +++ b/src/js/components/plugin-profiles-list.js @@ -2,7 +2,6 @@ import { html, render } from "/js/lib/lit-html.js"; import { Component } from "/js/components/component.js"; import { profileFeedTemplate } from "/js/templates/profileFeed.template.js"; import { Signal, ReactiveStore, effect } from "/js/signals.js"; -import { getContext } from "/js/context-provider.js"; class PluginProfilesList extends Component { static get observedAttributes() { @@ -12,8 +11,10 @@ class PluginProfilesList extends Component { connectedCallback() { if (this.initialized) return; this.initialized = true; - const { dataLayer } = getContext(this, "plugin-component-context"); - this.dataLayer = dataLayer; + if (!this.renderContext) { + throw new Error("plugin-profiles-list requires a renderContext property"); + } + this.dataLayer = this.renderContext.dataLayer; this.state = new ReactiveStore("plugin-profiles-list"); this.state.$dids = new Signal.State(this.parseDids()); this.state.$emptyMessage = new Signal.State( diff --git a/src/js/context-provider.js b/src/js/context-provider.js deleted file mode 100644 index c9bcf16d..00000000 --- a/src/js/context-provider.js +++ /dev/null @@ -1,24 +0,0 @@ -class ContextProvider extends HTMLElement { - set context(value) { - this._context = value; - } - get context() { - return this._context; - } -} -customElements.define("context-provider", ContextProvider); - -export function getContext(node, contextId) { - const selector = contextId - ? `context-provider[context-id="${contextId}"]` - : "context-provider"; - const provider = node.closest(selector); - if (!provider) { - throw new Error( - contextId - ? `getContext: no ancestor` - : "getContext: no ancestor", - ); - } - return provider.context; -} diff --git a/src/js/mainLayout.js b/src/js/mainLayout.js index 071bf8e0..cdc73080 100644 --- a/src/js/mainLayout.js +++ b/src/js/mainLayout.js @@ -48,7 +48,6 @@ function pluginPreviewBannerTemplate({ plugins }) { } import { Layout } from "/js/router.js"; import "/js/components/animated-sidebar.js"; -import "/js/context-provider.js"; export function mainLayoutTemplate({ isAuthenticated = true, @@ -63,12 +62,10 @@ export function mainLayoutTemplate({ previewingPlugins = [], onLongPressProfile = null, groupChatLinkService, - pluginComponentContext, }) { return html` - groupChatLinkService.handleAction( e.detail.actionType, @@ -104,7 +101,7 @@ export function mainLayoutTemplate({ onClickActiveItem: onClickActiveNavItem, onLongPressProfile, })} - + `; } @@ -134,14 +131,7 @@ export class MainLayout extends Layout { accountSwitcherService, pluginService, groupChatLinkService, - interactionHandlers, } = this.context; - const pluginComponentContext = { - isAuthenticated, - dataLayer, - pluginService, - postInteractionHandler: interactionHandlers.postInteractionHandler, - }; const { router, slot } = this; container.id = "main-layout"; @@ -189,7 +179,6 @@ export class MainLayout extends Layout { previewingPlugins, onLongPressProfile, groupChatLinkService, - pluginComponentContext, }), container, ); diff --git a/src/js/plugins/pluginRendering.js b/src/js/plugins/pluginRendering.js index 04f7f651..f143a9ec 100644 --- a/src/js/plugins/pluginRendering.js +++ b/src/js/plugins/pluginRendering.js @@ -52,6 +52,13 @@ const ALLOWED_TAGS = [ const ALLOWED_EVENTS = ["click", "change", "input"]; +// App components that need host services (dataLayer, interaction handlers) +// provided via renderContext +const HOST_COMPONENT_TAGS = new Set([ + "plugin-profiles-list", + "plugin-posts-feed", +]); + const HTML_NS = "http://www.w3.org/1999/xhtml"; const SVG_NS = "http://www.w3.org/2000/svg"; @@ -373,9 +380,10 @@ function resolveChildNamespace(parentNs, node) { // Render a serialized VirtualNode (text or element) into a DOM node. export class PluginRenderer { - constructor(pluginBridge, pluginId) { + constructor(pluginBridge, pluginId, renderContext = null) { this.pluginBridge = pluginBridge; this.pluginId = pluginId; + this.renderContext = renderContext; } createRoot() { @@ -446,6 +454,9 @@ export class PluginRenderer { let tag = resolveTag(node, this.pluginId); if (tag === "plugin-icon") tag = "app-icon"; const element = document.createElement(tag); + if (HOST_COMPONENT_TAGS.has(tag)) { + element.renderContext = this.renderContext; + } if (tag === "a") { element.setAttribute("target", "_blank"); element.setAttribute("rel", "noopener noreferrer"); diff --git a/src/js/plugins/pluginService.js b/src/js/plugins/pluginService.js index 4a18dd40..47271603 100644 --- a/src/js/plugins/pluginService.js +++ b/src/js/plugins/pluginService.js @@ -120,6 +120,7 @@ export class PermissionsDeclinedError extends Error { export class PluginService extends ReactiveStore { constructor(preferencesProvider, session, dataLayer, hiddenFeedItemsStore) { super("pluginService"); + this.renderContext = null; this.slingshot = new Slingshot(); this.registries = { sidebarItems: new SignalSet(), @@ -207,8 +208,12 @@ export class PluginService extends ReactiveStore { }); } + setRenderContext(renderContext) { + this.renderContext = renderContext; + } + getRenderer(pluginId) { - return new PluginRenderer(this.pluginBridge, pluginId); + return new PluginRenderer(this.pluginBridge, pluginId, this.renderContext); } // icon can be string | VirtualEl diff --git a/tests/unit/specs/components/plugin-posts-feed.test.js b/tests/unit/specs/components/plugin-posts-feed.test.js index d4aa450f..3897482f 100644 --- a/tests/unit/specs/components/plugin-posts-feed.test.js +++ b/tests/unit/specs/components/plugin-posts-feed.test.js @@ -1,15 +1,11 @@ import { describe, it, beforeEach, mock } from "node:test"; import assert from "node:assert/strict"; -import "/js/context-provider.js"; import "/js/components/plugin-posts-feed.js"; import { makeTestDataLayer } from "../../testHelpers.js"; -function mount(element, context) { - const provider = document.createElement("context-provider"); - provider.setAttribute("context-id", "plugin-component-context"); - provider.context = context; - provider.appendChild(element); - document.body.appendChild(provider); +function mount(element, renderContext) { + element.renderContext = renderContext; + document.body.appendChild(element); return element; } @@ -87,8 +83,8 @@ describe("plugin-posts-feed", () => { }); }); - describe("PluginPostsFeed - missing context provider", () => { - it("throws when connected outside a context-provider", () => { + describe("PluginPostsFeed - missing render context", () => { + it("throws when connected without a renderContext property", () => { const element = document.createElement("plugin-posts-feed"); let error = null; try { @@ -99,7 +95,7 @@ describe("plugin-posts-feed", () => { error = e; } assert(error !== null); - assert(error.message.includes("context-provider")); + assert(error.message.includes("renderContext")); }); }); diff --git a/tests/unit/specs/components/plugin-profiles-list.test.js b/tests/unit/specs/components/plugin-profiles-list.test.js index 77527e8a..a9bda6fc 100644 --- a/tests/unit/specs/components/plugin-profiles-list.test.js +++ b/tests/unit/specs/components/plugin-profiles-list.test.js @@ -1,15 +1,11 @@ import { describe, it, beforeEach, mock } from "node:test"; import assert from "node:assert/strict"; -import "/js/context-provider.js"; import "/js/components/plugin-profiles-list.js"; import { makeTestDataLayer } from "../../testHelpers.js"; function mount(element, dataLayer) { - const provider = document.createElement("context-provider"); - provider.setAttribute("context-id", "plugin-component-context"); - provider.context = { dataLayer }; - provider.appendChild(element); - document.body.appendChild(provider); + element.renderContext = { dataLayer }; + document.body.appendChild(element); return element; } diff --git a/tests/unit/specs/plugins/pluginRendering.test.js b/tests/unit/specs/plugins/pluginRendering.test.js index d0d0e4b7..8ad32142 100644 --- a/tests/unit/specs/plugins/pluginRendering.test.js +++ b/tests/unit/specs/plugins/pluginRendering.test.js @@ -52,6 +52,24 @@ describe("PluginRenderer:render with fresh roots", () => { assert.deepEqual(element.getAttribute("dids"), "did:test:a,did:test:b"); }); + it("stamps renderContext onto host components", () => { + const { bridge } = makeBridge(); + const renderContext = { dataLayer: {} }; + const renderer = new PluginRenderer(bridge, "demo", renderContext); + const profilesList = renderer.createRoot().render({ + tag: "plugin-profiles-list", + attrs: { dids: "did:test:a" }, + }); + const postsFeed = renderer.createRoot().render({ + tag: "plugin-posts-feed", + attrs: { uris: "at://a" }, + }); + assert(profilesList.renderContext === renderContext); + assert(postsFeed.renderContext === renderContext); + const plainDiv = renderer.createRoot().render({ tag: "div" }); + assert.deepEqual(plainDiv.renderContext, undefined); + }); + it("drops disallowed attributes from ", () => { const { bridge } = makeBridge(); const renderer = new PluginRenderer(bridge, "demo"); -- 2.51.2