Case study 03 · Mobile app
Enbase Mobile
A cross-platform app for field staff who look after remote equipment. I built alarm acknowledgement and authored the design-system rebrand.
- Role
- Feature contributor → rebrand author
- When
- 2024–2026
- Where
- Detechtion.AI
- Platforms
- iOS · Android · React Native
- components & screens re-themed in light and dark from one token setPublicly released in v1.3.0, August 2026.
- ~20
- pixel difference: the evidence that settled a font-weight question
- 0%
- the invisible character behind a missing-glyph box
- U+202F
- sign-in and recovery forms given platform-correct keyboard behaviour
- 7
Overview
A publicly released iOS and Android app for field staff who look after remote compression equipment. Push notifications flag downtime, and people open the affected asset to see its latest measurements, gauges and alarms, explore interactive trend charts and get directions to the site.
I shipped self-contained features in 2024, then authored the 2026 design-system rebrand that went out publicly in v1.3.0, working under a senior mobile engineer's code review and a product designer's visual direction. I did the 2026 work by directing an AI coding agent and requiring proof at every step.
My part. In 2024: alarm acknowledgement, keyboard behaviour across seven sign-in and account-recovery forms, an iOS-safe replacement for a misbehaving dropdown and end-to-end push-notification verification on iOS. In 2026: the design-system rebrand, then chart-tooltip fixes.
Try it
Glyph Lab
A made-up field app in a generic phone frame. Switch on device-style time formatting and watch the canvas tooltip grow a missing-glyph box, then apply the fix and inspect the code points yourself.
- Canvas renderer with no font fallback
- Code-point inspector
- In-browser test runner that proves the test catches the bug
- Alarm acknowledgement flow
Engineering stories
Problem, approach, outcome.
01
The invisible character: a missing-glyph box in a GPU-drawn tooltip
- Unicode & ICU
- Canvas text rendering
- Reanimated worklets
- Mutation-style test validation
Problem
After the rebrand I noticed a small missing-glyph box between the seconds and “AM” in the trend-chart tooltip. The same timestamps looked fine as native text elsewhere in the app, and fine in unit tests.
Approach
- Traced it with an AI coding agent. The device's JavaScript engine inserts a narrow no-break space (U+202F) before AM/PM, while the ICU build in the test runner uses a plain space.
- The new brand font has no glyph for that character. Native text hides this with per-glyph font fallback. Canvas text draws with exactly the font it's given, so the character became a box.
- A sweep found three canvas call sites with the same problem. The fix went into one shared helper written with explicit escape sequences, because an invisible character typed into source had already been silently flattened during an edit.
- I insisted on proof. The tests build the device string from code points, and they fail with the helper stubbed out and pass with it in place. When the CI reviewer pointed out the helper runs inside a UI-thread worklet, the fix was marked as one, and the project's own Babel output before and after confirmed it.
Outcome
One tested, worklet-safe utility replaced three ad-hoc fixes, with three new regression tests and a green suite.
- 3 call sites → 1 helper
- +3 regression tests
- Fails without the fix, passes with it
I spotted the symptom and set the proof bar. The AI agent did most of the tracing. The automated CI reviewer raised the worklet issue.
02
Rebranding a mobile app against design references that disagreed
- Design tokens & MD3 theming
- Reconciling sources of truth
- Working with design & review
Problem
The company moved to a new brand. The app's hand-tuned Material Design 3 theme had drifted from the new design system, and the design references themselves disagreed in places.
Approach
- Diffed every MD3 colour role in both themes against the design tokens, in tables, before changing any values. Where token files and reference screenshots disagreed, pixel-sampled screenshots were treated as ground truth.
- Spotted visual problems myself, such as a dark background rendering in two slightly different near-blacks and status styling missing from the light theme. Chasing them uncovered three real bugs: the navigation container received a theme object in the wrong shape, a dropdown referenced a colour role that doesn't exist (masked by a hard-coded override) and status pills read from the wrong colour system.
- Implemented the clean-ups review asked for: separate colours for two states, one status palette instead of two and a token renamed to match domain language. Theme mocks were centralized in the test suite.
Outcome
The rebrand restyled about 20 components and screens in both themes, with the suite green throughout. It shipped publicly in v1.3.0.
- ~20 components & screens
- 3 latent theming bugs fixed
- Tests green throughout
Directed an AI coding agent through the diffing and edits; the agent's audit traced the root causes of the three bugs. Code review drove several of the clean-ups, and the designer made the visual calls.
03
A font that was never linked, and a 0% pixel diff
- iOS/Android asset pipelines
- Evidence-based design decisions
Problem
Code review noticed that the theme named the new brand typeface everywhere, but nothing in the native projects had changed. The app was quietly rendering the platform default. The reviewer also asked whether a separate display weight made any visible difference.
Approach
- Confirmed the gap in the native manifests first, then linked the fonts on both platforms and did a full native rebuild.
- Verified the new letterforms with zoomed emulator screenshots and logcat, moved the canvas-drawn chart text to the new face and removed the legacy typeface from every native file.
- For the weight question, rendered the same headline in each weight, captured both and compared them with a small pixel-diff script instead of by eye.
Outcome
0% of pixels differed (maximum channel delta 0). With that evidence the designer and reviewer approved dropping the extra weight, and its font files were removed from both platforms.
- 0% pixel difference
- 1 font weight dropped
The reviewer found the gap and asked for the A/B test. An AI coding agent ran the rebuilds, captures and pixel-diff script at my direction; I owned the fix and the evidence.
04
A tooltip crash hidden in a native binding's argument check
- Reading native library source
- JS/native boundary debugging
- Honest verification
Problem
Review reported that press-and-drag on a trend chart crashed with a null-reference error on both platforms. Separately, tooltip text was measured with one font and drawn with another, so its box could be the wrong size.
Approach
- Registered the loaded typeface with the renderer's font provider so measuring and drawing agree.
- The first crash hypothesis, an async font-loading race, was disproven on a physical device, so the investigation moved into the open-source library's native binding.
- The binding checks how many arguments it received, not whether they hold values, so an explicit “undefined” argument before the font loaded made it throw. The fix calls the one-argument form until the font is ready.
- Scripted emulator gestures showed clean logs, but synthetic touches couldn't reliably trigger the gesture, so I reported the result as inconclusive and asked for confirmation on a device instead of claiming a pass.
Outcome
Root cause identified in library source and fixed in JavaScript only, with the limits of the verification stated plainly.
Review reported the crash, and a reviewer's device test disproved the first hypothesis. An AI coding agent traced the binding and ran the emulator checks at my direction; I set the verification bar and reported the result as inconclusive.
05
Acknowledging alarms from the field
- End-to-end feature delivery
- Async UI states
- Time-zone handling
Problem
Field staff needed to acknowledge an equipment alarm from their phone, not only from the web platform.
Approach
- Built it in layers: UI first, then the request through the app's layered API client.
- A confirmation dialog, a double-submit guard, a spinner while the request runs and clear errors. Afterwards it shows who acknowledged and when, in local time.
- Found and fixed a shared date helper that applied the time-zone offset twice, which corrected timestamps on several other screens too.
Outcome
Mobile users got a direct action on alarms, and a cross-screen timestamp bug went away.
More highlights
- Replaced the launcher icons on both platforms: Android legacy, round and adaptive layers at five densities, plus an opaque iOS icon, verified with a compiled-resource dump.
- Replaced a selection dropdown that misbehaved on iOS and made the logo scale to its container.
- Verified iOS push notifications end to end, from background modes to the native project wiring.
- Documented the theming system in the project's architecture guide so the next person can re-sync with the design system.
Stack
- App
- React NativeReact NavigationReact Native Paper (MD3)ReanimatedSkiaGesture Handler
- Platform
- Push notificationsiOS & Android asset pipelinesAdaptive icons
- Quality
- JestReact Native Testing LibraryADB + logcatPixel-diff tooling
What I took away
- Canvas text has no font fallback. Anything you draw yourself has to handle the characters the platform hands you.
- A test only counts if you've watched it fail without the fix.
- Say what you couldn't verify. “Inconclusive on the emulator” is more useful to a reviewer than a false pass.
Public info
What the company itself has published about this product. It's linked, not copied.