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
@@ -52,6 +52,7 @@ function descriptorPayload(
api: { baseUrl: `/api/v1/modules/${id}` },
display: { url: `/api/v1/module-ui/${id}/${version}/display` },
id,
page: { url: `/display/${id}` },
permissions: [`${id}.slideshow.view`],
realtime: { baseUrl: `/api/v1/module-realtime/${id}` },
version,
@@ -240,6 +241,7 @@ describe("display host — full-viewport boundary", () => {
expect(Object.keys(seenContext ?? {}).sort()).toEqual([
"api",
"contributions",
"display",
"moduleId",
"permissions",
"principal",
@@ -334,6 +336,7 @@ describe("display host — full-viewport boundary", () => {
module: {
contributions: [],
api: { baseUrl: "/api/v1/modules/announcements" },
display: { url: "/display/announcements" },
id: "announcements",
permissions: ["announcements.slideshow.view"],
realtime: { baseUrl: "/api/v1/module-realtime/announcements" },
@@ -22,6 +22,7 @@ function displayPayload(overrides: Record<string, unknown> = {}): {
api: { baseUrl: "/api/v1/modules/announcements" },
display: { url: "/api/v1/module-ui/announcements/1.2.0/display" },
id: "announcements",
page: { url: "/display/announcements" },
permissions: ["announcements.slideshow.view"],
realtime: { baseUrl: "/api/v1/module-realtime/announcements" },
version: "1.2.0",
@@ -45,6 +46,7 @@ describe("display host protocol — strict descriptor", () => {
module: {
apiBaseUrl: "/api/v1/modules/announcements",
contributions: [],
displayPageUrl: "/display/announcements",
id: "announcements",
permissions: ["announcements.slideshow.view"],
realtimeBaseUrl: "/api/v1/module-realtime/announcements",
+1
View File
@@ -43,6 +43,7 @@ function entry(id: string, name: string, version = "0.1.0", permissions: readonl
module: {
contributions: [],
api: { baseUrl: `/api/v1/modules/${id}` },
display: null,
id,
permissions,
realtime: { baseUrl: `/api/v1/module-realtime/${id}` },
+18 -1
View File
@@ -39,6 +39,7 @@ const inventoryPayload = {
{
contributions: [],
api: { baseUrl: "/api/v1/modules/example" },
display: { url: "/display/example" },
id: "example",
permissions: ["example.records.read"],
realtime: { baseUrl: "/api/v1/module-realtime/example" },
@@ -51,6 +52,7 @@ const inventoryPayload = {
const calendarDescriptor: HostModuleUiDescriptor = {
contributions: [],
apiBaseUrl: "/api/v1/modules/calendar",
displayPageUrl: undefined,
id: "calendar",
permissions: [],
realtimeBaseUrl: "/api/v1/module-realtime/calendar",
@@ -72,6 +74,7 @@ const navigationPayload = {
module: {
contributions: [],
api: { baseUrl: "/api/v1/modules/calendar" },
display: null,
id: "calendar",
permissions: [],
realtime: { baseUrl: "/api/v1/module-realtime/calendar" },
@@ -86,6 +89,7 @@ const navigationPayload = {
const exampleDescriptor: HostModuleUiDescriptor = {
contributions: [],
apiBaseUrl: "/api/v1/modules/example",
displayPageUrl: "/display/example",
id: "example",
permissions: ["example.records.read"],
realtimeBaseUrl: "/api/v1/module-realtime/example",
@@ -409,6 +413,7 @@ describe("module UI host context", () => {
const calendar: HostModuleUiDescriptor = {
contributions: [],
apiBaseUrl: "/api/v1/modules/calendar",
displayPageUrl: undefined,
id: "calendar",
permissions: [],
realtimeBaseUrl: "/api/v1/module-realtime/calendar",
@@ -438,7 +443,18 @@ describe("module UI host context", () => {
expect(none.has("core.authorization.grants.manage")).toBe(false);
});
it("exposes exactly the six contract members and the browser principal", () => {
it("exposes the host-owned display page at the API origin in split-origin deployments", () => {
const { implementation } = recordingFetch();
const context = createModuleUiContext({
config: { apiBaseUrl: "https://api.example.test", fetch: implementation },
descriptor: exampleDescriptor,
principal: { kind: "authenticated", userId: "user-1" },
});
expect(context.display?.url).toBe("https://api.example.test/display/example");
});
it("exposes exactly the seven contract members and the browser principal", () => {
const { implementation } = recordingFetch();
const context = contextFor(implementation);
@@ -446,6 +462,7 @@ describe("module UI host context", () => {
expect(Object.keys(context).sort()).toEqual([
"api",
"contributions",
"display",
"moduleId",
"permissions",
"principal",
@@ -139,6 +139,7 @@ function descriptor(overrides: Partial<HostModuleUiDescriptor> = {}): HostModule
version: "1.0.0",
},
],
displayPageUrl: "/display/signage",
id: "signage",
permissions: [],
realtimeBaseUrl: "/api/v1/module-realtime/signage",
@@ -12,6 +12,7 @@ const apiBaseUrl = "https://ekklesia.example.test";
const exampleDescriptor: HostModuleUiDescriptor = {
contributions: [],
apiBaseUrl: "/api/v1/modules/example",
displayPageUrl: undefined,
id: "example",
permissions: ["example.state.read"],
realtimeBaseUrl: "/api/v1/module-realtime/example",