Windows currently handles onboarding through a separate setup executable that runs before the app is even installed. macOS and Linux do not have an equivalent install-time hook, so role selection and addon pre-install need to move inside the main app itself, gated to first launch only.
Problem
Today, role selection (which drives addon pre-install) only happens through Binder-setup-windows.exe, a custom WebView2 installer with its own persona-selection UI. That flow does not exist for macOS or Linux:
- macOS ships as a
.dmg. Users drag Binder.app into Applications. There is no installer process to run custom UI before first launch.
- Linux currently ships as a direct tarball download, and will soon also support a
curl install script. Neither gives us a place to show a GUI mid-install.
Both platforms need role selection and addon pre-install moved into the main app, shown once on first launch, with explicit rules so it never reappears once a user has made a choice, including choosing not to pick a role.
Current state (for reference)
- Persona/role selection UI lives in
setup/windows/frontend/src/App.tsx (lines 7-26 define PERSONA_APPS, lines 56-77 render the grid, line 179 calls Install() with seed_apps).
- On install, the Windows setup app writes
~\AppData\Local\Binder\.first-run-apps.json with the selected apps (cpp/setup/installer.cpp:328-337).
- The main app consumes that marker file once, on first launch, in
Config::load() (cpp/src/config.cpp:103-120 for ApplyFirstRunAppsSeed(), lines 122-166 for the load/first-run logic), merges the apps into installed_apps in config.json, then deletes the marker so it is never re-applied.
- Platform paths are already branched with
#ifdef _WIN32 / #ifdef __APPLE__ / Linux fallback in both cpp/setup/installer.cpp:69-95 and cpp/src/config.cpp:21-51.
- macOS and Linux packaging (
.github/workflows/release.yml:115-194 for macOS .dmg, lines 196-262 for Linux tarball) only produce a binary or app bundle. There is no installer-time hook on either platform, and no in-app onboarding currently exists at all for them.
Goal
Move role selection and addon pre-install into the main app's UI for macOS and Linux, shown only on first launch, using the same underlying addon-seeding logic that already exists for Windows. Windows keeps its current installer-driven flow unchanged.
Non-goals
- No changes to the Windows setup app or its installer flow.
- No changes to macOS
.dmg or Linux tarball/curl packaging itself. This is purely an in-app UI and config change.
- No redesign of the persona grid's visual design. Reuse the existing options and addon mappings.
Proposed plan
- Add a new in-app onboarding screen to the main app's frontend that renders the same persona grid currently defined in
setup/windows/frontend/src/App.tsx. Reuse the component and PERSONA_APPS mapping if the build setup allows sharing it between the setup frontend and the main app frontend. Otherwise, port it into the main app's frontend directly.
- Add an explicit persisted flag to
config.json, for example "onboarding_completed": true. This flag, not just "is this the first launch," is what decides whether the screen shows. It must be set the moment the user either selects a role or explicitly skips, so a skip is permanent and does not re-prompt on the next launch.
- Decide and document the exact behavior when a user closes the onboarding window without clicking either "select" or "skip." Recommendation: closing without an explicit choice should NOT set the flag, so onboarding reappears on the next launch. Only an explicit "Skip" action and an explicit role selection should set
onboarding_completed.
- Branch the startup logic in
Config::load() (cpp/src/config.cpp) by platform:
- On
_WIN32, behavior is unchanged. The installer already seeds .first-run-apps.json and should also set onboarding_completed: true at that point, so the in-app screen never shows on Windows.
- On macOS and Linux, if
onboarding_completed is false or missing, show the new in-app onboarding screen before the rest of the UI renders.
- Reuse the existing addon merge logic from
ApplyFirstRunAppsSeed() (cpp/src/config.cpp:103-120) directly in-process for macOS and Linux, rather than writing and re-reading a marker JSON file. There is no separate installer process on these platforms, so the file-based handoff is unnecessary there.
- No changes needed to
.github/workflows/release.yml packaging steps for macOS or Linux.
Open questions to resolve before or during implementation
- Should "Skip" have any visible way to restart onboarding later from settings, in case a user skips by mistake?
- Does the existing
PERSONA_APPS to addon mapping need to become a single source of truth, for example a shared JSON or shared module, now that two frontends will use it, to avoid drift between the Windows setup frontend and the new in-app version?
Files likely involved
setup/windows/frontend/src/App.tsx, source for the persona grid to reuse or port
cpp/src/config.cpp, first-run/onboarding flag, startup branch, addon seeding
cpp/setup/installer.cpp, set onboarding_completed alongside the existing .first-run-apps.json write, Windows only
- Main app frontend, new onboarding screen component, exact path to be determined based on existing frontend structure
Acceptance criteria
- On a fresh macOS or Linux install, the role/addon selection screen appears on first launch.
- Selecting a role pre-installs the same addons as the equivalent Windows flow.
- Explicitly skipping the screen never shows it again on subsequent launches.
- Closing the window without an explicit choice shows it again on the next launch.
- Windows behavior is unchanged.
Windows currently handles onboarding through a separate setup executable that runs before the app is even installed. macOS and Linux do not have an equivalent install-time hook, so role selection and addon pre-install need to move inside the main app itself, gated to first launch only.
Problem
Today, role selection (which drives addon pre-install) only happens through
Binder-setup-windows.exe, a custom WebView2 installer with its own persona-selection UI. That flow does not exist for macOS or Linux:.dmg. Users dragBinder.appinto Applications. There is no installer process to run custom UI before first launch.curlinstall script. Neither gives us a place to show a GUI mid-install.Both platforms need role selection and addon pre-install moved into the main app, shown once on first launch, with explicit rules so it never reappears once a user has made a choice, including choosing not to pick a role.
Current state (for reference)
setup/windows/frontend/src/App.tsx(lines 7-26 definePERSONA_APPS, lines 56-77 render the grid, line 179 callsInstall()withseed_apps).~\AppData\Local\Binder\.first-run-apps.jsonwith the selected apps (cpp/setup/installer.cpp:328-337).Config::load()(cpp/src/config.cpp:103-120forApplyFirstRunAppsSeed(), lines 122-166 for the load/first-run logic), merges the apps intoinstalled_appsinconfig.json, then deletes the marker so it is never re-applied.#ifdef _WIN32/#ifdef __APPLE__/ Linux fallback in bothcpp/setup/installer.cpp:69-95andcpp/src/config.cpp:21-51..github/workflows/release.yml:115-194for macOS.dmg, lines 196-262 for Linux tarball) only produce a binary or app bundle. There is no installer-time hook on either platform, and no in-app onboarding currently exists at all for them.Goal
Move role selection and addon pre-install into the main app's UI for macOS and Linux, shown only on first launch, using the same underlying addon-seeding logic that already exists for Windows. Windows keeps its current installer-driven flow unchanged.
Non-goals
.dmgor Linux tarball/curl packaging itself. This is purely an in-app UI and config change.Proposed plan
setup/windows/frontend/src/App.tsx. Reuse the component andPERSONA_APPSmapping if the build setup allows sharing it between the setup frontend and the main app frontend. Otherwise, port it into the main app's frontend directly.config.json, for example"onboarding_completed": true. This flag, not just "is this the first launch," is what decides whether the screen shows. It must be set the moment the user either selects a role or explicitly skips, so a skip is permanent and does not re-prompt on the next launch.onboarding_completed.Config::load()(cpp/src/config.cpp) by platform:_WIN32, behavior is unchanged. The installer already seeds.first-run-apps.jsonand should also setonboarding_completed: trueat that point, so the in-app screen never shows on Windows.onboarding_completedis false or missing, show the new in-app onboarding screen before the rest of the UI renders.ApplyFirstRunAppsSeed()(cpp/src/config.cpp:103-120) directly in-process for macOS and Linux, rather than writing and re-reading a marker JSON file. There is no separate installer process on these platforms, so the file-based handoff is unnecessary there..github/workflows/release.ymlpackaging steps for macOS or Linux.Open questions to resolve before or during implementation
PERSONA_APPSto addon mapping need to become a single source of truth, for example a shared JSON or shared module, now that two frontends will use it, to avoid drift between the Windows setup frontend and the new in-app version?Files likely involved
setup/windows/frontend/src/App.tsx, source for the persona grid to reuse or portcpp/src/config.cpp, first-run/onboarding flag, startup branch, addon seedingcpp/setup/installer.cpp, setonboarding_completedalongside the existing.first-run-apps.jsonwrite, Windows onlyAcceptance criteria