i99dash docs
Guides

On-device testing

How to publish your mini-app and exercise it against a real i99dash host on a head unit.

i99dash dev covers ~80% of the dev loop with a fixture-backed WebView. The remaining 20% — verifying behaviour against the real host's catalog flow and platform constraints — only happens against an actual head unit. This guide walks the publish-and-test loop end-to-end.

If something breaks, jump to Troubleshooting → On-device testing — each pitfall in this guide links to its symptom→cause→fix entry.

What you need

  • A car running an i99dash host build, or a head unit you can adb into.
  • The head unit's IP on Wi-Fi (Settings → Developer options → Wireless debugging shows it as IP:port, typically port 5555).
  • i99dash CLI signed in (i99dash whoami shows your developer account).
  • The mini-app you want to test, with a working pnpm bundle step.

Step 1 — Connect ADB

adb connect 192.168.4.72:5555    # use the IP shown by Wireless debugging
adb devices                      # should print `<ip>:5555  device`

If the connection drops mid-session (head-unit Wi-Fi suspends aggressively), adb kill-server && adb connect <ip>:5555 brings it back.

Step 2 — Publish your mini-app

# Bump version + bundle
jq '.version = "0.3.7"' manifest.json > manifest.json.tmp \
  && mv manifest.json.tmp manifest.json
pnpm bundle

# Validate, build, upload, register
i99dash publish

The catalog auto-syncs on the head unit on the next mini-app launch. Do not edit the bundle directly on the device — see Catalog auto-pull on launch.

Step 3 — Install on the host

On the head unit's screen:

  1. Open Apps (grid icon) → Store tab.
  2. Tap Install on your mini-app.
  3. Switch to My Apps and tap your mini-app to launch.

To verify the bundle landed (debug-buildable hosts only — release builds lock down run-as):

adb shell run-as <host-package-id> \
    'find files/mini_apps -type f' | grep <your-app-id>
adb shell run-as <host-package-id> \
    'cat files/mini_apps/<your-app-id>/<version>/manifest.json'

Step 4 — Watch what the bridge actually returns

The on-device WebView console isn't piped to logcat. To see the host response shape during triage, surface the SDK's cause.issues (the ZodError it wraps) directly in your tester's UI:

} catch (e) {
  setBadge(card.badge, 'err', e?.code ?? 'error');
  // ZodError issues — exact field-by-field schema mismatches
  const issues = e?.cause?.issues
    ? '\n\nZod issues:\n' + JSON.stringify(e.cause.issues, null, 2)
    : (e?.cause ? '\n\nCause:\n' + String(e.cause) : '');
  showResult(card.result, (e?.stack ?? String(e)) + issues);
}

Two patterns to recognise:

Issue shapeMost likely cause
Every field received: "undefined"Host returned an error envelope, not a snapshot
One field received: "string" expected "number"Host emits the wrong type for that field
Required on a field that should be optionalSchema drift between the SDK you bundled and the SDK the host expects

Step 5 — Iterate

Catalog round-trip on every change is slow. To shorten the cycle:

  • For JS-only changes (UI, error messages, controller usage): bump manifest.json, pnpm bundle, i99dash publish — ~10s end-to-end.
  • For schema changes (you edited a Zod schema in the SDK source): repack the SDK locally (pnpm pack) and reinstall in your mini-app before re-publishing — see Stale strict schemas.
  • For pure HTML/CSS tweaks during a single session: stay in i99dash dev against the fixture bridge; only round-trip to the device when behaviour depends on the real host (permissions, live data, gating).

What the host gates

The host enforces a single gate on every bridge call:

GateWhat it checksFailure shape
Network egressExternal fetch() targets an origin declared in manifest.networkThe browser/CSP blocks the request; fetch() rejects with a TypeError

Mini-apps run with full host capability — there is no manifest-side permission system. If a family snapshot fails, it's because the host's bridge handler is unimplemented for that car (catch <Family>UnavailableError) or the data isn't currently available (catch the family's typed unavailable error).

On this page