fix: conform iOS SDK to the quackback: widget protocol (M1)#1
Conversation
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>
There was a problem hiding this comment.
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.
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>
BunsDev
left a comment
There was a problem hiding this comment.
I found a few issues that should be fixed before merging:
-
P1 — iOS build job is currently red. The new
ios-buildjob onmacos-14installs latest XcodeGen, which generatesobjectVersion = 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 butNavigationStackis used inHomeView.swift. -
P1 —
OpenViewno longer matches the canonical widget contract. The PR removes.changelogand documents onlyhome | new-post, but the current web SDK contract still supportshome,new-post,changelog, andhelp, and the widget route handleschangelog/helpmessages. This is a source-breaking regression for.changelogcallers and makes the mobile SDK less conformant. -
P2 —
.open/.closeare declared but native API calls do not emit them.OpenCovenFeedback.open(...)andOpenCovenFeedback.close()only present/dismiss the panel. Consumers registeringon(.open)oron(.close)will not hear native-initiated opens/closes, unlike the canonical web SDK.
Verification while reviewing:
swift testpassed: 52 tests, 0 failuresswiftlint lint --strict --config .swiftlint.ymlpassed: 0 violations- GitHub
Build SDK for iOScheck is failing - local
xcodebuild build -scheme FeedbackApp -destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NOfails after project generation on the iOS 15/NavigationStackmismatch
|
Patched the review findings in
Fresh local verification after the patch:
|
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:opencoven-feedback:instead ofquackback:),window.__opencoven-feedbackNative— the hyphen parses as subtraction), so the widget never found the native bridge and fell back towindow.parent.postMessage(void in a top-frameWKWebView),parseEventdecoded a fictional message shape that the real bridge never produces, and.openevent, 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
window.__quackbackNative.dispatch; message handler registered asquackback.quackback:prefix; the deadinitmessage is removed (theme comes fromconfig.json+ URL params).parseEventdecodes the real dispatch format — thequackback:eventwrapper (name/payload) plus standaloneready/close/navigate/identify-result/auth-changemessages.ready, open, close, post:created, vote, comment:created, identify, navigate, identify-result, auth-change. Fixes the.opencompile break correctly.OpenViewconstrained tohome/new-postper thequackback:opencontract.Safety net
BridgeContractTestsrun the bridge script in JavaScriptCore, proving the JS is valid anddispatchroutesvote/ready/navigate/identify-resultto the host end-to-end (stringcontains()checks could not).swift test+ SwiftLint on macOS, plusxcodegen generate+xcodebuildof the FeedbackApp target so app-target compile breaks recur in CI.Test plan
swift test— 52 tests pass (41 → 52), output pristineopencoven-feedback:/.submit/.changelog/initCommandreferences remainswift test, SwiftLint, app-targetxcodebuild) — first run on this PRvote) reaches the host🤖 Generated with Claude Code