oficina / docs Public

Record native editor acceptance and keyboard validation limits

Paul Campbell pushed by codex agent, operated by paul@keymail.dev 8e66e59bf157b2df8a7e45997fb0190994204350 parent c2e1a92
4 files changed, +109 −12
  • native/PARITY.md +4 −2
  • native/REVIEW.md +42 −5
  • native/ios/Review/DocsReviewPhone.swift +48 −1
  • native/ios/UITests/NativeUITests.swift +15 −4
diff --git a/native/PARITY.md b/native/PARITY.md
index 40aab42..78d1e8f 100644
--- a/native/PARITY.md
+++ b/native/PARITY.md
@@ -7,7 +7,7 @@ CRDT engine. No web editor or HTML rendering engine is embedded.
| Working web feature | Native implementation | Evidence / remaining acceptance |
| --- | --- | --- |
| List, title, search, create, shared marker, first-page thumbnail | JSON-backed SwiftUI workspace | Backend route tests; real iPhone/iPad create and reopen/export read |
-| Command-K jump to document | Native searchable sheet using server-ranked results, arrows/Enter/Escape | Simulator server-search and result-button navigation passed; keyboard interaction acceptance pending |
+| Command-K jump to document | Native searchable sheet using server-ranked results, arrows/Enter/Escape | iPhone result-button navigation and iPad actual responder-command dispatch + software submit passed; physical keyboard/HID acceptance remains unverified |
| Paragraph editing and multiple paragraphs | Generation-bound UTF-16 operations, native IME buffering | Actual WASM rapid-newline/typeahead, cross-paragraph replacement, emoji and AppKit marked-text tests |
| Bold, italic, inline code, clear formatting | Native toolbar and keyboard commands | iPhone/iPad bold persisted through fresh server export; command tests |
| Font family, size, headings, alignment, indentation | Bundled Liberation faces and native paragraph attributes | Projection/command tests; complete visual matrix still requires review |
@@ -32,4 +32,6 @@ UI fixture evidence and `benchmarks/README.md` for measured Mac interpreter cost
Disabled web menu items (including comments/history, find/replace, and insertion
menus which have no implemented handler) are not represented as delivered features.
-Full branch review and final four-app build acceptance remain separate gates.
+Independent production branch review approved at `c2e1a92`; final Mac, simulator
+and device builds for both apps passed (six targets). The manual acceptance
+limits above remain explicit; see `REVIEW.md` for exact runtime evidence.
diff --git a/native/REVIEW.md b/native/REVIEW.md
index 3ab9067..2449fcf 100644
--- a/native/REVIEW.md
+++ b/native/REVIEW.md
@@ -2,7 +2,21 @@
2026-09-13: independent specification review dispatched as gpt-6-astra with high reasoning. Actual runtime identity was not independently verified. The amended specification was approved after adding native known-schema editing parity, twelve locales/accessibility, sandboxed CRDT decoding, bounded per-document recovery, explicit UTF-16 positions, memory-only content and current stream authorization.
-This is specification approval only. Implementation, branch review and full parity acceptance are pending.
+Final independent production branch review: approved at `c2e1a92`, against
+inherited `d5ff04e17144583620ff3b9950fcf6722e7001e9`, with the evidence and manual
+acceptance limits below. The review covered the working web inventory, native
+Mac/iOS implementation, current authorization, sealed server storage, lossless
+CRDT identity/formatting, own-only undo, generation/recovery transitions, native
+clipboard/export, and the three narrowly scoped Yrs deletion backports. No
+additional concrete production blocker was established after the persisted-rule
+regression passed. The reviewer authored the isolated UI targets/runner and
+independent wire/deletion probes; production implementation was reviewed
+independently. This does not claim exhaustive manual parity acceptance.
+
+The final original server CI and native engine/Apple gate passed. All six
+parallel Mac/simulator/device builds passed in
+`../native-build-logs/20260913T095810.093604Z/results.json`. Source review preserves
+normal Yrs garbage collection; it does not adopt an unbounded tombstone policy.
## Isolated native review
@@ -71,11 +85,34 @@ and credential cleanup passed.
The iPhone jump-button case passed in
`native/build/review-20260913T094927.493167Z/Review.xcresult`: native search uses
the real ranked endpoint and the selected result opens the second document.
-The iPad Command-K case successfully opens search, but Return navigation remains
-a concrete failing acceptance case at this checkpoint. The phone keyboard case
-explicitly skips: Apple's [XCUIElement overview](https://developer.apple.com/documentation/xcuiautomation/xcuielement)
+The review-only `testRegisteredCommandAndSoftwareSubmitOpenDocument` case and
+selected XCTest suite passed in 20.223 seconds, recorded in
+`native/build/review-20260913T100847.314877Z/ui.log`.
+It locates the actual focused native editor, verifies its registered Command-K
+binding and priority, dispatches that fixed selector through UIKit's responder
+chain, searches the real endpoint, and submits with software-keyboard newline.
+The second document's native editor opens. Xcode then hung while finalizing its
+result bundle; the owned runner was interrupted and its normal cleanup removed
+credentials and stopped the fixture. Direct synthetic screenshots were retained.
+This is passing XCTest evidence, not a successful final xcodebuild exit. The
+fixed-command probe is confined to the separate review target and has no
+production launch hook.
+
+Physical hardware keyboard acceptance remains unverified. iPad `typeKey`
+occasionally opened search but also inserted a literal K; hardware Return
+injection did not open the result even after adding an explicit key handler.
+One hardware-connection attempt had the same Return result, and restored the
+original `ConnectHardwareKeyboard=0` preference afterward. These failed HID
+cases remain diagnostics and are not claimed as passing. The phone keyboard
+case explicitly skips: Apple's [XCUIElement overview](https://developer.apple.com/documentation/xcuiautomation/xcuielement)
documents keyboard interaction for macOS and iPadOS; attempted iPhone Command-K
-injection produced a literal K. No simulator keyboard preferences were changed.
+injection produced a literal K.
+
+Other unexecuted manual matrices remain listed in `PARITY.md`: full formatting
+and clipboard UI combinations, simultaneous live editing across devices, native
+sharing, VoiceOver/native-speaker review, final destination writes/print, and
+interactive Mac close/menu behavior. Their implemented paths and targeted
+tests were reviewed; those matrices are not represented as manually completed.
A failed XCTest case now gets 45 seconds for Xcode to finalize its result bundle;
if Xcode remains stuck, the runner stops only its own test process group and
diff --git a/native/ios/Review/DocsReviewPhone.swift b/native/ios/Review/DocsReviewPhone.swift
index 58d5889..f669020 100644
--- a/native/ios/Review/DocsReviewPhone.swift
+++ b/native/ios/Review/DocsReviewPhone.swift
@@ -15,8 +15,55 @@ import SwiftUI
}
var body: some Scene {
WindowGroup {
- if let client { DocsWorkspace(client: client, signOut: {}) }
+ if let client {
+ DocsWorkspace(client: client, signOut: {})
+ .overlay(alignment: .bottom) {
+ if ProcessInfo.processInfo.environment["DOCS_REVIEW_COMMAND_PROBE"] == "1" {
+ ReviewCommandProbe()
+ }
+ }
+ }
else { Text("Review fixture unavailable").accessibilityIdentifier("fixture.missing") }
}
}
}
+
+// Fixed-command review probe only. It inspects and dispatches the editor's real
+// registered Command-K action through the first-responder chain, without HID.
+import UIKit
+private struct ReviewCommandProbe: View {
+ @State private var status = "Waiting for native editor focus"
+ var body: some View {
+ Text(status).font(.caption).accessibilityIdentifier("review.command.probe")
+ .task {
+ for _ in 0..<200 {
+ guard !Task.isCancelled else { return }
+ let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
+ let windows: [UIWindow] = scenes.flatMap { $0.windows }
+ var focused: UITextView?
+ for window in windows {
+ if let editor = focusedEditor(in: window) { focused = editor; break }
+ }
+ if let editor = focused {
+ let registered: [UIKeyCommand] = editor.keyCommands ?? []
+ var found: UIKeyCommand?
+ for candidate in registered {
+ if candidate.input == "k" && candidate.modifierFlags == UIKeyModifierFlags.command { found = candidate; break }
+ }
+ guard let command = found, let selector = command.action, command.wantsPriorityOverSystemBehavior else {
+ status = "Missing native Command-K registration"; return
+ }
+ status = UIApplication.shared.sendAction(selector, to: nil, from: command, for: nil)
+ ? "Native Command-K dispatched" : "Native Command-K dispatch failed"
+ return
+ }
+ try? await Task.sleep(for: .milliseconds(100))
+ }
+ status = "Native editor never became first responder"
+ }
+ }
+ private func focusedEditor(in view: UIView) -> UITextView? {
+ if let editor = view as? UITextView, editor.accessibilityIdentifier == "docs.editor", editor.isFirstResponder { return editor }
+ return view.subviews.lazy.compactMap { focusedEditor(in: $0) }.first
+ }
+}
diff --git a/native/ios/UITests/NativeUITests.swift b/native/ios/UITests/NativeUITests.swift
index 6a361cc..418c8d8 100644
--- a/native/ios/UITests/NativeUITests.swift
+++ b/native/ios/UITests/NativeUITests.swift
@@ -66,9 +66,18 @@ final class NativeUITests: XCTestCase {
try exerciseJump(keyboard: true)
}
@MainActor func testJumpButtonSearchOpensDocument() throws { try exerciseJump(keyboard: false) }
- @MainActor private func exerciseJump(keyboard: Bool) throws {
+ @MainActor func testJumpButtonAndHardwareReturnOpensDocument() throws {
+ try XCTSkipUnless(UIDevice.current.userInterfaceIdiom == .pad,
+ "XCTest hardware keyboard injection requires iPadOS")
+ try exerciseJump(keyboard: false, hardwareReturn: true)
+ }
+ @MainActor func testRegisteredCommandAndSoftwareSubmitOpenDocument() throws {
+ try exerciseJump(keyboard: false, responderProbe: true)
+ }
+ @MainActor private func exerciseJump(keyboard: Bool, hardwareReturn: Bool = false, responderProbe: Bool = false) throws {
continueAfterFailure = false
let app = XCUIApplication()
+ if responderProbe { app.launchEnvironment["DOCS_REVIEW_COMMAND_PROBE"] = "1" }
app.launch()
defer { app.terminate() }
let first = app.buttons["Native review document"].firstMatch
@@ -77,8 +86,9 @@ final class NativeUITests: XCTestCase {
let editor = app.textViews["docs.editor"]
XCTAssertTrue(editor.waitForExistence(timeout: 15))
editor.tap()
- if keyboard { editor.typeKey("k", modifierFlags: .command) }
- else { app.buttons["docs.jump"].tap() }
+ if responderProbe { /* Review target dispatches the registered action after focus. */ }
+ else if keyboard { editor.typeKey("k", modifierFlags: .command) }
+ else { app.buttons["docs.jump"].firstMatch.tap() }
print("Synthetic jump shortcut hierarchy: \(app.debugDescription)")
try app.screenshot().pngRepresentation.write(to: URL.documentsDirectory.appendingPathComponent("jump-shortcut.review.png"), options: .atomic)
let query = app.textFields["docs.jump.query"]
@@ -86,7 +96,8 @@ final class NativeUITests: XCTestCase {
query.typeText("Second review")
let result = app.buttons.containing(.staticText, identifier: "Second review document").firstMatch
XCTAssertTrue(result.waitForExistence(timeout: 10), "Native search must show the existing server-ranked result")
- if keyboard { query.typeKey(XCUIKeyboardKey.return, modifierFlags: []) }
+ if responderProbe { query.typeText("\n") }
+ else if keyboard || hardwareReturn { query.typeKey(XCUIKeyboardKey.return, modifierFlags: []) }
else { result.tap() }
print("Synthetic jump return hierarchy: \(app.debugDescription)")
try app.screenshot().pngRepresentation.write(to: URL.documentsDirectory.appendingPathComponent("jump-return.review.png"), options: .atomic)