Skip to content

fix(hosts): draw Pocket faces and chat widgets the same on iOS and Android - #1447

Merged
valentinfernandez1 merged 15 commits into
mainfrom
fix/pocket-face-render-parity
Oct 26, 2026
Merged

valentinfernandez1 merged 15 commits into
mainfrom
fix/pocket-face-render-parity

Conversation

@valentinfernandez1

@valentinfernandez1 valentinfernandez1 commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes paritytech/platform-bugs#97.

What

A Pocket face or chat widget now draws the same on iOS and Android. The protocol docs say how each layout question is answered, and both hosts follow them:

Rule iOS before Android before
Padding goes around a fixed or minimum size inside it as rule
A Box with no alignment places children at the top start centred as rule
Rows and columns add no gap between children about 8 pt as rule
An arrangement only shares out space the stack was given stack stretched arranged inside a minimum size
SpaceAround leaves half a gap at each end equal gaps as rule
An unsized Spacer takes what its stack has left, and keeps its minimum as rule took no space, lost its minimum
A child filling the height of a row with no height matches the tallest child as rule grew to the card
An Effect is as large as its children, placed at the top start, and tints them filled the card, hid the children static tint
A minimum width or height alone is not a size: the node hugs its content a button, image or stack grew to its parent a Spacer with a minimum stopped being flexible
A border is drawn inside the node half outside as rule
A shaped background clips its content as rule not clipped
Cover and None images are cut to the node spilled out as rule
ScaleDown never enlarges enlarged as rule
An image's natural size is one pixel to a point or dp as rule one pixel to a pixel
An image with no size takes its natural size, shrunk to fit filled the width stretched or cropped when larger
An image sized on one side takes the other from its proportions (None: its natural size) as rule, except None grew to the space around it (Fill, Cover)
An image shows a spinner while it loads and an icon when it is missing, in the middle of its own space filled the space around it as rule
Images may be SVG could not read SVG as rule
FgSuccess is the design system's success green brand green as rule

How

  • renderer.rs documents each rule. The two lines the RFC copies are updated to match.
  • iOS: padding moves after the frame, and both node mappers fall back to BoxProps() so the top-start default lives in one place. Rows and columns share one ArrangedChildren view. The Effect draws its shine over the children's bounds. Face images go through the SVG processor the app already uses for remote images, and an image that cannot be read is dropped from the cache so a later draw retries it.
  • Android: a shaped background adds a clip in orderedForCompose, after the border. Rows and columns follow an unsized Spacer with a weighted one, and a row with no height uses IntrinsicSize.Max when a child fills the height. Images are wrapped in a painter that reports their size in dp, and are decoded no larger than the screen. The Effect tint sweeps across its children.
  • Chat widgets use the same renderers, so they change the same way.

Tests

  • iOS: WidgetNodeLayoutTests draws one tree per rule in a window and checks where each colour lands. There are also tests for the Box default, SVG loading, and an unreadable image being retried. Each failed before the fix. The full app test target passes, with IdentityProfileServiceTests skipped because it hangs on main.
  • Android: JsWidgetLayoutTest (instrumented) covers the Spacer, the row height and the natural image size, and JsModifierOrderTest covers the background clip. Each failed before the fix. The products, wallet and chats unit tests, the wallet card test and detekt pass.
  • On the iPhone 16 simulator and a Pixel emulator, 19 probe faces were drawn on both and measured.

The renderer format left padding against a fixed size, a Box without an
alignment, gaps between stack children, Spacer sizing, borders, shaped
backgrounds, image sizes and image formats to each host's toolkit, and
SwiftUI and Compose disagree on most of them. The docs now say how each
is drawn, so both hosts can be held to one answer.
Pocket faces and chat widgets now land where Android puts them:
- Padding goes around a fixed size instead of inside it.
- A Box, and an Effect, with no alignment place children at the top start.
- Rows and columns add no gap between children, and an arrangement only
  shares out space the stack was given; space-around leaves half a gap
  at each end.
- An Effect is as large as its children and tints them, with the
  children keeping the layout and the taps.
- A border is drawn inside the node.
- A button is as wide as its label.
- Images: Cover and None are cut to the node, ScaleDown never enlarges,
  an image with no size takes its natural size, and a missing image
  leaves its space empty.

The devicehood conformance bound moves to 97%: its surface is bars that
now touch, as on Android, so it covers most of the card.
Face images go through the SVG processor the app already uses for remote
images, and an image that cannot be read is dropped from the cache, so a
later draw tries it again rather than drawing nothing for good.

FgSuccess mapped to the brand green while its siblings map to their own
tokens; it now maps to fgSuccess, the green Android draws.
- A Spacer with no size along its stack takes an equal share of what the
  stack has left.
- A row with no height of its own is as tall as its tallest child, so a
  child filling the height matches it instead of the space around.
- A shaped background clips the content to its shape, leaving the
  border whole.
- A picture's natural size is one image pixel to a dp, as iOS draws one
  to a point.
- The Effect tint sweeps across its children, as the protocol says it is
  animated.
@valentinfernandez1 valentinfernandez1 added host-ios Touches the iOS host tree host-android Touches the Android host tree labels Oct 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request touches an app, which is not built by default. Add a label for each build you want:

  • iOS simulator build, ios-simulator-build: builds the iOS app, runs its tests and attaches a build for the iOS Simulator on a Mac.
  • iOS device build, ios-device-build: attaches a signed build that installs on a registered iPhone or iPad.
  • Android build, android-device-build: attaches an APK that installs on an Android phone or an emulator.

Each starts as soon as it is added and follows the branch from then on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation rfc rust Pull requests that update rust code labels Oct 15, 2026
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Bundle size report

Compared with main at ae56b99.

Raw Gzip Brotli
truapi-host 8.07 MiB 5.72 MiB 5.43 MiB
truapi-provider 4.03 MiB 1.27 MiB 960.2 KiB
truapi 6.72 MiB (+1.7 KiB) 951.1 KiB (+857 B, +0.1%) 777.4 KiB (+577 B, +0.1%)
trinity-stack 562.4 KiB 225.5 KiB 186.6 KiB
Total 19.37 MiB (+1.7 KiB) 8.14 MiB (+857 B) 7.31 MiB (+577 B)

WebAssembly modules

Raw Gzip Brotli
truapi-host/wasm/web/truapi_server_bg.wasm 2.96 MiB 1.03 MiB 788.3 KiB
truapi-host/wasm/web/truapi_verifiable_bg.wasm 4.89 MiB 4.64 MiB 4.61 MiB
truapi-provider/truapi_provider_bg.wasm 3.99 MiB 1.26 MiB 952.5 KiB
trinity-stack/wasm/trinity_stack_bg.wasm 525.1 KiB 217.1 KiB 179.3 KiB
Changed files (2)
Raw Gzip Brotli
truapi/playground/codegen/truapi-dts.js 338.1 KiB (+854 B, +0.2%) 57.6 KiB (+417 B, +0.7%) 46.1 KiB (+260 B, +0.6%)
truapi/explorer/codegen/types.js 266.0 KiB (+836 B, +0.3%) 36.8 KiB (+440 B, +1.2%) 29.1 KiB (+317 B, +1.1%)

Commit: 2b7a8ff

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

CI Status: 25 required jobs green, 22 passed and 3 skipped by path filter.

All job results
job result
android-app success
android-bindings success
bundle-size success
changes success
changeset-guard success
cli-package success
codegen success
e2e skipped
explorer success
headless-install success
host-wasm success
ios-app success
ios-bindings success
ios-swift success
licenses success
playground success
provider-android-bindings skipped
release-guard success
rust success
ts-client success
ts-debugger success
ts-host success
ts-trinity-stack success
wasm-provider success
workflow-lint skipped

Signing credentials: valid as of 2026-10-26

Commit 2b7a8ffe · run log

- iOS: a button given a width fills it; an image with no size is shrunk
  to fit the space it is given; an image sized on one side takes the
  other from its proportions instead of growing.
- iOS: the Effect tint covers its children's bounds, as on Android,
  rather than drawing the children a second time as a mask.
- iOS: a missing image's VoiceOver label sits on the node's size, so it
  can be reached.
- Android: only an image drawn at its natural size is decoded whole, and
  a row only measures intrinsic heights when a child fills the height.
- Tests poll for a loaded picture instead of waiting a fixed time, and
  the face conformance check also fails a face left unpainted.
A minimum width or height alone no longer counts as a node's own size:
a button, image or stack with only a minimum is as large as its content.
A Spacer stays flexible unless it has a fixed size along its stack.

Images draw the same on both: one with no size takes its natural size
shrunk to fit, one sized on a single side takes the other from its
proportions, and None keeps its natural size there.

The Android Effect builds its gradient once per size. The test raster
draws over magenta, so a dark surface is not mistaken for nothing
painted, and keeps one running count and bound per colour. The RFC's
other copied doc lines get the rules too.
…r-parity

# Conflicts:
#	hosts/ios/polkadot-appTests/Products/PocketFaceConformanceTests.swift
#	hosts/ios/polkadot-appTests/Products/WidgetNodeRaster.swift
…otocol docs carry

The image sizing rules are one table of cases, the stack gap and
arrangement checks one drawing, and the Effect's size and tint one test.
The Android picture tests share one helper. Host comments that repeated
the rules written in renderer.rs are gone; the ones left say why.
@valentinfernandez1 valentinfernandez1 added ios-simulator-build Build the iOS app, run its tests and attach a simulator build to this pull request android-instrumented-tests Run the Android instrumented tests on this pull request labels Oct 16, 2026
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

iOS simulator preview

Built from 2b7a8ffe0, stamped with it in TrUAPICommit.

gh run download 40354547885 --name simulator-preview-2b7a8ffe0
unzip polkadot-app-*.app.zip
xcrun simctl install booted polkadot-app.app
xcrun simctl launch booted io.parity.polkadotapp.develop

Or download it in a browser, which arrives as a zip wrapping
the .app.zip, so it needs unzipping twice.

An arm64 simulator slice, so it needs an Apple Silicon Mac and does not
install on a device. Kept for 14 days, after which the link stops
resolving and a new push rebuilds it while ios-simulator-build is
set.

@valentinfernandez1
valentinfernandez1 marked this pull request as ready for review October 16, 2026 15:46
@valentinfernandez1
valentinfernandez1 requested a review from a team October 16, 2026 15:46

@alexander-lisovyk alexander-lisovyk 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.

Lgtm, please resolve conflicts

…r-parity

Keep the loading spinner and missing-image icon from #1496 on Android and
draw them on iOS too, centred in the image's own space, in place of the
empty space this branch drew. The loaded picture keeps this branch's
sizing. The iOS image cache takes the SVG options and the per-source
options from #1445.

@TarikGul TarikGul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving to not block.

Comment thread rust/crates/truapi/src/v01/renderer.rs Outdated
…ap picture decodes

A Spacer is laid out at its own minimum and followed by the space it
shares out, so a stack short of room still gives it that minimum, as
iOS does. A row or column with only a minimum size keeps its children at
the start, as iOS does and the protocol docs say. A picture drawn at its
natural size is decoded no larger than the screen and never enlarged.

The layout tests now read pictures from PNG files, so they go through
the same decoding a product's pictures do.
…size

The shared serializer stored a drawn SVG as a PNG at the screen's scale
and read it back at scale 1, so the picture came back three times its
size. Face images now keep their own bytes on disk and are drawn again
from them.
valentinfernandez1 and others added 2 commits October 26, 2026 11:53
…does

Android buttons fill their width since #1480, so iOS does again and the
protocol docs say so.
@valentinfernandez1
valentinfernandez1 added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 0c77379 Oct 26, 2026
87 of 89 checks passed
@valentinfernandez1
valentinfernandez1 deleted the fix/pocket-face-render-parity branch October 26, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

android-instrumented-tests Run the Android instrumented tests on this pull request documentation Improvements or additions to documentation host-android Touches the Android host tree host-ios Touches the iOS host tree ios-simulator-build Build the iOS app, run its tests and attach a simulator build to this pull request rfc rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants