Drives the test through app.keyboards.firstMatch.keys[…].tap() when the
on-screen keyboard is up, which mirrors actual user input through the
UIKey pipeline. Falls back to app.typeText only when the simulator
suppresses the soft keyboard via Connect Hardware Keyboard.
Disable Connect Hardware Keyboard in the host's iphonesimulator defaults
so the soft keyboard stays visible during the test. With both verified,
this commit guarantees that pressing 'h' on the iOS keyboard reaches
FramebufferUIView.insertText("h").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Verifies:
- Title lives in the top 25% of the screen (guards against letterboxing regressions)
- Plus button opens Add Connection sheet; Cancel dismisses
- Settings gear opens Settings sheet; Done dismisses
- Search field accepts input and keeps the top chrome visible
- Empty-state CTA routes to Add Connection
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>