@teamscala/service-boot 0.23.373 → 0.23.374

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.
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.373",
9
+ "version": "0.23.374",
10
10
  "sideEffects": false,
11
11
  "imports": {
12
12
  "#service-boot/*": "./src/*",
@@ -599,6 +599,73 @@ export async function registerRegistryPageRoute(
599
599
  // Branding fetch failed — keep the default brand (no regression).
600
600
  }
601
601
  }
602
+ // BRAND FROM THE ORG THAT OWNS THE RESOURCE, not the website's owner.
603
+ // A public resource page (an assessment report, a shared document) is
604
+ // hosted on a shared website but BELONGS to a tenant. resolvePageBrand
605
+ // reads pageData.orgId, which is the WEBSITE's org, so every tenant's
606
+ // report rendered the host org's logo and palette.
607
+ //
608
+ // Measured 2026-08-17: the Launcha Authority Blueprint is owned by GMG
609
+ // APAC (#FF4500, launcha logo) and rendered with GBG Indo's navy and
610
+ // scala mark, because both pages live on the shared "Public Pages"
611
+ // site. The loader had already resolved the right org — nothing
612
+ // consumed it.
613
+ //
614
+ // `brand_org_path` names where the owning org id lives in the resolved
615
+ // loader data (e.g. "quiz.organisation_id"), so which resource owns the
616
+ // brand is a ROW, never a per-service branch
617
+ // (`business-logic-is-rows-plus-executors`). Absent or unresolvable,
618
+ // the website's org stands — this can only ever narrow the brand to a
619
+ // more specific owner, never blank it.
620
+ const brandOrgPath = (routeConfig as { brand_org_path?: string }).brand_org_path;
621
+ if (brandOrgPath) {
622
+ let cursor: unknown = resolved;
623
+ for (const key of brandOrgPath.split(".")) {
624
+ if (cursor == null || typeof cursor !== "object") {
625
+ cursor = undefined;
626
+ break;
627
+ }
628
+ cursor = (cursor as Record<string, unknown>)[key];
629
+ }
630
+ if (typeof cursor === "string" && cursor.length > 0) {
631
+ pageData.orgId = cursor;
632
+ // orgId alone is not enough. resolvePageBrand fills only the
633
+ // accent/secondary/muted gaps from the org; primaryColor, logo and
634
+ // companyName come from pageData.brand, which was built from the
635
+ // website's org. Replace those too, the same way the session
636
+ // override above does, or the page keeps the host's logo while
637
+ // claiming the tenant's org.
638
+ try {
639
+ const owner = await fetchOrganisationPageBranding(
640
+ getServerClient() as never,
641
+ cursor,
642
+ );
643
+ if (owner) {
644
+ const existing = (pageData.brand as Record<string, unknown>) ?? {};
645
+ const existingLogo = (existing.logo as Record<string, unknown>) ?? {};
646
+ pageData.brand = {
647
+ ...existing,
648
+ companyName: owner.company_name,
649
+ companyFriendlyName: owner.company_name,
650
+ primaryColor: owner.brand_primary_color ?? existing.primaryColor,
651
+ headingFont: owner.brand_heading_font ?? existing.headingFont,
652
+ bodyFont: owner.brand_body_font ?? existing.bodyFont,
653
+ logo: {
654
+ light: owner.company_logo_for_light_theme,
655
+ dark: owner.company_logo_for_dark_theme,
656
+ brand: existingLogo.brand ?? null,
657
+ },
658
+ };
659
+ }
660
+ } catch {
661
+ // Branding fetch failed — keep the website brand (no regression).
662
+ }
663
+ } else {
664
+ log.warn(
665
+ `[scala-install:${serviceSlug}] brand_org_path "${brandOrgPath}" resolved no org id for ${params.path}; keeping the website org brand`,
666
+ );
667
+ }
668
+ }
602
669
  warnUncoveredBlocks(pageData, components, serviceSlug, params.path, log);
603
670
  // Fire the AI-chat conversation prefetch NOW (in parallel with the
604
671
  // proposal/partner/client post-processors below) so its ~eager cost is