Also: Documentation home · DSL · Components · CLI · Polyglot plugins
Crepuscularity can target native mobile platforms through View IR (Intermediate Representation), enabling code sharing between iOS (SwiftUI) and Android (Jetpack Compose) applications.
Instead of compiling directly to native UI frameworks, Crepuscularity generates a JSON-based intermediate representation that native shells can interpret. This approach allows:
- Code sharing between platforms
- Runtime flexibility - update UI without app store releases
- Consistent styling across platforms
- Platform-specific optimizations
.crepus Template → Rust Parser → View IR (JSON) → Native Shell
↓
SwiftUI / Jetpack Compose
The crepuscularity-native crate converts templates to a structured JSON format that native applications can consume.
For non-Rust hosts, the stable boundary is the same View IR JSON emitted by crepus native ir. See Polyglot plugins.
Prefer the unified mobile workflow for new iOS + Android apps:
crepus mobile new my-mobile-app
cd my-mobile-app
crepus mobile dev --platform all
crepus mobile build --platform ios
crepus mobile run --platform ios
crepus mobile build --platform android# Scaffold an iOS XcodeGen app + Android Gradle module sharing a fixture.json
crepus native new my-mobile-app
cd my-mobile-app
# iOS — build, install, and launch the simulator app
crepus mobile build --platform ios --dir .
crepus mobile run --platform ios --dir .
# or: crepus native build ios --dir .
# Android — generate the Gradle wrapper (one-time), then build
cd android && gradle wrapper --gradle-version 8.10
./gradlew :app:assembleDebug
# or, from the project root:
# crepus native build android --dir <root>crepus native run android --dir <root> runs gradle :app:installDebug on a
connected device/emulator and prints the adb shell am start line you need to
launch the app.
The default shell contains View IR, host glue, and the app-local Rust bridge only: no vendored Bluetooth Java or Bluetooth permission. Add platform capabilities per app:
crepus native add sensors --dir .
crepus native add bluetooth --dir .
crepus native add image-picker --dir .
crepus native add photo-library --dir .Sensors installs real gyro/accelerometer bridges. Bluetooth installs BLE central scan bridges on
Android and iOS, plus only the required platform declarations. It supports status,
requestPermission, scan, and stopScan; discoveries arrive as bluetooth.device results.
| Capability | Methods | Boundary |
|---|---|---|
sensors |
status, latest, start, stop |
Android accelerometer/gyro; iOS Core Motion |
bluetooth |
status, requestPermission, scan, stopScan |
Android and iOS central scanning; discoveries are streamed as results |
geolocation |
status, requestPermission, getCurrentPosition, startWatch, stopWatch |
Android GPS/network watch and iOS CLLocationManager updates; locations stream as geolocation.update |
battery |
status, startWatch, stopWatch |
Android battery broadcast and iOS battery notifications; watch changes stream as battery.change |
appearance |
status, startWatch, stopWatch |
Current light/dark scheme on Android and iOS; watch changes stream as appearance.change |
haptics |
impact, selection, notification |
Android vibrator; iOS UIKit feedback generators |
clipboard |
get, set, clear, startWatch, stopWatch |
Android clipboard manager and iOS system pasteboard; watch changes stream as clipboard.change |
toast |
show |
Android system toast or a transient UIKit overlay; payload requires text and accepts duration: "short" or "long" |
privacy-screen (privacyscreen) |
status, enable, disable |
Blocks Android screenshots/recording or obscures the app while iOS is inactive |
browser (linking, app-launcher, phone, sms) |
open |
Android ACTION_VIEW; iOS UIApplication.open, including custom app, tel:, and sms: schemes |
share |
share |
Android chooser; iOS activity sheet; accepts text, url, and optional title |
image-picker (imagepicker, media-picker) |
pick |
Android system picker; iOS PHPicker; results stream as imagePicker.pick. The named pick_media action is installed with this capability. |
documentPicker (documents) |
pick |
Android/iOS multiple-document picker; emits imported cached-file metadata |
photo-library (photolibrary, media-library) |
scan, getRecentMedia |
Android MediaStore and iOS PhotoKit, each requesting media access only when called; emits one cached-file result per asset |
camera |
takePhoto |
Android system camera preview and iOS camera picker; emits one cached JPEG result |
video |
capture |
Android system video camera or iOS camera movie picker; emits cached video metadata |
dimensions |
get, getWindow |
Current Android window metrics or iOS screen bounds, expressed in display points |
dialog |
show |
Android alert dialog and iOS alert controller; accepts optional title, message, and button, then streams ok or cancel |
action-sheet (actionsheet) |
show |
Android choices dialog and iOS action sheet; accepts title and options, then streams the selected label and index |
app-state (appstate) |
get, startWatch, stopWatch |
Current Android lifecycle or iOS application foreground/background state; watch changes stream as appState.change |
app (app-info, appinfo) |
getInfo |
App name, identifier, version, and build metadata from Android package or iOS bundle |
screen-orientation (screenorientation) |
get, lock, unlock |
Current orientation plus portrait/landscape lock or unlock through Android activity policy and iOS scene geometry |
accessibility-info (accessibilityinfo, screen-reader) |
get, status, startWatch, stopWatch |
Reduce-motion and screen-reader state from Android accessibility settings or iOS UIAccessibility; watch changes stream as accessibilityInfo.change |
device (device-info, deviceinfo, platform) |
get, info |
Platform, manufacturer, model, and operating-system version from Android Build or iOS UIDevice |
preferences (storage, async-storage) |
get, set, remove, clear |
String values in Android SharedPreferences or iOS UserDefaults; key is required except for clear |
network (net-info, netinfo) |
status, startWatch, stopWatch |
Validated Android network or iOS NWPathMonitor status, including active transport; watch changes stream as network.change |
keyboard |
dismiss |
Hides Android's active input method or resigns iOS's current first responder |
settings (app-settings) |
open |
Opens the app's Android or iOS settings page |
local-notifications (notifications) |
status, requestPermission, post, schedule, cancel, list |
Android AlarmManager receiver and iOS UserNotifications; schedule with id plus future Unix-millisecond at or seconds; iOS list reports asynchronously |
secure-storage |
get, set, remove, clear |
Android Keystore-encrypted values and iOS Keychain values |
biometrics (authentication) |
status, authenticate |
AndroidX BiometricPrompt or iOS LocalAuthentication, streaming the authentication result |
permissions (permission) |
status, check, request |
Checks or requests camera, microphone, location, photoLibrary/photos, contacts, notifications, and bluetooth; notification checks report current iOS OS authorization asynchronously |
microphone (audio) |
status, requestPermission |
Android RECORD_AUDIO or iOS AVAudioSession recording authorization |
contacts |
status, requestPermission, list |
Android ContactsContract or iOS CNContactStore; access is declared only when installed |
calendar (calendars) |
status, check, request, list, create |
Android CalendarContract and iOS EventKit; create accepts title, optional Unix-millisecond start, end, calendarId, and notes |
in-app-browser (inappbrowser, web-browser) |
open |
Android Custom Tabs or iOS Safari View Controller |
system-bars (systembars, status-bar) |
status, set |
Android status/navigation colors and light-icon flags; iOS supported window appearance style |
deep-links (deeplinks, deep-link, url-events) |
status, getInitialUrl, open |
Receives crepus:// URLs as deepLinks.openUrl events and opens valid URLs through the platform |
filesystem enables its scoped Rust backend only after crepus native add filesystem; the
default app shell and share-extension builds exclude it. It supports readText, writeText,
delete, mkdir, list, and stat, and rejects absolute paths and parent-directory segments.
Capabilities installed by crepus native add stay local to the app and add only their required
platform declarations. Push delivery needs an APNs/FCM project and server credentials; payments,
wallets, Google Maps, cloud sync, and authentication need the app's own provider configuration.
Health, NFC, background execution, widgets, and media services also require
their platform-specific entitlement or user permission before an app can use them. They are not
included in the default scaffold or represented as successful no-op bridges.
The scaffold includes views/main.crepus as the Crepus-authored UI source.
When the template changes, sync the shared View IR fixture into the native
containers:
crepus native sync views/main.crepus --dir . --out desktop/share/dashboard.view-ir.json --var name=Ada --prettyFor iOS-only apps, use the iOS-specific scaffolder:
wax install xcodegen
crepus ios new my-ios-app
cd my-ios-app
crepus ios generate
open *.xcodeprojcrepus native new lays out a minimal Compose app under <dir>/android/.
For a deeper reference, see examples/native-shells/android/.
Convert .crepus templates to View IR JSON:
crepus native ir views/main.crepus --ctx context.json --pretty
crepus native sync views/main.crepus --dir my-mobile-app --out desktop/share/dashboard.view-ir.json --var name=Ada --pretty
crepus native codegen views/main.crepus --platform swiftui --out Generated --view-name DashboardView --var name=Ada
crepus native codegen views/main.crepus --platform compose --out android/app/src/main/java/dev/example/generated --view-name DashboardView --var name=Adause crepuscularity_core::TemplateContext;
use crepuscularity_native::{render_template_to_ir, to_json_pretty};
let mut ctx = TemplateContext::new();
ctx.set("name", "Ada");
let ir = render_template_to_ir(
r#"div flex flex-col gap-4
span
"Hello {name}""#,
&ctx,
)?;
let json = to_json_pretty(&ir)?;
println!("{}", json);This produces JSON that native shells can parse:
{
"version": 4,
"root": [
{
"kind": "stack",
"axis": "column",
"spacing": 16.0,
"style": {
"flexDirection": "column"
},
"children": [
{
"kind": "text",
"content": "Hello Ada"
}
]
}
]
}Add the local Swift package to your Xcode project:
- In Xcode: File → Add Package Dependencies
- Select Add Local... and choose the
ios/directory - Import
NativeShellin your Swift code
import NativeShell
import SwiftUI
struct ContentView: View {
var body: some View {
// Use the fixture
FixtureRootView()
// Or load custom IR
if let ir = loadViewIR() {
ViewIrRootView(ir: ir)
}
}
}The crepus ios commands create a complete Xcode project:
crepus ios new MyApp
cd MyApp
crepus ios generate # Creates .xcodeproj
crepus ios build # Builds for simulatorcrepus native new <name> lays out a Compose app under <name>/android/ —
the same source as examples/native-shells/android/:
// MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Load and render View IR
val ir = loadViewIR(assets)
ViewIrRootView(ir)
}
}
}- Flexbox:
flex,flex-row,flex-col,justify-center,items-center - Spacing:
gap-4,p-4,m-4 - Scroll containers:
scroll,scroll-x,scroll-y
- Colors:
bg-blue-500,text-white,border-gray-300 - Typography:
text-lg,font-bold,leading-relaxed - Borders:
rounded,border,border-2 - Shadows:
shadow,shadow-lg
- Basic elements:
div,span,button,img - Interactive:
button @clickemits an action string for the native shell to handle
- Slot rotate: Emits phrase data; the example shells currently display the first phrase
- Custom components: Via
include - Control flow:
if,for, andmatch - Slots: Component slots with caller context
The example SwiftUI and Jetpack Compose shells are intentionally small interpreters for the IR. They decode and render the core nodes, while app-specific image loading, navigation, action routing, accessibility, and animation behavior belong in the host shell.
For production mobile shells that should compile native source instead of interpreting JSON at runtime, generate SwiftUI or Jetpack Compose from the same View IR:
crepus native codegen views/main.crepus --platform swiftui --out Generated --view-name DashboardView --var name=Ada
crepus native codegen views/main.crepus --platform compose --out android/app/src/main/java/dev/example/generated --view-name DashboardView --var name=AdaThe generator keeps View IR as the stable compiler boundary, then writes DashboardView.swift or DashboardView.kt. The generated source covers current IR nodes with native SwiftUI/Compose controls and conservative textual fallbacks for host-specific surfaces like image loading and drop handling.
Since View IR is JSON, you can update UI at runtime:
// iOS
func updateUI(with newIR: ViewIR) {
// Parse new IR and update view hierarchy
viewIrRootView.updateIR(newIR)
}// Android
fun updateUI(newIR: ViewIR) {
// Recompose with new IR
setContent {
ViewIrRootView(newIR)
}
}- Design templates in
.crepusfiles - Generate IR using
crepuscularity-native - Test in native shells using the examples
- Integrate into your app's native code
For development, you can set up file watching:
# Watch templates and regenerate IR
cargo watch -x 'run --bin generate-ir'While the core layout is shared, each platform can customize:
- Custom fonts and SF Symbols
- iOS-specific animations and transitions
- Accessibility features
- Dark mode integration
- Material Design 3 components
- Platform animations
- System UI integration
- Dynamic color themes
- IR parsing happens at runtime - keep templates reasonably sized
- Includes are expanded before lowering to IR; file-backed includes reject absolute paths and
..traversal - Image loading should be optimized per platform
- Animations should be implemented in the host shell until animation nodes are added to the IR
- Memory usage scales with template complexity
See examples/native-shells/ for complete working examples (the same source
that crepus native new scaffolds):
- iOS: SwiftUI app with Swift Package integration
- Android: Jetpack Compose app with Gradle setup
- Shared fixture: Common View IR for testing
crepus native new <name> writes that tree (minus the binary Gradle wrapper
jar — generate it with gradle wrapper --gradle-version 8.10 or open the
project in Android Studio).
To add new features:
- Update the Rust parser in
crepuscularity-native - Extend the JSON schema (run
export-view-ir-schema) - Update native shells to handle new IR nodes
- Test across platforms
The IR format is designed to be extensible while maintaining compatibility.