Multi-instance <TimeToInitialDisplay> / <TimeToFullDisplay> coordination; a multi-signal TTID/TTFD system#6090
Multi-instance <TimeToInitialDisplay> / <TimeToFullDisplay> coordination; a multi-signal TTID/TTFD system#6090
<TimeToInitialDisplay> / <TimeToFullDisplay> coordination; a multi-signal TTID/TTFD system#6090Conversation
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
🤖 This preview updates automatically when you update the PR. |
97bd1be to
cb268c8
Compare
|
Moved it back to draft because multi-instance coordination needs to be handled more carefully. |
|
@cursor review |
| const gatedReady = props.ready === undefined ? undefined : focused && props.ready; | ||
| return <Component {...props} record={focused && props.record} ready={gatedReady} />; |
There was a problem hiding this comment.
Bug: When a component wrapped with createTimeToFullDisplay is unfocused, passing a ready prop causes it to permanently block the aggregate TTFD signal from firing.
Severity: MEDIUM
Suggested Fix
The gatedReady logic should be changed to return undefined instead of false when the screen is unfocused. This prevents the component from opting into the coordinator registry. Change const gatedReady = props.ready === undefined ? undefined : focused && props.ready; to const gatedReady = props.ready === undefined ? undefined : focused ? props.ready : undefined;.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/core/src/js/tracing/timetodisplay.tsx#L528-L529
Potential issue: When a component created with `createTimeToInitialDisplay` or
`createTimeToFullDisplay` is on an unfocused screen, the `gatedReady` logic incorrectly
evaluates to `false` instead of `undefined` if the `ready` prop is provided. This causes
the component to register with the coordinator as a checkpoint with `ready = false`.
Because the coordinator's `computeAggregate` function returns `false` if any checkpoint
is not ready, this single unfocused component permanently blocks the aggregate readiness
for that span's TTID/TTFD, preventing the signal from firing even when all other
checkpoints on the same span become ready.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1582d1d. Configure here.
| // | ||
| // the idea here is that wrappers built via createTimeToFullDisplay/createTimeToInitialDisplay | ||
| // can only record TTID/TTFD on a focused screen | ||
| const gatedReady = props.ready === undefined ? undefined : focused && props.ready; |
There was a problem hiding this comment.
Unfocused screen's ready=false blocks new screen's coordinator
Medium Severity
When a screen loses focus, gatedReady evaluates to false (via focused && props.ready) rather than undefined. Since false !== undefined, useCoordinatedDisplay treats this as useRegistry = true and registers an active not-ready checkpoint. After navigation, getActiveSpan() returns the new screen's span, so the unfocused component registers a blocking checkpoint on the new screen's coordinator, preventing the new screen's TTFD/TTID from ever resolving. The legacy record path was passive (just didn't trigger the reporter), but the ready path is active (registers a blocker). gatedReady needs to be undefined when unfocused so the component exits the coordinator entirely.
Reviewed by Cursor Bugbot for commit 1582d1d. Configure here.


📢 Type of change
📜 Description
Somthing that was requested by our customers: when a screen has multiple async data sources, you can now mount one
<TimeToFullDisplay>per source — the TTID/TTFD will get recorded only when all the sources reportready. That makes it possible to use TTID/TTFD when the screen comes with a bunch of individual components to handle not so simple real-world scenarios.For example, a screen might have:
All of these load independently, at different times, and the screen isn't actually fully displayed until every one of them has resolved and rendered. With the current SDK, the only way to handle this is to hoist state management above all of these components, track which ones have finished loading, and only fire the TTFD signal once everything reports in. That's a significant amount of orchestration code that really belongs in the SDK itself.
What we're doing here is basically making it work with multiple TTID/TTFD components to handle multiple signals coming from different sources.
The docs will be updated soon.
💚 How did you test it?
Tests were added.
📝 Checklist
sendDefaultPIIis enabled🔮 Next steps