Decision Logic Before Build
A coded iOS and Android clinical prototype that runs the actual decision logic, so logic errors surface before native build instead of in UAT.
- Clinical informatics
- Mobile R&D
- Cross-platform verification
- State machine design
Why the logic was built before the UI
01 · ConstraintA clinical mobile product can ship a logic error (an unflagged critical lab value, a wrong dose interval after an oral-to-IV switch) that no wireframe will reveal, because it only shows up when a care coordinator taps the wrong control and the state machine goes silent.
The constraint was Discovery-stage: these failures had to be caught before native build, before backend, before a device-management policy existed. So I built a coded iOS and Android prototype that runs the actual decision logic, not a mockup of it.
- Role
- Principal Product Designer and prototype engineer. Designed and coded both platform variants from scratch: the interaction model, the clinical data structure, the state machine, and the accessibility scaffold.
- Conditions
- No native app team, no build pipeline, no backend.
- Instrument
- Coded HTML as an R&D instrument. Each platform rendered at production constraints inside a 290px iframe. Business logic validated through live interaction (full workflow traversal, form submission, state transitions), not review of static screens.
A wireframe review asks whether the screen looks right. A coded prototype asks whether the workflow behaves correctly at every branching point.
The gap between a wireframe review and a functional prototype is the gap between seeing a screen and understanding a workflow. A wireframe can show that a medication reminder screen exists. It cannot show that the reminder fires at the right interval after dose entry. It cannot show that the abnormality flag on a lab result correctly propagates to the care team alert. It cannot show that navigating from the symptom diary to the medication screen and back preserves the user’s scroll position and clears the right form state.
These are logic problems. In clinical informatics, where regulatory requirements, care team trust, and patient safety converge on the same interface, logic problems that surface in UAT cost sprint cycles, compliance reviews, and organizational credibility. A logic error discovered in UAT becomes a compliance incident waiting to happen.
I weighed 3 instruments: a Figma clickthrough, a platform-split native spike, and a single-file coded prototype. I chose the coded prototype because only it executes the shared state machine under both Apple HIG and Material Design 3 at once, which is where the divergence lives.
4 tests, one artifact
Static design tools test one thing at a time. A Figma prototype tests visual design and click-through navigation. An accessibility audit tests markup in isolation. Platform usability testing requires platform-specific builds. Parity review requires running both builds at once. That is a sequential, expensive process in which each test generates findings that could have been integrated upstream. A coded HTML prototype collapses the sequence and tests 4 things in the same artifact.
Business logic
Does the state machine handle every branching case correctly: dose entry, abnormal flag, reminder conflict, empty state?
Accessibility structure
Are heading hierarchy, landmark roles, ARIA labels, and focus management correct at production font sizes and viewport constraints?
Platform usability
Do iOS and Android patterns (sheet modals vs. bottom drawers, segmented controls vs. chip filters, tab bars vs. navigation drawers) match platform expectations under clinical workflow pressure?
Cross-platform parity
Does the same clinical information arrive with equivalent meaning and equivalent next action on both platforms, or does platform-specific treatment create a divergence that becomes a care gap?
One state machine, 2 surfaces
02 · The artifactBoth variants of the SignalCare CDX prototype share an identical state machine. The clinical data model (medications, appointments, lab results, symptom diary entries, reminders, care messages) is the same object graph under both surfaces. Adding a reminder on iOS adds it to the same shared database that the Android variant reads. This is not a coincidence of the prototype design; it is the architecture the native app needs to replicate.
Both platform variants (iOS HIG-compliant, Android Material Design 3-compliant) run in the same browser tab, driven by that one data model. A clinical stakeholder can trigger the same action on both phones at once and watch whether the workflows stay equivalent. An engineer can inspect the semantic markup of both variants in a single review. A project manager can walk a regulatory reviewer through the entire clinical scope (14 routes, 8 sheet variants, full form validation) without scheduling 2 separate platform demos.
The prototype is not a preview of the native app. It is evidence that the clinical logic is sound enough to build a native app from.
Both variants in one tab, on one state machine. Open the prototype and trigger the same action on each surface to see whether the workflows stay equivalent.
Full app chrome on both platforms at once: iOS tab bar on the left, Android bottom navigation plus floating action button on the right, sharing the same unread counts from one state machine.
function renderMain(route) {
const content = {
calendar: renderCalendar(),
"appointment-detail": renderAppointmentDetail(route),
medication: renderMedication(),
"medication-detail": renderMedicationDetail(route),
diary: renderDiary(),
labs: renderLabs(),
"lab-detail": renderLabDetail(route),
messages: renderMessages(),
reminders: renderReminders(),
settings: renderSettings()
}[route.name];
...
}
The actual renderMain(route) dispatch object from the running prototype,
the map every native route had to preserve on handoff.
Defect 1: scroll state lost on return
03 · Defect 1 of 2The prototype surfaced 2 pre-build defects that static wireframes had not. The first was scroll state loss during deep-link navigation in the medication detail flow.
It lived in the route transition handler. The state machine was restoring the route but not the scroll
position of the previous view, so a user returning from a medication detail landed at the top of the
list instead of their prior position. The fix added a scrollRestoration step to the
route-onExit hook.
- User scrolls medication list to item 7.
- Taps item 7 to open medication detail.
- Returns to list; viewport resets to top.
State machine node: route.onExit, missing scroll restore.
- User scrolls medication list to item 7.
- Taps item 7 to open medication detail.
- Returns to list; viewport returns to item 7.
State machine node: route.onExit, persists and restores the
scroll offset.
A wireframe can show that the medication list and the medication detail both exist. It cannot show that navigating between them and back preserves the user’s scroll position and clears the right form state. The defect was only observable once the route transition ran.
Defect 2: flag ranking diverged across platforms
04 · Defect 2 of 2The second defect was flag ranking: an inconsistency in how abnormal lab flags were visually ranked between iOS and Android. The same data object produced different apparent priority, because iOS used color alone while Android used color plus an elevation badge.
The fix aligned both surfaces to the same visual-ranking grammar: color, icon shape, and label text, with elevation reserved for unread count only.
Each platform’s wireframes were internally consistent. The divergence only appeared when the same abnormal lab result was rendered by both surfaces from one data object at the same time, which is what the comparison view makes possible. A wireframe review cannot show that a flag marked critical propagates to the care team alert with equivalent priority on both platforms.
Both defects were identified, diagnosed, and fixed in the prototype. Neither lived in layout. They lived in the state machine and in the rendering rules it drives, which is exactly where a wireframe cannot look.
The parity question
05 · iOS and AndroidParity is not sameness. Each platform keeps its own conventions, so the question the prototype has to answer is whether the same clinical information arrives with equivalent meaning and equivalent next action on both platforms, or whether platform-specific treatment creates a divergence that becomes a care gap.
- iOS variant
- Apple HIG conventions throughout: system typeface stack, sheet modal pattern for contextual actions, segmented controls for view switching, status bar awareness with safe area insets, and iOS-native color semantics (system red for critical alerts, system orange for caution states, system green for confirmations). Every interaction pattern is one an iOS user recognizes from the system apps they use daily.
- Android variant
- Material Design 3 conventions: Manrope for display and IBM Plex Sans for body, bottom navigation with icon-plus-label targets, chip-based filtering for multi-select states, elevation hierarchy through surface and container tokens, and dynamic color roles. The typographic scale and spacing are tuned to Android’s denser information density norms.
- Shared
- Full app chrome on both platforms at once: iOS tab bar on the left, Android bottom navigation plus FAB on the right, sharing the same unread counts from one state machine.
The comparison view is designed as a clinical stakeholder instrument. A Run Script control triggers a scripted walkthrough, the same sequence executed on both phones simultaneously, so a product owner, clinical lead, or regulatory reviewer can observe platform parity directly, without managing 2 separate demos or switching between screenshots.
What the native team inherited
06 · HandoffThe prototype was handed to the native iOS and Android teams as a behavior contract. The state machine, route table, and assertions below capture the decisions that had already been validated before build began.
Behavior contract: validated routes and state transitions
- On launch, both variants render the same patient dashboard from the shared data model.
- Adding a medication reminder on iOS updates the shared state and reflects on Android within the same session.
- Navigating from the symptom diary to a medication detail and back preserves scroll position and clears transient form state.
- An abnormal lab result flagged as critical propagates to the care team alert on both platforms with equivalent priority.
- Empty states display for medications, appointments, and diary when no data exists, without breaking navigation.
- Form validation prevents submission with missing required fields and returns focus to the first invalid input.
- Sheet modal (iOS) and bottom drawer (Android) open from the same trigger, manage focus, and restore focus on close.
- Dose switch from oral to IV updates the interval calculation through the deterministic ruleset.
- Route count = 14; sheet variants = 8.
- Focus trap passes on sheet open and close.
- Cross-viewport layout stable at 290px effective width.
- Pill element single-line integrity across all routes.
- 2 single-file HTML prototypes (iOS and Android) combined in one comparison showcase; no build tools, no framework, no CDN dependencies.
- Vanilla JavaScript state machine with a shared clinical data model: medications, appointments, labs, diary, reminders, messages, reports, settings.
- 14 named routes per platform, covering the complete clinical scope of a specialty-care patient-facing application.
- 8 bottom sheet variants: appointment actions, reminder editor, add event, appointment reminder, contact details, and confirmation states.
- Full form validation with accessible error states, inline feedback, and submission confirmation flows.
- Semantic markup throughout: heading hierarchy, landmark roles (main, nav, region), aria-label on all interactive elements, focus management across sheet open and close cycles.
- Tested at 290px effective viewport, the production constraint imposed by the dual-phone comparison view at a standard 1440px display.
- CSS custom properties with @layer specificity management; no !important in prototype code.
The native teams inherited a behavior contract, with the route table and parity rules already fixed, rather than a brief to re-derive interactions during sprint planning. No post-build metric exists. The verified result is that 2 divergence defects were caught before any production code was written.
Parity is a behavior property
07 · TransferParity turned out to be a behavior property, not a visual one. Both defects lived in the state machine, in scroll restoration and in flag ranking, which is why a coded prototype found them and a wireframe review would not have.
Trade-offs made on the way
- Coded vs. Figma
- Traded faster visual iteration in Figma for a running state machine that exposes logic errors before native build.
- Single-file vs. split
- Traded cleaner separation of iOS and Android code for one shared state machine validated under HIG and Material Design 3 at once.
- Native vs. generic UI
- Traded a single reusable component set for iOS and Android patterns that survive clinical workflow pressure.
Clinical logic to fintech risk logic
| Clinical logic | Fintech equivalent |
|---|---|
| Abnormal lab flag propagation | Fraud and risk alert escalation |
| Medication reminder conflict resolution | Pre-trade compliance check conflicts |
| Empty state handling for missing labs | Missing data handling in credit models |
The domain changes the vocabulary, not the instrument. Across 15 years of decision interfaces in capital markets, climate risk, and clinical informatics, the same handover holds: teams build from working logic, not static specs.
- Discovery engagement, 2025. R&D prototype, no production deployment.
- Verified result is the 2 pre-build defects; no post-build metric exists.