Overview
The migration should be test-led. First capture the current prototype and website behavior, then extract the design system into modules/design, then replace the existing Compose UI screen by screen.
Source Of Truth
Use this precedence order when code disagrees.
targets/appWeb/src/website_main.tsxandtargets/appWeb/src/App.cssfor desktop website theme, palette tokens, typography, sticky prototype presentation, waitlist UX, and palette persistence.prototype/*.jsxfor full mobile prototype behavior, route inventory, screen-level interaction patterns, local state, stage language, cards, top bars, tab bars, campaign/chat/event/profile flows.modules/app/src/commonMain/kotlin/ai/bestbuds/appfor real app graph topology, repositories, route bindings, analytics, auth, and native behavior.modules/designas the final shared implementation home.
targets/appWeb/frontend was an older CRA/Tailwind/shadcn landing page. The only notable reference content was old product copy around a 7-day friendship system, access/foster phases, AI icebreakers, and meeting IRL. It did not contain the current appWeb palette, prototype behavior, or reusable implementation worth preserving, so it was deleted.
Current State
appWeb
The active appWeb entry points are src/main.tsx, src/App.tsx, src/website_main.tsx, src/App.css, src/prototype-inline.jsx, and src/SurfaceBackground.tsx.
Desktop website
Scroll-snapped narrative landing page with fixed top bar, palette selector, waitlist form, WebGL surface background, and sticky phone iframe.
Canonical defaults
Terracotta palette, Fraunces display serif, Space Grotesk body sans, organic cards, radius scale 1.5, accent intensity 1.3, texture off, cool stage language.
Showcased routes
create, landing, campaigns, chat, picks, and events. The mobile website is lighter and should only get smoke coverage for now.
Prototype
The standalone mobile prototype is the richest app reference. It includes onboarding, lobby, campaign, chat, picks, friends, events, create, search, notifications, invite, profile, settings, and home routes.
| Layer | Prototype examples | Migration value |
|---|---|---|
| Tokens | BB_TOKENS, stage definitions, card radius, accent alpha, texture layer | Source for Kotlin palette, stage, spacing, and radius models. |
| Atoms | Avatar, Pill, LiveDot, DayRing, IconBtn, BBWordmark | First shared Compose component set. |
| Molecules | AvatarStack, StageBadge, StageTrack, TrustRing, UserChip | Reusable product UI vocabulary for campaigns, chat, events, and profile. |
| Layouts | Phone, AppTopBar, TabBar | Screen scaffolding and navigation chrome references. |
| Product widgets | CampaignCard, BudbotCampaignPanel, EventCard, chat panels, inbox rows | Screen molecules and organisms for the real app. |
modules/design
The current design module is being replaced by a scoped atomic design surface. Public screen code should use Theme.Components { Card(...) }, Text, Chip, SearchBar, Event, and TopBar. Legacy wrapper names are not part of the migration path.
modules/app
The real app is graph-driven and should remain that way. The root route is /, onboarding is /onboarding, home is /home, top-level tabs include Chat, Campaign, Discover, and optional Events, with overflow graphs for Friends, Profile, and Dev.
| Area | Current files | Prototype target |
|---|---|---|
| Login/start | ui/StartScreen.kt | entry.jsx, onboarding.jsx |
| Onboarding | ui/onboarding/* | onboarding.jsx, flow-bridges.jsx |
| Chat inbox | ui/home/chats/ChatsScreen.kt, ChatViews.kt | chats-inbox.jsx |
| Chat thread | ChatScreen.kt, ChatViews.kt, views/MessageView.kt | chat.jsx, prompt/member moments |
| Campaigns | CampaignScreen.kt, CampaignComponents.kt | campaigns.jsx, campaign-detail.jsx |
| Discover | DiscoverScreen.kt | discover.jsx, create.jsx |
| Events | ui/home/events/* | events.jsx |
| Friends/profile | FriendsScreen.kt, ProfileScreen.kt, profile detail screens | picks.jsx, profile.jsx, member rows |
Discrepancies
| Discrepancy | Resolution |
|---|---|
appWeb desktop theme differs from older prototype defaults such as deep red, Instrument Serif, and Inter. | Make appWeb values canonical: Fraunces, Space Grotesk, cool stage language, radius scale 1.5, accent intensity 1.3, and the newer palette values. |
| appWeb only exercises a subset of prototype routes. | Desktop tests cover website behavior; mobile prototype tests cover app-like route behavior. |
targets/appWeb/frontend was a separate legacy frontend. | It has been scanned and deleted. Do not rebuild from that stack. |
modules/design is Material-ish and gradient-heavy. | Replace the internals with warm editorial/product tokens and scoped atomic components. Do not preserve legacy wrapper APIs. |
| The real Compose app has behavior not present in the prototype. | Use the prototype for look and interaction feel; keep real data and behavior in app code. |
| React/Kotlin portability is uneven. | Put portable data and Compose components in commonMain, React DOM wrappers in jsMain, and keep WebGL/iframe host details in TypeScript until worth porting. |
Testing Plan
Add richer tests under tests/bestbudsWeb before extraction and migration.
Desktop website
Cover landing render, wordmark, headline, waitlist form, sticky prototype, palette persistence, story section navigation, iframe route sync, waitlist success/failure mocks, desktop width sanity, and console/page error guards.
Mobile prototype
Use an iPhone-sized Playwright project around 390x844. Cover onboarding, campaigns, campaign detail, chats, chat, picks, friends, events, event detail, create event, utility routes, and palette postMessage sync.
Mobile website
Keep it light: hero copy, inline preview render, palette control behavior, lazy-loaded final prototype iframe, and no console/page errors.
Recommended test shape
tests/bestbudsWeb/playwright/
landing-desktop.spec.ts
prototype-mobile.spec.ts
landing-mobile-smoke.spec.ts
helpers/
consoleErrors.ts
localStorage.ts
waitlistMocks.ts
iframe.ts
Commands
npm run test:bestbudsWeb:playwright
npm run test:bestbudsWeb:maestro
npm run test:bestbudsWeb
data-route and data-local-action selectors. Add stable data-testid attributes in prototype source only where tests become brittle.
Atomic Design Extraction
Token layer
Create canonical token files in modules/design/src/commonMain/kotlin/ai/bestbuds/design/tokens.
BestBudsPalette.kt
BestBudsTypography.kt
BestBudsSpacing.kt
BestBudsRadii.kt
BestBudsStage.kt
BestBudsElevation.kt
Suggested model: BestBudsPaletteName, BestBudsPalette, BestBudsTokens, BestBudsStage, BestBudsStageStyle, and BestBudsThemeState. Include helpers for color alpha, readable-on color, card radius, and stage accent selection.
BestBudsPalette, BestBudsTokens, ColorTokens, CampaignStage, and StageTokens now live in modules/design. The stage model covers Forming, Matched, Milestone, BestBuds, and Wrapped, with app screens deriving stage from campaign metadata until the backend exposes a first-class stage field.
Scoped component API
Use a CompositionLocal-backed component set rather than public BB* component names. Screens should enter the design scope and write product-generic component calls such as Card, Text, Chip, SearchBar, Event, and TopBar. The current bridge is Theme.Components { ... }, backed by LocalBestBudsComponentSet and BestBudsComponentSet.
themed {
Components {
Card {
Text("Coffee crawl")
Chip("Matched")
PrimaryButton("Open Chat")
}
}
}
Do not keep previous public component APIs as migration adapters. New atoms, molecules, and templates can use BestBuds-prefixed file/class names when useful, but their screen-facing component functions should be scoped and unprefixed.
| Level | Components |
|---|---|
| Atoms | Wordmark, Text, IconButton, Button, Pill, LiveDot, Avatar, DayRing, TrustRing, StageBadge, SearchField, TextField, Card, Divider, ReactionChip. |
| Molecules | TopBar, TabBar, AvatarStack, StageTrack, CampaignCard, CampaignDetailHeader, BudbotPanel, ChatRow, ChatBubble, MessageInput, ReplyPreview, Event, EventInvite, MemberRow, ProfileHeader, FilterRow, SettingsSection. |
| Templates | ScreenScaffold, ScrollableScreen, HomeShell, DetailScaffold, FormScaffold, EmptyState, LoadingState, ErrorState. |
modules/design.
Card, Text, Chip, PrimaryButton, JoinButton, StageBadge, StageTrack, and TrustRing. Internal implementation names may remain descriptive, but screen code should stay inside Components { ... } and avoid preserving old public APIs.
React And Karakum
Use commonMain for portable design data and Compose components. Use jsMain only for React-specific wrappers.
modules/design/src/commonMain/kotlin/ai/bestbuds/design/
tokens/
theme/
components/atoms/
components/molecules/
components/templates/
modules/design/src/jsMain/kotlin/ai/bestbuds/design/react/
BestBudsReactTokens.kt
ReactAtoms.kt
ReactPrototypeHost.kt
modules/design/ts/
package.json
tsconfig.json
karakum.config.json
index.ts
karakum.ts
src/
Follow the reaktor-graph convention: TypeScript package in ts/, build: tsc -b, postbuild: karakum --config karakum.config.json, generated externals into ts/import, and Dependeasy web {} includes ts/import in jsMain.
SurfaceBackground.tsx, exact prototype iframe hosting during migration, and DOM-only route/palette sync logic. Do not block Compose app restyling on perfect React/Kotlin parity.
Migration Sequence
- Baseline tests: expand Playwright desktop and mobile coverage, add fixtures for console errors, localStorage reset, iframe access, and waitlist mocks.
- Token-first design rewrite: replace current design token base with appWeb/prototype tokens, remove legacy wrappers, and document native font fallback strategy.
- Shared primitives: build wordmark, avatar, avatar stack, pills, buttons, icon buttons, cards, stage badges/tracks, day/trust rings, top bar, tab bar, event cards, and campaign cards.
- React/web pieces: move reusable appWeb pieces into
modules/design/jsMainandmodules/design/tswhere useful, including palette serialization and prototype host wrappers. - Real app screens: restyle
StartScreen, onboarding, campaigns, discover, chats, chat, events, friends, profile, and dev in that order. - Clean up: remove old gradient tokens, delete unused duplicate UI, update READMEs, and refresh generated JS only intentionally.
Screen Notes
| Screen area | Keep | Change |
|---|---|---|
| Start and onboarding | Apple/Google login, impersonated test users, pending login resume, auth status/errors. | Prototype-style welcome, wordmark, warm card, clear auth actions, avatar/chip impersonation, and onboarding controls. |
| Campaigns and discover | SocialRepository calls, analytics events, route shape. | Stage-aware cards, StageBadge, StageTrack, DayRing, AvatarStack, and prototype filters/cards. |
| Chat | Reverse LazyColumn, pagination, websocket behavior, reply drag, action sheets, reactions, copy/forward, retry/discard, stickers, image fullscreen, mentions, BotUser, typing/presence subtitle. | Warm card/ink/cream bubbles, prototype top bar language, rounded composer, Budbot panel message styling, and pill reaction chips. |
| Events | Search analytics, save/unsave analytics, create-event form state, private/public tabs. | Prototype event cards, RSVP/invite controls, create event segmented options, warm save CTA, and FAB styling. |
| Profile and friends | Profile repository calls, logout behavior, friend/group navigation, age calculation, hobbies parsing. | Profile header, avatar, stat chips, interest pills, member rows, message CTA, and prototype text field/card style. |
Acceptance And Risks
Acceptance criteria
- appWeb desktop tests and mobile prototype tests pass.
modules/designowns canonical BestBuds tokens and shared components.- appWeb no longer defines independent duplicate palette constants; any remaining web-only constants must be deleted or moved into the shared token layer.
- All real app screens use
modules/designcomponents for core UI structure and no screen depends on previous public component APIs. - Graph routes, ports, repositories, analytics, auth, and chat behavior remain unchanged unless explicitly scoped.
- Android, iOS, and Desktop targets remain buildable.
- BestBuds Maestro validation passes with the migrated UI in place:
npm run maestro:android,npm run maestro:ios, andnpm run test:bestbudsWeb:maestro.
Risks
- Native font strategy for
FrauncesandSpace Grotesk. - Chat gesture, reverse list, modal sheet, and IME regressions during restyle.
- Brittle prototype selectors unless stable test IDs are added sparingly.
- WebGL background should stay TypeScript until Kotlin/JS porting has clear value.
- Dirty generated artifacts should not be refreshed accidentally.
- Legacy
targets/appWeb/frontendshould not distract the migration.
Immediate Next Steps
- Finish replacing direct Material/profile helper usage in
ProfileScreen,FriendProfileScreen,GroupProfileScreen,DevScreen, event scaffolds, chat views, and remaining home shells. - Move generic loading, profile placeholder, editable field, info row, avatar, and empty/error states behind the scoped component API instead of public helper functions.
- Keep feature behavior stable while changing presentation: auth, graph routes, chat actions, analytics, work tasks, and profile navigation should keep the same Maestro-observable semantics.
- Run the final validation gate after app migration:
npm run maestro:android,npm run maestro:ios,npm run test:bestbudsWeb, plus target builds for Android, Darwin, Desktop, and app web.