From 9d95ed9c68393a228769a845177d8d958f5d37d4 Mon Sep 17 00:00:00 2001 From: Michael Assraf Date: Fri, 9 Oct 2026 19:04:14 -0400 Subject: [PATCH 1/8] feat(trust-center): CU-17tkuw5w0k7 offer the page as a PDF; share the section and monitoring rules with the file [preview:none] Co-Authored-By: Claude Opus 5.5 --- .../__tests__/trust-center-page.test.tsx | 8 +++ .../help-center-pages/trust-center-page.tsx | 56 +++++++++---------- .../src/types/trust-center.ts | 56 +++++++++++++++++-- 3 files changed, 88 insertions(+), 32 deletions(-) diff --git a/openframe-frontend-core/src/components/help-center-pages/__tests__/trust-center-page.test.tsx b/openframe-frontend-core/src/components/help-center-pages/__tests__/trust-center-page.test.tsx index 1513e80d00..ac71737964 100644 --- a/openframe-frontend-core/src/components/help-center-pages/__tests__/trust-center-page.test.tsx +++ b/openframe-frontend-core/src/components/help-center-pages/__tests__/trust-center-page.test.tsx @@ -208,6 +208,14 @@ describe('TrustCenterPage', () => { expect(screen.getByRole('link', { name: 'View Privacy policy' })).toHaveAttribute('target', '_blank'); }); + it("offers the page as a PDF: the hub's pdf route beside the endpoint, query string kept, in a new tab", () => { + render(); + // PageLayout renders its actions once per breakpoint (desktop + mobile bar). + const link = screen.getAllByRole('link', { name: 'Download PDF' })[0]; + expect(link).toHaveAttribute('href', '/content/api/trust-center/pdf?tenant=a'); + expect(link).toHaveAttribute('target', '_blank'); + }); + it('hides empty sections and the request CTA when nothing is gated', () => { render( ReactNode; } -/** Sections that have content, in page order. */ -function visibleSections(data: TrustCenterPublic): Array<(typeof TRUST_CENTER_SECTIONS)[number]> { - const hasContent: Record = { - ai: data.aiPractices.length > 0, - compliance: data.frameworks.length > 0, - controls: data.controlDomains.length > 0, - documents: data.documents.length > 0, - subprocessors: data.subprocessors.length > 0, - faq: data.faqs.length > 0, - contact: true, - }; - return TRUST_CENTER_SECTIONS.filter(section => hasContent[section.id]); -} - /** * The Controls section's first answer, from the page's own server data: the * first category's controls with no query — exactly what the controls endpoint @@ -136,12 +124,9 @@ function monitoringStatus( hydrated: boolean, nowMs: number, ): { status: 'success' | 'pending' | 'missing'; label: string } { - if (!data.connected) return { status: 'missing', label: 'Live control monitoring is not enabled yet' }; // Before mount the clock is the server's, so the text claims nothing either way. - if (!hydrated) return { status: 'missing', label: 'Checking monitoring status' }; - return isTrustCenterMonitored(data, nowMs) - ? { status: 'success', label: 'Controls continuously monitored' } - : { status: 'pending', label: 'Monitoring paused' }; + if (data.connected && !hydrated) return { status: 'missing', label: 'Checking monitoring status' }; + return trustCenterMonitoringStatus(data, nowMs); } export function TrustCenterPage({ @@ -180,7 +165,7 @@ export function TrustCenterPage({ documentTitle: null, }); - const sections = useMemo(() => (data ? visibleSections(data) : []), [data]); + const sections = useMemo(() => (data ? visibleTrustCenterSections(data) : []), [data]); // Anchors. The scroll spy lights the rail from whatever scrolls the sections // (the window, or a host shell's `
` — OpenFrame's // `AppLayout`) and keeps the URL's hash on the section in view (`syncHash`; @@ -202,12 +187,27 @@ export function TrustCenterPage({ const closeRequest = useCallback(() => setRequest(current => ({ ...current, open: false })), []); const hasGatedDocuments = data?.documents.some(document => document.access === 'request') ?? false; - const actions = hasGatedDocuments - ? [{ label: 'Request access', variant: 'accent' as const, onClick: () => openRequest(null) }] + // The PDF is the page's own content as a file (the hub's `pdf` route beside + // `endpoint`), so it is offered once there is content. A new tab: the answer + // is an attachment, which the browser saves without leaving the page. + const actions: PageActionButton[] | undefined = data + ? [ + { + label: 'Download PDF', + variant: 'outline', + icon: