[austin] Checkpoint Igloo UI polish stack#7
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughSeven submodule pointers are bumped. The Playwright test harness gains three new live runtime specs (permissions override, recover execution, welcome unlock), reworked visual snapshot builders parameterized by peer permission states, extensively expanded page-object helpers, a new opt-in ChangesTest Harness Expansion and Visual Coverage
Dev Docs, Plans, Reports, and Submodules
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
test/igloo-pwa/specs/app-shell.spec.ts (1)
70-103: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winKeep the stored-profile fixtures aligned with the shared seed contract.
These manual seeds now disagree with
createPwaStoredProfileSeed(...):seededDashboardProfile()encodesgroup_public_key = '22'.repeat(32)butgroup_package_json.group_pk = "22", and the inline persisted profile usesrelay_profile: 'browser'even though shared seeds derive that field fromrelays[0](or'local'). That means the reload/unlock coverage here is exercising impossible persisted state and can miss profile-hydration regressions. Prefer building these fixtures through the shared helper, or at least makegroup_package_json.group_pkandrelay_profilematch the real saved shape.Also applies to: 236-243
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/igloo-pwa/specs/app-shell.spec.ts` around lines 70 - 103, The stored-profile fixtures in app-shell.spec.ts are diverging from the shared seed shape, so update the test data to match createPwaStoredProfileSeed and the real persisted profile contract. In seededDashboardProfile(), make group_package_json.group_pk consistent with group_public_key, and ensure relay_profile is derived from relays[0] (or local) instead of hardcoding a mismatched value; likewise, update the inline persisted profile fixture to use the same shared helper or equivalent fields. This keeps the reload/unlock tests aligned with the actual hydration path and the seeded profile symbols used here.
🧹 Nitpick comments (1)
test/igloo-pwa/support/shell-signer.ts (1)
97-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNarrow
runtimeOptionsto the supported profile fields.
Record<string, unknown>lets misspelled keys silently land in the profile JSON, so the live specs fail later with a timeout instead of a type error. A small typed shape here would catch that at compile time.Proposed change
+type ShellRuntimeOptions = Partial<{ + sign_timeout_secs: number; + ping_timeout_secs: number; + request_ttl_secs: number; + state_save_interval_secs: number; +}>; + export async function startShellSigner(input: { bfprofile: string; packageSecret: string; relayUrl: string; label?: string; - runtimeOptions?: Record<string, unknown>; + runtimeOptions?: ShellRuntimeOptions; }): Promise<ShellSigner> {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/igloo-pwa/support/shell-signer.ts` around lines 97 - 103, The startShellSigner input currently uses a generic Record<string, unknown> for runtimeOptions, which allows unsupported or misspelled fields to slip into the generated profile JSON. Replace it with a narrow typed interface or type alias that lists only the supported profile fields, and update the startShellSigner signature and any related usage to accept that shape. Keep the change centered around startShellSigner so compile-time errors catch invalid runtimeOptions keys before the live specs fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/docs/WORKFLOWS.md`:
- Around line 77-80: The WORKFLOWS guidance for test:guards:visual:captures is
incomplete because it only mentions generated .tmp artifacts and misses that the
same guard can fail on aligned-note text. Update the instructions around
test:guards:visual:captures and check-pwa-visual-manifest.mjs to explicitly
mention that the guard also rejects aligned entries whose notes match
ALIGNED_UNCERTAINTY_PATTERNS, so failures from stale uncertainty notes are
covered alongside .tmp artifacts.
In `@test/igloo-pwa/specs/dashboard-visual.spec.ts`:
- Line 338: The timestamp in the dashboard visual fixture is inconsistent with
the surrounding `events` values, which are in seconds. Update the affected event
entry in `dashboard-visual.spec.ts` so it uses a seconds-based timestamp like
the other items in the `events` array, keeping the fixture format consistent for
ordering and rendering.
In `@test/igloo-pwa/specs/rotation-create.spec.ts`:
- Line 81: The final dashboard verification in rotation-create.spec.ts is too
weak because the checks around the dashboard assertions no longer verify the
deterministic profile-label state set earlier in the test. Update the end-state
checks in the rotation/onboarding flow to assert the expected profile-label
values in addition to using p.dashboard.expectDashboard(), and keep those
assertions in the same final validation points so regressions in landing on the
wrong profile are caught.
In `@test/igloo-pwa/specs/settings-visual.spec.ts`:
- Around line 311-338: The replace-share visual spec is registering multiple
page.addInitScript-based state injections in one flow, which can persist across
reloads and make snapshot order flaky. Update the test around
injectReplaceShareVisualState, seedState, and the applying/failed/success
captures so it uses a single init-script bridge for the whole sequence, or split
those states into separate tests/pages to avoid accumulated scripts.
In `@test/igloo-pwa/support/pages.ts`:
- Around line 465-481: The expectPeerPermission helper currently only checks
that a permission token is visible, so it can pass even when the active
selection never changed. Update expectPeerPermission in pages.ts to assert the
selected/active state for the matching token in the Permissions tab, using the
same direction/method/state locator and the relevant stateful attribute or
accessibility state rather than visibility alone. Keep toggleFirstPeerPermission
unchanged and make the expectation verify the actual active chip styling/state.
In `@test/igloo-pwa/support/ui.ts`:
- Around line 189-190: The `expectPwaSignerSignReady` helper is matching the
first exact “Ready” text anywhere on the page, which can allow the check to pass
on an unrelated element. Update this helper to scope the lookup to the dashboard
surface used by the signer flow, using the existing `Page`-based locator chain
in `expectPwaSignerSignReady` so it only asserts the intended card or container
is ready. Keep the timeout behavior the same, but narrow the selector so other
dialogs or cards with the same label do not satisfy the probe early.
---
Outside diff comments:
In `@test/igloo-pwa/specs/app-shell.spec.ts`:
- Around line 70-103: The stored-profile fixtures in app-shell.spec.ts are
diverging from the shared seed shape, so update the test data to match
createPwaStoredProfileSeed and the real persisted profile contract. In
seededDashboardProfile(), make group_package_json.group_pk consistent with
group_public_key, and ensure relay_profile is derived from relays[0] (or local)
instead of hardcoding a mismatched value; likewise, update the inline persisted
profile fixture to use the same shared helper or equivalent fields. This keeps
the reload/unlock tests aligned with the actual hydration path and the seeded
profile symbols used here.
---
Nitpick comments:
In `@test/igloo-pwa/support/shell-signer.ts`:
- Around line 97-103: The startShellSigner input currently uses a generic
Record<string, unknown> for runtimeOptions, which allows unsupported or
misspelled fields to slip into the generated profile JSON. Replace it with a
narrow typed interface or type alias that lists only the supported profile
fields, and update the startShellSigner signature and any related usage to
accept that shape. Keep the change centered around startShellSigner so
compile-time errors catch invalid runtimeOptions keys before the live specs
fail.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 29deace8-0404-41e2-92e2-487e61ea5ee3
📒 Files selected for processing (40)
dev/BACKLOG.mddev/HISTORY.mddev/plans/bifrost-rs-peer-telemetry-and-approval-spec-2026-06-10.mddev/plans/dashboard-settings-export-paper-redesign-2026-06-01.mddev/plans/igloo-ui-remaining-paper-sync-hard-cut-plan-2026-05-25.mddev/plans/settings-onboard-sponsor-unblock-plan-2026-06-19.mddev/reports/igloo-ui-paper-design-system-investigation-2026-05-20.mddev/reports/settings-sidebar-alignment-audit-2026-06-19.mdrepos/bifrost-rsrepos/igloo-chromerepos/igloo-homerepos/igloo-paperrepos/igloo-pwarepos/igloo-sharedrepos/igloo-uitest/README.mdtest/docs/WORKFLOWS.mdtest/igloo-pwa/specs/app-shell.spec.tstest/igloo-pwa/specs/dashboard-visual.spec.tstest/igloo-pwa/specs/import-visual.spec.tstest/igloo-pwa/specs/onboard-visual.spec.tstest/igloo-pwa/specs/permissions-runtime-live.spec.tstest/igloo-pwa/specs/permissions-visual.spec.tstest/igloo-pwa/specs/recover-execution.spec.tstest/igloo-pwa/specs/recover-visual.spec.tstest/igloo-pwa/specs/rotation-create.spec.tstest/igloo-pwa/specs/rotation-update.spec.tstest/igloo-pwa/specs/settings-visual.spec.tstest/igloo-pwa/specs/welcome-unlock-live.spec.tstest/igloo-pwa/specs/welcome-visual.spec.tstest/igloo-pwa/support/flows.tstest/igloo-pwa/support/pages.tstest/igloo-pwa/support/shell-signer.tstest/igloo-pwa/support/ui.tstest/igloo-pwa/visual-manifest.jsontest/package.jsontest/scripts/check-pwa-visual-manifest-negative.shtest/scripts/check-pwa-visual-manifest.mjstest/scripts/report-pwa-visual-comparison.mjstest/shared/browser-artifacts.ts
| After running `npm --prefix test run test:e2e:igloo-pwa:visual`, run | ||
| `npm --prefix test run test:guards:visual:captures` before generating or sharing | ||
| the comparison report. This opt-in guard checks only generated `.tmp` artifacts, | ||
| so it is not part of the always-on manifest-shape guard. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Clarify that this command can also fail on aligned-note text.
test:guards:visual:captures still runs check-pwa-visual-manifest.mjs, and that checker now also rejects aligned entries whose notes match ALIGNED_UNCERTAINTY_PATTERNS at test/scripts/check-pwa-visual-manifest.mjs Lines 107-112. Saying it checks only generated .tmp artifacts will misdirect anyone debugging a failure from stale uncertainty notes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/docs/WORKFLOWS.md` around lines 77 - 80, The WORKFLOWS guidance for
test:guards:visual:captures is incomplete because it only mentions generated
.tmp artifacts and misses that the same guard can fail on aligned-note text.
Update the instructions around test:guards:visual:captures and
check-pwa-visual-manifest.mjs to explicitly mention that the guard also rejects
aligned entries whose notes match ALIGNED_UNCERTAINTY_PATTERNS, so failures from
stale uncertainty notes are covered alongside .tmp artifacts.
5699c60 to
1af3c17
Compare
- Add device_name to OnboardProfileMaterial with serde(default) - Thread device_name through material_from_onboard_payload, material_result_from_profile, recover_profile - FfiApp::export_profile carries resolved carrier label - 7 Rust integration tests in export_recipe.rs: - profile round-trip preserves group members + device_name - share round-trip preserves secret bytes - both reject wrong password (VAL-SET-015) - empty-name legacy fallback sentinel - missing active material returns error:no_active_profile - Cargo examples: export_decode, export_smoke - Validation scripts: verify-export-artifact.sh, iOS/Android focus shells - Maestro flow: export-focused-paste-back.yaml - Export recipe docs for validators - Regenerate Android UniFFI bindings for deviceName property - Clippy fixes in updates.rs and nostr_connect_smoke.rs Refs: VAL-SET-015, VAL-LOAD-001..019, VAL-CROSS-004/007/009, VAL-SHELL-012/016
Fixes critical regression from 8e02b5a where signer startup was broken on both iOS and Android because dashboard.profile_info was never populated. - OnboardStored: populate profile_info from onboarding.resolved before navigating to Dashboard - LoadProfileStored: populate profile_info from load_profile.resolved before navigating to Dashboard - OpenProfile: seed minimal profile_info and emit RestoreFromSecureStorage side effect - iOS AppManager: handle RestoreFromSecureStorage by loading full material from Keychain - Android AppManager: handle RestoreFromSecureStorage by loading full material from EncryptedSharedPreferences Tests: 177 passed, clippy clean.
… rows The mobile-create-keyset-flow feature wires the bridge-supplied `events_len` through to the actor. The previous code discarded it in `AppAction::SignerStatusUpdate` and hardcoded `cache.events_len = 0` on every poll, so the dashboard event log could never reflect a real runtime transition even when the bridge reported a fresh event. The actor now tracks the highest `events_len` it has ingested in a new `SignerRuntimeState.runtime_observed_events_len` field. When the shell-supplied count advances past that baseline exactly one safe INFO row is prepended with an RFC-3339 timestamp; unchanged or recovered counts never duplicate the row, and existing Rust-emitted entries stay intact in newest-first order. The polling task mirrors the change. It computes a fingerprint of the observable runtime metadata (readiness, relay-connected, peer-online map, pending-ops count) and advances `cache.events_len` by one each time the fingerprint differs from the previous poll. Because bifrost-bridge-tokio does not expose a per-event payload stream, the fingerprint is the local substitute that keeps the runtime event log truthful without fabricating payload-bearing rows. The fingerprint resets on `stop_signer` so a fresh `start_signer` always registers as one event. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…up workers The mobile-create-keyset-flow feature wires bridge-supplied events_len through the actor, dedupes per-tick INFO rows, and mirrors the change in the polling task's runtime fingerprint tracker. The companion note documents both the actor contract (runtime_observed_events_len field and the comparison logic in AppAction::SignerStatusUpdate) and the polling-task contract (cache.events_len, fingerprint builder, and last_obs_fingerprint reset on stop_signer), plus the binding-regeneration requirement so later Android/iOS work does not miss the regenerated data class FfiConverter read/write helpers. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…t, iOS, Android Adds the full Create / Rotate Keyset create-mode wizard covering Generate parameters and validation, Device Profile without local-storage password (share picker + relays), Review accuracy, Accept storing/starting the local profile, Distribute per-share copy/QR/save forms with password-gated bfonboard1 outputs and status chips, Finish routing to dashboard, abandonment semantics, and busy feedback during heavy Argon2id work. Fulfills VAL-CREATE-001 through VAL-CREATE-022. Includes: - Rust: state/keyset.rs with KeysetFlowState, KeysetFlowStep, KeysetValidationError, GeneratedShare, KeysetBundleRecord, DistributeStatus, DistributeShareRecord; 17 new AppAction variants; 4 new AppUpdate side effects; FfiApp::generate_keyset and FfiApp::encode_distribute_onboard; 4 new state-machine tests. - iOS SwiftUI: CreateKeysetEntry/Generate/DeviceProfile/Review/ Distribute views with StepProgressStrip, ModeChip, DistributeStatusChip, QrCodeModal + QrCodeImage (CoreImage). - Android Compose: full Create Keyset screens with mode selector, share picker, distribute forms, status chips, zxing QR dialog; AppManager.kt shell helpers for the four new AppUpdate variants. Verification: cargo test (145 cases passed), cargo fmt --check, cargo clippy -D warnings, rmp bindings --check all, iOS xcodebuild iphonesimulator, Android assembleDebug. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…id, and library Implements the QR affordances that bind the Create Keyset Distribute step (modal QR display via existing CoreImage / zxing renderers re-validated here) and the OnboardDevice connect flow's new Scan QR entry point. - VAL-QR-001 — Create Keyset Distribute step's QrCodeModal (iOS) and QrDialog (Android) render the bfonboard1 envelope as a real scannable QR. The modal payload text surfaces via accessibilityIdentifier qr_payload_text (iOS) + testTag qr_payload_text (Android) so the legacy hierarchy dump can recover the full envelope. - VAL-QR-002 — Scan QR sheet degrades on iOS Simulator + Android emulator with no working rear camera. iOS probes AVCaptureDevice.default(for: .video) (nil on Simulator); Android gates on Build.FINGERPRINT (generic / sdk / emulator / google_sdk) plus CameraManager.cameraIdList + LENS_FACING_BACK to keep AVD rear-camera false-positives out of the real-camera branch. - VAL-QR-003 — Paste fallback feeds the trimmed bfonboard text into the existing OnboardConnect state via the onScanned / onUsePayload closures. The actor's trim + OnboardConnect + performOnboardHandshake chain treats the resulting state identically to manual paste. - Library user-testing.md gains a QR Decode Recipe section documenting the canonical zbarimg invocation + optional ImageMagick crop rotation for screenshot decode. - Two focused Rust regression tests in state_machine_tests.rs pin the canonical bfonboard1 envelope shape and the trim-equivalence contract across QR paste + manual paste. Both tests use placeholder hex-shaped fields (PLACEHOLDER_CREDENTIAL constant) following the same fixture pattern as bifrost-rs' own BfOnboardPayload tests. - Two focused Maestro flows (qr-scan-android.yaml / qr-scan-ios.yaml) drive Hub -> OnboardEntry -> OnboardConnect -> Scan QR -> fallback -> paste -> Use This Package -> password -> Connect -> review -> Save -> dashboard against the live demo stack. Build / lint / test results: - cargo test --workspace: 147 + 8 + 7 + 24 cases pass (186 total, includes the 2 new QR regression tests). - cargo fmt --check: clean. - cargo clippy --workspace --all-targets -- -D warnings: clean. - iOS xcodebuild build (Debug, iphonesimulator, arm64): BUILD SUCCEEDED. - Android assembleDebug: BUILD SUCCESSFUL. - Maestro qr-scan-ios.yaml: all steps COMPLETED on iOS RMP iPhone 15 / 26.5. - Maestro qr-scan-android.yaml: all steps COMPLETED on rmp_api35. Evidence in apps/igloo-mobile/library/evidence/VAL-QR-mobile-qr-display-and-scan-support/. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…droid Brings RMP rotate-share behavior online end-to-end on the shared Rust TEA core plus the iOS SwiftUI and Android Compose shells. Rust core (): - New typed state (, , , ) and rotation-source picker (, ). - New AppAction variants (Open/Update/Connect/HandshakeSuccess/ HandshakeFailure/Replace/ClearError/Reset + Keyset*RotationSource*) and AppUpdate side-effect variants (PerformRotateShareHandshake, ReplaceProfileFromRotate, PerformKeysetRotation). - Typed-error mapping, group/same-profile preemptive rejection, rotate-mode Generate validation gate, rewrite helper, and 28 red-step tests in . FFI: , , , , plus /sfot decoder. Swift and Kotlin UniFFI bindings regenerated ( gitignored; tracked). iOS shell (): - AppManager convenience methods, AppUpdate apply-handlers for performRotateShareHandshake (off-main Thread reusing rust.onboard) and replaceProfileFromRotate (Keychain + profile-index swap). - New + with stable accessibility identifiers for the iOS simulator Maestro flows. Android shell (): - AppManager methods + reconcile cases for PerformRotateShareHandshake (off-main) and ReplaceProfileFromRotate (EncryptedSharedPreferences swap + hub status update). - + Compose surfaces with matched testTags gating the route. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ation + round-trip recovery Materialization paths in igloo-mobile-core (OnboardStored, LoadProfileStored, CreateKeysetAccepted, RotateShareReplace) now publish a NIP-44 encrypted kind-10000 backup event to the profile's configured relay using frostr-utils's create_encrypted_profile_backup + build_profile_backup_event (reused unchanged from the WASM bridge; reimplementation is out of policy). The combined side-effect variant for create + rotate keeps the actor's single side-effect-per-update pattern intact while still emitting the backup publish alongside the storage swap. The dashboard mirrors the publish result via BackupPublishStatus so a validator trace sees the share-derived author and relay count without instrumenting the actor. FfiApp::recover_profile is rewired to fetch the latest matching kind-10000 event from the relay via a Nostr REQ filtered by the share-derived x-only author, decrypt it with frostr_utils::parse_profile_backup_event, and rebuild a full multi-member OnboardProfileMaterial so the round-trip closes cleanly against a freshly-published backup. Tests: - cargo test --workspace: 147 passed; 0 failed - cargo test -- --ignored live tests: 3 passed; 0 failed (publish + relay-side filtered proof + round-trip recovery) - cargo fmt --check + cargo clippy -D warnings: clean - uniffi-bindgen kotlin + swift: generates with new types Includes scripts/verify-relay-backup.py for validator replay of the relay-side filtered proof and library/evidence/... with captured unit + live test logs. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ine suite
Adds the production-grade Rust test suite for VAL-CROSS-005 and
VAL-CROSS-008 — the cross-platform keyset interop and rotation
lifecycle assertions. Replaces an earlier broken fixture (compile
errors + logic failures) with a clean suite:
VAL-CROSS-005 (cross-platform keyset interop via copied bfonboard):
* bfonboard1 envelope round-trips losslessly across the same
Rust path running on iOS and Android shells.
* iOS-minted envelopes decode on the Android path with identical
group pubkey; Android-minted envelopes decode on iOS.
* Wrong-password decode fails on every platform path.
* Each direction's decoded share verifies against the source
bundle's FROSTR group via frostr_utils::verify_share.
VAL-CROSS-008 (cross-device rotation lifecycle):
* Rotation preserves the group public key byte-for-byte while
replacing every share public key (zero intersection pre/post).
* Pre-rotation shares are rejected against the rotated group;
rotated shares verify against the rotated group.
* Post-rotation bfonboard1 envelopes decode on the other
platform's path and verify against the rotated group but not
the pre-rotation group.
* Full 64-hex share pubkeys and group pubkeys are emitted for
direct byte comparison so the contract's pre/post identity
checks retain fidelity when visual rendering truncates.
Test results on this branch:
* cross_platform_keyset_interop — 9/9 passed
* rotate_share_flow — 28/28 passed (pre-existing)
* cargo fmt --check — clean
* cargo clippy --workspace --all-targets -- -D warnings — clean
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…s Rust, iOS, Android Preserve rotated share secret (Rust core): - Add share_seckey_hex to AppAction::RotateShareHandshakeSuccess and RotatePreviewIdentity so the live handshake can carry the rotated share secret from the FFI through to the actor. - build_rotated_material_bytes now requires a 64-hex share_seckey_hex on the preview and returns Err on missing or truncated secrets so the actor surfaces a typed Unexpected error rather than emitting a tombstone-side-effect. - Test rotate_share_replace_emits_swap_side_effect_and_routes_dashboard gains an assertion that the emitted material JSON contains the rotated share secret (issue 1); new rotate_share_replace_without_share_secret_emits_no_side_effect proves the missing-secret path refuses to dispatch. Regenerate UniFFI bindings (tracked on Android): - Re-run cargo run -p uniffi-bindgen to capture the new ReplaceProfileFromRotateAndPublishBackup side effect, the PublishProfileBackup side effect, the BackupPublishResult record, and the publishBackup FFI surface. Android binding lands verbatim (issue 2); iOS bindings are gitignored but regenerated locally via just ios-gen-swift during the build cycle. iOS AppManager fixes (issues 3 + 4): - rotateShareConnect dispatches the action only; the duplicate off-Main performRotateShareHandshake call is removed so the actor's AppUpdate::PerformRotateShareHandshake side effect wins and the handshakes stop racing. - rotateShareReplace dispatches the action only; the empty Data() shell-side performReplaceProfileFromRotate call is removed so the actor's full material blob (now carrying the rotated share secret) reaches secure storage instead of an empty tombstone. - Reconciler learns the new .replaceProfileFromRotateAndPublishBackup case (calls performReplaceProfileFromRotate + publishRotatedBackupIfPossible). - Decoders extract share_seckey_hex from the FFI material blob on PerformRotateShareHandshake completion via JSONSerialization. Android AppManager + Compose fixes (issues 5 + 6): - New openRotateShareConnect(profileId, shortId, deviceLabel) helper dispatches AppAction.OpenRotateShareConnect the same way the iOS shell already does. - Settings tab Rotate Share button dispatches the helper with the active profile's id, short id, and device label; falls back to navigateToRotateShare when no active profile is open. - Reconciler learns AppUpdate.ReplaceProfileFromRotateAndPublishBackup and AppUpdate.PublishProfileBackup; new performPublishBackup helper forwards BackupPublishResult via AppAction.BackupPublishCompleted. - Initial DashboardState literal sets lastBackupPublish = null so the regenerated Kotlin binding compiles end-to-end. Tests + verification: - cargo test --workspace: 147 passed (29 rotate_share_flow tests, +1 new for missing-secret rejection). - cargo fmt --check + cargo clippy --workspace --all-targets -D warnings: clean. - just ios-full: iOS simulator build succeeds for arm64 debug. - just android-full: Android debug build succeeds across all three ABIs (arm64-v8a, armeabi-v7a, x86_64). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…le via keyset.accepted_profile_id CreateKeysetDistributeFinish previously read hub.profiles.first() to find the profile the user just built. Once multiple profiles are stored on a device, that list-order assumption breaks: a previously-restored profile, a rotate-share accept, or any future action that mutates hub order can leave first() pointing at the wrong row, silently marking the wrong profile Active. Add KeysetFlowState.accepted_profile_id and pin it from CreateKeysetAccept (actor-derived) and CreateKeysetAccepted (shell-reported) so the two sides cannot diverge. CreateKeysetDistributeFinish now targets that id explicitly via the same iterate-and-match pattern CreateKeysetAccepted already uses, with a defensive fallback that re-adds the row if the hub list was rebuilt between Accept and Finish. Reset clears the pin so a re-entry starts fresh (VAL-CREATE-021). State-machine tests pin the contract: - keyset_finish_targets_accepted_profile_id_not_hub_first (reproduction: hub has [alice, new_bob], Finish must mark new_bob Active) - keyset_finish_routes_to_dashboard_when_accepted_profile_id_is_set - keyset_finish_falls_back_to_hub_when_accepted_profile_id_unset - keyset_accept_pins_accepted_profile_id - keyset_accepted_keeps_accepted_profile_id_in_sync_with_shell Verified: cargo test --workspace -- --test-threads=9 (152 passed, 0 failed), cargo fmt --check, cargo clippy --workspace -- -D warnings, rmp bindings --check all, xcodebuild iOS simulator Debug, just android-full. New UniFFI field requires regenerating Android Kotlin binding (regenerated and committed); iOS Swift binding is gitignored. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…4 in Android shell
The Android shell's RotateShareConnectScreen pre-filled with
ws://127.0.0.1:8194, which is the iOS Simulator host-loopback value.
On the Android emulator that URL routes into the emulator's own
loopback namespace and never reaches dev-relay, so every Android
user who did not override the form sat on a connection-timeout
that masqueraded as a network problem.
Centralise the Android platform-correct default (10.0.2.2:8194)
through a new RelayDefaults object and route all three call sites
that previously held the literal on disk:
* MainApp.kt OnboardConnectScreen (was already the right value,
refactored for single-source consistency)
* MainApp.kt RotateShareConnectScreen (the actual bug)
* AppManager.kt injectOnboardCredentials fallback
* MainActivity.kt companion-object DEFAULT_RELAY_URL,
now sourced from RelayDefaults.DEFAULT so the Compose forms
and the debug intent path share one constant
The Rust core is intentionally untouched. Its default_relay_url
helper stays ws://127.0.0.1:8194 (a neutral best-effort for
host-side Rust runs) because the platform-correct default lives in
the shell layer; shells override at form initialization, not on
submit, and both platforms' forms remain fully editable.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…sourceId on keyset OutlinedTextField nodes
Compose semantics modifiers on the wrapped OutlinedTextField controls in
the Create Keyset wizard (Generate, Device Profile), the per-row Distribute
share card, and the Rotate Share connect screen now set
testTagsAsResourceId = true alongside the existing testTag value, so the
underlying android.widget.EditText nodes expose the canonical ids as
resource-id attributes to uiautomator and Maestro. Without this flag
Maestro's id selection cannot resolve the field, producing the Compose
TextField text-entry limitation that was blocking keyset validation
flows.
Canonical ids applied to wrapped OutlinedTextField modifiers (all include
testTagsAsResourceId = true in the same semantics block):
CreateKeysetGenerateScreen
- input_group_name
- input_threshold
- input_count
CreateKeysetDeviceProfileScreen
- input_device_name
- input_relays
CreateKeysetDistributeScreen (DistributeShareCard, per row)
- input_label_<shareIdx>
- input_password_<shareIdx>
- input_confirm_password_<shareIdx>
RotateShareConnectScreen
- input_package (renamed from input_rotate_package)
- input_password (renamed from input_rotate_password)
- input_relays (renamed from input_rotate_relay)
The three affected composables also gain
@OptIn(ExperimentalComposeUiApi::class) so the testTagsAsResourceId
property compiles under the current Compose UI 1.6.x experimental API
gating. testTag is invisible to users, no observable product behavior
changes.
Verified:
- ./gradlew :app:assembleDebug builds without errors.
- uiautomator dumps on emulator-5554 captured under
apps/igloo-mobile/library/evidence/mobile-android-textfield-testtags-parity-fix/
show resource-id="input_group_name"|"input_threshold"|"input_count"
on the Generate screen EditText nodes,
"input_device_name"|"input_relays" on the Device Profile screen,
and "input_label_<idx>"|"input_password_<idx>"|"input_confirm_password_<idx>"
on the Distribute share cards.
- cargo fmt --check, cargo clippy -D warnings, and cargo test
--workspace (152 passed, 0 failed) all clean.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…URL scheme Add a debug-gated iOS URL scheme that drives the full Create Keyset wizard to Dashboard completion via xcrun simctl openurl, bypassing Maestro 2.6.0's brittle SwiftUI TextField/Button automation on iOS 26.5. The URL: igloo://test-create-keyset?group_name=X&threshold=N&count=N&device_name=Y&relay=ws://… is parsed in App.swift::onOpenURL → AppManager.testCreateKeyset → AppAction::DiagnosticsCreateKeysetRun. The Rust actor runs frostr_utils::create_keyset inline, builds the OnboardProfileMaterial, populates dashboard.profile_info, inserts an Active hub row pinned by keyset.accepted_profile_id, and emits StoreKeysetCreatedProfile. The shell writes to Keychain via the existing storeKeysetCreatedProfile handler, then auto-dispatches CreateKeysetDistributeFinish so the URL scheme lands on Dashboard without any SwiftUI button-tap. Gating: DEBUG build + IGLOO_KEYSET_DIAGNOSTICS=1 env var. Release builds never expose the URL scheme and a malformed URL is a no-op. Mirrors the existing igloo://test-inject (onboarding) and igloo://test-save-to-dashboard URL schemes. Validation: - 13 new Rust state-machine tests for DiagnosticsCreateKeysetRun covering happy-path side effect, dashboard identity population, accepted_profile_id pin, distribute rows, dashboard route, and invalid-input no-ops (threshold>count, threshold<2, blank group/device/relay, fully-blank inputs). - iOS Maestro flow + validation script + evidence directory under apps/igloo-mobile/library/evidence/mobile-ios-keyset-debug-url-scheme-2026-06-15/. - Headline proof: post-injection dashboard screenshot with the freshly-built ScriptDevice profile, identity block rendering Share /Group Pubkey, and keyset-diagnostics OSLog event captured. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
….DEFAULT on Android The Android Compose `CreateKeysetDeviceProfileScreen` (Step 2 of 4 in the Create Keyset wizard) pre-filled its `input_relays` OutlinedTextField with `ws://127.0.0.1:8194` — the iOS Simulator host-loopback value. The Android emulator cannot reach the host via `127.0.0.1` (it routes into the emulator's own loopback namespace), so every Android user who did not override the form sat on a relay-connection timeout that masqueraded as a network problem. `mobile-android-relay-url-platform-default-fix` (commit `bed0c4c`) added the single-source `RelayDefaults.DEFAULT = "ws://10.0.2.2:8194"` and routed `OnboardConnectScreen`, `RotateShareConnectScreen`, and `MainActivity` through it. This follow-up closes the same path on the Create Keyset wizard's Device Profile step. The shared Rust actor (`AppAction::CreateKeysetGenerationSuccess`) still pre-fills `KeysetFlowState.relays = [default_relay_url()]` so the actor stays platform-neutral; the Android Compose override reads that vector and rewrites the pure iOS-Simulator prefill to `RelayDefaults.DEFAULT` while preserving any user-typed relay URL verbatim. iOS continues to receive `ws://127.0.0.1:8194` from its own shell (`ContentView.swift` `CreateKeysetDeviceProfileView.onAppear`), which is the platform-correct value for the iOS Simulator's shared host loopback namespace. The override lives in Compose, not in the Rust actor, so the previous fix's rationale about platform-neutral defaults remains intact. Validation: - 4 new `RelayDefaultsTest` cases pin the override contract: pure iOS prefill → Android default, empty actor relays → Android default, user-typed list preserved verbatim, mixed (user + iOS) list preserved. Pre-existing 3 cases still pin `DEFAULT = ws://10.0.2.2:8194` and the ws://+port-8194 shape. - `cargo fmt --check`, `cargo clippy --workspace -- -D warnings`, `cargo test --workspace -- --test-threads=9` → 152 passed / 0 failed / 0 ignored. No Rust regression. - `./gradlew :app:testDebugUnitTest --tests com.frostr.igloo.RelayDefaultsTest` → 7 passed / 0 failed / 0 ignored. - `./gradlew :app:assembleDebug` → BUILD SUCCESSFUL (only the pre-existing Kotlin/AGP deprecation warnings from unmodified call sites). Evidence captured at `apps/igloo-mobile/library/evidence/mobile-android-create-keyset-wizard-relay-url-fix-2026-06-16/` because no live Android emulator was available in this worker session; the unit-test + source-level proof above pins the same contract that a follow-up uiautomator dump will surface (`input_relays` text will read `ws://10.0.2.2:8194` instead of `ws://127.0.0.1:8194` per the pre-fix `mobile-android-textfield-testtags-parity-fix/create-keyset-device-profile-scrolled.xml` dump). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…con parity * polish(mobile-android-settings-icon-text-parity): replace the text-only Maintenance/Save/Logout buttons on the Android Settings tab with a MaintenanceRow icon+text composable that mirrors the iOS HStack layout; iconography uses the existing [K]/[L]/[+]/[I]/[R] glyph tokens to stay inside the design vocabulary without taking on a new Material-icons dependency. No functional change to VAL-SET/VAL-ROTATE surface. * polish(mobile-dashboard-header-accessibility-identifiers): pin `dashboard_header_title` and `dashboard_header_subtitle` testTags on both shells so the post-restart / VAL-CROSS-002 validator can confirm post-onboard identity through the full hierarchy without scrolling (orchestrator note after onboarding-and-runtime user-testing round 1). Back buttons on the dashboard header get the same `btn_back_dashboard` identifier on Android as the iOS SwiftUI view already exposes. * feat(mobile-cross-parity-16-view-reachability): add the canonical Maestro flow that visits every view in the 16-view parity inventory through real navigation from the hub (no deep link, no debug URL scheme, no app intent). Paired with the existing `hub-validation.yaml` shell-baseline flow and the new Android-only system-back companion flow. * feat(mobile-cross-parity-android-system-back): dedicated VAL-SHELL-014 companion flow that exercises `pressKey: back` end-to-end. The shared `cross-parity-16-view-reachability.yaml` never invokes `pressKey: back`, so iOS validators no longer risk a system-back step in a shared flow. * docs(mobile-parity-library): cross-parity-16-view-reachability matrix and final-polish-cleanup-notes commit the documented acceptance for the orchestrator notes that landed after settings-and-maintenance, the iOS layout fix, onboarding-and-runtime round 1, and 971e4d2. Existing untracked Maestro flows and services/igloo-demo/entrypoint.sh remain user-owned. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ofile override to actor The Android Create Keyset wizard Device Profile Compose override (commit ee7234e, mobile-android-create-keyset-wizard-relay-url-fix) already rewrote the iOS-Simulator localhost prefill (`ws://127.0.0.1:8194`) to `RelayDefaults.DEFAULT` (Android emulator's `ws://10.0.2.2:8194`) for *display*. It did not sync that override back to the actor's `keyset.relays`, so the Review step kept rendering the stale iOS-Simulator localhost (`ws://127.0.0.1:8194`) even when Device Profile clearly showed `ws://10.0.2.2:8194`. The mismatch violated VAL-CREATE-008 ("each displayed value exactly matches what was entered or generated") and silently poisoned every Android wizard run's relay list. Add a one-shot `LaunchedEffect(Unit)` on first composition of `CreateKeysetDeviceProfileScreen` that dispatches `AppAction.CreateKeysetUpdateRelays([RelayDefaults.DEFAULT])` whenever the Compose-side override fires, so the actor carries the platform-correct value into the Review step and beyond. iOS Compose needs no change: the actor's iOS-Simulator localhost prefill IS its platform-correct value, so the override trigger condition never fires in practice and the sync dispatch stays a no-op there. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ofile_id
Validator report: tapping one hub profile row sometimes opened a
different stored profile's dashboard — the row's openProfile
lambda was being captured from the wrong iteration when the hub
list grew or re-ordered, so the dispatched AppAction.OpenProfile
profile_id did not match the row the user actually tapped.
Root cause: the Android hub iteration was a bare
for (profile in manager.state.hub.profiles) { ProfileRowView(...) }
loop without a stable key(...). Compose's slot table keys those
positional iterations, so when the list grew (insert-at-front from
NEW onboard, OnboardStored, ProfileRestored, CreateKeysetAccept)
the captured `profile` in each row's onClick lambda could lag behind
the visible row, and a tap landed on a position whose onClick
resolved to a stale profile_id. The iOS shell already uses
`ForEach(..., id: \\.profileId)` for the same reason.
Fix: wrap each ProfileRowView in a `key(profile.profileId) { ... }`
block so the slot-table key is the actual profile_id and the
captured lambda always carries the profile the user can see in that
row. Imports `androidx.compose.runtime.key` once near the existing
runtime imports.
Regression coverage: add `open_profile_routes_to_dashboard_with_matching_identity`
to `apps/igloo-mobile/rust/tests/state_machine_tests.rs` that
exercises the actor's OpenProfile -> dashboard.profile_info
round-trip across A -> B -> A taps, asserting identity parity
between the dispatched profile_id and the dashboard
profile_info.{profile_id, device_name}. Combined with the
shell-side slot-table key, this pins the contract from both ends.
Verification: `just android-full` builds clean;
`cargo test --workspace -- --test-threads=9` reports 153 passed
including the new regression test; `cargo fmt --check` and
`cargo clippy --workspace --all-targets -- -D warnings` clean.
Live hub-row -> dashboard identity verified against the demo-stack
on emulator-5554 (bob as test-r2 / carol as test-cross-003); hierarchy
dumps under `apps/igloo-mobile/library/evidence/mobile-android-hub-profile-row-routing-fix-2026-06-16/`
show `dashboard_header_subtitle` correctly swaps between the two
profile_ids (f1e748b7 <-> 4a36d42d) with no cross-contamination. A
focused Maestro flow under `apps/igloo-mobile/flows/hub-profile-row-routing-fix-android.yaml`
and a paired helper script `apps/igloo-mobile/scripts/inject-android-bob-and-carol-two-profiles.sh`
reproduce the precondition plus execution path so future validators
can re-run the row-tap parity evidence without recreating the harness
out of band.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Add comprehensive README.md with architecture, build, test, and limitation docs - Add 30+ Maestro validation flows for iOS and Android - Fix igloo-demo entrypoint to mkdir runtime dir before use - Replace hardcoded demo passwords with Maestro env var references Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Add helper scripts for Maestro, clipboard, URL injection, verification - Add onboard_provisioner_offline.rs integration test - Add services.yaml for mission runtime - Add tools/ and uniffi-bindgen/ directories - Add library/evidence/ to .gitignore (generated test artifacts) - Fix synthetic password in test to avoid false-positive secret detection Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
… Sign Ready path
The round-5 mobile-signer-runtime-console validator observed that the
iOS Start signer tap never advanced SignerStatusCard past Stopped: the
Maestro tap resolved to the outer VStack's identifier instead of the
inner Button, leaving the SwiftUI Button action un-fired even when
the click hit-tested successfully.
Two compounding symptoms were identified and fixed:
1. SignerStatusCard identifier shadowing. The outer VStack carried
.accessibilityIdentifier('signer_status_card'), and iOS 26.5 /
Maestro 2.6.0 forwarded that identifier into the inner Button,
shadowing the Button's own .accessibilityIdentifier('btn_start_signer')
/ .accessibilityIdentifier('btn_stop_signer'). The fix moves the
'signer_status_card' identifier to the visible status Text, keeps
the Button's own identifier (placed before .buttonStyle(.plain) to
match the working pattern in ProfileRow), and adds
.contentShape(RoundedRectangle(...)) so the whole pill stays
hit-testable.
2. Keychain -> simulator file-fallback asymmetry. On iOS Simulator
(DEBUG-only), storeProfile falls back to a file under Application
Support / IglooSimulatorKeychainFallback when Keychain returns
errSecMissingEntitlement (-34018), but loadProfileMaterial only
queried the Keychain. The fixed path also consults the same
fallback file under #if DEBUG && targetEnvironment(simulator),
and deleteProfileMaterial cleans up the fallback file so a
re-onboard after a failed upload does not surface stale material.
Verified end-to-end with scripts/run-focus-ios-signer-start-fix.sh:
the app startSigner invokes FfiApp.start_signer with material_len=713,
rust.startSigner returns true, and Sign Ready becomes visible within
the 60 s envelope (simctl log show + maestro-start.log captured in
apps/igloo-mobile/library/evidence/mobile-signer-runtime-validation-followup-2026-06-16/ios-fix/).
The Android parity leg remains open; scripts/run-focus-android-signer-sign-readiness-recovery.sh
captures the remaining failure (Start tap propagates, relay connects,
alice's PONG yields incoming nonces, but Restoring never advances to
Sign Ready over a 90 s envelope + Stop/Start rearm). The autoping
bootstrap and shell merge logic are unchanged from the prior
mobile-signer-runtime-restoring-readiness-fix; the Android-side
follow-up is documented in library/evidence/mobile-signer-runtime-validation-followup-2026-06-16/README.md
so a later worker can diff the iOS success trace against the Android
snapshot matrix.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
… NostrSdkAdapter in settle-time shim The Android signing-path NostrSdkAdapter was the only place in the rust core that talked to the relay without the explicit settle-window shim that the (previously onboard-only) VerifiedNostrSdkAdapter wrapper already supplied. Without those settle windows the autoping PING fired right after connect()/subscribe() returned, racing the relay pool's background WS handshake on Android and leaving peer_last_seen None even after alice's PONG-with-nonces landed. * Wrap start_signer()'s adapter in igloo_mobile_core::signer::VerifiedNostrSdkAdapter::for_signing with explicit 1 s / 750 ms / 750 ms settle windows after connect / subscribe / publish. * Tag every tracing event with path="signing" so the signing-path timeline is grep-distinguishable from the onboard path. * Add a once-guarded tracing subscriber (EnvFilter honouring RUST_LOG) attached to a custom AndroidLogWriter that pipes tracing events through android_logger into adb logcat under tag "igloo-mobile-rust" so the focused proof scripts can grep for the connect / subscribe / publish / inbound-event breadcrumbs. * Best-effort Android-only URL sanity pass that rewrites ws://localhost:* / ws://127.0.0.1:* (host-loopback aliases that hit the emulator's own loopback instead of the host's 10.0.2.2 alias) to ws://10.0.2.2:* on Android targets; emits an info-level breadcrumb so the rewrite is auditable. * Add focused script apps/igloo-mobile/scripts/run-focus-android-signer-sign-readiness.sh with logcat capture, 5-step Maestro dance, intermediate 5/15/30/45/60 s snapshot loops, Stop -> Start cycle rearm assertion, and a static iOS-vs-Android snapshot matrix diff file. * Add apps/igloo-mobile/rust/tests/signing_path_adapter.rs with 4 assertions covering the new constructor, settle-window constants, default filter shape, and the Android loopback sanity pass-through on non-Android targets. 153 Rust tests pass on the state_machine_tests.rs binary; the wider workspace grows to 251 tests across 10 binaries. Clippy and cargo fmt are clean. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…shed validation worker - Add signer_starting AtomicBool guard to prevent concurrent start_signer invocations (iOS Maestro double-tap race) - Honor caller-supplied relay_url in FfiApp::onboard instead of always using package-embedded relays - Canonicalize ws://localhost to ws://127.0.0.1 on iOS/macOS to avoid IPv6 loopback resolution issues - Add signer_runtime_recovery.rs test covering OnboardProfileMaterial round-trip, backward compatibility, readiness mapping, and status JSON shape - Update focused proof scripts Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…fixes and focused validation scripts - Android: expose CopyableKeyRow and copy-button testTags as resource-ids so Maestro can tap identity_group_pubkey_copy / identity_share_pubkey_copy. - iOS: move CopyableKeyRow accessibilityIdentifier onto the value Text and add accessibilityValue so the 64-hex pubkey is reachable for clipboard verification. - Add run-focus-android-signer-copy-and-ping.sh (VAL-SIGNER-017/018 on Android via OnboardConnect paste-back) and run-focus-ios-signer-copy-keys.sh. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Finish the reconstructed Igloo Mobile RMP mission with native iOS and Android coverage for onboarding, create/load/export, QR display, rotate share, backup publication, settings persistence, and focused validation helpers. Review cleanup: diagnostics-gate the iOS test-inject path before it can start onboarding, keeping release builds and unflagged debug launches inert. Validation: just doctor; cargo fmt --manifest-path rust/Cargo.toml --all --check; cargo test --manifest-path rust/Cargo.toml --workspace; cargo clippy --manifest-path rust/Cargo.toml --workspace --all-targets -- -D warnings; just ios-build; just android-full; ./gradlew :app:testDebugUnitTest; npm --prefix test run test:guards; git diff --check; Android emulator launch smoke; EVIDENCE_DIR=.tmp/... just focus-ios-export.
6a2c493 to
66cb183
Compare
Summary
bifrost-rs->igloo-shared-> browser WASM consumersChild PRs
4a153f3)0852b8c)7678926)badee12)bacc044)d0f2bfa)c52cdcc)Validation
Manual Runtime Wash
ECDH share sent