Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Cars24 SDUI — React Native TypeScript

Server-Driven UI framework for Cars24. The server sends JSON; the client renders the page. Change the JSON → the page changes on every user's device, no app release.


Screen chosen: Cars24 Home / Landing Page

Why: The home screen is the most component-dense page in the app. It has 8 visually distinct section types, two horizontal rails, two vertical grids, interactive chip tabs, tappable cards with navigation intents, and a bottom-sheet trigger — all of which stress-test every architectural concern simultaneously. A trivially simple screen would hide the gaps in the system.


Setup

# Install dependencies
npm install

# iOS (requires Xcode + CocoaPods)
cd ios && pod install && cd ..
npx react-native run-ios

# Android
npx react-native run-android

The app opens the SDUI-driven home screen by default. To see the static baseline screen, change initialRouteName in RootNavigator.tsx to "StaticHome".


Architecture Overview

src/
├── sdui/
│   ├── engine/
│   │   ├── ComponentRegistry.ts   # Map<type, entry> — never returns null
│   │   ├── ActionDispatcher.ts    # Map<type, handler> + ActionContext injection
│   │   ├── SchemaValidator.ts     # Zod envelope validation
│   │   └── SDUIRenderer.tsx       # Recursive memo renderer
│   ├── components/
│   │   ├── layouts/               # HorizontalRail, TwoColumnGrid, SectionHeader, SectionContainer
│   │   ├── sections/              # AppHeader, SearchBar, CategoryChips, ManageVehicleSection, PromoBanner
│   │   ├── cards/                 # BuyCarCard, SellCarCard, LoanCard, ServiceCard, UsedCarCard, ManageVehicleCard
│   │   └── UnknownComponent.tsx   # Graceful fallback (dev=visible, prod=null)
│   ├── actions/                   # NavigateAction, BottomSheetAction, ChipSelectAction, OpenUrlAction, ToastAction
│   ├── hooks/                     # useSDUIPayload, useActionDispatch
│   ├── store/                     # sdui.store.ts (Zustand)
│   ├── theme/                     # tokens.ts (Colors, Spacing, Radius, FontSize)
│   ├── types/                     # SDUISchema.ts, Actions.ts, ComponentProps.ts
│   └── bootstrap.ts               # Registers all components + actions (called once at app start)
├── screens/
│   ├── SDUIScreen.tsx             # SDUI-driven screen with TTR/TTI logging
│   └── StaticHomeScreen.tsx       # Hardcoded baseline for benchmarking
├── navigation/
│   └── RootNavigator.tsx          # React Navigation + ActionContext injection
└── data/
    └── home_screen.json           # Mock server payload

JSON Schema Design Rationale

Every SDUIComponent has this shape:

{
  "id": "stable_unique_id",
  "type": "registered_component_type",
  "props": { "...component-specific data..." },
  "children": [ "...nested SDUIComponents..." ],
  "action": { "type": "navigate", "payload": { "screen": "CarDetail" } },
  "visibility": { "condition": "logged_in" },
  "minClientVersion": "1.0",
  "maxClientVersion": "2.0"
}

Key decisions:

  • props is Record<string, unknown> at the schema level — each component's Zod schema validates and narrows at the boundary. The renderer stays generic.
  • children enables arbitrary nesting. Layout components (horizontal_rail, two_column_grid) consume _childComponents injected by the renderer.
  • action is a discriminated union with type + payload. New interaction types are new action variants, never new props on components.
  • visibility is evaluated client-side so the server can send conditional content without multiple endpoints.
  • minClientVersion/maxClientVersion enable gradual rollouts — old clients skip unknown components gracefully.

Versioning Strategy

Three-layer approach:

  1. schemaVersion on every payload (e.g. "1.0"). The client validates this first. Unsupported versions log a warning but don't fail — unknown versions may still render fine with additive changes.

  2. minClientVersion / maxClientVersion on individual components. The renderer skips (does not crash) components outside the client's version range. This enables gradual component rollouts:

    • Add a new component type on the server with minClientVersion: "2.0"
    • Old clients (v1.x) skip it and render nothing
    • New clients (v2.x) render it
  3. Additive-only schema changes as the server contract:

    • Adding fields: old clients ignore them (Zod strips unknown fields by default)
    • Removing fields: new clients default them (Zod .optional().default(...))
    • Changing field types: requires a new type string, not a mutation of an existing one

Unknown component fallback: The ComponentRegistry.resolve() function NEVER returns null. Unknown types render UnknownComponent (invisible in production, visible dev-only box in development). This is the crash-safety guarantee.


Component Registry

16 registered types at launch:

Type Category
horizontal_rail Layout
two_column_grid Layout
section_header Layout
section_container Layout
spacer Primitive
app_header Section
search_bar Section
category_chips Section (interactive)
manage_vehicle_section Section
promo_banner Section
buy_car_card Card
sell_car_card Card
loan_card Card
service_card Card
used_car_card Card
manage_vehicle_card Card

Action System

5 registered action handlers:

Type Effect
navigate Pushes a screen onto the navigation stack
chip_select Updates selected chip in Zustand store
open_bottom_sheet Opens a registered bottom sheet (Alert in demo)
open_url Opens URL in system browser or in-app WebView
toast Shows an in-app toast message

Trade-offs

Decision Upside Cost
Zod at envelope + component boundary Runtime safety, typed components ~15KB bundle, small parse cost
Zustand over Context Zero unnecessary re-renders One more dependency
Recursive renderer with memo Handles arbitrary nesting Stack depth limit needed for pathological payloads
Additive-only schema Old clients never break Schema debt accumulates; needs periodic cleanup
FlashList over FlatList Significantly better scroll perf ~20KB extra
ScrollView for main page scroll Simpler, avoids nested FlatList issues Not windowed — full page renders upfront

What I'd do in production but cut for timebox:

  • Replace Alert-based bottom sheet with @gorhom/bottom-sheet
  • Wire chip selection to filter visible sections via visibility rules
  • Add error boundary around SDUIPageRenderer
  • Add react-native-reanimated for smooth chip selection transitions
  • Add schema migration layer for breaking changes

Submission deliverables

File Description
README.md Setup, architecture, schema design, versioning, trade-offs
PERF.md Static vs SDUI benchmark (Android emulator, debug build)
COVERAGE.md Component registry, coverage claim, extension strategy
AI_WORKFLOW.md AI tool stack, prompt stories, failure case, verification
src/data/home_screen.json Mock server payload driving the home screen
Screen recording 3–5 min demo (JSON render, interactions, unknown fallback, live JSON edit)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages