# iOS Feature Parity Tracker Tracks what `remote-client-ios` (source of truth) actually does vs. what `remote-client-android` has built, so gaps get found by checking this list against the iOS source, not by surprise mid-conversation. **Status:** ✅ Matched · ⚠️ Partial/Different (deliberate or otherwise) · ❌ Not built · ❓ iOS behavior not yet verified against source ## Top button row - ✅ Single horizontal row, natural (not stretched) size, iOS's chained trailing/leading order - ❌ `tabletIdField` — iOS has a live editable field; Android hardcodes `TABLET_DEST_ID = 1` - ❌ `autoSwitchButton` — auto preset-switching toggle, not built - ❌ `gridButton` — visual grid-overlay toggle, not built (no grid overlay exists at all) - ❌ `cancelChangesButton` — revert unsaved Arrange-mode edits, not built - ❌ Preset-switch confirmation alert (`isLocked==false && isNewPreset` → "Save & Switch"/"Discard & Switch") — Android just always re-renders silently ## Networking / protocol - ✅ WebSocket connect/dispatch, Bonjour discovery, `control`/`control_f` sends - ⚠️ `save_layout` — iOS **never sends this over the wire**, it's 100% local (`UserDefaults`) only. Android sends `save_layout` to the desktop on every drag/resize. Known, deliberate-at-the-time divergence, not yet reconciled. - ❌ `widget_visibility` on Hide — iOS sends `{"event":"widget_visibility","uid":...,"dest_id":0}` on hide, telling the desktop to un-route that widget (unchecks its remote checkbox there). Android's Hide is local-only, desktop has no idea. ## Widgets — regular (preset-routed) - ⚠️ `FaderWidget` — confirmed against source: iOS's regular fader has the same `modeButton` (REL/ABS) and 90dp top/bottom `padHeight` dead zone as `FocusFaderWidget`, both fit inside the same 540dp total (not additive) — ported to Android's `FaderWidgetView` (now `FrameLayout`-based, mirroring `FocusFaderWidgetView`'s structure). Android's total height is 540+180=720dp (not iOS's literal 540) to keep the active drag range unshrunk — deliberate, pre-existing divergence, not a bug. Android's regular fader also got a **FINE** button, which iOS's regular `FaderWidget` does NOT have (FINE is Focus-only on iOS) — added by direct request, an intentional Android-only addition beyond parity. - ✅ `ToggleWidget`/`TransportWidget` — 75×75dp, matched - ✅ `FeedbackWidget` — 220×100dp, matched. (Verified 2026-07-29: this is really "matched by both being unused" — `FeedbackWidget.swift` exists on iOS but is never instantiated, and Android has no regular-feedback view or model. The 220×100 constant only ever applied to the *Focus* feedback widget.) - ✅ `TitleWidget` — confirmed against source: iOS hardcodes a single `uid = "title-big"` instance too (`idx` in `defaultFrame(idx:)` is unused for title — leftover grid-placement signature shared with faders/toggles/feedbacks). Android's one-title-per-preset binding matches. ## Widgets — Channel Focus - ✅ `FocusFaderWidget` — REL/ABS + FINE buttons, dead-zone touch math - ✅ Focus Fader length rescale context-menu options ("50% Longer" / "70% Longer" / "Reset Length") — height-only, bottom-edge anchored, multiplier applied to default (not current) height. - ✅ `FocusToggleWidget` — momentary mode + Gray/White non-darkenable color quirk - ✅ `FocusFeedbackWidget` — chrome-less (transparent background, no resting card border, no caption view, 8dp padding), 32sp monospace semibold at `#EBEBEB`, single line. Box is a pure function of (font size, text) via `fittedSize`, re-fit center-anchored on every `widget_feedback`. Pinch drives **font size**, not the box; "Reset size" restores 32sp; min 12sp. Font size is not persisted — it's back-derived from the saved height on load, matching iOS exactly (see "Networking" note: no schema change). Two knowing approximations: iOS's `.semibold` (weight 600) is only expressible from API 28, so API 24–27 falls back to BOLD; and Android still flashes the activity ring green on incoming `widget_feedback` where iOS's `applyFeedbackText` doesn't flash at all. - Perf note (2026-07-29): re-fitting calls `setLayoutParams()`, which on Android `requestLayout()`s up the tree — unlike iOS's `bounds.size`, which costs siblings nothing. This was briefly suspected of causing fader stutter and reverted; the stutter turned out to persist without this code, so it was reinstated. The unproven suspect is instead the faders' own `setLayerType(LAYER_TYPE_SOFTWARE)` plus per-`onDraw` `LinearGradient` allocation. If this widget ever *does* need optimizing, the cheap fix is skipping the re-fit when the newly measured size equals the current one — monospace means most readout updates measure identical. - ⚠️ `LogWidget` — iOS is a draggable, pinch-resizable floating console with **In/Out/Local category tabs**, only rebuilding its TextView when the visible category matches what's being logged (the actual reason it doesn't have Android's perf problem). Android's log panel is a fixed bottom overlay, single combined stream, no categories, no drag/resize. ## Context menu (long-press, Arrange mode only) - ❌ Align Left/Center/Right (`TextAlignableWidget`) — not built - ❌ Text Color (`TextColorableWidget`, from the named palette matching desktop's `palette.py`) — not built, local-only styling on iOS (no wire event) - ⚠️ Hide — present on Android but wrong semantics (see "Networking" above: no `widget_visibility` send) - ✅ Reset Size — box dimensions for regular widgets, font size for `FocusFeedbackWidget` (matching iOS, which means two different things by the same menu item). - ✅ Focus Fader length rescale (see above) — `remote-client-android/.../arrange/{ArrangeCardView,ArrangeUILayout,ArrangeFunctionsAlerts}.java` ## Arrange mode / layout - ✅ Free 2D drag, pinch-resize (generic, top-left anchored) - ✅ Per-preset + Channel Focus separate layout persistence - ✅ Clear Layout (per-mode) + Reset ALL Layouts (Android-only addition, flagged as an iOS backlog item in memory) - ⚠️ Default placement: iOS uses `defaultFrame(idx:)` per-type grids at fixed screen-relative coordinates (with real overlap between types by iOS's own design). Android stacks everything at `(0,0)` — simpler, deliberately chosen over Android's earlier over-engineered fit-to-screen algorithm. --- **When picking something off this list to build:** re-read the actual iOS source file first (don't assume from this doc alone — it's a snapshot, not guaranteed current), confirm the ❓ items against source before treating them as gaps.