feat: expose canonical module display URLs

This commit is contained in:
ekklesia
2026-09-30 18:20:40 -05:00
parent f445a83ea5
commit 3ca6b2cb32
21 changed files with 123 additions and 16 deletions
+1
View File
@@ -271,6 +271,7 @@ describe("Dashboard navigation — reading", () => {
expect(livestream?.module).toEqual({
contributions: [],
api: { baseUrl: "/api/v1/modules/livestream" },
display: null,
id: "livestream",
permissions: [],
realtime: { baseUrl: "/api/v1/module-realtime/livestream" },
+9 -3
View File
@@ -274,6 +274,7 @@ describe("module display routes — authorization and descriptor", () => {
url: moduleDisplayUiArtifactUrl({ moduleId: "announcements", version: "1.4.2" }),
},
id: "announcements",
page: { url: moduleDisplayPageUrl("announcements") },
permissions: [displayPermission],
realtime: { baseUrl: moduleRealtimeBaseUrl("announcements") },
version: "1.4.2",
@@ -351,9 +352,14 @@ describe("module display routes — role and lifecycle separation", () => {
expect((await app.request(moduleDisplayPageUrl("announcements"))).status).toBe(200);
expect((await app.request(MODULE_UI_INVENTORY_PATH)).status).toBe(200);
expect(
((await (await app.request(MODULE_UI_INVENTORY_PATH)).json()) as { modules: unknown[] })
.modules,
).toHaveLength(1);
(
(await (await app.request(MODULE_UI_INVENTORY_PATH)).json()) as {
modules: Array<{ display: { url: string } | null }>;
}
).modules,
).toEqual([
expect.objectContaining({ display: { url: moduleDisplayPageUrl("announcements") } }),
]);
});
it("has no display page, descriptor, or artifact when the module is outside the eligible set", async () => {
+4 -2
View File
@@ -439,6 +439,7 @@ describe("end to end: a discovered module's UI mounts in the Ekklesia host", ()
expect(bootstrap.descriptor).toEqual({
apiBaseUrl: "/api/v1/modules/example",
contributions: [],
displayPageUrl: "/display/example",
id: "example",
permissions: ["example.records.read", "example.display.view"],
realtimeBaseUrl: "/api/v1/module-realtime/example",
@@ -454,8 +455,8 @@ describe("end to end: a discovered module's UI mounts in the Ekklesia host", ()
);
const artifactUrl = await publishedArtifactUrl(harness, sourceUrl);
// 4. The generic loader receives the unchanged five-member browser context and mounts the
// display artifact independently of the Dashboard artifact.
// 4. The generic loader receives the host-owned browser context, including the canonical
// display page, and mounts the display artifact independently of the Dashboard artifact.
const context = createModuleUiContext({
config: harness.config,
descriptor: bootstrap.descriptor,
@@ -464,6 +465,7 @@ describe("end to end: a discovered module's UI mounts in the Ekklesia host", ()
expect(Object.keys(context).sort()).toEqual([
"api",
"contributions",
"display",
"moduleId",
"permissions",
"principal",
@@ -244,6 +244,7 @@ describe("module UI inventory — what it lists", () => {
{
contributions: [],
api: { baseUrl: "/api/v1/modules/example" },
display: null,
id: "example",
permissions: [],
realtime: { baseUrl: "/api/v1/module-realtime/example" },
@@ -340,6 +341,7 @@ describe("module UI inventory — what it lists", () => {
expect(Object.keys(modules[0] ?? {}).sort()).toEqual([
"api",
"contributions",
"display",
"id",
"permissions",
"realtime",
+1
View File
@@ -947,6 +947,7 @@ describe("module UI artifact publication — the generic loader consumes what is
return {
api: { request: async () => new Response(null, { status: 200 }) },
contributions: NO_MODULE_UI_CONTRIBUTIONS,
display: undefined,
moduleId: "announcements",
permissions: { has: () => false },
principal: { kind: "anonymous" as const },