@teamscala/service-boot 0.23.373 → 0.23.375
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
|
@@ -559,7 +559,20 @@ export async function registerRegistryPageRoute(
|
|
|
559
559
|
// authoritative, the menu switcher changes which tenant's pages you
|
|
560
560
|
// view. Anonymous/public (no session) keeps the resolved brand. Same
|
|
561
561
|
// fetchOrganisationPageBranding pattern as the /sign-in brand override.
|
|
562
|
+
//
|
|
563
|
+
// A PUBLIC PAGE IS NEVER PERSONALISED TO THE READER'S ORG. Personalising
|
|
564
|
+
// the brand is right for a portal — you are looking at your own
|
|
565
|
+
// workspace. It is wrong for a public document, which belongs to the org
|
|
566
|
+
// that PUBLISHED it and must look identical to every reader.
|
|
567
|
+
//
|
|
568
|
+
// Measured 2026-08-17: the Launcha Authority Blueprint, owned by GMG
|
|
569
|
+
// APAC, rendered correctly for an anonymous customer and in GBG Indo's
|
|
570
|
+
// navy with the scala logo for any signed-in staff member — so the two
|
|
571
|
+
// people checking it before it went to clients were the only two who
|
|
572
|
+
// could not see what a client sees. Silent, and inverted: the reviewers
|
|
573
|
+
// got the wrong page.
|
|
562
574
|
if (
|
|
575
|
+
routeConfig.render_mode !== "public" &&
|
|
563
576
|
sessionOrgId &&
|
|
564
577
|
sessionOrgId !== (pageData.orgId as string | undefined) &&
|
|
565
578
|
// Page-org-first (portal-branding-resolves-page-org-first): the
|
|
@@ -599,6 +612,73 @@ export async function registerRegistryPageRoute(
|
|
|
599
612
|
// Branding fetch failed — keep the default brand (no regression).
|
|
600
613
|
}
|
|
601
614
|
}
|
|
615
|
+
// BRAND FROM THE ORG THAT OWNS THE RESOURCE, not the website's owner.
|
|
616
|
+
// A public resource page (an assessment report, a shared document) is
|
|
617
|
+
// hosted on a shared website but BELONGS to a tenant. resolvePageBrand
|
|
618
|
+
// reads pageData.orgId, which is the WEBSITE's org, so every tenant's
|
|
619
|
+
// report rendered the host org's logo and palette.
|
|
620
|
+
//
|
|
621
|
+
// Measured 2026-08-17: the Launcha Authority Blueprint is owned by GMG
|
|
622
|
+
// APAC (#FF4500, launcha logo) and rendered with GBG Indo's navy and
|
|
623
|
+
// scala mark, because both pages live on the shared "Public Pages"
|
|
624
|
+
// site. The loader had already resolved the right org — nothing
|
|
625
|
+
// consumed it.
|
|
626
|
+
//
|
|
627
|
+
// `brand_org_path` names where the owning org id lives in the resolved
|
|
628
|
+
// loader data (e.g. "quiz.organisation_id"), so which resource owns the
|
|
629
|
+
// brand is a ROW, never a per-service branch
|
|
630
|
+
// (`business-logic-is-rows-plus-executors`). Absent or unresolvable,
|
|
631
|
+
// the website's org stands — this can only ever narrow the brand to a
|
|
632
|
+
// more specific owner, never blank it.
|
|
633
|
+
const brandOrgPath = (routeConfig as { brand_org_path?: string }).brand_org_path;
|
|
634
|
+
if (brandOrgPath) {
|
|
635
|
+
let cursor: unknown = resolved;
|
|
636
|
+
for (const key of brandOrgPath.split(".")) {
|
|
637
|
+
if (cursor == null || typeof cursor !== "object") {
|
|
638
|
+
cursor = undefined;
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
cursor = (cursor as Record<string, unknown>)[key];
|
|
642
|
+
}
|
|
643
|
+
if (typeof cursor === "string" && cursor.length > 0) {
|
|
644
|
+
pageData.orgId = cursor;
|
|
645
|
+
// orgId alone is not enough. resolvePageBrand fills only the
|
|
646
|
+
// accent/secondary/muted gaps from the org; primaryColor, logo and
|
|
647
|
+
// companyName come from pageData.brand, which was built from the
|
|
648
|
+
// website's org. Replace those too, the same way the session
|
|
649
|
+
// override above does, or the page keeps the host's logo while
|
|
650
|
+
// claiming the tenant's org.
|
|
651
|
+
try {
|
|
652
|
+
const owner = await fetchOrganisationPageBranding(
|
|
653
|
+
getServerClient() as never,
|
|
654
|
+
cursor,
|
|
655
|
+
);
|
|
656
|
+
if (owner) {
|
|
657
|
+
const existing = (pageData.brand as Record<string, unknown>) ?? {};
|
|
658
|
+
const existingLogo = (existing.logo as Record<string, unknown>) ?? {};
|
|
659
|
+
pageData.brand = {
|
|
660
|
+
...existing,
|
|
661
|
+
companyName: owner.company_name,
|
|
662
|
+
companyFriendlyName: owner.company_name,
|
|
663
|
+
primaryColor: owner.brand_primary_color ?? existing.primaryColor,
|
|
664
|
+
headingFont: owner.brand_heading_font ?? existing.headingFont,
|
|
665
|
+
bodyFont: owner.brand_body_font ?? existing.bodyFont,
|
|
666
|
+
logo: {
|
|
667
|
+
light: owner.company_logo_for_light_theme,
|
|
668
|
+
dark: owner.company_logo_for_dark_theme,
|
|
669
|
+
brand: existingLogo.brand ?? null,
|
|
670
|
+
},
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
} catch {
|
|
674
|
+
// Branding fetch failed — keep the website brand (no regression).
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
log.warn(
|
|
678
|
+
`[scala-install:${serviceSlug}] brand_org_path "${brandOrgPath}" resolved no org id for ${params.path}; keeping the website org brand`,
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
602
682
|
warnUncoveredBlocks(pageData, components, serviceSlug, params.path, log);
|
|
603
683
|
// Fire the AI-chat conversation prefetch NOW (in parallel with the
|
|
604
684
|
// proposal/partner/client post-processors below) so its ~eager cost is
|