import { defineModuleUi } from "../../src/index.js"; import type { ModuleUiHostContext } from "../../src/index.js"; /** * A stand-in for a real module's compiled `entrypoints.ui` artifact. It is deliberately tiny and * deliberately host-neutral: it never constructs an API URL, never imports Core, an app, or a * surface, and never reads a global. Everything it needs arrives in the host context. * * This is the artifact both fake hosts in `module-ui-contract.test.ts` mount, unchanged. */ export interface AnnouncementsUiTrace { readonly mounted: string[]; readonly unmounted: string[]; readonly requestedPaths: string[]; readonly canPublish: boolean[]; } export const trace: AnnouncementsUiTrace = { mounted: [], unmounted: [], requestedPaths: [], canPublish: [], }; export default defineModuleUi({ id: "announcements", version: "0.1.0", async mount(container: Element, context: ModuleUiHostContext) { trace.mounted.push(context.moduleId); // Presentation-only gating: the module asks the host whether to render the publish control. // Whatever the answer, the server decides what actually happens. const canPublish = context.permissions.has("announcements.posts.publish"); trace.canPublish.push(canPublish); // The whole portability point: the module asks for its own resource by relative path and has // no idea what URL the host turns it into. const response = await context.api.request("posts"); trace.requestedPaths.push("posts"); container.textContent = `announcements:${response.status}:${canPublish ? "editor" : "reader"}`; return () => { trace.unmounted.push(context.moduleId); container.textContent = ""; }; }, });