What is in use: hand built previews
These components draw a phone frame and render a simplified copy of an app screen with React and inline styles.
They share:
ClientAppPhoneFrameinmodules/shared/components/ClientAppPhoneFrame.tsx. A bezel with a scrolling inner area. Props:width(default 280),height(default 560),theme(defaultAPP_DARK).APP_DARKandAPP_LIGHTinmodules/shared/lib/mobile-preview-theme.ts. The trainee app’s palette as plain colour values.HomeSkins.tsxfor the home screen themes (classic,glass,poster,bento).
The live preview bundle
The second mechanism runs the real trainee app. The mobile repo can export the Expo app for the web (react-native-web). The web app serves that bundle and is meant to embed it in an iframe.What exists in the frontend
Treat the rest of this section as the contract to follow if the tab is rebuilt.
Why the rewrite exists
public/ does not resolve a directory to its index.html. Without the rewrite, /trainee-preview falls through to the [organizationSlug] dynamic route, which treats trainee-preview as a studio slug.
The bundle must be served at the directory root. Expo Router strips the configured base path and matches what remains. Pointing the iframe at /trainee-preview/index.html loads the files but leaves the router with /index.html, which matches no route. The comment on TRAINEE_PREVIEW_BASE_PATH in next.config.ts says the same.
The message contract
modules/shared/lib/trainee-preview.ts:
- Send the token with
postMessage. Never put it in the iframe URL. - Before replying, check that
event.sourceis the iframe’scontentWindowand thatevent.originequalstraineePreviewOrigin(). isPreviewReadyMessage(event.data)checks the message type.
Keeping the bundle current
The bundle is built from the mobile repo and written intoapps/saas/public/trainee-preview. Nothing in the frontend builds it. If the mobile app changes and the export is not rerun, the preview shows an old app.
Two deployment details:
output: "standalone"does not copypublic/. TheDockerfileand thedev.ymlworkflow both copyapps/saas/publicinto the release, which is how the bundle reaches production. It has to be on disk at build time.- The
.gitignoreline meant to ignore the bundle is damaged. It readsapps/saas/public/trainee-preview/packages/database/prisma/generated/, two ignore rules joined into one path. As written it ignores neither the preview bundle nor the old Prisma output.
Choosing between the two
For a new editor, build a hand written preview withClientAppPhoneFrame and the APP_DARK palette. That is what every current screen does, and it updates as the coach types.
The live bundle shows a real trainee’s real data and cannot drift from the app, but it only shows saved state and needs the token flow above.