HAProxy UI ships today as a macOS/Linux/Windows desktop app. The next distribution target is iPad, deliberately without a rewrite. The intended use is an iPad-as-ops-console: landscape on a desk, a drag-and-pinch flow graph, the same editing power.
Decision: stay on Tauri 2 + SvelteKit
Flutter was considered and rejected. The rewrite cost is not justified for a secondary surface, and the ops-console use case maps cleanly onto the current stack with touch and responsive work only. No backend rewrite. Estimated 3–4 weeks.
These rules are load-bearing
The constraints below exist to prevent iOS-precluding decisions. They are already how the canvas is built — this is not future work to retrofit, it is the current contract. Read this before designing any feature.
Input
Pointer Events only. No
mouse*ortouch*handlers anywhere. One code path serves finger, trackpad and Pencil.No hover-only affordances. Every state must be reachable by tap.
:hoverstyles are enhancements only.Tap targets ≥ 44×44 pt (iOS HIG).
min-h-11 min-w-11is the floor.Right-click becomes long-press. The
contextmenuevent is desktop-only; long-press handlers own context menus.
Layout
Assume landscape. Primary widths: 1366×1024 (12.9″), 1194×834 (11″), 1024×768 (10.9″). Portrait is honored but not the design target.
Sidebars and panels collapse to a drawer below 1024 px, using the
bits-uisheet rather than a custom modal.Safe-area insets on every fixed-position element, via
env(safe-area-inset-*).No pixel-fixed widths on root containers.
min-w-*/max-w-*, neverw-[1366px].
Graph
Every @xyflow/svelte instance uses this configuration, and initial node positions in src/lib/graph/layout.ts are integer multiples of the snap grid:
panOnDrag: [1, 2], // 1 = one finger, 2 = twozoomOnPinch: true,zoomOnScroll: false,panOnScroll: false,selectionOnDrag: false,selectionKeyCode: 'Meta',snapGrid: [16, 16],minZoom: 0.2,maxZoom: 2,
Scroll-zoom is off and pinch-zoom is on, which is the opposite of the desktop default and is what makes trackpad and finger behave the same way. Do not deviate without an iPad regression test.
Rust backend
No blocking I/O in commands. On iOS the main thread is the UI thread. Use
tokio::spawn_blockingor async I/O.No
std::process::Command. The iOS sandbox blocksfork/exec. If HAProxy tooling is needed, use a Rust crate or proxy through the Dataplane HTTP API.No
keyringcall without a#[cfg(target_os)]guard — already partially in place, keep enforcing.No filesystem paths outside
app_data_dir(). The iOS sandbox is strict.
Dependencies and web platform
No new native npm dependency without a Tauri 2 mobile compatibility check.
No Electron APIs. Tauri exposes
window.__TAURI__.Everything must work in WKWebView (iOS Safari ~15 baseline). No bleeding-edge CSS, no
SharedArrayBufferwithout COOP/COEP, nonavigator.sharewithout a fallback.
Phases
Phase | Work | Estimate |
|---|---|---|
0 | Toolchain — Xcode 15+, Rust, | ~1 day |
1 | iPad build runs — | ~2–3 days |
2 | Flow graph touch — apply the fixed xyflow config everywhere, 44 pt node targets, long-press menus, | ~1 week |
3 | Landscape ops polish — keyboard passthrough, responsive columns at 1366/1194/1024, external display and Stage Manager | ~3–4 days |
4 | Input flexibility — | ~2 days |
5 | Distribution — Ad Hoc | — |
Distribution notes
A free Apple ID allows sideloading only, with 7-day certificate renewal. A paid account ($99/yr) enables TestFlight and App Store distribution.
The iPad build does not use
tauri-plugin-updater— it is reinstalled via the App Store. The desktop updater pipeline does not apply.
When to reconsider Flutter
Only if one of these bites after Phase 2:
xyflow drag latency exceeds 16 ms on M1 or newer
iPad-exclusive features become blockers (Pencil hover, Stage Manager drag-out)
The team adopts Dart as a primary stack for unrelated reasons
iPhone support becomes a requirement