Design system migration guide
Move Claire surfaces onto shared semantic tokens and native primitives, incrementally.
This guide moves the existing Expo client toward the visual system the mockups demonstrate. The migration must stay incremental — do not rewrite routing, data fetching, and presentation in one change.
Migration goals#
- One semantic token source for iOS, Android, macOS, Windows, and marketing web.
- A shared product language, without pretending mobile and desktop layouts are the same thing.
- One-off colors, radii, and text sizes replaced by primitives.
- Messaging, bridge, authentication, and AI behaviour preserved throughout the visual rollout.
- Every migrated screen easy to compare against the design reference.
The design-system package#
src/
tokens/ color · type · space · radius · motion
theme/ ThemeProvider · useTheme
primitives/ ClaireText · ClaireButton · ClaireIconButton · ClaireCard
ClaireAvatar · ClaireChip · ClaireField · ClaireDivider
patterns/ ConversationRow · MessageBubble · PlatformBadge
AIAssistCard · LoopCardStart inside mobile/design-system/ if workspace wiring would slow the first pull request, then extract to packages/ when the desktop bootstrap begins.
Token translation#
The HTML mockups use CSS custom properties as documentation only. Native apps import typed token objects.
export const colors = {
ink: '#10120F',
cream: '#F4F1EA',
paper: '#FFFDF8',
lime: '#DFFF64',
sky: '#B9DCFF',
focus: '#3C68FF',
success: '#18794E',
warning: '#B75D00',
danger: '#C83A3A',
} as const;
export const space = { 1: 4, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32, 12: 48, 16: 64, 24: 96 } as const;
export const radius = { control: 12, card: 20, panel: 32, feature: 48, pill: 999 } as const;Semantic typography#
Define variants rather than passing raw font sizes.
| Variant | Mobile | Desktop | Usage |
|---|---|---|---|
display | 42/42 | 52/52 | Onboarding and major empty states |
screenTitle | 31/34 | 28/31 | Screen or window destination title |
sectionTitle | 20/24 | 18/22 | Section and inspector headings |
body | 15/22 | 14/20 | Primary reading text |
bodySmall | 13/18 | 12/17 | Supporting content |
label | 11/15 | 10/14 | Controls and metadata |
monoLabel | 10/14 | 9/13 | AI and status labels |
Every text component must support Dynamic Type and system font scaling. Avoid fixed-height containers around user-generated text.
Component rules#
Avatar#
- Always set equal width and height,
aspectRatio: 1, andflexShrink: 0. - The platform badge is positioned relative to the avatar wrapper and never allowed to change avatar layout.
- The text fallback uses initials with a stable contact-derived color.
Platform badge#
- Include a glyph and an accessible platform name — color alone is insufficient.
- Keep the badge subordinate to the person and the conversation.
- Use real vector or SF Symbol assets in production, not the letter placeholders.
AI assist card#
- Always state why it appeared: quick context, loop found, suggested reply, or answer.
- Generated answers link back to their source messages.
- The primary action is explicit; dismissal and correction are always available.
- AI color is contextual — sky, lavender, or a warm loop yellow — never a generic gradient.
Button#
- Mobile touch target at least 44 points, even when the icon is smaller.
- Desktop pointer target at least 28 points, ideally 32–36.
- Focus ring: a 3-point focus token with offset.
- Destructive actions use the danger token only once intent is clear.
Existing screen mapping#
| Existing route | New target | Migration note |
|---|---|---|
app/(tabs)/dashboard.tsx | Daily brief | Keep the smart-card data; replace section composition. |
app/(tabs)/messages.tsx | Unified inbox | Move platform filters into chips; normalize conversation rows. |
app/chat/[chatId].tsx | Chat | Add the AI context ribbon and inline loop card behind flags. |
app/(tabs)/loops.tsx | Loops | Introduce summary metrics and source-message links. |
app/(tabs)/contacts.tsx | People | Add a context-needed section and a relationship entry. |
app/chat/settings/[chatId].tsx | Relationship memory | Recompose prompt, type, and tone around a contact model. |
app/(tabs)/settings.tsx | Settings hub | Split Claire behaviour from app and infrastructure settings. |
PlatformAuthModal.tsx | Connection setup | Render steps from capability and connection definitions. |
MessageCard.tsx | Conversation and message patterns | Split the inbox row from the chat bubble instead of one card doing both. |
Recommended rollout#
- Snapshot and protect behaviour. Capture screenshots and flow tests for sign-in, inbox filters, chat send, platform auth, loop tracking, and relationship settings. Add a
newDesignSystemflag that switches presentation only — never maintain two data implementations. - Tokens and primitives. Typed tokens, then text, button, icon button, card, avatar, chip, field, and divider. Test light mode first.
- Shared patterns. Platform badge, conversation row, message bubble and composer, AI assist and loop cards, settings row and toggle, and the empty, error, and skeleton states.
- Migrate the core loop, one screen at a time behind the flag: inbox, chat, daily brief, loops, search.
- People and settings. Replace free-form relationship strings with an enum plus an optional custom label; keep the prompt user-authored and visible.
- Remove legacy styling. Delete old color constants only when no references remain, then drop the flag.
- Extract for desktop. Move tokens and primitives to
packages/design-system, and keep navigation and window composition outside the shared package.
Navigation migration#
The proposed mobile shell is Home, Inbox, Loops, and Search, with People as an Inbox subview and Settings opening from the profile.
Relationship memory data model#
type RelationshipType =
| 'business' | 'client' | 'colleague' | 'mentor'
| 'family' | 'close_friend' | 'friend' | 'acquaintance'
| 'dating' | 'partner' | 'former_partner'
| 'community' | 'service_provider' | 'other';
type SuggestionTone = 'warm_direct' | 'professional' | 'casual' | 'playful' | 'custom';
interface RelationshipMemory {
contactId: string;
type: RelationshipType;
customType?: string;
prompt?: string;
suggestionTone: SuggestionTone;
updatedAt: string;
}Platform-specific styling policy#
Three layers, in order:
- Shared semantics. Colors, spacing, typography variants, state names.
- Shared patterns. Data and interaction contract.
- Platform composition. Mobile tab screens versus desktop panes and windows.
So ConversationRow.tsx shares behaviour and base visuals, while ConversationRow.macos.tsx and ConversationRow.windows.tsx add host keyboard conventions, hover, context menus, selection, and pointer density. Do not scale a 390-point phone screen to fill a desktop window.
Styling technology#
Native production code consumes token objects and React Native style props. Do not make Tailwind or NativeWind a requirement for the shared desktop package until both desktop-host compatibility spikes are proven.
- Existing NativeWind screens can import token values through the client Tailwind mapping.
- New shared primitives accept semantic props and produce native styles internally.
- Feature screens must not contain raw hex codes.
- Use continuous border curves on Apple platforms where supported.
- Use native
boxShadow, not the legacyshadow*orelevationAPIs.
Accessibility checklist#
- 44-point mobile touch targets and visible keyboard focus on desktop.
- Correct roles and labels for icon-only buttons.
- Platform badges announced after the contact or conversation name.
- Dynamic Type, system text scaling, reduced motion, and reduced transparency.
- Contrast testing for cream, paper, and all pastel surfaces.
- Text or icons accompany every status color.
- Message state — sending, failed, read — is never communicated by color alone.
Visual QA matrix#
Test each migrated screen at:
- iPhone SE width, standard iPhone, and large iPhone; iPad split view if enabled.
- macOS and Windows at 1024×680 minimum, 1280×800, and 1440×900.
- Increased text size at 135% and 200%.
- Reduce Motion, Increase Contrast, and VoiceOver.
- Empty, one-item, typical, and high-density data.
- Every platform capability combination.
- Light mode — add dark only after semantic dark tokens are approved.
Definition of done, per screen#
- No raw colors, radii, or spacing values outside an approved exception.
- All icon-only controls have accessible names and correct target sizes.
- Existing functional flow tests pass, and new visual states have screenshot coverage.
- Loading, empty, offline, error, and partial states all exist.
- User-generated text scales without clipping.
- Platform-specific actions are capability-gated.
- Design reference and implementation differ only for documented native behaviour.