@betterportal/theme-bootstrap1 10.1.42 → 10.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/bp-resources/SKILL.md +5 -4
  2. package/bp-resources/ui-guide.md +2 -0
  3. package/lib/bp-contracts/org.betterportal.theme.bootstrap1.json +34 -4
  4. package/lib/index.d.ts +1 -1
  5. package/lib/index.js +1 -1
  6. package/lib/plugins/service-betterportal-theme-bootstrap1/.bp-generated/registry.d.ts +3 -0
  7. package/lib/plugins/service-betterportal-theme-bootstrap1/.bp-generated/registry.d.ts.map +1 -0
  8. package/lib/plugins/service-betterportal-theme-bootstrap1/.bp-generated/registry.js +33 -0
  9. package/lib/plugins/service-betterportal-theme-bootstrap1/.bp-generated/registry.js.map +1 -0
  10. package/lib/plugins/service-betterportal-theme-bootstrap1/adapter.js +1 -1
  11. package/lib/plugins/service-betterportal-theme-bootstrap1/assets.d.ts.map +1 -1
  12. package/lib/plugins/service-betterportal-theme-bootstrap1/assets.js +4 -5
  13. package/lib/plugins/service-betterportal-theme-bootstrap1/assets.js.map +1 -1
  14. package/lib/plugins/service-betterportal-theme-bootstrap1/index.d.ts +0 -4
  15. package/lib/plugins/service-betterportal-theme-bootstrap1/index.d.ts.map +1 -1
  16. package/lib/plugins/service-betterportal-theme-bootstrap1/index.js +4 -104
  17. package/lib/plugins/service-betterportal-theme-bootstrap1/index.js.map +1 -1
  18. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_footer/index.d.ts +6 -0
  19. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_footer/index.d.ts.map +1 -0
  20. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_footer/index.js +7 -0
  21. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_footer/index.js.map +1 -0
  22. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_nav/index.d.ts +6 -0
  23. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_nav/index.d.ts.map +1 -0
  24. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_nav/index.js +7 -0
  25. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_nav/index.js.map +1 -0
  26. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_theme-selector.d.ts +6 -0
  27. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_theme-selector.d.ts.map +1 -0
  28. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_theme-selector.js +7 -0
  29. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/_theme-selector.js.map +1 -0
  30. package/lib/plugins/service-betterportal-theme-bootstrap1/{theme → shell}/index.d.ts +2 -9
  31. package/lib/plugins/service-betterportal-theme-bootstrap1/{theme → shell}/index.d.ts.map +1 -1
  32. package/lib/plugins/service-betterportal-theme-bootstrap1/{theme → shell}/index.js +3 -13
  33. package/lib/plugins/service-betterportal-theme-bootstrap1/shell/index.js.map +1 -0
  34. package/lib/schemas/service-betterportal-theme-bootstrap1.json +1 -8
  35. package/lib/schemas/service-betterportal-theme-bootstrap1.plugin.json +1 -8
  36. package/package.json +11 -6
  37. package/lib/plugins/service-betterportal-theme-bootstrap1/theme/index.js.map +0 -1
@@ -9,9 +9,10 @@ description: Build BetterPortal service UI that fits the active Bootstrap1 shell
9
9
  2. Identify the service view, methods, schemas, required permissions and stable `viewId`.
10
10
  3. Render server-side JSX/HTMX for the `bootstrap1` theme. Return only service content, never the shell.
11
11
  4. Use `ctx.routeUrl()` for service requests and `ctx.uiRouteUrl()` only for GET page navigation.
12
- 5. Prefer Bootstrap components and native HTML controls. Add custom CSS or JavaScript only when the theme and platform primitives cannot express the behavior.
13
- 6. Include loading, empty, validation, forbidden and service-unavailable states.
14
- 7. Check keyboard operation, visible labels, heading order and responsive wrapping.
15
- 8. Verify JSON and HTML representations against the published schemas.
12
+ 5. Use the framework `BPElement` JSX helper with `ctx` for cross-service or active-shell fragments. Reference a declared dependency alias, never a service UUID or absolute URL. Keep loading/error/status UI inside `bp-loading`, `bp-status`, and `bp-nok`; an explicit `bp-ok` must have exactly one `<template />`.
13
+ 6. Prefer Bootstrap components and native HTML controls. Add custom CSS or JavaScript only when the theme and platform primitives cannot express the behavior.
14
+ 7. Include loading, empty, validation, forbidden and service-unavailable states.
15
+ 8. Check keyboard operation, visible labels, heading order and responsive wrapping.
16
+ 9. Verify JSON and HTML representations against the published schemas.
16
17
 
17
18
  For side tasks, use `data-bp-sidebar` and `data-bp-sidebar-open`. For authenticated downloads, use `hx-download`. For live refresh, emit an `HX-Trigger` event and subscribe with `hx-trigger="event from:body"`.
@@ -14,6 +14,8 @@ Use Bootstrap 5 classes and server-rendered JSX/HTMX. BetterPortal service rende
14
14
 
15
15
  - Use real `<form>` elements and labelled controls. Validation text uses `.invalid-feedback` or `.form-text`.
16
16
  - Use `ctx.routeUrl()` for form actions, HTMX calls, downloads and SSE. `ctx.uiRouteUrl()` is only for GET navigation through a mounted page route.
17
+ - Use `<BPElement ctx={ctx} service="dependency-alias" path="/contract/path" fragment="location.id">` for cross-service UI fragments. Never put service UUIDs or absolute service URLs in renderer code; the server consumes `ctx` and resolves the app-mounted provider.
18
+ - Use `service="shell"` and a singular shell fragment id to reuse active-shell UI such as `theme-selector`. Add `bp-loading`, optional `bp-ok` with exactly one `<template />`, status-specific `bp-status`, and `bp-nok` states as needed.
17
19
  - Mutations should return a useful fragment and emit an `HX-Trigger` event for passive regions that need to reload.
18
20
  - Disable buttons only while their own request is active. Keep a visible loading indicator and an error fragment.
19
21
 
@@ -4,17 +4,20 @@
4
4
  "pluginId": "org.betterportal.theme.bootstrap1",
5
5
  "title": "Bootstrap1 Theme",
6
6
  "description": "Bootstrap 5 + htmx theme that renders BetterPortal app shells.",
7
- "version": "10.1.42",
7
+ "version": "10.1.44",
8
8
  "category": "theme",
9
9
  "deploymentModes": [
10
10
  "self-hosted"
11
11
  ],
12
12
  "capabilities": [
13
13
  "theme",
14
+ "renderer.bootstrap5",
14
15
  "view.json",
15
16
  "view.metadata"
16
17
  ],
17
- "supportedThemes": [],
18
+ "supportedRenderers": [
19
+ "bootstrap5"
20
+ ],
18
21
  "supportedRenderModes": [],
19
22
  "views": [],
20
23
  "configSchemas": [
@@ -246,7 +249,7 @@
246
249
  "title": "Bootstrap1 UI guide",
247
250
  "description": "Layout, component, HTMX, responsive and accessibility rules.",
248
251
  "mediaType": "text/markdown; charset=utf-8",
249
- "content": "# Bootstrap1 UI guide\n\nUse Bootstrap 5 classes and server-rendered JSX/HTMX. BetterPortal service renderers return fragments; the theme owns the shell, navigation, visible URL and global assets.\n\n## Layout\n\n- Render primary content for the `main` slot. Never emit another page shell, `<html>`, `<head>`, sidebar navigation or top bar.\n- Start pages with a semantic heading and a short action row when actions exist.\n- Use `.container-fluid`, Bootstrap grid classes and responsive gaps. Avoid fixed widths except a declared `data-bp-sidebar-width`.\n- Use cards for grouped settings, tables for comparable records and list groups for short navigation or status collections.\n- Keep primary actions on the right of desktop action rows and allow them to wrap on small screens.\n\n## Forms and mutations\n\n- Use real `<form>` elements and labelled controls. Validation text uses `.invalid-feedback` or `.form-text`.\n- Use `ctx.routeUrl()` for form actions, HTMX calls, downloads and SSE. `ctx.uiRouteUrl()` is only for GET navigation through a mounted page route.\n- Mutations should return a useful fragment and emit an `HX-Trigger` event for passive regions that need to reload.\n- Disable buttons only while their own request is active. Keep a visible loading indicator and an error fragment.\n\n## Components\n\n- Buttons: `.btn`, with one obvious `.btn-primary` action per group.\n- Status: Bootstrap badges with text; never communicate state by colour alone.\n- Destructive actions: `.btn-outline-danger` or `.btn-danger`, with confirmation proportional to impact.\n- Side panels: wrap content in `data-bp-sidebar`, add `data-bp-sidebar-title`, and open it with `data-bp-sidebar-open`. The theme converts it to an offcanvas.\n- Modals and offcanvas content may be teleported by the shell. Do not depend on a specific DOM parent after activation.\n\n## Accessibility\n\n- Preserve heading order, visible labels, keyboard focus and native controls.\n- Every icon-only action needs an accessible name.\n- Tables need headers; use a responsive wrapper instead of shrinking text.\n- Focus the first meaningful control when opening a task panel and return focus to its trigger when it closes.\n\n## Avoid\n\n- No SPA framework, client router, iframe, client state store or hardcoded service hostname.\n- No inline copy of Bootstrap or shell JavaScript.\n- No theme-origin mutation URL. Service requests go to service URLs.\n- No custom colour that bypasses the app palette when a Bootstrap semantic token exists.\n"
252
+ "content": "# Bootstrap1 UI guide\n\nUse Bootstrap 5 classes and server-rendered JSX/HTMX. BetterPortal service renderers return fragments; the theme owns the shell, navigation, visible URL and global assets.\n\n## Layout\n\n- Render primary content for the `main` slot. Never emit another page shell, `<html>`, `<head>`, sidebar navigation or top bar.\n- Start pages with a semantic heading and a short action row when actions exist.\n- Use `.container-fluid`, Bootstrap grid classes and responsive gaps. Avoid fixed widths except a declared `data-bp-sidebar-width`.\n- Use cards for grouped settings, tables for comparable records and list groups for short navigation or status collections.\n- Keep primary actions on the right of desktop action rows and allow them to wrap on small screens.\n\n## Forms and mutations\n\n- Use real `<form>` elements and labelled controls. Validation text uses `.invalid-feedback` or `.form-text`.\n- Use `ctx.routeUrl()` for form actions, HTMX calls, downloads and SSE. `ctx.uiRouteUrl()` is only for GET navigation through a mounted page route.\n- Use `<BPElement ctx={ctx} service=\"dependency-alias\" path=\"/contract/path\" fragment=\"location.id\">` for cross-service UI fragments. Never put service UUIDs or absolute service URLs in renderer code; the server consumes `ctx` and resolves the app-mounted provider.\n- Use `service=\"shell\"` and a singular shell fragment id to reuse active-shell UI such as `theme-selector`. Add `bp-loading`, optional `bp-ok` with exactly one `<template />`, status-specific `bp-status`, and `bp-nok` states as needed.\n- Mutations should return a useful fragment and emit an `HX-Trigger` event for passive regions that need to reload.\n- Disable buttons only while their own request is active. Keep a visible loading indicator and an error fragment.\n\n## Components\n\n- Buttons: `.btn`, with one obvious `.btn-primary` action per group.\n- Status: Bootstrap badges with text; never communicate state by colour alone.\n- Destructive actions: `.btn-outline-danger` or `.btn-danger`, with confirmation proportional to impact.\n- Side panels: wrap content in `data-bp-sidebar`, add `data-bp-sidebar-title`, and open it with `data-bp-sidebar-open`. The theme converts it to an offcanvas.\n- Modals and offcanvas content may be teleported by the shell. Do not depend on a specific DOM parent after activation.\n\n## Accessibility\n\n- Preserve heading order, visible labels, keyboard focus and native controls.\n- Every icon-only action needs an accessible name.\n- Tables need headers; use a responsive wrapper instead of shrinking text.\n- Focus the first meaningful control when opening a task panel and return focus to its trigger when it closes.\n\n## Avoid\n\n- No SPA framework, client router, iframe, client state store or hardcoded service hostname.\n- No inline copy of Bootstrap or shell JavaScript.\n- No theme-origin mutation URL. Service requests go to service URLs.\n- No custom colour that bypasses the app palette when a Bootstrap semantic token exists.\n"
250
253
  },
251
254
  {
252
255
  "id": "bootstrap1.skill",
@@ -254,7 +257,7 @@
254
257
  "title": "Bootstrap1 UI skill",
255
258
  "description": "Agent workflow for producing theme-compatible service UI.",
256
259
  "mediaType": "text/markdown; charset=utf-8",
257
- "content": "---\nname: betterportal-bootstrap1-ui\ndescription: Build BetterPortal service UI that fits the active Bootstrap1 shell.\n---\n\n# BetterPortal Bootstrap1 UI\n\n1. Read the active app's `/llms-api.txt` and this theme's UI guide.\n2. Identify the service view, methods, schemas, required permissions and stable `viewId`.\n3. Render server-side JSX/HTMX for the `bootstrap1` theme. Return only service content, never the shell.\n4. Use `ctx.routeUrl()` for service requests and `ctx.uiRouteUrl()` only for GET page navigation.\n5. Prefer Bootstrap components and native HTML controls. Add custom CSS or JavaScript only when the theme and platform primitives cannot express the behavior.\n6. Include loading, empty, validation, forbidden and service-unavailable states.\n7. Check keyboard operation, visible labels, heading order and responsive wrapping.\n8. Verify JSON and HTML representations against the published schemas.\n\nFor side tasks, use `data-bp-sidebar` and `data-bp-sidebar-open`. For authenticated downloads, use `hx-download`. For live refresh, emit an `HX-Trigger` event and subscribe with `hx-trigger=\"event from:body\"`.\n"
260
+ "content": "---\nname: betterportal-bootstrap1-ui\ndescription: Build BetterPortal service UI that fits the active Bootstrap1 shell.\n---\n\n# BetterPortal Bootstrap1 UI\n\n1. Read the active app's `/llms-api.txt` and this theme's UI guide.\n2. Identify the service view, methods, schemas, required permissions and stable `viewId`.\n3. Render server-side JSX/HTMX for the `bootstrap1` theme. Return only service content, never the shell.\n4. Use `ctx.routeUrl()` for service requests and `ctx.uiRouteUrl()` only for GET page navigation.\n5. Use the framework `BPElement` JSX helper with `ctx` for cross-service or active-shell fragments. Reference a declared dependency alias, never a service UUID or absolute URL. Keep loading/error/status UI inside `bp-loading`, `bp-status`, and `bp-nok`; an explicit `bp-ok` must have exactly one `<template />`.\n6. Prefer Bootstrap components and native HTML controls. Add custom CSS or JavaScript only when the theme and platform primitives cannot express the behavior.\n7. Include loading, empty, validation, forbidden and service-unavailable states.\n8. Check keyboard operation, visible labels, heading order and responsive wrapping.\n9. Verify JSON and HTML representations against the published schemas.\n\nFor side tasks, use `data-bp-sidebar` and `data-bp-sidebar-open`. For authenticated downloads, use `hx-download`. For live refresh, emit an `HX-Trigger` event and subscribe with `hx-trigger=\"event from:body\"`.\n"
258
261
  },
259
262
  {
260
263
  "id": "bootstrap1.page-template",
@@ -266,6 +269,33 @@
266
269
  "content": "/** @jsxImportSource jsx-htmx */\nimport type { PageRenderer } from \"@betterportal/framework\";\n\nexport const render: PageRenderer = (data, ctx) => (\n <section class=\"container-fluid py-3\">\n <div class=\"d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3\">\n <div>\n <h1 class=\"h3 mb-1\">Page title</h1>\n <p class=\"text-body-secondary mb-0\">Explain the task in one sentence.</p>\n </div>\n <a class=\"btn btn-primary\" href={ctx.uiRouteUrl(\"example.create\") ?? \"#\"}>Create</a>\n </div>\n <div class=\"card shadow-sm\">\n <div class=\"card-body\">{JSON.stringify(data)}</div>\n </div>\n </section>\n);\n"
267
270
  }
268
271
  ],
272
+ "shell": {
273
+ "service": "bootstrap1",
274
+ "renderer": "bootstrap5",
275
+ "fragments": [
276
+ {
277
+ "id": "footer",
278
+ "kind": "block",
279
+ "title": "Footer fragments",
280
+ "description": "Ordered fragments shown in the page footer.",
281
+ "defaultItems": []
282
+ },
283
+ {
284
+ "id": "nav",
285
+ "kind": "block",
286
+ "title": "Topbar fragments",
287
+ "description": "Ordered fragments shown on the right side of the topbar.",
288
+ "defaultItems": []
289
+ },
290
+ {
291
+ "id": "theme-selector",
292
+ "kind": "fragment",
293
+ "title": "Theme selector",
294
+ "description": "Lets the user choose light, dark, or system color mode.",
295
+ "defaultItems": []
296
+ }
297
+ ]
298
+ },
269
299
  "cacheHints": {
270
300
  "metadataTtlSeconds": 1800
271
301
  }
package/lib/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export * from "./plugins/service-betterportal-theme-bootstrap1/theme/index.js";
1
+ export * from "./plugins/service-betterportal-theme-bootstrap1/shell/index.js";
2
2
  export * from "./plugins/service-betterportal-theme-bootstrap1/index.js";
3
3
  //# sourceMappingURL=index.d.ts.map
package/lib/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export * from "./plugins/service-betterportal-theme-bootstrap1/theme/index.js";
1
+ export * from "./plugins/service-betterportal-theme-bootstrap1/shell/index.js";
2
2
  export * from "./plugins/service-betterportal-theme-bootstrap1/index.js";
3
3
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ import type { BetterPortalRegistry } from "@betterportal/framework";
2
+ export declare const registry: BetterPortalRegistry;
3
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../../src/plugins/service-betterportal-theme-bootstrap1/.bp-generated/registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEpE,eAAO,MAAM,QAAQ,EAAE,oBA4BtB,CAAC"}
@@ -0,0 +1,33 @@
1
+ // AUTO-GENERATED by BetterPortal codegen - DO NOT EDIT
2
+ import * as shellFragment0 from "../shell/_footer/index.js";
3
+ import * as shellFragment1 from "../shell/_nav/index.js";
4
+ import * as shellFragment2 from "../shell/_theme-selector.js";
5
+ export const registry = {
6
+ routes: [],
7
+ shellFragments: [
8
+ {
9
+ id: "footer",
10
+ kind: "block",
11
+ title: shellFragment0.title,
12
+ description: shellFragment0.description,
13
+ defaultItems: shellFragment0.defaultItems,
14
+ render: shellFragment0.render
15
+ },
16
+ {
17
+ id: "nav",
18
+ kind: "block",
19
+ title: shellFragment1.title,
20
+ description: shellFragment1.description,
21
+ defaultItems: shellFragment1.defaultItems,
22
+ render: shellFragment1.render
23
+ },
24
+ {
25
+ id: "theme-selector",
26
+ kind: "fragment",
27
+ title: shellFragment2.title,
28
+ description: shellFragment2.description,
29
+ render: shellFragment2.render
30
+ },
31
+ ]
32
+ };
33
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../src/plugins/service-betterportal-theme-bootstrap1/.bp-generated/registry.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,KAAK,cAAc,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,cAAc,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAC;AAG9D,MAAM,CAAC,MAAM,QAAQ,GAAyB;IAC5C,MAAM,EAAE,EACP;IACD,cAAc,EAAE;QACd;YACE,EAAE,EAAE,QAAQ;YACZ,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,cAAc,CAAC,KAAK;YAC3B,WAAW,EAAE,cAAc,CAAC,WAAW;YACvC,YAAY,EAAE,cAAc,CAAC,YAAY;YACzC,MAAM,EAAE,cAAc,CAAC,MAAM;SAC9B;QACD;YACE,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,cAAc,CAAC,KAAK;YAC3B,WAAW,EAAE,cAAc,CAAC,WAAW;YACvC,YAAY,EAAE,cAAc,CAAC,YAAY;YACzC,MAAM,EAAE,cAAc,CAAC,MAAM;SAC9B;QACD;YACE,EAAE,EAAE,gBAAgB;YACpB,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,cAAc,CAAC,KAAK;YAC3B,WAAW,EAAE,cAAc,CAAC,WAAW;YACvC,MAAM,EAAE,cAAc,CAAC,MAAM;SAC9B;KACF;CACF,CAAC"}
@@ -38,7 +38,7 @@ export const Bootstrap1AdapterSource = js(() => {
38
38
  new bootstrap.Popover(el);
39
39
  });
40
40
  };
41
- window.BetterPortalThemeAdapter = {
41
+ window.BetterPortalShellAdapter = {
42
42
  initComponents,
43
43
  disposeComponents(root) {
44
44
  if (!root || !bootstrap)
@@ -1 +1 @@
1
- {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-theme-bootstrap1/assets.tsx"],"names":[],"mappings":"AAWA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAwBD,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAkC/F"}
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-theme-bootstrap1/assets.tsx"],"names":[],"mappings":"AAWA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAwBD,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAiC/F"}
@@ -1,7 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { createRequire } from "node:module";
3
3
  import { fileURLToPath } from "node:url";
4
- import { BETTERPORTAL_BROWSER_SOURCE_PREAMBLE, betterPortalShellRuntimeSource, buildBetterPortalThemeRuntimeAsset, loadThemeRuntimeVendorAsset } from "@betterportal/theme-runtime";
4
+ import { BETTERPORTAL_BROWSER_SOURCE_PREAMBLE, betterPortalShellRuntimeSource, buildBetterPortalShellRuntimeAsset, loadShellRuntimeVendorAsset } from "@betterportal/theme-runtime";
5
5
  import { Bootstrap1AdapterSource } from "./adapter.js";
6
6
  const require = createRequire(import.meta.url);
7
7
  const BootstrapCssPath = require.resolve("bootstrap/dist/css/bootstrap.min.css");
@@ -29,7 +29,7 @@ export async function loadBootstrap1Asset(assetPath) {
29
29
  if (normalized === "bootstrap.bundle.min.js") {
30
30
  return cachedAsset(normalized, () => readTextAsset(BootstrapBundlePath, "application/javascript; charset=utf-8"));
31
31
  }
32
- const vendor = await loadThemeRuntimeVendorAsset(normalized);
32
+ const vendor = await loadShellRuntimeVendorAsset(normalized);
33
33
  if (vendor)
34
34
  return vendor;
35
35
  if (normalized === "betterportal-logo.png") {
@@ -43,13 +43,12 @@ export async function loadBootstrap1Asset(assetPath) {
43
43
  }
44
44
  if (normalized === "bootstrap1-shell.js") {
45
45
  return {
46
- body: [BETTERPORTAL_BROWSER_SOURCE_PREAMBLE, Bootstrap1AdapterSource, betterPortalShellRuntimeSource("bootstrap1")].join("\n;\n"),
46
+ body: [BETTERPORTAL_BROWSER_SOURCE_PREAMBLE, Bootstrap1AdapterSource, betterPortalShellRuntimeSource()].join("\n;\n"),
47
47
  contentType: "application/javascript; charset=utf-8"
48
48
  };
49
49
  }
50
50
  if (normalized === "bootstrap1-core.js") {
51
- return cachedAsset(normalized, () => buildBetterPortalThemeRuntimeAsset({
52
- themeId: "bootstrap1",
51
+ return cachedAsset(normalized, () => buildBetterPortalShellRuntimeAsset({
53
52
  adapterSource: Bootstrap1AdapterSource
54
53
  }));
55
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-theme-bootstrap1/assets.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-theme-bootstrap1/assets.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACL,oCAAoC,EACpC,8BAA8B,EAC9B,kCAAkC,EAClC,2BAA2B,EAC5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAOvD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC;AACjF,MAAM,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC;AACzF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuC,CAAC;AAElE,SAAS,aAAa,CAAC,QAAgB,EAAE,WAAmB;IAC1D,OAAO,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,eAAe,CAAC,QAAgB,EAAE,WAAmB;IAC5D,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,oBAAoB,CAAC,SAAiB,EAAE,WAAmB;IAClE,OAAO,eAAe,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,KAAK,SAAS,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;AACjG,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,IAAuC;IACvE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,OAAO,UAAU,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,SAAiB;IACzD,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,UAAU,KAAK,mBAAmB,EAAE,CAAC;QACvC,OAAO,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,UAAU,KAAK,yBAAyB,EAAE,CAAC;QAC7C,OAAO,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,mBAAmB,EAAE,uCAAuC,CAAC,CAAC,CAAC;IACpH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,2BAA2B,CAAC,UAAU,CAAC,CAAC;IAC7D,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,IAAI,UAAU,KAAK,uBAAuB,EAAE,CAAC;QAC3C,OAAO,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,oBAAoB,CAAC,uBAAuB,EAAE,WAAW,CAAC,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,UAAU,KAAK,6BAA6B,EAAE,CAAC;QACjD,OAAO,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,oBAAoB,CAAC,6BAA6B,EAAE,WAAW,CAAC,CAAC,CAAC;IACzG,CAAC;IACD,IAAI,UAAU,KAAK,6BAA6B,EAAE,CAAC;QACjD,OAAO,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,oBAAoB,CAAC,6BAA6B,EAAE,WAAW,CAAC,CAAC,CAAC;IACzG,CAAC;IACD,IAAI,UAAU,KAAK,qBAAqB,EAAE,CAAC;QACzC,OAAO;YACL,IAAI,EAAE,CAAC,oCAAoC,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;YACrH,WAAW,EAAE,uCAAuC;SACrD,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,KAAK,oBAAoB,EAAE,CAAC;QACxC,OAAO,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,kCAAkC,CAAC;YACtE,aAAa,EAAE,uBAAuB;SACvC,CAAC,CAAC,CAAC;IACN,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -8,7 +8,6 @@ declare const Config: import("@bsb/base").BSBPluginConfigClass<av.ObjectSchema<{
8
8
  betterportal: av.OptionalSchema<av.ObjectSchema<{
9
9
  bpConfigPath: av.OptionalSchema<av.StringSchema>;
10
10
  configApiToken: av.OptionalSchema<av.StringSchema>;
11
- configEncryptionKey: av.OptionalSchema<av.StringSchema>;
12
11
  controlPlaneUrl: av.OptionalSchema<av.StringSchema>;
13
12
  serviceApiKey: av.OptionalSchema<av.StringSchema>;
14
13
  bootstrapStatePath: av.StringSchema;
@@ -36,7 +35,6 @@ export declare class Plugin extends BPService<InstanceType<typeof Config>, typeo
36
35
  betterportal: av.OptionalSchema<av.ObjectSchema<{
37
36
  bpConfigPath: av.OptionalSchema<av.StringSchema>;
38
37
  configApiToken: av.OptionalSchema<av.StringSchema>;
39
- configEncryptionKey: av.OptionalSchema<av.StringSchema>;
40
38
  controlPlaneUrl: av.OptionalSchema<av.StringSchema>;
41
39
  serviceApiKey: av.OptionalSchema<av.StringSchema>;
42
40
  bootstrapStatePath: av.StringSchema;
@@ -83,8 +81,6 @@ export declare class Plugin extends BPService<InstanceType<typeof Config>, typeo
83
81
  private handleThemeStyle;
84
82
  private handleThemeBrand;
85
83
  private handleThemeNav;
86
- private buildLocationFragments;
87
- private handleThemeFragments;
88
84
  private logThemeContextResolutionFailure;
89
85
  private tagRequestContext;
90
86
  private renderBootstrapErrorPage;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-theme-bootstrap1/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAG1B,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EACL,0BAA0B,EA0B1B,KAAK,oBAAoB,EAK1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,SAAS,EAET,KAAK,mBAAmB,EAEzB,MAAM,0BAA0B,CAAC;AAuKlC,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;GASX,CAAC;AAEF,QAAA,MAAM,YAAY;;;;;;;CAOhB,CAAC;AAEH,qBAAa,MAAO,SAAQ,SAAS,CAAC,YAAY,CAAC,OAAO,MAAM,CAAC,EAAE,OAAO,YAAY,CAAC;IACrF,MAAM,CAAC,MAAM;;;;;;;;;;;;;;;;;;QAAU;IACvB,MAAM,CAAC,YAAY;;;;;;;MAAgB;IACnC,SAAS,CAAC,WAAW,6BAAoC;gBAE7C,GAAG,EAAE,qBAAqB,CAAC,YAAY,CAAC,OAAO,MAAM,CAAC,EAAE,OAAO,YAAY,CAAC;IAIxF,SAAS,CAAC,UAAU,IAAI,mBAAmB;IAgB3C,OAAO,KAAK,YAAY,GAEvB;IAED,SAAS,CAAC,kBAAkB,IAAI;QAAE,mBAAmB,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE;cASpE,YAAY,CAAC,SAAS,EAAE,oBAAoB,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7F,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,uBAAuB;IAmB/B,OAAO,CAAC,cAAc;IAsCtB,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,kBAAkB;YAYZ,gBAAgB;IAS9B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,YAAY;YAIN,qBAAqB;YAmBrB,qBAAqB;YAuCrB,gBAAgB;YAYhB,gBAAgB;YAWhB,cAAc;IA2B5B,OAAO,CAAC,sBAAsB;YA2BhB,oBAAoB;IA0BlC,OAAO,CAAC,gCAAgC;IAyBxC,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,wBAAwB;IA8GhC,OAAO,CAAC,gBAAgB;IAkExB,OAAO,CAAC,kBAAkB;YAsIZ,yBAAyB;YAkCzB,cAAc;YAiBd,kBAAkB;YAoClB,mBAAmB;YAgCnB,WAAW;YAwBX,WAAW;YAkRX,cAAc;YASd,YAAY;CAU3B;AAED,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-theme-bootstrap1/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAG1B,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EACL,0BAA0B,EA0B1B,KAAK,oBAAoB,EAK1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,SAAS,EAET,KAAK,mBAAmB,EAEzB,MAAM,0BAA0B,CAAC;AAkKlC,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;GASX,CAAC;AAEF,QAAA,MAAM,YAAY;;;;;;;CAOhB,CAAC;AAEH,qBAAa,MAAO,SAAQ,SAAS,CAAC,YAAY,CAAC,OAAO,MAAM,CAAC,EAAE,OAAO,YAAY,CAAC;IACrF,MAAM,CAAC,MAAM;;;;;;;;;;;;;;;;;QAAU;IACvB,MAAM,CAAC,YAAY;;;;;;;MAAgB;IACnC,SAAS,CAAC,WAAW,6BAAoC;gBAE7C,GAAG,EAAE,qBAAqB,CAAC,YAAY,CAAC,OAAO,MAAM,CAAC,EAAE,OAAO,YAAY,CAAC;IAIxF,SAAS,CAAC,UAAU,IAAI,mBAAmB;IAgB3C,OAAO,KAAK,YAAY,GAEvB;IAED,SAAS,CAAC,kBAAkB,IAAI;QAAE,mBAAmB,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE;cASpE,YAAY,CAAC,SAAS,EAAE,oBAAoB,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7F,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,uBAAuB;IAmB/B,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,kBAAkB;YAYZ,gBAAgB;IAS9B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,YAAY;YAIN,qBAAqB;YAmBrB,qBAAqB;YAuCrB,gBAAgB;YAYhB,gBAAgB;YAWhB,cAAc;IA2B5B,OAAO,CAAC,gCAAgC;IAyBxC,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,wBAAwB;IA8GhC,OAAO,CAAC,gBAAgB;IAkExB,OAAO,CAAC,kBAAkB;YAsIZ,yBAAyB;YAkCzB,cAAc;YAiBd,kBAAkB;YAoClB,mBAAmB;YAgCnB,WAAW;YAwBX,WAAW;YA0NX,cAAc;YASd,YAAY;CAU3B;AAED,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC"}
@@ -2,10 +2,11 @@ import { createConfigSchema, createEventSchemas } from "@bsb/base";
2
2
  import * as av from "anyvali";
3
3
  import { InMemoryServiceConfigStore, buildThemeAiManifest, resolveThemeLlmsContext, renderThemeLlmsApi, renderThemeLlmsDev, renderThemeLlmsIndex, renderThemeLlmsUi, buildServiceViewUrl, buildOriginPolicy, htmlResponse, inferServicePathFromViewId, registerServiceConfigRoutes, resolveServiceForTenant, resolveAppRoute, resolveRequestContextDetailed, resolveThemeRequestContext, serviceBaseUrl, eventObservability, eventHeaders, jsonResponse, resolveThemeHostname, withObservedEvent } from "@betterportal/framework";
4
4
  import { BPService, BetterPortalConfigSchema } from "@betterportal/plugin-bsb";
5
- import { isUserFacingRoute, renderBootstrap1HostPage, renderNavItems, shellStyles, renderBrand } from "./theme/index.js";
5
+ import { isUserFacingRoute, renderBootstrap1HostPage, renderNavItems, shellStyles, renderBrand } from "./shell/index.js";
6
6
  import { toHtmlString } from "@betterportal/framework";
7
7
  import { loadBootstrap1Asset } from "./assets.js";
8
8
  import { Bootstrap1DeveloperResources } from "./resources.js";
9
+ import { registry } from "./.bp-generated/registry.js";
9
10
  // Parse-only base for relative request URLs. Never emit this origin.
10
11
  const RELATIVE_URL_PARSE_BASE = "http://betterportal.invalid";
11
12
  const PluginConfigSchema = av.object({
@@ -117,12 +118,6 @@ function resolveSafeServiceTarget(service, path, themeOrigin) {
117
118
  * Any service response can fire these via the HX-Trigger header (e.g. auth
118
119
  * after login/logout); fragments that listen reload, everyone else ignores.
119
120
  */
120
- function fragmentTriggerSpec(fragmentKey, pluginId) {
121
- const triggers = ["load", `bp:fragment:${fragmentKey} from:body`];
122
- if (pluginId)
123
- triggers.push(`bp:fragments:${pluginId} from:body`);
124
- return triggers.join(", ");
125
- }
126
121
  function resolveSafeServiceViewTarget(service, route, currentPath, themeOrigin) {
127
122
  const viewUrl = buildServiceViewUrl(service, route, currentPath);
128
123
  const parsed = parseAbsoluteHttpUrl(viewUrl);
@@ -175,11 +170,11 @@ export class Plugin extends BPService {
175
170
  description: "Bootstrap 5 + htmx theme that renders BetterPortal app shells.",
176
171
  category: "theme",
177
172
  capabilities: ["theme"],
173
+ shell: { service: "bootstrap1", renderer: "bootstrap5", fragments: [] },
178
174
  developerResources: Bootstrap1DeveloperResources,
179
175
  configSchemas: THEME_CONFIG_SCHEMAS
180
176
  },
181
- // Theme exposes manual routes (not bp-routes/) - registered in onRegistered.
182
- registry: { routes: [] }
177
+ registry
183
178
  };
184
179
  }
185
180
  get betterportal() {
@@ -278,7 +273,6 @@ export class Plugin extends BPService {
278
273
  this.app.get("/.well-known/bp/theme/style", (event) => this.handleThemeStyle(event));
279
274
  this.app.get("/.well-known/bp/theme/brand", (event) => this.handleThemeBrand(event));
280
275
  this.app.get("/.well-known/bp/theme/nav", (event) => this.handleThemeNav(event));
281
- this.app.get("/.well-known/bp/theme/fragments", (event) => this.handleThemeFragments(event));
282
276
  this.app.get("/**", (event) => this.handleIndex(event));
283
277
  }
284
278
  resolveThemeAiContext(activeEvent) {
@@ -392,45 +386,6 @@ export class Plugin extends BPService {
392
386
  const html = Array.isArray(rendered) ? rendered.map((r) => toHtmlString(r)).join("") : toHtmlString(rendered);
393
387
  return htmlResponse(html, 200, "text/html; mode=fragment", { "cache-control": "no-store" });
394
388
  }
395
- buildLocationFragments(portalConfig, requestContext, location, themeOrigin) {
396
- const appFragments = requestContext.app.fragments;
397
- const assignments = appFragments?.[location] ?? [];
398
- return assignments
399
- .filter((a) => a.enabled)
400
- .map((a) => {
401
- const binding = resolveServiceForTenant(portalConfig, a.serviceId, requestContext);
402
- if (!binding)
403
- return null;
404
- const safeTarget = resolveSafeServiceTarget(binding.service, a.targetPath, themeOrigin);
405
- if (!safeTarget.ok)
406
- return null;
407
- // Load-triggered fragments must be absolute before client JS runs.
408
- return {
409
- fragmentId: a.fragmentId,
410
- serviceId: a.serviceId,
411
- pluginId: binding.service.serviceId,
412
- url: safeTarget.url,
413
- fragmentKey: `${location}.${a.fragmentId}`
414
- };
415
- })
416
- .filter((x) => x !== null);
417
- }
418
- async handleThemeFragments(event) {
419
- const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
420
- const location = url.searchParams.get("location") ?? "nav";
421
- const portalConfig = this.requirePortalConfig();
422
- const requestContext = resolveThemeRequestContext(portalConfig, eventHeaders(event), resolveThemeHostname(eventHeaders(event), this.headerTrustOptions()) ?? undefined, this.headerTrustOptions());
423
- if (!requestContext) {
424
- this.logThemeContextResolutionFailure(event);
425
- return htmlResponse("", 200, "text/html; mode=fragment");
426
- }
427
- this.tagRequestContext(event, requestContext.tenant.id, requestContext.app.id);
428
- const frags = this.buildLocationFragments(portalConfig, requestContext, location, url.origin);
429
- const escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
430
- const appendFragmentKey = (targetUrl, fragmentKey) => `${targetUrl}${targetUrl.includes("?") ? "&" : "?"}_f=${fragmentKey}`;
431
- const html = frags.map((f) => `<div data-bp-fragment="${escape(f.fragmentId)}" data-bp-fragment-location="${escape(location)}" data-bp-service="${escape(f.serviceId)}" hx-get="${escape(appendFragmentKey(f.url, f.fragmentKey))}" hx-trigger="${escape(fragmentTriggerSpec(f.fragmentKey, f.pluginId))}" hx-target="this" hx-swap="innerHTML"><span class="placeholder-glow"><span class="placeholder col-12 rounded-pill"></span></span></div>`).join("");
432
- return htmlResponse(html, 200, "text/html; mode=fragment", { "cache-control": "no-store" });
433
- }
434
389
  logThemeContextResolutionFailure(event, error) {
435
390
  const obs = eventObservability(event);
436
391
  if (!obs)
@@ -984,60 +939,6 @@ export class Plugin extends BPService {
984
939
  : initialSafeTarget && !initialSafeTarget.ok
985
940
  ? initialSafeTarget.error
986
941
  : undefined;
987
- const resolvedFragments = {};
988
- // New fragments config
989
- const appFragments = requestContext.app.fragments;
990
- if (appFragments && Object.keys(appFragments).length > 0) {
991
- for (const [location, assignments] of Object.entries(appFragments)) {
992
- resolvedFragments[location] = assignments
993
- .filter(a => a.enabled)
994
- .map(a => {
995
- const binding = resolveServiceForTenant(portalConfig, a.serviceId, requestContext);
996
- if (!binding)
997
- return null;
998
- const safeTarget = resolveSafeServiceTarget(binding.service, a.targetPath, themeOrigin);
999
- if (!safeTarget.ok)
1000
- return null;
1001
- // Load-triggered fragments must be absolute before client JS runs.
1002
- return {
1003
- fragmentId: a.fragmentId,
1004
- serviceId: a.serviceId,
1005
- pluginId: binding.service.serviceId,
1006
- url: safeTarget.url,
1007
- fragmentKey: `${location}.${a.fragmentId}`
1008
- };
1009
- })
1010
- .filter((x) => x !== null);
1011
- }
1012
- }
1013
- else {
1014
- // Backward compat: map old slots to fragments
1015
- for (const slot of requestContext.app.slots) {
1016
- if (!slot.enabled)
1017
- continue;
1018
- const dotIdx = slot.slotId.indexOf(".");
1019
- if (dotIdx === -1)
1020
- continue;
1021
- const location = slot.slotId.slice(0, dotIdx);
1022
- const id = slot.slotId.slice(dotIdx + 1);
1023
- const binding = resolveServiceForTenant(portalConfig, slot.serviceId, requestContext);
1024
- if (!binding)
1025
- continue;
1026
- if (!resolvedFragments[location])
1027
- resolvedFragments[location] = [];
1028
- const viewPath = inferServicePathFromViewId(slot.viewId);
1029
- const safeTarget = resolveSafeServiceTarget(binding.service, viewPath, themeOrigin);
1030
- if (!safeTarget.ok)
1031
- continue;
1032
- resolvedFragments[location].push({
1033
- fragmentId: id,
1034
- serviceId: slot.serviceId,
1035
- pluginId: binding.service.serviceId,
1036
- url: safeTarget.url,
1037
- fragmentKey: slot.slotId
1038
- });
1039
- }
1040
- }
1041
942
  const originPolicy = buildOriginPolicy(requestContext);
1042
943
  const baseTheme = this.applyThemeServiceConfig(requestContext.app.themeConfig, this.readStoredThemeValues(requestContext.tenant.id, requestContext.app.id));
1043
944
  const mergedThemeConfig = {
@@ -1082,7 +983,6 @@ export class Plugin extends BPService {
1082
983
  initialServiceId: currentRoute?.serviceId,
1083
984
  routeLinks,
1084
985
  navItems: navItems,
1085
- resolvedFragments,
1086
986
  loginUrl,
1087
987
  chrome: currentRoute?.chrome,
1088
988
  aiManifestUrl: "/.well-known/bp/ai.json",