The pattern
Three defects, three platforms, one shape — each fixed (or not) separately:
|
desktop |
Android |
iOS |
Modifier.testableClickable / testableWithHandler |
fixed with rememberUpdatedState |
stale closure (#32) |
no click-handler registration at all (hasClickHandler returns false) |
| test-automation HTTP server |
Ktor CIO |
a second Ktor CIO copy |
hand-rolled POSIX socket/bind/listen/accept |
TestAutomationHandler (11 routes, JSON), TestAutomationState (registry) |
commonMain |
← shared |
← shared |
testableClickable is expect in commonMain with three actuals. It is pure Compose over TestAutomationState, which is already common — there is nothing platform-specific in it. The desktop actual got the rememberUpdatedState fix; Android's didn't, so #32 exists; iOS's registers nothing.
The server is the same story one layer down. Desktop and Android are both Ktor CIO — two copies of the same code. iOS is its own HTTP implementation, and every iOS-only automation bug so far lives there: the starved accept loop (#28), and now responses the driver cannot parse (Invalid control character at: line 1 column 77, run 33777943107 — the first run in which the iOS server ever answered /health in CI). Ktor CIO runs on Kotlin/Native.
The ask
Don't fix #32 on Android. Delete the three actuals and put one testableClickable/testableWithHandler in commonMain, with the rememberUpdatedState indirection desktop already has. Android and iOS get the fix by not having their own copy.
Then the same for the server: one Ktor CIO TestAutomationServer in commonMain, delete the Android duplicate and the iOS POSIX server. The framing/JSON class disappears structurally rather than by finding each instance.
Why now
Adopting 0.5.199 through CIRISAgent's five-platform gate found #30 (desktop), #32 (Android), and the iOS server issues in sequence — each a platform-specific copy of behaviour the other platforms already had right. A gate can only find these one platform and one release at a time. A single implementation means the next fix is the last one.
Refs #28, #30, #31, #32.
The pattern
Three defects, three platforms, one shape — each fixed (or not) separately:
Modifier.testableClickable/testableWithHandlerrememberUpdatedStatehasClickHandlerreturnsfalse)socket/bind/listen/acceptTestAutomationHandler(11 routes, JSON),TestAutomationState(registry)testableClickableisexpectin commonMain with threeactuals. It is pure Compose overTestAutomationState, which is already common — there is nothing platform-specific in it. The desktopactualgot therememberUpdatedStatefix; Android's didn't, so #32 exists; iOS's registers nothing.The server is the same story one layer down. Desktop and Android are both Ktor CIO — two copies of the same code. iOS is its own HTTP implementation, and every iOS-only automation bug so far lives there: the starved accept loop (#28), and now responses the driver cannot parse (
Invalid control character at: line 1 column 77, run 33777943107 — the first run in which the iOS server ever answered/healthin CI). Ktor CIO runs on Kotlin/Native.The ask
Don't fix #32 on Android. Delete the three
actuals and put onetestableClickable/testableWithHandlerin commonMain, with therememberUpdatedStateindirection desktop already has. Android and iOS get the fix by not having their own copy.Then the same for the server: one Ktor CIO
TestAutomationServerin commonMain, delete the Android duplicate and the iOS POSIX server. The framing/JSON class disappears structurally rather than by finding each instance.Why now
Adopting 0.5.199 through CIRISAgent's five-platform gate found #30 (desktop), #32 (Android), and the iOS server issues in sequence — each a platform-specific copy of behaviour the other platforms already had right. A gate can only find these one platform and one release at a time. A single implementation means the next fix is the last one.
Refs #28, #30, #31, #32.