The earlier UIKeyInput conformance was declared in a separate extension. ObjC
protocol conformance via Swift extension is fragile when the protocol
inherits another @objc protocol (UIKeyInput inherits UITextInputTraits) — the
runtime didn't always pick up insertText:, so the on-screen keyboard came
up but characters never reached controller.type(_:).
Fix: declare UIKeyInput conformance directly on FramebufferUIView's class
declaration, with insertText / deleteBackward / hasText as native members.
Also caught and fixed by the new UI test:
- The toolbar's keyboard-icon button had a 20×13 hit region (SF Symbol size)
even though the visual frame was 34×34 — XCUI taps couldn't land on it
reliably. .contentShape(Rectangle()) widens the hit area to the frame.
- accessibilityValue is reserved by iOS for UIKeyInput-classed views (treats
them as TextView), so a separate hidden "fb-diag" accessibility probe
records keyboard plumbing events for the test to verify.
Tests:
- KeyboardInputTests (5): pure mapping from String → X11 keysym down/up pairs
- ScreensUITests.testSoftwareKeyboardSendsCharactersToFramebuffer:
opens a session, taps the keyboard toggle, types "hi" via the system
keyboard, and asserts the framebuffer's diagnostic probe records
[ins:h] and [ins:i] — proving the chars reach controller.type(_:)
- A SwiftUI state probe (sessionview-state) verifies the binding flips,
which guards against future tap-routing regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The toolbar's keyboard icon used to toggle a custom function-key bar — it
never opened the iOS system keyboard, so users couldn't type into the remote.
Fix: FramebufferUIView now conforms to UIKeyInput + UITextInputTraits, so
becoming first responder presents the iOS keyboard. Tapping the keyboard
button toggles software-keyboard visibility on the framebuffer view via a
SwiftUI binding. While the keyboard is up, an inputAccessoryView toolbar
(esc / tab / ctrl / ⌘ / ⌥ / ←↓↑→ / dismiss) sits directly above it, with
each button forwarded to the existing controller.send… APIs.
The standalone SoftKeyboardBar overlay is no longer used.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RoyalVNCKit prioritizes .diffieHellman (ARD) over .vnc during handshake when
both are offered. My delegate adapter was passing an empty username to
VNCUsernamePasswordCredential, so any Mac with user-account screen sharing
enabled rejected the credential before .vnc fallback could happen.
Fix: persist a username on SavedConnection and pipe it through to the
credential callback. Leave blank to use the VNC-only password path.
AddConnection footer now explains the two Mac paths:
• User account (ARD) — macOS short name + full account password
• VNC-only password — blank username + ≤8 char password
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iOS 26's NavigationStack reserves space for its floating Liquid Glass nav bar
even with `.toolbar(.hidden, for: .navigationBar)` — that's why "Screens"
landed ~180pt down from the dynamic island with a wedge of black above it.
Removed the NavigationStack from the list entirely. Layout is now a plain
top-anchored VStack: top chrome (gear + Screens + plus + search) flush with
the safe-area top, then a ScrollView with LazyVStack of cards filling the
rest of the screen. Sessions present via fullScreenCover instead of
NavigationLink, so we don't need NavigationStack here at all.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The system nav bar floated low on iOS 26 and left a wedge of black above the
title. Replaced it with a custom top row pinned to the safe-area top: gear ⟶
big "Screens" wordmark ⟶ +. Search bar lives directly beneath, sections start
right after — no centered-in-the-void layout. Background gets a subtle blue
radial bloom so the floating glass buttons have something to anchor to.
Saved-empty state is now a glass card with an icon and a gradient CTA button.
Connection rows are full-width glass cards with rounded corners; long-press
gives Edit / Open in New Window / Delete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Edit reuses AddConnectionView with an `editing:` parameter that prefills the
form and updates in-place; password field becomes optional ("leave blank to
keep current"). Surfaced via context menu and a leading-edge swipe action on
each saved row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>