@@ -1190,23 +1190,6 @@ function OsacEmbedFullscreenPage() {
11901190/** Allowlist: registry ids are kebab-case; some use dots (e.g. fleet-admin-rbac-v1.1). */
11911191const HPUX_PROTOTYPE_ID_RE = / ^ [ a - z ] [ a - z 0 - 9 . _ - ] { 0 , 79 } $ / i;
11921192
1193- /**
1194- * Look up design doc and recording URLs from the manifest for a given hpux prototype id.
1195- * Matches by checking whether the entry's `prototypeUrl` contains `prototype=<id>`.
1196- * Returns undefined for each field when the entry has no value so callers can safely coalesce.
1197- */
1198- function lookupManifestLinks ( prototypeId : string ) : { designDocUrl ?: string | null ; recordingUrl ?: string | null } {
1199- if ( ! prototypeId ) return { } ;
1200- const param = `prototype=${ prototypeId } ` ;
1201- const entry = manifest . prototypes . find (
1202- ( p ) => typeof p . prototypeUrl === "string" && p . prototypeUrl . includes ( param ) ,
1203- ) ;
1204- if ( ! entry ) return { } ;
1205- return {
1206- designDocUrl : entry . designDocUrl ,
1207- recordingUrl : entry . prototypeRecordingUrl ,
1208- } ;
1209- }
12101193
12111194/**
12121195 * Prototypes that share one hub embed with a version dropdown (see {@link HpuxPrototypesEmbedFullscreenPage}).
@@ -1362,8 +1345,9 @@ function HpuxPrototypesEmbedFullscreenPage() {
13621345 const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
13631346 useEmbedFullscreenChrome ( ) ;
13641347
1365- const [ designNotes , setDesignNotes ] = useState < HpuxDesignNotesData | null > ( null ) ;
1366- const [ designNotesPrototypeName , setDesignNotesPrototypeName ] = useState < string > ( "" ) ;
1348+ // iframeDesignNotes: set by postMessage from the hpux-prototypes iframe after it loads.
1349+ const [ iframeDesignNotes , setIframeDesignNotes ] = useState < HpuxDesignNotesData | null > ( null ) ;
1350+ const [ iframePrototypeName , setIframePrototypeName ] = useState < string > ( "" ) ;
13671351 const [ isDesignNotesOpen , setIsDesignNotesOpen ] = useState ( false ) ;
13681352 const [ prototypeStatus , setPrototypeStatus ] = useState < string | undefined > ( undefined ) ;
13691353
@@ -1372,6 +1356,45 @@ function HpuxPrototypesEmbedFullscreenPage() {
13721356 const prototype = searchParams . get ( "prototype" ) ?. trim ( ) ?? "" ;
13731357 const valid = prototype . length > 0 && HPUX_PROTOTYPE_ID_RE . test ( prototype ) ;
13741358
1359+ // Look up the manifest entry for the active prototype. This is the primary data source for
1360+ // design notes — available immediately on page load, no iframe postMessage required.
1361+ const manifestEntry = useMemo ( ( ) => {
1362+ if ( ! prototype ) return undefined ;
1363+ const param = `prototype=${ prototype } ` ;
1364+ return manifest . prototypes . find (
1365+ ( p ) => typeof p . prototypeUrl === "string" && p . prototypeUrl . includes ( param ) ,
1366+ ) ;
1367+ } , [ prototype ] ) ;
1368+
1369+ // Derive design notes from the manifest entry (available before the iframe loads).
1370+ const manifestDesignNotes = useMemo ( ( ) : HpuxDesignNotesData | null => {
1371+ if ( ! manifestEntry ?. designNotes ) return null ;
1372+ return {
1373+ designerNotes : manifestEntry . designNotes . designerNotes ,
1374+ navigationGuide : manifestEntry . designNotes . navigationGuide ,
1375+ personaName : manifestEntry . persona ?? undefined ,
1376+ designDocUrl : manifestEntry . designDocUrl ?? undefined ,
1377+ recordingUrl : manifestEntry . prototypeRecordingUrl ?? undefined ,
1378+ ownerName : manifestEntry . designer ?? manifestEntry . author ,
1379+ } ;
1380+ } , [ manifestEntry ] ) ;
1381+
1382+ // Merge manifest (base) with iframe postMessage data (override). Manifest fills gaps immediately;
1383+ // the iframe response overwrites individual fields when it arrives.
1384+ const effectiveDesignNotes = useMemo ( ( ) : HpuxDesignNotesData | null => {
1385+ if ( ! manifestDesignNotes && ! iframeDesignNotes ) return null ;
1386+ return {
1387+ designerNotes : iframeDesignNotes ?. designerNotes || manifestDesignNotes ?. designerNotes || "" ,
1388+ navigationGuide : iframeDesignNotes ?. navigationGuide ?? manifestDesignNotes ?. navigationGuide ,
1389+ ownerName : iframeDesignNotes ?. ownerName ?? manifestDesignNotes ?. ownerName ,
1390+ ownerSlack : iframeDesignNotes ?. ownerSlack ?? manifestDesignNotes ?. ownerSlack ,
1391+ personaName : iframeDesignNotes ?. personaName ?? manifestDesignNotes ?. personaName ,
1392+ jiraUrl : iframeDesignNotes ?. jiraUrl ?? manifestDesignNotes ?. jiraUrl ,
1393+ recordingUrl : iframeDesignNotes ?. recordingUrl ?? manifestDesignNotes ?. recordingUrl ,
1394+ designDocUrl : iframeDesignNotes ?. designDocUrl ?? manifestDesignNotes ?. designDocUrl ,
1395+ } ;
1396+ } , [ manifestDesignNotes , iframeDesignNotes ] ) ;
1397+
13751398 // Listen for design notes data posted from the hpux-prototypes iframe on load.
13761399 useEffect ( ( ) => {
13771400 const handler = ( event : MessageEvent ) => {
@@ -1381,7 +1404,7 @@ function HpuxPrototypesEmbedFullscreenPage() {
13811404 event . data . type === "hpux-prototype-loaded"
13821405 ) {
13831406 const dn = event . data . designNotes as HpuxDesignNotesData | null ;
1384- setDesignNotes (
1407+ setIframeDesignNotes (
13851408 dn
13861409 ? {
13871410 ...dn ,
@@ -1394,7 +1417,7 @@ function HpuxPrototypesEmbedFullscreenPage() {
13941417 }
13951418 : null ,
13961419 ) ;
1397- setDesignNotesPrototypeName ( typeof event . data . prototypeName === "string" ? event . data . prototypeName : "" ) ;
1420+ setIframePrototypeName ( typeof event . data . prototypeName === "string" ? event . data . prototypeName : "" ) ;
13981421 setPrototypeStatus ( typeof event . data . status === "string" ? event . data . status : undefined ) ;
13991422 setIsDesignNotesOpen ( false ) ;
14001423 }
@@ -1413,9 +1436,9 @@ function HpuxPrototypesEmbedFullscreenPage() {
14131436 return ( ) => clearTimeout ( t ) ;
14141437 } , [ prototype , valid ] ) ;
14151438
1416- // Reset design notes and status when the prototype param changes so stale data never shows .
1439+ // Reset iframe-sourced design notes and status when the prototype param changes.
14171440 useEffect ( ( ) => {
1418- setDesignNotes ( null ) ;
1441+ setIframeDesignNotes ( null ) ;
14191442 setIsDesignNotesOpen ( false ) ;
14201443 setPrototypeStatus ( undefined ) ;
14211444 } , [ prototype ] ) ;
@@ -1424,7 +1447,7 @@ function HpuxPrototypesEmbedFullscreenPage() {
14241447 return < Navigate to = "/" replace /> ;
14251448 }
14261449
1427- const manifestLinks = useMemo ( ( ) => lookupManifestLinks ( prototype ) , [ prototype ] ) ;
1450+ const effectivePrototypeName = iframePrototypeName || manifestEntry ?. title || "" ;
14281451
14291452 const { backTo, backLabel, versionOptions } = resolveHpuxEmbedVersionContext ( prototype ) ;
14301453 const hubBase = import . meta. env . BASE_URL ;
@@ -1435,7 +1458,7 @@ function HpuxPrototypesEmbedFullscreenPage() {
14351458 const src = `${ hubBase } hpux-prototypes/?${ iframeQs . toString ( ) } ` ;
14361459 const label = `Shared HPUX Prototypes: ${ prototype } ` ;
14371460
1438- const designNotesButton = designNotes ? (
1461+ const designNotesButton = effectiveDesignNotes ? (
14391462 < Button
14401463 variant = "plain"
14411464 size = "sm"
@@ -1448,13 +1471,13 @@ function HpuxPrototypesEmbedFullscreenPage() {
14481471 </ Button >
14491472 ) : null ;
14501473
1451- const designNotesPanelContent = designNotes ? (
1474+ const designNotesPanelContent = effectiveDesignNotes ? (
14521475 < DrawerPanelContent defaultSize = "420px" minSize = "350px" >
14531476 < DrawerHead >
14541477 < div >
14551478 < Title headingLevel = "h2" size = "xl" > Design Notes</ Title >
14561479 < Content component = "small" style = { { color : "var(--pf-t--global--text--color--subtle)" } } >
1457- { designNotesPrototypeName }
1480+ { effectivePrototypeName }
14581481 </ Content >
14591482 </ div >
14601483 < DrawerActions >
@@ -1464,8 +1487,8 @@ function HpuxPrototypesEmbedFullscreenPage() {
14641487 < DrawerPanelBody >
14651488 { /* Resources row — compact, at top */ }
14661489 { ( ( ) => {
1467- const effectiveDesignDocUrl = designNotes . designDocUrl ?? manifestLinks . designDocUrl ;
1468- const effectiveRecordingUrl = designNotes . recordingUrl ?? manifestLinks . recordingUrl ;
1490+ const effectiveDesignDocUrl = effectiveDesignNotes . designDocUrl ;
1491+ const effectiveRecordingUrl = effectiveDesignNotes . recordingUrl ;
14691492 const linkStyle : React . CSSProperties = { paddingLeft : 0 , paddingRight : 0 , fontSize : "var(--pf-t--global--font--size--sm)" } ;
14701493 const notLinkedStyle : React . CSSProperties = { color : "var(--pf-t--global--text--color--subtle)" , display : "inline-flex" , alignItems : "center" , gap : "4px" , fontSize : "var(--pf-t--global--font--size--sm)" } ;
14711494 return (
@@ -1480,45 +1503,45 @@ function HpuxPrototypesEmbedFullscreenPage() {
14801503 ) : (
14811504 < span style = { notLinkedStyle } > < VideoIcon aria-hidden style = { { color : "#c9190b" , opacity : 0.5 } } /> Recording — Not linked</ span >
14821505 ) }
1483- { designNotes . jiraUrl && (
1484- < Button variant = "link" isInline icon = { < ExternalLinkAltIcon aria-hidden /> } iconPosition = "end" component = "a" href = { designNotes . jiraUrl } target = "_blank" rel = "noopener noreferrer" style = { linkStyle } > Jira</ Button >
1506+ { effectiveDesignNotes . jiraUrl && (
1507+ < Button variant = "link" isInline icon = { < ExternalLinkAltIcon aria-hidden /> } iconPosition = "end" component = "a" href = { effectiveDesignNotes . jiraUrl } target = "_blank" rel = "noopener noreferrer" style = { linkStyle } > Jira</ Button >
14851508 ) }
14861509 </ div >
14871510 ) ;
14881511 } ) ( ) }
14891512
14901513 { /* Persona + Designer — single compact row */ }
1491- { ( designNotes . personaName || designNotes . ownerName ) && (
1514+ { ( effectiveDesignNotes . personaName || effectiveDesignNotes . ownerName ) && (
14921515 < div style = { { display : "flex" , flexWrap : "wrap" , gap : "var(--pf-t--global--spacer--sm)" , alignItems : "center" , marginBottom : "var(--pf-t--global--spacer--lg)" } } >
1493- { designNotes . personaName && (
1494- < Label isCompact color = "purple" > { designNotes . personaName } </ Label >
1516+ { effectiveDesignNotes . personaName && (
1517+ < Label isCompact color = "purple" > { effectiveDesignNotes . personaName } </ Label >
14951518 ) }
1496- { designNotes . ownerName && (
1519+ { effectiveDesignNotes . ownerName && (
14971520 < Content component = "small" style = { { color : "var(--pf-t--global--text--color--subtle)" } } >
1498- { designNotes . ownerName } { designNotes . ownerSlack ? ` — @${ designNotes . ownerSlack } ` : "" }
1521+ { effectiveDesignNotes . ownerName } { effectiveDesignNotes . ownerSlack ? ` — @${ effectiveDesignNotes . ownerSlack } ` : "" }
14991522 </ Content >
15001523 ) }
15011524 </ div >
15021525 ) }
15031526
1504- { designNotes . designerNotes && (
1527+ { effectiveDesignNotes . designerNotes && (
15051528 < div style = { { marginBottom : "var(--pf-t--global--spacer--md)" } } >
15061529 < Title headingLevel = "h3" size = "md" style = { { marginBottom : "var(--pf-t--global--spacer--sm)" } } >
15071530 Designer Notes
15081531 </ Title >
15091532 < Content component = "p" >
1510- { designNotes . designerNotes }
1533+ { effectiveDesignNotes . designerNotes }
15111534 </ Content >
15121535 </ div >
15131536 ) }
15141537
1515- { designNotes . navigationGuide && designNotes . navigationGuide . length > 0 && (
1538+ { effectiveDesignNotes . navigationGuide && effectiveDesignNotes . navigationGuide . length > 0 && (
15161539 < div style = { { marginBottom : "var(--pf-t--global--spacer--md)" } } >
15171540 < Title headingLevel = "h3" size = "md" style = { { marginBottom : "var(--pf-t--global--spacer--sm)" } } >
15181541 Where to navigate
15191542 </ Title >
15201543 < ol style = { { listStyle : "none" , margin : 0 , padding : 0 } } >
1521- { designNotes . navigationGuide . map ( ( entry , index ) => (
1544+ { effectiveDesignNotes . navigationGuide . map ( ( entry , index ) => (
15221545 < li key = { index } style = { { marginBottom : "var(--pf-t--global--spacer--md)" } } >
15231546 < div style = { { display : "flex" , alignItems : "center" , flexWrap : "wrap" , gap : "var(--pf-t--global--spacer--sm)" , marginBottom : entry . notes ? "var(--pf-t--global--spacer--xs)" : 0 } } >
15241547 < span style = { { minWidth : "1.25rem" , fontWeight : 700 , color : "var(--pf-t--global--text--color--subtle)" } } >
@@ -1534,7 +1557,7 @@ function HpuxPrototypesEmbedFullscreenPage() {
15341557 { entry . notes }
15351558 </ Content >
15361559 ) }
1537- { index < designNotes . navigationGuide ! . length - 1 && (
1560+ { index < effectiveDesignNotes . navigationGuide ! . length - 1 && (
15381561 < Divider style = { { marginTop : "var(--pf-t--global--spacer--sm)" } } />
15391562 ) }
15401563 </ li >
@@ -1559,7 +1582,7 @@ function HpuxPrototypesEmbedFullscreenPage() {
15591582 extraActions = { designNotesButton }
15601583 />
15611584 < Drawer
1562- isExpanded = { isDesignNotesOpen && designNotes !== null }
1585+ isExpanded = { isDesignNotesOpen && effectiveDesignNotes !== null }
15631586 position = "end"
15641587 className = "ops-hub-design-notes-drawer"
15651588 >
0 commit comments