Skip to content

fix: conform iOS SDK to the quackback: widget protocol (M1)#1

Merged
BunsDev merged 7 commits into
mainfrom
feat/ios-sdk-protocol-conformance
May 29, 2026
Merged

fix: conform iOS SDK to the quackback: widget protocol (M1)#1
BunsDev merged 7 commits into
mainfrom
feat/ios-sdk-protocol-conformance

Conversation

@apple-techie
Copy link
Copy Markdown

Summary

The iOS SDK was functionally dead against a live instance. The
Quackback→OpenCoven rebrand renamed the wire protocol, but the web widget's
protocol is frozen on the quackback: namespace
(lib/shared/widget/types.ts, lib/client/widget-bridge.ts). As shipped:

  • every command was sent on the wrong channel (opencoven-feedback: instead of quackback:),
  • the injected bridge script was invalid JavaScript (window.__opencoven-feedbackNative — the hyphen parses as subtraction), so the widget never found the native bridge and fell back to window.parent.postMessage (void in a top-frame WKWebView),
  • parseEvent decoded a fictional message shape that the real bridge never produces, and
  • the demo app referenced a non-existent .open event, so it could not compile.

This PR restores conformance to the canonical contract and adds the test/CI
safety net that would have caught these bugs. It implements milestone M1 of
the design doc included here (docs/superpowers/specs/2026-05-28-ios-sdk-conformance-native-design.md); M2 (typed event payloads, identity hardening, config gating) and M3 (SwiftUI surface, WebView UX, release) follow in later PRs.

Changes

Protocol conformance

  • Bridge script emits valid JS defining window.__quackbackNative.dispatch; message handler registered as quackback.
  • All inbound commands use the quackback: prefix; the dead init message is removed (theme comes from config.json + URL params).
  • parseEvent decodes the real dispatch format — the quackback:event wrapper (name/payload) plus standalone ready/close/navigate/identify-result/auth-change messages.
  • Event enum matches the contract: ready, open, close, post:created, vote, comment:created, identify, navigate, identify-result, auth-change. Fixes the .open compile break correctly.
  • OpenView constrained to home/new-post per the quackback:open contract.

Safety net

  • BridgeContractTests run the bridge script in JavaScriptCore, proving the JS is valid and dispatch routes vote/ready/navigate/identify-result to the host end-to-end (string contains() checks could not).
  • GitHub Actions CI: swift test + SwiftLint on macOS, plus xcodegen generate + xcodebuild of the FeedbackApp target so app-target compile breaks recur in CI.

Test plan

  • swift test — 52 tests pass (41 → 52), output pristine
  • No stale opencoven-feedback: / .submit / .changelog / initCommand references remain
  • CI green on macOS runner (swift test, SwiftLint, app-target xcodebuild) — first run on this PR
  • Manual smoke against a live instance: launcher opens widget, identify takes effect, a typed event (e.g. vote) reaches the host

Note: the WKWebView/UIKit path is not compilable on the macOS dev host (no iOS SDK installed locally); those mechanical edits (handler name + removed init call) are covered by the new CI app-build job.

🤖 Generated with Claude Code

apple-techie and others added 4 commits May 28, 2026 02:19
The Quackback→OpenCoven rebrand renamed the wire protocol, but the web
widget's protocol is frozen on the `quackback:` namespace. As shipped, the
SDK was functionally dead against a live instance: every command was sent on
the wrong channel wrapped in invalid JavaScript, and the demo app referenced a
non-existent `.open` event so it could not compile.

This restores conformance to the canonical contract
(lib/shared/widget/types.ts, lib/client/widget-bridge.ts):

- Bridge script emits valid JS defining `window.__quackbackNative.dispatch`;
  message handler registered as `quackback`.
- All inbound commands use the `quackback:` prefix; the dead `init` message is
  removed (theme comes from config.json + URL params).
- `parseEvent` decodes the real dispatch format — the `quackback:event`
  wrapper (name/payload) plus standalone `ready`/`close`/`navigate`/
  `identify-result`/`auth-change` messages.
- Event enum matches the contract: ready, open, close, post:created, vote,
  comment:created, identify, navigate, identify-result, auth-change.
- `OpenView` constrained to `home`/`new-post` per the `quackback:open` contract.

Adds the safety net that would have caught these bugs:

- BridgeContractTests run the bridge script in JavaScriptCore, proving the JS
  is valid and `dispatch` routes vote/ready/navigate/identify-result to the
  host end-to-end (string `contains()` checks could not).
- GitHub Actions CI: swift test + swiftlint on macOS, plus xcodegen +
  xcodebuild of the FeedbackApp target so app-target compile breaks recur in CI.

Implements milestone M1 of the design at
docs/superpowers/specs/2026-05-28-ios-sdk-conformance-native-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First CI run surfaced four issues, none reproducible on the newer local
toolchain:

- Event tests captured and mutated a local `var` inside the `@Sendable`
  event handler, which the runner's compiler rejects ("mutation of captured
  var in concurrently-executing code"). Replaced with thread-safe, Sendable
  `Counter`/`EventLog` holders captured by `let`.
- project.yml declared a `FeedbackAppTests` target pointing at a directory
  that never existed, failing xcodegen validation. Removed the dead target.
- xcodegen generated no shared scheme, so `xcodebuild -scheme FeedbackApp`
  could not resolve. Added an explicit `FeedbackApp` scheme.
- .swiftlint.yml used invalid keys `included_paths`/`excluded_paths` (correct
  keys are `included`/`excluded`), so the `.build/` exclusion never applied and
  `--strict` linted SwiftPM-generated files. Fixed the keys.

Verified locally: swift test (52 pass), xcodegen generate, scheme resolution,
and swiftlint --strict (0 violations, 17 files).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XcodeGen 2.45.4 emits objectVersion 77, which the runner's Xcode 15.4 cannot
read ("future Xcode project file format"). Rather than couple CI to a specific
XcodeGen/Xcode pairing, build the package directly with xcodebuild for the iOS
Simulator. This compiles the `#if canImport(UIKit)` path (WebView, launcher,
panel) that `swift test` on macOS skips, using the runner's own project format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`OpenCovenFeedback` is a caseless enum used as a namespace, so its
`@objc launcherTapped` + `#selector` + `addTarget(self,...)` could never
compile for iOS — `@objc`/`#selector` require a class. This latent break was
invisible because there was no iOS CI and `swift test` on macOS skips the
`#if canImport(UIKit)` path; the new iOS build job surfaced it.

Move the tap target onto `LauncherButton` (a UIButton subclass) via an
`onTap` closure; the enum sets the closure instead of acting as the target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@BunsDev BunsDev requested a review from Copilot May 29, 2026 17:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restores the iOS SDK’s widget bridge to the frozen quackback: wire protocol and adds tests/CI coverage intended to prevent protocol regressions.

Changes:

  • Updates bridge commands, native handler naming, event parsing, event enum cases, and supported open views to match the widget contract.
  • Adds JavaScriptCore bridge contract tests and expands protocol/event unit tests.
  • Adds CI, SwiftLint config fixes, demo app updates, and an M1–M3 conformance design doc.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Sources/OpenCovenFeedback/Internal/JSBridge.swift Reworks command builders, event parsing, and injected bridge script for quackback:.
Sources/OpenCovenFeedback/Internal/FeedbackWebView.swift Registers the quackback script handler and removes the old init handshake.
Sources/OpenCovenFeedback/OpenCovenFeedbackEvent.swift Expands public event enum to match the widget contract.
Sources/OpenCovenFeedback/OpenView.swift Restricts open views to contract-supported values.
Sources/OpenCovenFeedback/OpenCovenFeedback.swift Refactors launcher tap handling through a Swift closure.
Sources/OpenCovenFeedback/Internal/LauncherButton.swift Adds the class-hosted tap selector and callback.
Tests/OpenCovenFeedbackTests/JSBridgeTests.swift Updates and expands protocol command/parser tests.
Tests/OpenCovenFeedbackTests/BridgeContractTests.swift Adds JavaScriptCore execution tests for the injected bridge.
Tests/OpenCovenFeedbackTests/OpenCovenFeedbackEventTests.swift Updates event emitter tests for the new event surface.
FeedbackApp/Sources/FeedbackApp/Views/HomeView.swift Updates changelog entry behavior to open the widget.
FeedbackApp/Sources/FeedbackApp/Config/AppConfiguration.swift Updates demo listener from submit to post-created.
Example/OpenCovenFeedbackExample/OpenCovenFeedbackExampleApp.swift Updates example listener to the new event case.
project.yml Adds a FeedbackApp scheme and removes the old test target.
.swiftlint.yml Corrects SwiftLint include/exclude key names.
.github/workflows/ci.yml Adds package test/lint and iOS build workflow jobs.
docs/superpowers/specs/2026-05-28-ios-sdk-conformance-native-design.md Adds the conformance/native-layer design plan.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/OpenCovenFeedback/Internal/JSBridge.swift Outdated
Comment thread .github/workflows/ci.yml Outdated
BunsDev and others added 2 commits May 29, 2026 12:51
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Member

@BunsDev BunsDev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a few issues that should be fixed before merging:

  1. P1 — iOS build job is currently red. The new ios-build job on macos-14 installs latest XcodeGen, which generates objectVersion = 77; Xcode 15.4 cannot open that project format. In addition, when generated locally with a newer Xcode, the build then fails because the app deployment target is iOS 15 but NavigationStack is used in HomeView.swift.

  2. P1 — OpenView no longer matches the canonical widget contract. The PR removes .changelog and documents only home | new-post, but the current web SDK contract still supports home, new-post, changelog, and help, and the widget route handles changelog/help messages. This is a source-breaking regression for .changelog callers and makes the mobile SDK less conformant.

  3. P2 — .open / .close are declared but native API calls do not emit them. OpenCovenFeedback.open(...) and OpenCovenFeedback.close() only present/dismiss the panel. Consumers registering on(.open) or on(.close) will not hear native-initiated opens/closes, unlike the canonical web SDK.

Verification while reviewing:

  • swift test passed: 52 tests, 0 failures
  • swiftlint lint --strict --config .swiftlint.yml passed: 0 violations
  • GitHub Build SDK for iOS check is failing
  • local xcodebuild build -scheme FeedbackApp -destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NO fails after project generation on the iOS 15/NavigationStack mismatch

@BunsDev
Copy link
Copy Markdown
Member

BunsDev commented May 29, 2026

Patched the review findings in a138a5d:

  • Fixed the iOS build path by pinning XcodeGen project output to Xcode 15.3 format (objectVersion = 63) and replacing iOS-16-only NavigationStack usage with iOS 15-compatible fallbacks.
  • Restored OpenView parity with the canonical widget contract by supporting .changelog and .help, and restored the demo changelog button to use .changelog.
  • Added native .open / .close emissions for programmatic SDK opens/closes while avoiding duplicate close events for iframe-initiated close.
  • Added coverage for changelog/help open commands and kept the conformance doc aligned.

Fresh local verification after the patch:

  • swift test — 53 tests, 0 failures
  • swiftlint lint --strict --config .swiftlint.yml — 0 violations
  • xcodegen generate — generated objectVersion = 63, compatibilityVersion = "Xcode 15.3"
  • xcodebuild build -scheme FeedbackApp -destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NO — build succeeded

@BunsDev BunsDev merged commit 47f19dd into main May 29, 2026
2 checks passed
@BunsDev BunsDev deleted the feat/ios-sdk-protocol-conformance branch May 29, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants