Skip to content

Commit 531a534

Browse files
fix: resolve race condition where Design Notes button never appeared in hub top bar
The hpux-prototypes iframe posts hpux-prototype-loaded during bootstrap, which fires before the hub's useEffect listener is registered when the iframe loads from cache. Fix: hub now sends an hpux-hub-ready ping to the iframe 100 ms after mount (and on each prototype change), and PrototypeContext re-sends hpux-prototype-loaded on receiving that ping if a prototype is already loaded. Also adds designNotes to shiri-alerting-ui-v2 so the button appears for both shiri-alerting-ui-v2 and observability-agentic-troubleshooting-ai as intended. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 63ba8d9 commit 531a534

3 files changed

Lines changed: 65 additions & 2 deletions

File tree

hpux-prototypes/src/app/core/PrototypeContext.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export const PrototypeProvider: React.FC<PrototypeProviderProps> = ({ children }
2424
const [isBootstrapping, setIsBootstrapping] = useState(true);
2525
const [error, setError] = useState<Error | null>(null);
2626

27+
// Ref keeps current prototype accessible inside event handlers without stale closures.
28+
const currentPrototypeRef = useRef<PrototypeModule | null>(null);
29+
currentPrototypeRef.current = currentPrototype;
30+
2731
/**
2832
* Load a prototype by ID
2933
*/
@@ -84,6 +88,34 @@ export const PrototypeProvider: React.FC<PrototypeProviderProps> = ({ children }
8488
const loadPrototypeRef = useRef(loadPrototype);
8589
loadPrototypeRef.current = loadPrototype;
8690

91+
// When embedded in the hub, respond to the hub's ready ping by re-sending prototype data.
92+
// This resolves the race where the iframe's postMessage fires before the hub's listener
93+
// is registered (common with cached/fast loads).
94+
useEffect(() => {
95+
if (window.parent === window) return;
96+
const handleHubReady = (event: MessageEvent) => {
97+
if (
98+
event.data !== null &&
99+
typeof event.data === 'object' &&
100+
event.data.type === 'hpux-hub-ready' &&
101+
currentPrototypeRef.current
102+
) {
103+
const proto = currentPrototypeRef.current;
104+
window.parent.postMessage(
105+
{
106+
type: 'hpux-prototype-loaded',
107+
designNotes: proto.config.designNotes ?? null,
108+
prototypeName: proto.config.name,
109+
status: proto.config.status,
110+
},
111+
'*',
112+
);
113+
}
114+
};
115+
window.addEventListener('message', handleHubReady);
116+
return () => window.removeEventListener('message', handleHubReady);
117+
}, []);
118+
87119
/**
88120
* Initialize registry, then load prototype from (in order): ?prototype=, URL path match,
89121
* or last session — supports GitHub Pages shared links and same-tab reload.

hpux-prototypes/src/app/prototypes/shiri-alerting-ui-v2/prototype.config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,23 @@ export const config: PrototypeConfig = {
4545
// Metadata
4646
createdAt: '2025-12-24',
4747
updatedAt: '2025-12-24',
48+
49+
designNotes: {
50+
overview:
51+
'v2 of the Multi-cluster Alerting UI. Navigation moves from a dedicated Alerting page to a filtering model: clicking a cluster in the heatmap filters the Firing alerts tab in place. Keeps the SRE in one context rather than bouncing between views.',
52+
pages: [
53+
{
54+
name: 'Clusters Health',
55+
path: '/fleet-management/alerting',
56+
notes:
57+
'Heatmap entry point. Each cluster cell is clickable and sets the active filter for the Firing Alerts tab. Severity bands and cluster status badges are the primary scannable signal.',
58+
},
59+
{
60+
name: 'Firing Alerts',
61+
path: '/fleet-management/alerting/firing',
62+
notes:
63+
'Full alert table filtered by the selected cluster. Column set, sort order, and bulk-action affordances are open for stakeholder review — confirm with Shiri before finalising.',
64+
},
65+
],
66+
},
4867
};

hub/src/App.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Dispatch, SetStateAction } from "react";
2-
import { useEffect, useMemo, useState } from "react";
2+
import { useEffect, useMemo, useRef, useState } from "react";
33

44
import {
55
Link,
@@ -1306,6 +1306,8 @@ function HpuxPrototypesEmbedFullscreenPage() {
13061306
const [isDesignNotesOpen, setIsDesignNotesOpen] = useState(false);
13071307
const [prototypeStatus, setPrototypeStatus] = useState<string | undefined>(undefined);
13081308

1309+
const iframeRef = useRef<HTMLIFrameElement>(null);
1310+
13091311
const prototype = searchParams.get("prototype")?.trim() ?? "";
13101312
const valid = prototype.length > 0 && HPUX_PROTOTYPE_ID_RE.test(prototype);
13111313

@@ -1327,6 +1329,16 @@ function HpuxPrototypesEmbedFullscreenPage() {
13271329
return () => window.removeEventListener("message", handler);
13281330
}, []);
13291331

1332+
// Fix race condition: after the prototype iframe (re)loads, ping it so it re-sends its data
1333+
// even if its postMessage fired before our listener was registered.
1334+
useEffect(() => {
1335+
if (!valid) return;
1336+
const t = setTimeout(() => {
1337+
iframeRef.current?.contentWindow?.postMessage({ type: "hpux-hub-ready" }, "*");
1338+
}, 100);
1339+
return () => clearTimeout(t);
1340+
}, [prototype, valid]);
1341+
13301342
// Reset design notes and status when the prototype param changes so stale data never shows.
13311343
useEffect(() => {
13321344
setDesignNotes(null);
@@ -1436,7 +1448,7 @@ function HpuxPrototypesEmbedFullscreenPage() {
14361448
className="ops-hub-design-notes-drawer"
14371449
>
14381450
<DrawerContent panelContent={designNotesPanelContent}>
1439-
<iframe key={src} title={label} className="ops-hub-embed-fullscreen-frame" src={src} />
1451+
<iframe ref={iframeRef} key={src} title={label} className="ops-hub-embed-fullscreen-frame" src={src} />
14401452
</DrawerContent>
14411453
</Drawer>
14421454
</div>

0 commit comments

Comments
 (0)