Record native editor acceptance and keyboard validation limits
4 files changed,
+109
−12
native/PARITY.md+4 −2native/REVIEW.md+42 −5native/ios/Review/DocsReviewPhone.swift+48 −1native/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) |