@flamingo-stack/openframe-frontend-core 0.0.668 → 0.0.669-snapshot.20260927145656
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/dist/{chunk-B5UP36KY.cjs → chunk-22E6CSIS.cjs} +12 -12
- package/dist/{chunk-B5UP36KY.cjs.map → chunk-22E6CSIS.cjs.map} +1 -1
- package/dist/{chunk-6KJV6GAJ.js → chunk-5IP7FYUN.js} +4 -4
- package/dist/{chunk-GF6WJ4MG.js → chunk-5Q6BE56A.js} +6 -6
- package/dist/{chunk-NZD2WTJ2.cjs → chunk-5ZVYPAFQ.cjs} +3 -3
- package/dist/{chunk-NZD2WTJ2.cjs.map → chunk-5ZVYPAFQ.cjs.map} +1 -1
- package/dist/{chunk-Z3HUP4G7.cjs → chunk-635CAJ36.cjs} +32 -32
- package/dist/{chunk-Z3HUP4G7.cjs.map → chunk-635CAJ36.cjs.map} +1 -1
- package/dist/{chunk-GO63IT6K.js → chunk-6P335PRA.js} +5 -5
- package/dist/{chunk-5V3LERVC.js → chunk-6X66J23J.js} +4 -4
- package/dist/{chunk-6W4LUHMY.cjs → chunk-7VZZ5S5V.cjs} +206 -206
- package/dist/{chunk-6W4LUHMY.cjs.map → chunk-7VZZ5S5V.cjs.map} +1 -1
- package/dist/{chunk-EV2KJ4D2.cjs → chunk-7WK544AC.cjs} +7 -7
- package/dist/{chunk-EV2KJ4D2.cjs.map → chunk-7WK544AC.cjs.map} +1 -1
- package/dist/{chunk-FOH4MNOZ.js → chunk-C5CTLTKJ.js} +3 -3
- package/dist/{chunk-TFVUM6KK.cjs → chunk-CK4BZRVJ.cjs} +10 -10
- package/dist/{chunk-TFVUM6KK.cjs.map → chunk-CK4BZRVJ.cjs.map} +1 -1
- package/dist/{chunk-Z3GN3MIW.cjs → chunk-D2HYPVYQ.cjs} +81 -81
- package/dist/{chunk-Z3GN3MIW.cjs.map → chunk-D2HYPVYQ.cjs.map} +1 -1
- package/dist/{chunk-WVCDX7FE.cjs → chunk-DAMA3A5Z.cjs} +15 -15
- package/dist/{chunk-WVCDX7FE.cjs.map → chunk-DAMA3A5Z.cjs.map} +1 -1
- package/dist/{chunk-P6IJXAEE.js → chunk-DLJGCDHX.js} +2 -1
- package/dist/{chunk-P6IJXAEE.js.map → chunk-DLJGCDHX.js.map} +1 -1
- package/dist/{chunk-IJPV7OMA.js → chunk-EHB5LETA.js} +591 -466
- package/dist/chunk-EHB5LETA.js.map +1 -0
- package/dist/{chunk-CFUUVLYQ.cjs → chunk-GXKKWFZR.cjs} +33 -33
- package/dist/{chunk-CFUUVLYQ.cjs.map → chunk-GXKKWFZR.cjs.map} +1 -1
- package/dist/{chunk-IZ6F7LOE.js → chunk-IHOSMLBC.js} +3 -3
- package/dist/{chunk-DK3BHDUZ.js → chunk-INIITSC3.js} +3 -3
- package/dist/{chunk-ZHRIFUN3.cjs → chunk-L5TTJXLU.cjs} +28 -28
- package/dist/{chunk-ZHRIFUN3.cjs.map → chunk-L5TTJXLU.cjs.map} +1 -1
- package/dist/{chunk-7UW2BBFI.cjs → chunk-LGIBMKRM.cjs} +59 -59
- package/dist/{chunk-7UW2BBFI.cjs.map → chunk-LGIBMKRM.cjs.map} +1 -1
- package/dist/{chunk-IBQT4RTS.js → chunk-O3PPPILJ.js} +3 -3
- package/dist/{chunk-CWK4QHVF.js → chunk-OABFQXAI.js} +3 -3
- package/dist/{chunk-VEQWH5KH.cjs → chunk-OHLOPLOM.cjs} +30 -30
- package/dist/{chunk-VEQWH5KH.cjs.map → chunk-OHLOPLOM.cjs.map} +1 -1
- package/dist/{chunk-PHLBRU5P.js → chunk-P7FKI2UW.js} +3 -3
- package/dist/{chunk-YH3BUB4W.js → chunk-PTZKMV35.js} +5 -5
- package/dist/{chunk-E5HUHKHH.js → chunk-QGHMHW7R.js} +7 -7
- package/dist/{chunk-VJ4XBTR5.cjs → chunk-QH6PMRGF.cjs} +22 -22
- package/dist/{chunk-VJ4XBTR5.cjs.map → chunk-QH6PMRGF.cjs.map} +1 -1
- package/dist/{chunk-37NYF4W2.cjs → chunk-QRUW4UXB.cjs} +2 -1
- package/dist/chunk-QRUW4UXB.cjs.map +1 -0
- package/dist/{chunk-LJOELK7P.js → chunk-RG7XYHN7.js} +2 -2
- package/dist/{chunk-Y7SFGCF4.cjs → chunk-RRJ6EL63.cjs} +20 -20
- package/dist/{chunk-Y7SFGCF4.cjs.map → chunk-RRJ6EL63.cjs.map} +1 -1
- package/dist/{chunk-FJNX67W2.cjs → chunk-SEW23M5X.cjs} +5 -5
- package/dist/{chunk-FJNX67W2.cjs.map → chunk-SEW23M5X.cjs.map} +1 -1
- package/dist/{chunk-K4WYWTQV.cjs → chunk-TLMPNPYS.cjs} +6 -6
- package/dist/{chunk-K4WYWTQV.cjs.map → chunk-TLMPNPYS.cjs.map} +1 -1
- package/dist/{chunk-SHGMDYCR.js → chunk-VEAA5C5X.js} +2 -2
- package/dist/{chunk-5CC33IJ7.js → chunk-VXRWR56S.js} +6 -6
- package/dist/{chunk-NNYND3I4.js → chunk-XUHLWFEM.js} +3 -3
- package/dist/{chunk-QHL4GYYY.cjs → chunk-ZYFDPD73.cjs} +577 -452
- package/dist/chunk-ZYFDPD73.cjs.map +1 -0
- package/dist/components/case-studies/index.cjs +16 -16
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/chat/entity-cards/design-doc-card.d.ts +23 -0
- package/dist/components/chat/entity-cards/design-doc-card.d.ts.map +1 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/index.d.ts +1 -0
- package/dist/components/chat/entity-cards/index.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +11 -3
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +10 -2
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/docs/index.cjs +8 -8
- package/dist/components/docs/index.js +7 -7
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +5 -5
- package/dist/components/faq/index.js +4 -4
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/help-center-pages/index.cjs +197 -89
- package/dist/components/help-center-pages/index.cjs.map +1 -1
- package/dist/components/help-center-pages/index.d.ts +1 -1
- package/dist/components/help-center-pages/index.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.js +143 -35
- package/dist/components/help-center-pages/index.js.map +1 -1
- package/dist/components/help-center-pages/trust-center-page.d.ts +6 -0
- package/dist/components/help-center-pages/trust-center-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/trust-center-sections.d.ts +17 -2
- package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
- package/dist/components/index.cjs +62 -54
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +23 -15
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +40 -40
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/navigation/index.cjs +7 -7
- package/dist/components/navigation/index.js +6 -6
- package/dist/components/onboarding-guides/index.cjs +7 -7
- package/dist/components/onboarding-guides/index.js +6 -6
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +8 -8
- package/dist/components/tickets/index.js +7 -7
- package/dist/components/ui/index.cjs +12 -4
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +11 -3
- package/dist/index.cjs +27 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +25 -7
- package/dist/index.js.map +1 -1
- package/dist/utils/design-doc-readiness.d.ts +20 -0
- package/dist/utils/design-doc-readiness.d.ts.map +1 -0
- package/dist/utils/index.cjs +28 -3
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +24 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/list-url.d.ts.map +1 -1
- package/dist/utils/source-icons.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/entity-cards/design-doc-card.tsx +114 -0
- package/src/components/chat/entity-cards/dispatch.tsx +62 -1
- package/src/components/chat/entity-cards/index.ts +7 -0
- package/src/components/help-center-pages/__tests__/trust-center-page.test.tsx +28 -1
- package/src/components/help-center-pages/index.ts +1 -1
- package/src/components/help-center-pages/trust-center-page.tsx +59 -16
- package/src/components/help-center-pages/trust-center-sections.tsx +159 -8
- package/src/utils/design-doc-readiness.ts +34 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/list-url.ts +1 -0
- package/src/utils/source-icons.ts +3 -0
- package/dist/chunk-37NYF4W2.cjs.map +0 -1
- package/dist/chunk-IJPV7OMA.js.map +0 -1
- package/dist/chunk-QHL4GYYY.cjs.map +0 -1
- /package/dist/{chunk-6KJV6GAJ.js.map → chunk-5IP7FYUN.js.map} +0 -0
- /package/dist/{chunk-GF6WJ4MG.js.map → chunk-5Q6BE56A.js.map} +0 -0
- /package/dist/{chunk-GO63IT6K.js.map → chunk-6P335PRA.js.map} +0 -0
- /package/dist/{chunk-5V3LERVC.js.map → chunk-6X66J23J.js.map} +0 -0
- /package/dist/{chunk-FOH4MNOZ.js.map → chunk-C5CTLTKJ.js.map} +0 -0
- /package/dist/{chunk-IZ6F7LOE.js.map → chunk-IHOSMLBC.js.map} +0 -0
- /package/dist/{chunk-DK3BHDUZ.js.map → chunk-INIITSC3.js.map} +0 -0
- /package/dist/{chunk-IBQT4RTS.js.map → chunk-O3PPPILJ.js.map} +0 -0
- /package/dist/{chunk-CWK4QHVF.js.map → chunk-OABFQXAI.js.map} +0 -0
- /package/dist/{chunk-PHLBRU5P.js.map → chunk-P7FKI2UW.js.map} +0 -0
- /package/dist/{chunk-YH3BUB4W.js.map → chunk-PTZKMV35.js.map} +0 -0
- /package/dist/{chunk-E5HUHKHH.js.map → chunk-QGHMHW7R.js.map} +0 -0
- /package/dist/{chunk-LJOELK7P.js.map → chunk-RG7XYHN7.js.map} +0 -0
- /package/dist/{chunk-SHGMDYCR.js.map → chunk-VEAA5C5X.js.map} +0 -0
- /package/dist/{chunk-5CC33IJ7.js.map → chunk-VXRWR56S.js.map} +0 -0
- /package/dist/{chunk-NNYND3I4.js.map → chunk-XUHLWFEM.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-url.d.ts","sourceRoot":"","sources":["../../src/utils/list-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAaH;;;;iEAIiE;AACjE,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAKtE;
|
|
1
|
+
{"version":3,"file":"list-url.d.ts","sourceRoot":"","sources":["../../src/utils/list-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAaH;;;;iEAIiE;AACjE,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAKtE;AAsFD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,SAAK,GAAG,MAAM,GAAG,IAAI,CAa5F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-icons.d.ts","sourceRoot":"","sources":["../../src/utils/source-icons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"source-icons.d.ts","sourceRoot":"","sources":["../../src/utils/source-icons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmEpD,CAAC;AAEF;;6DAE6D;AAC7D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGxF;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAyEzD,CAAC;AAEF;;kEAEkE;AAClE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iCAAiC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmEpE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjF;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK5E"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DesignDocCard (pure presentation) — a design doc as the entity card every surface shows it with: the full card
|
|
5
|
+
* (`default`, the `RoadmapCard` layout without its image slot, since a doc has no image of its own and a constant glyph
|
|
6
|
+
* says nothing: title, DRI and date, readiness (the hub's own wording, `design-doc-readiness`), summary, sign-off caption and the people) for admin surfaces, and the chat's compact card (`DesignDocChatCard` in `dispatch.tsx`, which reads
|
|
7
|
+
* the same readiness and meta). The card writes no click logic beyond its `href` anchor.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { DesignDoc } from '../../../types/design-doc';
|
|
11
|
+
import { formatDateShort } from '../../../utils/date-formatters';
|
|
12
|
+
import {
|
|
13
|
+
DESIGN_DOC_READINESS_DISPLAY,
|
|
14
|
+
designDocReadiness,
|
|
15
|
+
formatCompletionLabelWithBlockers,
|
|
16
|
+
} from '../../../utils/design-doc-readiness';
|
|
17
|
+
import { AvatarStack, type AvatarStackPerson } from '../../ui/avatar-stack';
|
|
18
|
+
import { StatusBadge } from '../../ui/status-badge';
|
|
19
|
+
import { safeHref } from '../utils/compact-card-classes';
|
|
20
|
+
|
|
21
|
+
/** The people behind a doc: the DRI first, then the implementation owners (display only). */
|
|
22
|
+
export function designDocPeople(doc: Pick<DesignDoc, 'author' | 'feature_leads'>): AvatarStackPerson[] {
|
|
23
|
+
const people: AvatarStackPerson[] = [];
|
|
24
|
+
if (doc.author?.full_name)
|
|
25
|
+
people.push({
|
|
26
|
+
key: `dri-${doc.author.id ?? doc.author.full_name}`,
|
|
27
|
+
name: doc.author.full_name,
|
|
28
|
+
avatarUrl: doc.author.avatar_url,
|
|
29
|
+
});
|
|
30
|
+
for (const p of doc.feature_leads ?? []) {
|
|
31
|
+
if (!p.full_name) continue;
|
|
32
|
+
people.push({ key: `lead-${p.id}`, name: p.full_name, avatarUrl: p.avatar_url });
|
|
33
|
+
}
|
|
34
|
+
return people;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** "DRI Jane Doe · updated Sep 24, 2026" — the card's subtitle. */
|
|
38
|
+
export function designDocMetaLine(doc: Pick<DesignDoc, 'author' | 'updated_at'>): string {
|
|
39
|
+
return [doc.author?.full_name ? `DRI ${doc.author.full_name}` : null, `updated ${formatDateShort(doc.updated_at)}`]
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join(' · ');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DesignDocCardProps {
|
|
45
|
+
doc: DesignDoc;
|
|
46
|
+
/** The doc's page. */
|
|
47
|
+
href?: string | null;
|
|
48
|
+
target?: '_blank';
|
|
49
|
+
rel?: 'noopener' | 'noopener noreferrer';
|
|
50
|
+
className?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function DesignDocCardSkeleton() {
|
|
54
|
+
return (
|
|
55
|
+
<div className="flex h-full animate-pulse flex-col gap-[16px] rounded-[6px] border border-ods-border bg-ods-card p-[24px]">
|
|
56
|
+
<div className="flex items-center gap-[16px]">
|
|
57
|
+
<div className="flex flex-1 flex-col gap-2">
|
|
58
|
+
<div className="h-5 w-3/4 rounded bg-ods-bg" />
|
|
59
|
+
<div className="h-4 w-1/3 rounded bg-ods-bg/70" />
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="h-4 w-full rounded bg-ods-bg/60" />
|
|
63
|
+
<div className="h-4 w-5/6 rounded bg-ods-bg/60" />
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function DesignDocCard({ doc, href, target, rel, className }: DesignDocCardProps) {
|
|
69
|
+
const readiness = DESIGN_DOC_READINESS_DISPLAY[designDocReadiness(doc.completion)];
|
|
70
|
+
const people = designDocPeople(doc);
|
|
71
|
+
const link = safeHref(href ?? null);
|
|
72
|
+
const body = (
|
|
73
|
+
<>
|
|
74
|
+
<div className="flex w-full items-center gap-[16px]">
|
|
75
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
76
|
+
<div className="flex min-h-[48px] items-center">
|
|
77
|
+
<h3 className="line-clamp-2 flex-1 text-ods-text-primary text-h3">{doc.title || 'Untitled design doc'}</h3>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="flex min-h-[20px] items-center">
|
|
80
|
+
<p className="truncate text-ods-text-secondary text-h5">{designDocMetaLine(doc)}</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
<StatusBadge
|
|
84
|
+
text={readiness.label}
|
|
85
|
+
colorScheme={readiness.scheme}
|
|
86
|
+
variant="button"
|
|
87
|
+
singleLine
|
|
88
|
+
className="hidden md:inline-flex"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
<div className="md:hidden">
|
|
92
|
+
<StatusBadge text={readiness.label} colorScheme={readiness.scheme} variant="button" singleLine />
|
|
93
|
+
</div>
|
|
94
|
+
<div className="flex min-h-[72px] items-center">
|
|
95
|
+
<p className="line-clamp-3 text-ods-text-secondary text-h4">{doc.summary || ''}</p>
|
|
96
|
+
</div>
|
|
97
|
+
<div className="flex-1" />
|
|
98
|
+
<div className="flex w-full items-center justify-between gap-2">
|
|
99
|
+
<span className="truncate text-ods-text-secondary text-h6">
|
|
100
|
+
{formatCompletionLabelWithBlockers(doc.completion)}
|
|
101
|
+
</span>
|
|
102
|
+
{people.length > 0 ? <AvatarStack size="xs" people={people} className="shrink-0" /> : null}
|
|
103
|
+
</div>
|
|
104
|
+
</>
|
|
105
|
+
);
|
|
106
|
+
const outer = `flex h-full flex-col gap-[16px] rounded-[6px] border border-ods-border bg-ods-card p-[24px] transition-all hover:border-ods-accent ${className ?? ''}`;
|
|
107
|
+
return link ? (
|
|
108
|
+
<a href={link} target={target} rel={rel} className={outer}>
|
|
109
|
+
{body}
|
|
110
|
+
</a>
|
|
111
|
+
) : (
|
|
112
|
+
<div className={outer}>{body}</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -33,6 +33,7 @@ import React, { type ReactNode } from 'react';
|
|
|
33
33
|
import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context';
|
|
34
34
|
import Image from '../../../embed-shims/next-image';
|
|
35
35
|
import { useRouter } from '../../../embed-shims/next-navigation';
|
|
36
|
+
import type { DesignDoc } from '../../../types/design-doc';
|
|
36
37
|
import {
|
|
37
38
|
TRUST_CENTER_DOCUMENT_TYPE,
|
|
38
39
|
TRUST_CENTER_PAGE_PATH,
|
|
@@ -45,6 +46,11 @@ import {
|
|
|
45
46
|
type TrustFrameworkMonitoringEntry,
|
|
46
47
|
} from '../../../types/trust-center';
|
|
47
48
|
import { formatDateShort } from '../../../utils/date-formatters';
|
|
49
|
+
import {
|
|
50
|
+
DESIGN_DOC_READINESS_DISPLAY,
|
|
51
|
+
designDocReadiness,
|
|
52
|
+
formatCompletionLabel,
|
|
53
|
+
} from '../../../utils/design-doc-readiness';
|
|
48
54
|
import { faqItemAnchor } from '../../../utils/faq-anchor';
|
|
49
55
|
import { formatDateUTC as formatDate } from '../../../utils/format';
|
|
50
56
|
import { programMetaFormatters, programMetaLine } from '../../../utils/program-instant';
|
|
@@ -62,6 +68,7 @@ import { CodeIcon } from '../../icons-v2-generated/coding/code-icon';
|
|
|
62
68
|
import { CodeSquareIcon } from '../../icons-v2-generated/coding/code-square-icon';
|
|
63
69
|
import { CodingBranchIcon } from '../../icons-v2-generated/coding/coding-branch-icon';
|
|
64
70
|
import { CodingCommitIcon } from '../../icons-v2-generated/coding/coding-commit-icon';
|
|
71
|
+
import { CodingMergeIcon } from '../../icons-v2-generated/coding/coding-merge-icon';
|
|
65
72
|
import { CodingPullRequestIcon } from '../../icons-v2-generated/coding/coding-pull-request-icon';
|
|
66
73
|
import { PackageIcon } from '../../icons-v2-generated/coding/package-icon';
|
|
67
74
|
import { CallIcon } from '../../icons-v2-generated/communication/call-icon';
|
|
@@ -115,6 +122,7 @@ import { CaseStudyCardSkeleton } from './case-study-card';
|
|
|
115
122
|
import { ChatVideoEntityCard } from './chat-video-entity-card';
|
|
116
123
|
import { CustomerInterviewCardSkeleton } from './customer-interview-card';
|
|
117
124
|
import { DeletedDataCard } from './deleted-data-card';
|
|
125
|
+
import { designDocMetaLine } from './design-doc-card';
|
|
118
126
|
import {
|
|
119
127
|
parseGithubTitle,
|
|
120
128
|
formatActivityId,
|
|
@@ -1068,6 +1076,46 @@ function CustomerInterviewChatCard({
|
|
|
1068
1076
|
);
|
|
1069
1077
|
}
|
|
1070
1078
|
|
|
1079
|
+
/**
|
|
1080
|
+
* Design doc → the doc glyph, its readiness as the pill (the hub's own wording: "Ready to build" / "Not ready",
|
|
1081
|
+
* `design-doc-readiness`) and "DRI · updated · n/m reviews signed off" under the title. The hub's card route attaches the doc's list row as
|
|
1082
|
+
* `item.doc`; a row without it (an older hub) renders the ref's own title and preview.
|
|
1083
|
+
*/
|
|
1084
|
+
function DesignDocChatCard({
|
|
1085
|
+
item,
|
|
1086
|
+
chatRef,
|
|
1087
|
+
isNewTab,
|
|
1088
|
+
discuss,
|
|
1089
|
+
}: {
|
|
1090
|
+
item: (ChatCardItem & { doc?: DesignDoc }) | undefined;
|
|
1091
|
+
chatRef: ChatRef;
|
|
1092
|
+
isNewTab: boolean;
|
|
1093
|
+
discuss?: CardDiscussAction;
|
|
1094
|
+
}) {
|
|
1095
|
+
const displayRef = fetchedItemDisplayRef(item, chatRef);
|
|
1096
|
+
const doc = item?.doc;
|
|
1097
|
+
const readiness = doc ? DESIGN_DOC_READINESS_DISPLAY[designDocReadiness(doc.completion)] : null;
|
|
1098
|
+
return (
|
|
1099
|
+
<MingoInfoCard
|
|
1100
|
+
title={doc?.title ?? displayRef.title}
|
|
1101
|
+
description={
|
|
1102
|
+
doc
|
|
1103
|
+
? [designDocMetaLine(doc), formatCompletionLabel(doc.completion)].join(' · ')
|
|
1104
|
+
: (displayRef.preview ?? undefined)
|
|
1105
|
+
}
|
|
1106
|
+
icon={<FileContentIcon size={24} />}
|
|
1107
|
+
status={
|
|
1108
|
+
readiness
|
|
1109
|
+
? { label: readiness.label, variant: readiness.scheme === 'success' ? 'success' : 'grey' }
|
|
1110
|
+
: { label: 'Design doc', variant: 'grey' }
|
|
1111
|
+
}
|
|
1112
|
+
anchorProps={buildAnchorProps(displayRef.url, isNewTab)}
|
|
1113
|
+
menuGroups={cardMenuGroups(displayRef.url, discuss)}
|
|
1114
|
+
menuAriaLabel="Design doc actions"
|
|
1115
|
+
/>
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1071
1119
|
/** Investor update → presentation icon + "Investor update" pill. Title falls
|
|
1072
1120
|
* back to "Update #N" when the row has no explicit title. */
|
|
1073
1121
|
function InvestorUpdateChatCard({
|
|
@@ -1411,7 +1459,6 @@ interface GlyphCardConfig {
|
|
|
1411
1459
|
/** The OpenFrame logo every OpenFrame surface uses (the `openframe` icon name). */
|
|
1412
1460
|
const OpenFrameGlyph = resolveIcon('openframe');
|
|
1413
1461
|
const REF_GLYPH_CARD_CONFIGS: Record<string, GlyphCardConfig> = {
|
|
1414
|
-
design_doc: { label: 'Design doc', icon: () => <FileContentIcon size={24} /> },
|
|
1415
1462
|
openframe_tenant: { label: 'OpenFrame tenant', icon: () => <OpenFrameGlyph size={24} /> },
|
|
1416
1463
|
prospect_call: { label: 'Prospect call', icon: () => <CallIcon size={24} />, media: true },
|
|
1417
1464
|
// Code intelligence (product-hub internal): the review rules a repository is
|
|
@@ -1429,6 +1476,9 @@ const REF_GLYPH_CARD_CONFIGS: Record<string, GlyphCardConfig> = {
|
|
|
1429
1476
|
code_symbol: { label: 'Code symbol', icon: () => <CodeSquareIcon size={24} /> },
|
|
1430
1477
|
code_duplicate: { label: 'Duplicate code', icon: () => <Copy01Icon size={24} /> },
|
|
1431
1478
|
code_impact: { label: 'Change impact', icon: () => <CodingPullRequestIcon size={24} /> },
|
|
1479
|
+
// A change set: pull requests across repositories declared as one change, with the ClickUp tasks and design
|
|
1480
|
+
// docs its pull requests are attached to. ONE card for it wherever a set is shown (chat, a design doc's page).
|
|
1481
|
+
change_set: { label: 'Change set', icon: () => <CodingMergeIcon size={24} /> },
|
|
1432
1482
|
};
|
|
1433
1483
|
function refGlyphRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1434
1484
|
return registryEntries(REF_GLYPH_CARD_CONFIGS, (cfg, docType) =>
|
|
@@ -1698,6 +1748,17 @@ const CHAT_CARD_REGISTRY: Record<string, ChatCardRegistryEntry> = {
|
|
|
1698
1748
|
/>
|
|
1699
1749
|
),
|
|
1700
1750
|
},
|
|
1751
|
+
design_doc: {
|
|
1752
|
+
label: 'Design doc',
|
|
1753
|
+
contentRefType: 'design_doc',
|
|
1754
|
+
bareInline: true,
|
|
1755
|
+
noComposedHref: true,
|
|
1756
|
+
fallbackHref: (item: { url?: string | null }) => item?.url ?? null,
|
|
1757
|
+
skeleton: () => <MingoInfoCardSkeleton />,
|
|
1758
|
+
render: (item, chatRef, opts) => (
|
|
1759
|
+
<DesignDocChatCard item={item} chatRef={chatRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />
|
|
1760
|
+
),
|
|
1761
|
+
},
|
|
1701
1762
|
customer_interview: {
|
|
1702
1763
|
label: 'Customer interview',
|
|
1703
1764
|
contentRefType: 'customer_interview',
|
|
@@ -69,6 +69,13 @@ export {
|
|
|
69
69
|
type OnboardingGuideCardProps,
|
|
70
70
|
} from './onboarding-guide-card';
|
|
71
71
|
export { RoadmapCard, RoadmapCardSkeleton, type RoadmapCardProps, type VoteType } from './roadmap-card';
|
|
72
|
+
export {
|
|
73
|
+
DesignDocCard,
|
|
74
|
+
DesignDocCardSkeleton,
|
|
75
|
+
designDocMetaLine,
|
|
76
|
+
designDocPeople,
|
|
77
|
+
type DesignDocCardProps,
|
|
78
|
+
} from './design-doc-card';
|
|
72
79
|
export { RoadmapVoteButton, type RoadmapVoteButtonProps } from './roadmap-vote-button';
|
|
73
80
|
export { TaskTypeIcon, type TaskTypeIconProps } from './task-type-icon';
|
|
74
81
|
export {
|
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
type TrustCenterPublic,
|
|
15
15
|
} from '../../../types/trust-center';
|
|
16
16
|
import { TRUST_CENTER_FIXTURE_WINDOW_MS, makeTrustCenterData } from '../__fixtures__/trust-center';
|
|
17
|
-
import { TrustCenterPage } from '../trust-center-page';
|
|
17
|
+
import { TrustCenterPage, TrustCenterPageSkeleton } from '../trust-center-page';
|
|
18
|
+
import { TRUST_SECTION_LEADS } from '../trust-center-sections';
|
|
18
19
|
|
|
19
20
|
// The real ContactForm needs the endpoints + chat runtimes; the page only
|
|
20
21
|
// decides WHAT it is handed, so a stub that echoes its props is the honest
|
|
@@ -114,6 +115,32 @@ describe('TrustCenterPage', () => {
|
|
|
114
115
|
expect(String(fetchMock.mock.calls[0][0])).toBe('/content/api/trust-center');
|
|
115
116
|
});
|
|
116
117
|
|
|
118
|
+
it('while it loads, the skeleton shows the real section headings and leads the content will fill', async () => {
|
|
119
|
+
let answer: (response: Response) => void = () => {};
|
|
120
|
+
fetchMock.mockReturnValueOnce(new Promise<Response>(resolve => (answer = resolve)));
|
|
121
|
+
render(<TrustCenterPage endpoint="/content/api/trust-center" />);
|
|
122
|
+
const loading = await screen.findByLabelText('Loading the trust center');
|
|
123
|
+
for (const [label, lead] of [
|
|
124
|
+
['Compliance', TRUST_SECTION_LEADS.compliance],
|
|
125
|
+
['Controls', TRUST_SECTION_LEADS.controls],
|
|
126
|
+
['Documents', TRUST_SECTION_LEADS.documents],
|
|
127
|
+
] as const) {
|
|
128
|
+
expect(within(loading).getByRole('heading', { level: 2, name: label, hidden: true })).toBeInTheDocument();
|
|
129
|
+
expect(within(loading).getByText(lead as string)).toBeInTheDocument();
|
|
130
|
+
}
|
|
131
|
+
act(() => answer(jsonResponse(makeData())));
|
|
132
|
+
expect(await screen.findByText('SOC 2 Type II')).toBeInTheDocument();
|
|
133
|
+
expect(screen.queryByLabelText('Loading the trust center')).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('TrustCenterPageSkeleton wears the page chrome it is given: no title and subtitle means no header at all', () => {
|
|
137
|
+
const { rerender } = render(<TrustCenterPageSkeleton shell={false} title="" subtitle="" />);
|
|
138
|
+
expect(screen.queryByRole('heading', { level: 1 })).toBeNull();
|
|
139
|
+
expect(screen.getByLabelText('Loading the trust center')).toBeInTheDocument();
|
|
140
|
+
rerender(<TrustCenterPageSkeleton shell={false} />);
|
|
141
|
+
expect(screen.getByRole('heading', { level: 1, name: TRUST_CENTER_TITLE })).toBeInTheDocument();
|
|
142
|
+
});
|
|
143
|
+
|
|
117
144
|
it('is ONE page: every section is an anchored h2, in reading order — AI & data use after Subprocessors, Contact before FAQ', () => {
|
|
118
145
|
render(<TrustCenterPage initialData={makeData()} />);
|
|
119
146
|
const headings = screen.getAllByRole('heading', { level: 2 }).map(h => h.textContent);
|
|
@@ -21,7 +21,7 @@ export { RoadmapPage, type RoadmapPageProps } from './roadmap-page';
|
|
|
21
21
|
export { ProductReleasesListPage, type ProductReleasesListPageProps } from './product-releases-list-page';
|
|
22
22
|
export { DeliveryPage, type DeliveryPageProps } from './delivery-page';
|
|
23
23
|
export { OnboardingGuidesCatalogPage, type OnboardingGuidesCatalogPageProps } from './onboarding-guides-catalog-page';
|
|
24
|
-
export { TrustCenterPage, type TrustCenterPageProps } from './trust-center-page';
|
|
24
|
+
export { TrustCenterPage, TrustCenterPageSkeleton, type TrustCenterPageProps } from './trust-center-page';
|
|
25
25
|
|
|
26
26
|
// Existing full-pages re-exported for a single Help Center import site.
|
|
27
27
|
export { FaqDocumentPage, type FaqDocumentPageProps } from '../faq';
|
|
@@ -61,6 +61,10 @@ import {
|
|
|
61
61
|
DocumentRequestModal,
|
|
62
62
|
DocumentsSection,
|
|
63
63
|
SubprocessorsSection,
|
|
64
|
+
TRUST_BODY_GRID_CLASS,
|
|
65
|
+
TRUST_SECTIONS_COLUMN_CLASS,
|
|
66
|
+
TRUST_SECTION_LEADS,
|
|
67
|
+
TRUST_STATUS_ROW_CLASS,
|
|
64
68
|
TrustCenterSkeleton,
|
|
65
69
|
TrustSection,
|
|
66
70
|
} from './trust-center-sections';
|
|
@@ -146,7 +150,6 @@ export function TrustCenterPage({
|
|
|
146
150
|
title = TRUST_CENTER_TITLE,
|
|
147
151
|
subtitle = TRUST_CENTER_TAGLINE,
|
|
148
152
|
}: TrustCenterPageProps) {
|
|
149
|
-
const router = useRouter();
|
|
150
153
|
const { data, isLoading, error, reload } = useSelfFetch<TrustCenterPublic>(endpoint, {
|
|
151
154
|
initialData,
|
|
152
155
|
revalidateOnVisibleAfterMs: TRUST_CENTER_CACHE_SECONDS * 1000,
|
|
@@ -191,11 +194,6 @@ export function TrustCenterPage({
|
|
|
191
194
|
const openRequest = useCallback((documentTitle: string | null) => setRequest({ open: true, documentTitle }), []);
|
|
192
195
|
const closeRequest = useCallback(() => setRequest(current => ({ ...current, open: false })), []);
|
|
193
196
|
|
|
194
|
-
const backCfg =
|
|
195
|
-
backButton === false
|
|
196
|
-
? undefined
|
|
197
|
-
: { label: backButton.label ?? 'Back to home', onClick: () => router.push(backButton.href ?? '/') };
|
|
198
|
-
|
|
199
197
|
const hasGatedDocuments = data?.documents.some(document => document.access === 'request') ?? false;
|
|
200
198
|
const actions = hasGatedDocuments
|
|
201
199
|
? [{ label: 'Request access', variant: 'accent' as const, onClick: () => openRequest(null) }]
|
|
@@ -205,19 +203,19 @@ export function TrustCenterPage({
|
|
|
205
203
|
// sections show comes from `visibleSections` — this map only says what each renders.
|
|
206
204
|
const views: Record<TrustCenterSectionId, TrustSectionView> = {
|
|
207
205
|
ai: {
|
|
208
|
-
lead:
|
|
206
|
+
lead: TRUST_SECTION_LEADS.ai,
|
|
209
207
|
render: d => <AiSection practices={d.aiPractices} />,
|
|
210
208
|
},
|
|
211
209
|
compliance: {
|
|
212
|
-
lead:
|
|
210
|
+
lead: TRUST_SECTION_LEADS.compliance,
|
|
213
211
|
render: d => <ComplianceSection frameworks={d.frameworks} />,
|
|
214
212
|
},
|
|
215
213
|
controls: {
|
|
216
|
-
lead:
|
|
214
|
+
lead: TRUST_SECTION_LEADS.controls,
|
|
217
215
|
render: d => <ControlsSection endpoint={endpoint} seed={controlsSeed(d)} />,
|
|
218
216
|
},
|
|
219
217
|
documents: {
|
|
220
|
-
lead:
|
|
218
|
+
lead: TRUST_SECTION_LEADS.documents,
|
|
221
219
|
render: d => (
|
|
222
220
|
<DocumentsSection
|
|
223
221
|
documents={d.documents}
|
|
@@ -227,7 +225,7 @@ export function TrustCenterPage({
|
|
|
227
225
|
),
|
|
228
226
|
},
|
|
229
227
|
subprocessors: {
|
|
230
|
-
lead:
|
|
228
|
+
lead: TRUST_SECTION_LEADS.subprocessors,
|
|
231
229
|
render: d => <SubprocessorsSection subprocessors={d.subprocessors} />,
|
|
232
230
|
},
|
|
233
231
|
faq: { render: d => <FaqSection initialFaqs={d.faqs} heading={null} /> },
|
|
@@ -243,7 +241,7 @@ export function TrustCenterPage({
|
|
|
243
241
|
const status = monitoringStatus(data, hydrated, nowMs);
|
|
244
242
|
body = (
|
|
245
243
|
<>
|
|
246
|
-
<div className=
|
|
244
|
+
<div className={TRUST_STATUS_ROW_CLASS}>
|
|
247
245
|
<StatusIndicator status={status.status} label={status.label} />
|
|
248
246
|
{data.connected ? (
|
|
249
247
|
<DataAttribution
|
|
@@ -263,8 +261,8 @@ export function TrustCenterPage({
|
|
|
263
261
|
) : null}
|
|
264
262
|
</div>
|
|
265
263
|
|
|
266
|
-
<div className=
|
|
267
|
-
<div className=
|
|
264
|
+
<div className={TRUST_BODY_GRID_CLASS}>
|
|
265
|
+
<div className={TRUST_SECTIONS_COLUMN_CLASS}>
|
|
268
266
|
{sections.map(section => {
|
|
269
267
|
const view = views[section.id];
|
|
270
268
|
return (
|
|
@@ -296,6 +294,34 @@ export function TrustCenterPage({
|
|
|
296
294
|
);
|
|
297
295
|
}
|
|
298
296
|
|
|
297
|
+
return (
|
|
298
|
+
<TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton} actions={actions}>
|
|
299
|
+
{body}
|
|
300
|
+
</TrustCenterChrome>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* The page's chrome, ONE copy for the page and its skeleton: the standalone
|
|
306
|
+
* `PageShell` or the host's padded box (`shell`), and the frozen `PageLayout`
|
|
307
|
+
* header (title, subtitle, back button, actions).
|
|
308
|
+
*/
|
|
309
|
+
function TrustCenterChrome({
|
|
310
|
+
shell,
|
|
311
|
+
title,
|
|
312
|
+
subtitle,
|
|
313
|
+
backButton,
|
|
314
|
+
actions,
|
|
315
|
+
children,
|
|
316
|
+
}: Required<Pick<TrustCenterPageProps, 'shell' | 'title' | 'subtitle' | 'backButton'>> & {
|
|
317
|
+
actions?: Array<{ label: string; variant: 'accent'; onClick: () => void }>;
|
|
318
|
+
children: ReactNode;
|
|
319
|
+
}) {
|
|
320
|
+
const router = useRouter();
|
|
321
|
+
const backCfg =
|
|
322
|
+
backButton === false
|
|
323
|
+
? undefined
|
|
324
|
+
: { label: backButton.label ?? 'Back to home', onClick: () => router.push(backButton.href ?? '/') };
|
|
299
325
|
const inner = (
|
|
300
326
|
<PageLayout
|
|
301
327
|
title={title}
|
|
@@ -306,9 +332,26 @@ export function TrustCenterPage({
|
|
|
306
332
|
actions={actions}
|
|
307
333
|
actionsVariant="primary-buttons"
|
|
308
334
|
>
|
|
309
|
-
{
|
|
335
|
+
{children}
|
|
310
336
|
</PageLayout>
|
|
311
337
|
);
|
|
312
|
-
|
|
313
338
|
return shell ? <PageShell>{inner}</PageShell> : <div className="page-shell-content">{inner}</div>;
|
|
314
339
|
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* The whole page while a host loads the data itself (the hub's admin preview):
|
|
343
|
+
* the SAME chrome and the same body skeleton `TrustCenterPage` shows while it
|
|
344
|
+
* self-fetches. Pass the chrome props the page will get.
|
|
345
|
+
*/
|
|
346
|
+
export function TrustCenterPageSkeleton({
|
|
347
|
+
shell = true,
|
|
348
|
+
backButton = false,
|
|
349
|
+
title = TRUST_CENTER_TITLE,
|
|
350
|
+
subtitle = TRUST_CENTER_TAGLINE,
|
|
351
|
+
}: Pick<TrustCenterPageProps, 'shell' | 'backButton' | 'title' | 'subtitle'>) {
|
|
352
|
+
return (
|
|
353
|
+
<TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton}>
|
|
354
|
+
<TrustCenterSkeleton />
|
|
355
|
+
</TrustCenterChrome>
|
|
356
|
+
);
|
|
357
|
+
}
|