frontend-app-api: expose bootstrap app state

Expose the prepared bootstrap tree together with subscription-based phase updates so createApp can render from app state directly instead of forcing rerenders through a sign-in callback.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-12 22:39:25 +01:00
parent 72dd26a3a6
commit 89f83827c9
3 changed files with 11 additions and 15 deletions
@@ -3,4 +3,4 @@
'@backstage/frontend-defaults': patch
---
Adds `prepareSpecializedApp` as a new two-phase app wiring API for rendering a sign-in page before full app finalization. The sign-in step is now exposed as a bootstrap component whose `onReady` callback signals that the prepared app can now be finalized, while the opaque reusable `sessionState` is stored internally on the prepared app and returned from `tryFinalize()` and `finalize()`. That session state can also be passed into a future `prepareSpecializedApp` call to skip sign-in and reuse the prepared session. The existing `createSpecializedApp` API is now deprecated and backed by `prepareSpecializedApp().finalize()`, while `createApp` has been updated to use the same prepare/finalize flow.
Adds `prepareSpecializedApp` as a new two-phase app wiring API for rendering a bootstrap tree before full app finalization. The bootstrap phase is now exposed as a partial app tree that consumers can render while subscribing to phase transitions, while the opaque reusable `sessionState` is stored internally on the prepared app and returned from `getFinalizedApp()` and `finalize()`. That session state can also be passed into a future `prepareSpecializedApp` call to skip sign-in and reuse the prepared session. The existing `createSpecializedApp` API is now deprecated and backed by `prepareSpecializedApp().finalize()`, while `createApp` has been updated to use the same prepare/finalize flow.
@@ -15,10 +15,10 @@
*/
export {
type BootstrapSpecializedApp,
type FinalizedSpecializedApp,
prepareSpecializedApp,
type PreparedSpecializedApp,
type PreparedSpecializedAppSignInProps,
type SpecializedAppSessionState,
createSpecializedApp,
type CreateSpecializedAppOptions,
+9 -13
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { JSX, lazy, ReactNode, Suspense, useReducer } from 'react';
import { JSX, lazy, ReactNode, Suspense, useSyncExternalStore } from 'react';
import {
ConfigApi,
coreExtensionData,
@@ -149,21 +149,17 @@ export function createApp(options?: CreateAppOptions): {
function PreparedAppRoot(props: {
preparedApp: PreparedSpecializedApp;
}): JSX.Element {
const signIn = props.preparedApp.getSignIn();
const SignIn = signIn.Component;
const [, triggerRerender] = useReducer((count: number) => count + 1, 0);
const bootstrapApp = props.preparedApp.getBootstrapApp();
const finalizedApp: FinalizedSpecializedApp | undefined =
props.preparedApp.tryFinalize();
useSyncExternalStore(
props.preparedApp.subscribe,
props.preparedApp.getFinalizedApp,
);
if (!finalizedApp) {
return (
<SignIn
onReady={() => {
triggerRerender();
}}
/>
);
return bootstrapApp.tree.root.instance!.getData(
coreExtensionData.reactElement,
)!;
}
const errorPage = maybeCreateErrorPage(finalizedApp);