Skip to content

TASK-21683: validate Lottie mascot in native WebViews - #3219

Merged
innolope-dev merged 11 commits into
devfrom
innolope/TASK-21683-lottie-native-testflight
Sep 17, 2026
Merged

innolope-dev merged 11 commits into
devfrom
innolope/TASK-21683-lottie-native-testflight

Conversation

@innolope-dev

Copy link
Copy Markdown
Collaborator

What this does

  • merges the complete Lottie mascot work from feat(mascot): move the mascot to Lottie and drop 6 MB of rasters #2728 onto current dev, preserving the newer setup, carousel, drawer, QR-pay, landing-height, and account-state changes
  • replaces runtime mascot GIF/WebP animations with the shared SVG lottie-web player, including offscreen pause, reduced-motion behavior, one shared animation clock, and load-failure isolation
  • adds /dev/lottie-profile for real Capacitor WebView measurements (1/3/10 instances, rAF timing, slow frames, long tasks, renderer readiness, and device metadata)
  • adds a branch-scoped signed-build mode for this exact branch: iOS uploads a same-version TestFlight build; Android uploads a same-version Play-internal build; both keep only the Lottie profiler, enable WebView inspection, skip production OTA publication, and do not create release/replacement tags

Why this is draft

TASK-21683 requires measurements in the real Android WebView before choosing live Lottie or generated raster output. Desktop profiling is only a proxy. Do not merge this PR until the floor-device and thermal-repeat runs are recorded.

Validation

  • pnpm typecheck (Node 22)
  • pnpm lint (0 errors; existing warnings only)
  • 415 focused Jest assertions across mascot, route gates, native routing, setup, QR-pay, card, claim, account deletion, and invites
  • actionlint for the changed iOS/Android workflows (existing unrelated shellcheck findings ignored)
  • profile-mode pnpm native:build succeeds under Node 22
  • emitted native export contains /dev/lottie-profile and /dev/deferred, while unrelated /dev tools are pruned

Device protocol

See docs/LOTTIE-NATIVE-PROFILE.md. The acceptance case is one walking mascot on a floor Android device before and after a five-minute thermal run; 3 and 10 instances are stress diagnostics. iOS TestFlight gets a waving-chill 1/3-instance sanity run.

0xkkonrad and others added 8 commits August 18, 2026 11:39
The raster mascots cannot scale and cannot be driven at runtime. This adds the
vector player they get replaced with: lottie-web's light SVG build, the ten rigs,
and the vintage stutter applied at runtime rather than baked into the files.

Sizing comes from each pose's ART bounding box, not the 1050x1000 canvas. The
comps carry different per-pose padding, so canvas sizing renders every pose at a
different apparent size — measured at -20% to +23% against the WebP it replaces.
MASCOT_ART_FILL is the single knob for mascot size across the whole app.

One rAF clock drives every instance on the page, gated by IntersectionObserver and
disabled under prefers-reduced-motion. The player and each pose's JSON load through
dynamic imports, so nothing enters the SSR graph and a screen downloads only the
pose it shows.

Call sites still reference the removed barrel exports; they move in the next commit.
Replaces the animated WebP/GIF mascots across all call sites. Most are a direct
swap; a few passed the image URL through a data structure and needed the structure
to carry a pose instead:

- Setup steps now describe an illustration as {src} or {pose}, because three steps
  use a still and five use a mascot, and SetupWrapper renders whichever it gets.
- InvitesPageLayout takes a pose rather than an image URL; both callers only ever
  passed mascots.
- Home carousel CTAs take a mascotPose.

RejectionAssetD3 deliberately stays on rasters. It is drawn with D3 and captured
into a shareable image, and a live Lottie cannot be serialized that way, so the
three poses it uses keep their WebP behind a dedicated barrel export.

Removes 10 GIFs and 7 WebPs. The GIF fallbacks existed only because old WebKit
cannot animate WebP; lottie-web's SVG renderer has no such problem, so
isLegacyWebKit goes with them. getIOSMajorVersion stays — instrumentation-client
still uses it for Sentry replay gating.

Adds the component to the /dev/components showcase with all ten poses, per the
house rule that the showcase carries every component and variant.
… slightly

Measured the rendered artwork height on real screens against the old rasters and
found two problems.

The setup and invites heroes had lost 14-18% of their height. Both had swapped a
square sprite's box for an unrelated dvh height. The sprite was 320x320 and sized
by width with object-contain, so aspect-square restores the box it actually
occupied.

The rest were 3-6% short because MASCOT_ART_FILL sat at the median of the old
per-pose fills rather than the top of the range. At 0.93 no screen ends up smaller
than before while the set still renders at one consistent size.

Verified on 7 screens across 6 mobile widths: landing +5%, careers +2%,
maintenance +2%, setup signup +7%, invalid invite -2%. The two waving-chill setup
heroes remain ~13% shorter because that rig is genuinely wider than the whistling
sprite it replaces, so it trades height for width in the same box.
…e qr-pay mascot

Two findings from review.

The lazy loader had no catch. The app installs a global unhandledrejection handler
that reloads the page on ChunkLoadError, so a mascot chunk failing to fetch — during
a deploy, or on a flaky connection — would reload the screen under the user. That
lands on terminal screens like claim and payment success, discarding their state,
because a decorative asset could not be fetched. The old <img> failed silently; so
does this now.

The qr-pay mascot lost its centring. Its old <Image layout="fill"> made next/image
emit an inline inset:0, which silently overrode the h-32 w-32 classes and stretched
it across the wrapper. The new host honours those classes, so with no insets it fell
back to the wrapper's top-left corner. It now centres explicitly.

Also asserts that every rig is authored on the canvas the art boxes were measured
against. Re-exporting one comp at a different size would otherwise mis-centre that
pose with the whole suite still green. Writing it turned up that 'walking' reaches
16.8 units past the bottom edge mid-stride — harmless, since the art box drives
placement and carries the overflow with it, and the old sprite came off the same
comp.
The mascot came out a different height on nearly every screen.

Two causes. A square box fits the wider of the artwork's two dimensions, and the
poses run 0.55 to 1.21 wide-to-tall, so 'waving-chill' rendered 23% shorter than
'thinking' in the same box — that is the setup landing and finish steps, which
both use it. And the setup hero sized itself as a percentage of a container that
grows and shrinks with the white content panel below, so the same pose came out
smaller on the steps whose panel carries more copy.

The host now takes its aspect from the pose's art box, so a call site can give a
height alone and every pose renders at that height. Two shared constants replace
the per-call-site boxes: MASCOT_HERO_CLASS for the screens the mascot leads (setup
steps, invite, waitlist) and MASCOT_STATE_CLASS for the ones where it sits above a
headline. The invalid-invite screen was on 96px against 128px everywhere else in
that second group; it joins them.

Measured on the setup finish step at 390x844: 220px tall before, 274px after, and
no longer a different size from the invite page next to it.
The mascot still changed size between screens that should have matched.

State screens — the invalid-invite and claim errors, card status, the physical-card
waitlist — drew it at 118px, against 208-301px on the setup steps a user had just
walked through. They carry a headline, a line of copy and a button and nothing else,
so there is room: 12rem puts the mascot in the same family without competing with the
headline. Measured on the invalid-invite screen it goes 118px to 178px, against 89px
for the raster it replaced.

The delete-account modal no longer shares that constant. It is an icon in a fixed 8rem
slot rather than a mascot the screen is built around, so it sizes to the slot.

The claim and payment success screens and the empty state were still on square boxes,
which fit the wider of the artwork's two dimensions and so render a wide pose shorter
than a narrow one. They now give a height and let the pose take its own width, the same
rule the hero and state screens follow.

Hero screens now sit within 3-10px of each other across 375, 390 and 430.
…scroll

Four things review caught.

The careers mascot was sized by width inside a half-width column, so it grew with
the viewport instead of staying put — around 1000px tall on a desktop, against an
intrinsic 320px sprite before. It now takes a height like every other screen.

The landing hero measured its host box to place the peanut's feet 6% into the
headline, but the drawing fills 93% of that box and is centred in it, so the feet
floated above the headline by the leftover padding instead of overlapping it. It
now measures the artwork.

The virtual clock lived in the effect, so it reset every time IntersectionObserver
toggled. Scrolling a mascot out and back restarted its loop, and a loop={false}
one-shot would replay on each re-entry. It survives in a ref now.

The showcase's sizing callout still warned that a height-only class renders nothing,
which stopped being true when the host started taking its aspect from the pose — and
contradicted MASCOT_STATE_CLASS, which is height-only and on five screens.
# Conflicts:
#	src/app/(mobile-ui)/dev/components/page.tsx
#	src/app/(mobile-ui)/dev/home-ctas/page.tsx
#	src/app/(mobile-ui)/qr-pay/page.tsx
#	src/app/maintenance/page.tsx
#	src/assets/mascot/index.ts
#	src/components/Card/ApplicationStatusScreen.tsx
#	src/components/Card/PhysicalCardScreen.tsx
#	src/components/Card/share-asset/RejectionAssetD3.tsx
#	src/components/Claim/Generic/ClaimError.view.tsx
#	src/components/Claim/Link/Onchain/Success.view.tsx
#	src/components/Global/ConfirmInviteModal/index.tsx
#	src/components/Global/EmptyStates/NoDataEmptyState.tsx
#	src/components/Global/NoMoreJailModal/index.tsx
#	src/components/Home/HomeCarouselCTA/CarouselCTA.tsx
#	src/components/Home/HomeCarouselCTA/index.tsx
#	src/components/Invites/InvitesPage.tsx
#	src/components/Invites/InvitesPageLayout.tsx
#	src/components/Invites/JoinWaitlistPage.tsx
#	src/components/Jobs/index.tsx
#	src/components/LandingPage/hero.tsx
#	src/components/Payment/Views/Error.validation.view.tsx
#	src/components/Settings/DeleteAccountButton.tsx
#	src/components/Settings/__tests__/DeleteAccountButton.test.tsx
#	src/components/Setup/Setup.consts.tsx
#	src/components/Setup/components/SetupWrapper.tsx
#	src/features/payments/shared/components/PaymentSuccessView.tsx
@innolope-dev innolope-dev self-assigned this Sep 16, 2026
@notion-workspace

Copy link
Copy Markdown

@vercel

vercel Bot commented Sep 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
peanut-wallet Ready Ready Preview Sep 17, 2026 9:46am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 660228a0-7ed8-44ed-86f0-45df00b1c76f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7997.19 → 8007.82 (+10.63)
Findings: +9 net (+131 new, -122 resolved)

🆕 New findings (131)

  • critical complexity — src/app/(setup)/setup/page.tsx — CC 130, MI 57.38, SLOC 448
  • critical complexity — src/utils/native-routes.ts — CC 123, MI 53.33, SLOC 329
  • critical complexity — src/components/Setup/Views/Residence.tsx — CC 103, MI 61.51, SLOC 303
  • critical complexity — src/hooks/useHomeCarouselCTAs.tsx — CC 62, MI 58.18, SLOC 292
  • critical complexity — src/features/payments/shared/components/PaymentSuccessView.tsx — CC 59, MI 55.48, SLOC 175
  • high hotspot — src/app/(mobile-ui)/qr-pay/page.tsx — 155 commits, +2071/-3576 lines since 6 months ago
  • high hotspot — src/app/(setup)/setup/page.tsx — 47 commits, +670/-338 lines since 6 months ago
  • high method-complexity — src/utils/native-routes.ts:114 — mapDeepLinkPath CC 47 SLOC 135
  • high hotspot — src/hooks/useHomeCarouselCTAs.tsx — 44 commits, +529/-367 lines since 6 months ago
  • high complexity — src/components/Setup/components/SetupWrapper.tsx — CC 40, MI 61.92, SLOC 78
  • high complexity — src/components/Claim/Link/Onchain/Success.view.tsx — CC 39, MI 59.86, SLOC 159
  • high complexity — src/app/(mobile-ui)/dev/lottie-profile/page.tsx — CC 38, MI 63.47, SLOC 148
  • high complexity — src/components/Global/PeanutMascot/index.tsx — CC 38, MI 63.63, SLOC 131
  • high complexity — src/components/Home/HomeCarouselCTA/index.tsx — CC 30, MI 67.49, SLOC 114
  • high complexity — src/components/Card/ApplicationStatusScreen.tsx — CC 18, MI 48.29, SLOC 52
  • medium high-mdd — src/components/Setup/Views/Residence.tsx:32 — ResidenceStep: MDD 209.4 (uses across many lines from declarations)
  • medium high-mdd — src/components/Invites/JoinWaitlistPage.tsx:35 — JoinWaitlistPage: MDD 148.2 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useHomeCarouselCTAs.tsx:84 — useHomeCarouselCTAs: MDD 142.5 (uses across many lines from declarations)
  • medium high-mdd — src/features/payments/shared/components/PaymentSuccessView.tsx:93 — PaymentSuccessView: MDD 108.7 (uses across many lines from declarations)
  • medium high-mdd — src/app/(setup)/setup/page.tsx:58 — SetupPageContent: MDD 104.3 (uses across many lines from declarations)

…and 111 more.

✅ Resolved (122)

  • src/app/(setup)/setup/page.tsx — CC 130, MI 57.4, SLOC 447
  • src/utils/native-routes.ts — CC 118, MI 53.61, SLOC 322
  • src/components/Setup/Views/Residence.tsx — CC 102, MI 61.33, SLOC 299
  • src/hooks/useHomeCarouselCTAs.tsx — CC 62, MI 58.14, SLOC 293
  • src/features/payments/shared/components/PaymentSuccessView.tsx — CC 59, MI 55.47, SLOC 175
  • src/app/(mobile-ui)/qr-pay/page.tsx — 153 commits, +2063/-3568 lines since 6 months ago
  • src/app/(setup)/setup/page.tsx — 46 commits, +669/-336 lines since 6 months ago
  • src/utils/native-routes.ts:113 — mapDeepLinkPath CC 44 SLOC 131
  • src/hooks/useHomeCarouselCTAs.tsx — 43 commits, +522/-361 lines since 6 months ago
  • src/components/Claim/Link/Onchain/Success.view.tsx — CC 39, MI 59.85, SLOC 159
  • src/components/Setup/components/SetupWrapper.tsx — CC 38, MI 61.94, SLOC 79
  • src/components/LandingPage/hero.tsx — CC 31, MI 61.96, SLOC 88
  • src/components/Home/HomeCarouselCTA/index.tsx — CC 30, MI 67.5, SLOC 114
  • src/components/Card/ApplicationStatusScreen.tsx — CC 18, MI 48.54, SLOC 52
  • src/components/Setup/Views/Residence.tsx:33 — ResidenceStep: MDD 209.4 (uses across many lines from declarations)
  • src/components/Invites/JoinWaitlistPage.tsx:36 — JoinWaitlistPage: MDD 148.2 (uses across many lines from declarations)
  • src/hooks/useHomeCarouselCTAs.tsx:82 — useHomeCarouselCTAs: MDD 143.3 (uses across many lines from declarations)
  • src/features/payments/shared/components/PaymentSuccessView.tsx:94 — PaymentSuccessView: MDD 109.7 (uses across many lines from declarations)
  • src/app/(setup)/setup/page.tsx:59 — SetupPageContent: MDD 104.3 (uses across many lines from declarations)
  • src/components/Invites/InvitesPage.tsx:36 — InvitePageContent: MDD 104.2 (uses across many lines from declarations)

…and 102 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Global/PeanutMascot/index.tsx 0.0 8.0 +8.0
src/app/(mobile-ui)/dev/lottie-profile/page.tsx 0.0 6.4 +6.4
src/components/Global/PeanutMascot/PeanutMascot.utils.ts 0.0 4.7 +4.7
src/components/Global/PeanutMascot/PeanutMascot.consts.ts 0.0 3.9 +3.9
src/utils/webkit.utils.ts 4.5 3.1 -1.4
src/assets/mascot/index.ts 5.1 1.4 -3.8

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 7629 ran, 0 failed, 0 skipped, 2.8m

📊 Coverage (unit)

metric %
statements 78.7%
branches 65.8%
functions 72.8%
lines 79.8%
⏱ 10 slowest test cases
time test
🐢 9.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Network failure keeps loading while retries remain, then shows the generic error
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › User KYC not approved fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_VOLUME_NEAR_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_RECENT_REFUND fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › a refused idempotency key tells the user to scan again, not to contact support
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › routes the KYC rejection on its wire code, and does not retry it
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_SOURCE_OVER_MONTHLY_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_USER_NOT_PROVISIONED fails fast with copy that names the real cause
3.1s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
3.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Going offline blames the connection, and reconnecting clears it for the recovered scan
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

🖼 Visual diff — 4 screens moved

8 of 96 shots changed · 88 identical · baseline 404f40e → head 3edacd7

worst % screen widths
15.68% avatar-picker 320, 430
6.12% setup-pending 320, 430
2.66% empty-history 320, 430
1.03% guest-invite 320, 430

job summary · before/after/diff images — artifact

Fixture screenshots, no backend. Advisory — this check never blocks a merge. Posted from the default branch by ds-shots-comment.yml; the report it renders is untrusted data.

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Chip review — no blocking findings — this is not an approval

One major finding: the new iOS profile build cannot resolve its native version from the workflow's shallow checkout.

Findings

  • MAJOR · .github/workflows/ios-release.yml:114 · Fetch full history before resolving the iOS profile version
    A blank profile dispatch reaches release-version.mjs native-floor, but the iOS checkout still uses the default depth of 1. The exact-head App Release iOS run fails on this line with release-version: shallow clone, so it never builds or uploads the TestFlight profiler described by this PR. Set fetch-depth: 0 on the iOS checkout, as the Android workflow already does, and assert that requirement in the workflow test.

  • MAJOR · .github/workflows/android-release.yml:177 · [moonshotai/kimi-k3] Debuggable signed builds share the production version and can be promoted to production
    The profile mode produces a production-signed app (Android: same versionName as the current native floor, uploaded to Play internal; iOS: same marketing version, uploaded to TestFlight) with WebView remote inspection enabled (WEBVIEW_DEBUG / CAPACITOR_DEBUG) and a live /dev/lottie-profile route on the production origin. Because the build reuses the production versionName and the production signing identity, nothing in the artifact itself prevents it from being promoted from Play internal to a higher track or from TestFlight into App Store review — a single mistaken click in Play Console/App Store Connect ships a remotely-inspectable WebView for peanut.me to real users, which on a wallet app exposes anything visible in the WebView (session state, passkey flows, balances) to anyone with device debugging access. Fix: stamp profile builds with a hard distinguisher that makes promotion impossible — e.g. a different applicationId/bundle-id suffix (me.peanut.wallet.lottieprofile), or a different versionCode range / prerelease version string that fails the store's promotion checks, and have the app refuse to boot outside an explicit profile flag baked only into those builds.

Checked clean

  • Pinned head, supplied base, and merge base all match the requested SHAs.
  • Mascot loading, shared animation clock, offscreen pause, reduced-motion behavior, sizing, failure isolation, and migrated UI consumers.
  • Native export pruning, profiler route gates, and deep-link routing keep unrelated production dev routes closed.
  • Android profile release passed at this head, uploaded only to Play internal, skipped production OTA publication, and skipped the replacement tag job.
  • Exact-head typecheck, ESLint, unit, design-system lint, and design-system screenshot checks passed; screen-library publishing failed on Cloudflare token verification outside this diff.

Security review by moonshotai/kimi-k3: 1 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 75a1610ad1d7 · Context: repo, ci · Took 21m

Comment thread .github/workflows/ios-release.yml

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Chip review — no blocking findings — this is not an approval

The iOS history issue is fixed, but the Android profiling artifact remains eligible for production promotion.

Findings

  • MAJOR · .github/workflows/android-release.yml:282 · Make the inspectable Android artifact impossible to promote
    This profile path still builds the production-signed me.peanut.wallet release with the current production versionName, a fresh higher versionCode, and WebView debugging enabled. Restricting inputs.track only chooses the initial Play track; the same internal-test AAB can later be promoted to production. If that happens, users receive an inspectable build containing /dev/lottie-profile, and the shared versionName gives the OTA lane no way to distinguish it from normal binaries. Build the profiler with a non-production application ID/flavor or distribute it through a separate non-promotable app/Internal App Sharing path.

  • MAJOR · .github/workflows/ios-release.yml:323 · [moonshotai/kimi-k3] Inspectable signed iOS build ships under the production version
    For dispatches from the profile branch, CAPACITOR_DEBUG=true is passed into the xcodebuild archive of the production app (same bundle id, signing, and reused marketing version via native-floor), enabling WebView inspection in a TestFlight-signed build. Anyone who obtains the build can attach a Web Inspector to the live WebView and read/modify in-page state on a production-identity wallet app, and because the version matches production the artifact is hard to distinguish from a real release. This is the iOS counterpart of the Android debuggable-build issue (P2). Fix: build profile/TestFlight builds with a distinct bundle id or at minimum a distinct marketing version suffix, and gate CAPACITOR_DEBUG behind a configuration that cannot be promoted or re-signed as production.

Checked clean

  • Exact detached head, merge base, trusted author, base ref, and PR metadata matched the supplied values.
  • P1 is fixed: the iOS checkout now uses fetch-depth 0 before native-floor tag resolution.
  • Reviewed the mascot loader, shared animation clock, resize/intersection/reduced-motion behavior, per-pose assets, and migrated call sites.
  • Reviewed the native profiler gate, static-export pruning, deep-link allowlist, iOS and Android release guards, and upload paths.
  • Exact-head typecheck, lint, unit, screen-test, and native-export CI checks passed; visual captures and the dispatched iOS profile release were still running.
  • A local focused Jest rerun was unavailable because this detached worktree has no installed Jest binary; the exact-head unit CI job passed.
  • Bundled Lottie JSON files contain no remote asset URLs.

Security review by moonshotai/kimi-k3: 1 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 3edacd7c4bdd · Context: repo, ci, web · Took 14m

Comment thread .github/workflows/android-release.yml
@innolope-dev
innolope-dev merged commit ad142d9 into dev Sep 17, 2026
40 of 41 checks passed
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.

2 participants