C12 Native Apps — UX Assessment

Prepared for C12 Business Forums · RoleModel Software

21 September 2026 · Hotwire Native iOS 1.2.2 & dev.hotwire 1.2.4 · 849 Swift + 817 Kotlin

What was reviewed. Both shipping applications, read from source in the C12 Rails monorepo at mobile/ios/C12 and mobile/android/C12 — 15 Swift files (849 lines, hotwire-native-ios 1.2.2, iOS 16.4+, marketing version 1.1.1) and 13 Kotlin files (817 lines, dev.hotwire 1.2.4, minSdk 28, targetSdk 35). Every finding below cites a file, a key or a value that was read directly.

Correction to an earlier draft. A first pass of this assessment was written against c12_core_ios — bundle com.rolemodelsoftware.C12CORE, a 241-line turbo-ios prototype from 2021. That repository is not what C12 ships, and every code-level finding taken from it has been withdrawn. The strategic sections were unaffected and are now verified against the real source.

Executive Summary

1,666
Lines of native code
0 of 12
Modern surfaces used
5
Divergences between apps

Two shipping apps, 849 lines of Swift and 817 lines of Kotlin, both on current Hotwire Native, both with a real bridge, remote path configuration, working push that routes to the exact screen, and native document viewing. This is a well-built pair of apps, and the sections below should be read in that light.

Two things are true at once. First, the two apps have drifted apart — a different second tab, two different brand colours, two different bridge component sets, and a dark mode that one platform declares and neither delivers. Second, and more importantly, neither app has stepped outside itself. Searching both codebases for WidgetKit, ActivityKit, App Intents, Glance, PassKit, VisionKit, EventKit, CarPlay and the on-device model frameworks returns nothing at all.

So the work splits cleanly. Converge the two apps — one palette, one tab set, one appearance decision, real deep links. Then spend the native budget on the four places the phone beats a web view for this particular membership: meeting day, glanceable presence, the monthly report, and private on-device intelligence.

This is not a modernisation project. The modernisation already happened. Both apps run current Hotwire Native with a mature bridge and server-driven routing — the hard, unglamorous work is done and done well. What is missing is everything that lives outside the app window, and that gap is now measured rather than assumed: zero references to any modern platform surface across 1,666 lines of shipping native code.

Then

What these apps already get right, read from the shipping source.

Now

Where the two apps stand today, organised by what matters most.

1
Experience Gaps

These are the things a C12 member meets in normal use. None need new features — they are unfinished edges on an otherwise solid app.

Universal links and App Links are not configured. iOS declares only webcredentials:c12app.com — password autofill, not link handling — and the Android manifest has no autoVerify intent filter.
A notification tap lands in the app correctly. A link in an email, a calendar invite or a text message still opens Safari or Chrome and asks a member to sign in again.
Deep Links
The iOS tab bar uses the same hue for selected and unselected: tintColor = primaryColor and unselectedItemTintColor = primaryColor.withAlphaComponent(0.6).
Selected and unselected differ only by opacity. In daylight, or for a member who has turned contrast down, the active tab is hard to pick out.
Wayfinding
Neither app has a dark appearance. Android declares Theme.Material3.DayNight and then hard-codes windowBackground, colorSurface and colorOnSurface to white and black. iOS never reads userInterfaceStyle.
A member who runs their phone in dark mode — which is most of them at 6am and after dinner — gets a full-white screen from C12 and nothing else.
Appearance
Failure paths write to the console. print("Couldn’t register remote notifications") on iOS, Log.d on Android.
If push registration fails, the member is never told. They simply stop getting notifications and assume C12 has gone quiet.
Error Handling
Android sets launchMode="singleInstance" on the single activity.
This puts C12 in its own task stack. Back and recents can behave in ways members do not expect, especially returning from an opened document.
Navigation
The app requests com.google.android.gms.permission.AD_ID.
An advertising identifier in a confidential peer-advisory product. It forces a Play data-safety declaration that is hard to justify to a membership that shares payroll and prayer requests.
Trust
2
Where the Two Apps Disagree

The most useful thing this assessment found. iOS and Android have drifted, and each difference is small enough to fix in a sprint and awkward enough to be worth fixing.

The second tab is a different destination. iOS ships Forum → /cadre. Android ships Resources → /resource_collections.
Two members in the same forum, one on each platform, are looking at different apps. Training, support and screenshots all have to fork.
Information Architecture
Two brand colours. iOS sets UIColor(red: 0.00, green: 0.22, blue: 0.28) — #003847. Android sets color_brand to #2E3E46.
The same product is two different navy-teals depending on the phone. Neither matches the other, and Android adds #335C66 for buttons and #DEE5E7 for the tab indicator.
Brand
Different bridge component sets. iOS registers eight, including ButtonComponent, CloseButtonComponent and EnableNotificationsComponent. Android registers seven, including a NavbarComponent iOS does not have.
A Rails view that asks for native chrome gets it on one platform and not the other. The web team has to remember which is which.
Bridge
Different bundled path rules. The iOS file routes /meeting_members, /transfer, /withdraw, /assign, /edit_staffing and a native /numbers screen. The Android file has none of those.
Presentation differs per platform for the same URL. Both do load the shared server configuration, so this is drift in the fallback, not a hard break — but it is drift.
Routing
Dark mode is declared on one platform and implemented on neither. Android opts into DayNight and then overrides it to light. iOS never opts in at all.
Two different half-answers to the same question. Consolidating this is one decision, made once, applied twice.
Appearance

Tabs and brand, side by side

Read directly from C12.swift, Tabs.kt, SceneDelegate.swift and colors.xml.

iOS — tab 2
Forum → /cadre
C12.swift
Android — tab 2
Resources → /resource_collections
Tabs.kt
iOS — brand
#003847 · UIColor(0.00, 0.22, 0.28)
SceneDelegate.swift
Android — brand
#2E3E46 · color_brand
colors.xml
Android — extras
#335C66 buttons · #DEE5E7 tab indicator
colors.xml

Colour

One product currently renders in five colours across two apps, none of which is a C12 brand colour. Contrast ratios below are against WCAG AA.

What ships today — five colours for one brand
#003847 iOS brand
#2E3E46 Android brand
#335C66 Android buttons
#DEE5E7 Android tab
#FFFFFF forced surface
C12’s own brand colours, from joinc12.com
#003B49 Deep teal
#36606B Teal
#E1B680 Gold
#FDD79A Sand
#FFFFFF Paper
A shared pairing that clears WCAG AA on both platforms
Aa
Sand on deep teal — 10.4:1
Aa
Ink on gold — 10.9:1
Aa
Deep teal on paper — 13.7:1
Aa
Paper on teal — 6.5:1
3
Modernization Moments

Table stakes rather than ambitions: a current Android target, a release you can identify, tests that catch regressions, and an app that does not crash on a bad payload.

Android targets SDK 35 — Android 15. Android 16 shipped and Android 17 is current.
Behaviour changes, Material 3 Expressive and the newer notification and widget surfaces are all out of reach until the target moves.
Platform Reach
Material Components 1.12.0, no Material 3 Expressive adoption.
The Android app looks like Android 14 while the phones it runs on look like Android 17. It reads as dated next to everything else on the home screen.
Platform Reach
The checked-in iOS entitlements set aps-environment to development.
Worth confirming the release configuration flips this to production. If it does not, push silently fails for every App Store build.
Configuration
Android versionName is still "1.0" while the store lists a later build.
Crash reports, support conversations and analytics cannot be tied to a specific release.
Housekeeping
Neither app has a single test. No test target on iOS, no test or androidTest source set on Android.
A broken route, a bridge component that stops registering, or a login redirect regression reaches the store before anybody notices.
Confidence
Eleven forced unwraps sit in the iOS entry path, including window!, urlStack.last!, Bundle.main.url(…)! and six UIImage(systemName:)! calls.
Each is a crash rather than a degraded experience. A malformed urlStack from the server ends the session instead of falling back to the notifications list.
Reliability
Accessibility is inherited rather than designed. Neither app declares accessibility labels on native chrome, and neither responds to Dynamic Type or Android font scale beyond what the web view does.
C12 membership is chief executives responsible for at least five employees. Most of them have their phone text turned up. That is the median rendering, not an edge case.
Accessibility
4
Strategic Opportunities

This is the pitch, and it is now measured rather than assumed. C12 members are chief executives who gather one full day a month and carry confidential business and personal detail in this app. That combination maps almost exactly onto what iOS and Android added over the last two years — and neither app references any of it.

Grepping both shipping codebases for WidgetKit, ActivityKit, AppIntents, androidx.glance, AppWidgetProvider, PassKit, VisionKit, EventKit, CarPlay and FoundationModels returns zero matches.
Every concept in the two design boards is verified absent. This is not a guess about what might be missing — it is the measured gap between what C12 ships and what the platforms now offer.
Verified Gap
Meeting day has no surface outside the app. No Live Activity on iOS, no Live Update on Android.
C12 members gather one full day a month. It is the single highest-engagement day in the product and the phone is doing nothing with it.
Meeting Day
No home screen presence. No WidgetKit extension, no Glance widget.
Eleven months of the year a member needs two facts — when is the next forum, and what do I owe someone. Both already exist in the Rails app.
Presence
No on-device intelligence. Neither app imports Foundation Models or ML Kit GenAI.
Discussion threads carry payroll, redundancies, marriages and prayer requests. On-device summarisation is the only form of this feature C12 could responsibly ship, and it is available on both platforms today.
Private AI
The monthly report is still typed. No VisionKit, no ML Kit text recognition.
Monthly reporting is the heaviest job in the product. A CEO reads figures off a printed profit and loss and retypes them on a phone.
The Work
Nothing reaches Siri, Spotlight, Assistant, Wallet, Calendar or CarPlay. No App Intents, no PassKit, no EventKit, no CarPlay scene.
C12 only exists when a member deliberately opens it. Every one of these is a route back in that costs the member nothing.
Reach

UX Principles Assessment

Twelve principles evaluated against what the shipping code actually does.

Aligned
Ship a change once, see it everywhere
Path configuration is served from Rails on both platforms. A routing change reaches iOS, Android and the browser on a web deploy.
Aligned
A notification takes you to the thing
Push carries a urlStack and both apps route to the exact screen, already signed in.
Aligned
Documents open the way the phone opens documents
QuickLook on iOS, a themed PDF viewer on Android. Curriculum is not trapped in a web view.
Aligned
The platform’s own gestures still work
Android is edge-to-edge with predictive back enabled. It behaves like an Android app, not a port.
Needs Attention
A link about C12 should open C12
No applinks entitlement and no verified App Link filter. Emailed links hand members to a browser and a second sign-in.
Needs Attention
The product should look like itself
Two brand colours across two apps, plus two more teals on Android.
Needs Attention
Nothing should be able to crash the app
Eleven forced unwraps in the iOS entry path, including the window and the notification URL stack.
Opportunity
You should always know where you are
The iOS tab bar separates selected from unselected by opacity alone.
Opportunity
Respect the appearance the member chose
No dark mode on either platform, and Android declares one it does not honour.
Opportunity
Text should scale to the reader
Neither app responds to Dynamic Type or Android font scale beyond the web view’s own reflow.
Opportunity
Meet people where they already are
No widget, no Live Activity, no Siri or Assistant action on either platform.
Opportunity
Both platforms should be the same product
The second tab is a different destination on each. Members in one forum see two different apps.

Next

Converge first, then go where the app has never been.

Converge & Finish

3–4 weeks
Make the two apps one product.
  • One brand palette, applied on both
  • Same second tab on both platforms
  • Dark mode, decided once
  • Universal links and verified App Links
  • Android to SDK 36+ and Material 3 Expressive
  • Remove the forced unwraps and the AD_ID permission
  • A smoke test suite in CI on both
Clears every “Needs Attention” finding and every divergence.
No new member-facing features yet.

The C12 Companion

5–7 months
The full concept set.
  • Everything above
  • Live Activities and Live Updates for meeting day
  • On-device summaries — Foundation Models, Gemini Nano
  • Monthly report captured by camera
  • Curriculum as audio, with CarPlay
  • Voice notes to attributed commitments
  • App Intents, Spotlight, Watch, Controls
C12 becomes present in a member’s day rather than in their app drawer.
Largest scope; sequence after the frontier work proves out.

Component Opportunities

iOS #003847 + Android #2E3E46
→
One shared brand palette
Plus a dark variant, decided once
Forum /cadre vs Resources /resource_collections
→
One agreed second tab
Same app on both phones
webcredentials only
→
applinks + verified App Links
Emailed links land in the app
No dark appearance
→
System-driven light and dark
Android already declares DayNight
Android SDK 35 · Material 1.12
→
SDK 36+ · Material 3 Expressive
Catches up two platform releases
11 forced unwraps
→
Graceful fallbacks
A bad urlStack stops crashing
Console-only error paths
→
Member-visible states with retry
Push failures stop being silent
Zero tests
→
XCUITest / Espresso smoke suite
Routing and login regressions caught pre-review
No home-screen presence
→
WidgetKit and Glance
Next forum, todos due
No meeting-day surface
→
Live Activities and Live Updates
Agenda, presenter, time remaining
Typed monthly report
→
VisionKit and ML Kit capture
Six fields read off the printed P&L
No on-device AI
→
Foundation Models, Gemini Nano
Summaries that never leave the phone

What’s Next

C12 has two apps that already do the hard part well. The question this assessment leaves on the table is not whether to modernise — it is where to spend the next block of native effort now that the foundation is sound.

A focused first phase could be a working Home Screen widget and a meeting-day Live Activity on the existing Rails backend, on real member data, in a few weeks rather than a few quarters — something C12 leadership could hold at the next forum. The interactive prototype attached to this assessment already shows what that looks like.

A few questions worth exploring together:

  • Is the second tab meant to be Forum or Resources? One of the two apps is wrong today.
  • How sensitive is discussion content? That decides how hard we lean on the on-device AI story.
  • Should Android reach parity with iOS, or follow one release behind?
  • Does dark mode matter to this membership, or is light-only a deliberate choice?
  • Which matters more first — converging the two apps, or being present on meeting day?