Skip to content

Native theme fidelity suite + Material 3 fidelity fixes#5274

Open
shai-almog wants to merge 76 commits into
masterfrom
native-theme-fidelity-suite
Open

Native theme fidelity suite + Material 3 fidelity fixes#5274
shai-almog wants to merge 76 commits into
masterfrom
native-theme-fidelity-suite

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

What

Adds a data-driven fidelity test suite (scripts/fidelity-app) that, for every component with a native equivalent, renders the real native OS widget (rasterized off-screen) alongside the CN1 component under the native theme, and measures a per-component similarity score. Routine CI renders only the CN1 side and diffs against committed native goldens; a one-way ratchet (FidelityGate) fails only when a change drops a pair below its baseline.

It then drives the Android Material 3 theme from 94.9% → 96.2% overall fidelity through real framework + theme fixes — every change verified pixel-for-pixel against the native golden, no metric softening.

Framework fixes (each fixes a real Material-fidelity bug)

Fix Effect
FloatingActionButton honors a fabDiameterMM constant (Material's fixed 56dp) instead of the legacy icon*11/4 (~71dp) heuristic FAB 85.7 → 98.5
Tabs.paintAnimatedIndicator reads tabsAnimatedIndicatorThicknessMm as a float (an int read silently dropped "0.45" → a 2×-too-thick indicator) indicator 16px → 7px
New Tabs.paintBottomDivider (opt-in tabsBottomDividerBool) paints the full-width M3 tab divider directly — a CSS border-bottom does not paint on the custom tab-row Container; colour comes from the TabsDivider UIID (light/dark aware) Tabs light 84.9 → 91.5
DefaultLookAndFeel disabled-unchecked checkbox/radio box reads the *UncheckedColorUIID's own .disabled style, so the greyed box outline diverges from the (darker) disabled label text, as Material renders them CheckBox 93.4 → 95.3, Radio 94.2 → 96.0

Plus the tuned native-themes/android-material/theme.css and recompiled shipped .res (Themes/, Ports, JS mirror).

Host tooling

ProcessScreenshots --mode fidelity, RenderFidelityReport, FidelityGate (ratchet), cn1ss.sh helpers, run-{android,ios}-fidelity-tests.sh, and the scripts-fidelity GitHub workflow.

Known limitation — iOS native references blocked

The iOS round cannot yet collect native UIKit references: rendering the native widget inside a ParparVM native method NPEs as soon as it does real UIKit work (a trivial stub delivers cleanly; reproduces identically with or without dispatch_sync, and String-arg/BOOL-return marshal fine — so it is neither a threading nor a marshaling fault). Documented in com_codenameone_fidelity_NativeWidgetFactoryImpl.m. Resolving it needs a ParparVM runtime fix, or rendering the native reference via a PeerComponent + Display.screenshot() instead of a NativeInterface method. The Android off-screen path (View.draw → Bitmap) works fully.

🤖 Generated with Claude Code

shai-almog and others added 2 commits June 24, 2026 06:18
Adds a data-driven fidelity test suite (scripts/fidelity-app) that renders
each component under the native theme alongside the REAL native OS widget
(off-screen rasterized) and measures per-component visual fidelity, gated by
a one-way ratchet vs a committed baseline. Android round raises overall
Material 3 fidelity 94.9% -> 96.2% via real framework fixes (verified pixel
vs the native golden, no metric softening):

- FloatingActionButton: honor a fabDiameterMM theme constant for the Material
  56dp fixed diameter instead of the icon*11/4 (~71dp) heuristic. FAB 85->98.
- Tabs.paintAnimatedIndicator: read tabsAnimatedIndicatorThicknessMm as a
  float (an int read dropped "0.45" -> 2x-too-thick indicator).
- Tabs.paintBottomDivider: new opt-in (tabsBottomDividerBool) full-width M3
  divider painted directly (a border-bottom does not paint on the custom
  tab-row Container); colour from the TabsDivider UIID (light/dark aware).
- DefaultLookAndFeel: disabled-unchecked checkbox/radio box reads the
  *UncheckedColorUIID's own .disabled style, so the greyed box outline can
  differ from the darker disabled label text (Material renders them distinctly).

Theme (native-themes/android-material/theme.css) + recompiled shipped res.

Host tooling: ProcessScreenshots --mode fidelity, RenderFidelityReport,
FidelityGate (ratchet), cn1ss.sh helpers, run-*-fidelity-tests.sh, and the
scripts-fidelity GitHub workflow.

iOS round is blocked: rendering the native UIKit reference inside a ParparVM
native method NPEs whenever it does real UIKit work (a trivial stub delivers;
not a threading or marshaling fault). Documented in the iOS NativeWidgetFactory
impl; needs a ParparVM fix or a PeerComponent+screenshot redesign.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

JavaSE simulator screenshot updates

Compared 11 screenshots: 10 matched, 1 updated.

  • javase-single-native-theme-ios-modern — updated screenshot. Screenshot differs (2200x1400 px, bit depth 8).

    javase-single-native-theme-ios-modern
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 1540x980.
    Full-resolution PNG saved as javase-single-native-theme-ios-modern.png in workflow artifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Native fidelity (Android, Material 3)

48 pairs compared -- median 95.2%, worst 57.4% (Toolbar_normal_dark), 25th pct 94.5%, mean 93.7%.

Distribution -- >=99%: 3 | 95-99%: 25 | 90-95%: 15 | <90%: 5

Component State Appearance Fidelity SSIM mean delta vs base
Toolbar normal dark 57.4% 0.357 14.41 -34.8
Tabs normal light 68.4% 0.678 8.87 -23.2
Tabs normal dark 79.3% 0.663 11.99 -20.2
Dialog normal dark 85.2% 0.803 9.99 -5.8
RaisedButton disabled dark 88.3% 0.891 4.34 -10.4
Dialog normal light 91.2% 0.864 4.46 -2.8
FlatButton normal dark 92.6% 0.928 3.09 -0.3
Button disabled dark 92.6% 0.915 2.58 -1.2
FlatButton normal light 93.1% 0.931 2.63 -1.4
RadioButton normal dark 94.2% 0.957 2.58 -0.8
CheckBox selected dark 94.3% 0.942 3.06 +0.2
RadioButton normal light 94.4% 0.958 2.19 -0.8
RadioButton selected dark 94.5% 0.957 2.85 -1.1
Button normal dark 94.5% 0.940 3.72 -4.0
CheckBox normal dark 94.5% 0.943 3.17 +0.4
CheckBox normal light 94.6% 0.945 2.72 -0.1
CheckBox disabled dark 94.8% 0.946 1.73 -1.8
CheckBox disabled light 94.8% 0.949 1.58 -1.8
RadioButton disabled dark 95.0% 0.957 1.39 -2.1
CheckBox selected light 95.0% 0.944 2.43 -0.5
RadioButton selected light 95.0% 0.958 2.18 -1.1
Switch selected light 95.1% 0.960 1.87 -2.5
Switch selected dark 95.2% 0.960 2.19 -2.2
RadioButton disabled light 95.2% 0.960 1.28 -2.0
Switch disabled dark 95.2% 0.955 1.00 +1.7
Button normal light 95.4% 0.944 2.90 -3.2
Switch normal light 95.6% 0.955 1.71 +1.5
Switch normal dark 95.8% 0.955 1.64 +4.4
TextField disabled dark 96.0% 0.958 0.90 +0.9
RaisedButton normal dark 96.0% 0.943 2.09 -2.7
Switch disabled light 96.2% 0.964 0.72 +5.1
Button disabled light 96.5% 0.953 1.25 -1.5
ProgressBar normal dark 96.6% 0.961 2.40 -3.4
RaisedButton disabled light 96.9% 0.954 0.93 -1.8
RaisedButton normal light 97.0% 0.955 1.65 -1.6
TextField disabled light 97.0% 0.958 0.93 -1.2
ProgressBar normal light 97.0% 0.969 1.81 -3.0
FloatingActionButton normal dark 97.1% 0.952 1.43 -1.9
FloatingActionButton pressed dark 97.1% 0.952 1.43 -1.9
TextField normal dark 97.2% 0.951 2.16 +1.8
TextField normal light 97.2% 0.951 1.92 +1.6
FloatingActionButton normal light 97.7% 0.964 0.68 -1.4
FloatingActionButton pressed light 97.7% 0.964 0.68 -1.4
Toolbar normal light 98.3% 0.977 3.32 +1.9
Slider normal dark 98.6% 0.991 0.75 +1.5
Slider normal light 99.5% 0.992 0.22 -0.3
Slider disabled dark 99.5% 0.992 0.26 -0.3
Slider disabled light 99.5% 0.992 0.22 -0.3

Side-by-side comparisons (worst first)

  • Toolbar_normal_dark -- 57.41% fidelity (SSIM 0.3574) (-34.77 vs baseline)

    native Toolbar_normal_dark cn1 Toolbar_normal_dark
    Left: native widget. Right: Codename One render.

  • Tabs_normal_light -- 68.39% fidelity (SSIM 0.6777) (-23.15 vs baseline)

    native Tabs_normal_light cn1 Tabs_normal_light
    Left: native widget. Right: Codename One render.

  • Tabs_normal_dark -- 79.27% fidelity (SSIM 0.6633) (-20.16 vs baseline)

    native Tabs_normal_dark cn1 Tabs_normal_dark
    Left: native widget. Right: Codename One render.

  • Dialog_normal_dark -- 85.21% fidelity (SSIM 0.8026) (-5.84 vs baseline)

    native Dialog_normal_dark cn1 Dialog_normal_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_dark -- 88.29% fidelity (SSIM 0.8909) (-10.40 vs baseline)

    native RaisedButton_disabled_dark cn1 RaisedButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • Dialog_normal_light -- 91.17% fidelity (SSIM 0.8637) (-2.76 vs baseline)

    native Dialog_normal_light cn1 Dialog_normal_light
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_dark -- 92.58% fidelity (SSIM 0.9278) (-0.27 vs baseline)

    native FlatButton_normal_dark cn1 FlatButton_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_disabled_dark -- 92.64% fidelity (SSIM 0.9153) (-1.20 vs baseline)

    native Button_disabled_dark cn1 Button_disabled_dark
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_light -- 93.13% fidelity (SSIM 0.9313) (-1.36 vs baseline)

    native FlatButton_normal_light cn1 FlatButton_normal_light
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_dark -- 94.16% fidelity (SSIM 0.9565) (-0.78 vs baseline)

    native RadioButton_normal_dark cn1 RadioButton_normal_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_dark -- 94.28% fidelity (SSIM 0.9415) (+0.15 vs baseline)

    native CheckBox_selected_dark cn1 CheckBox_selected_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_light -- 94.40% fidelity (SSIM 0.9580) (-0.75 vs baseline)

    native RadioButton_normal_light cn1 RadioButton_normal_light
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_dark -- 94.47% fidelity (SSIM 0.9565) (-1.09 vs baseline)

    native RadioButton_selected_dark cn1 RadioButton_selected_dark
    Left: native widget. Right: Codename One render.

  • Button_normal_dark -- 94.49% fidelity (SSIM 0.9400) (-3.99 vs baseline)

    native Button_normal_dark cn1 Button_normal_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_dark -- 94.52% fidelity (SSIM 0.9431) (+0.39 vs baseline)

    native CheckBox_normal_dark cn1 CheckBox_normal_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_light -- 94.64% fidelity (SSIM 0.9449) (-0.08 vs baseline)

    native CheckBox_normal_light cn1 CheckBox_normal_light
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_dark -- 94.80% fidelity (SSIM 0.9457) (-1.83 vs baseline)

    native CheckBox_disabled_dark cn1 CheckBox_disabled_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_light -- 94.83% fidelity (SSIM 0.9486) (-1.77 vs baseline)

    native CheckBox_disabled_light cn1 CheckBox_disabled_light
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_dark -- 94.96% fidelity (SSIM 0.9565) (-2.08 vs baseline)

    native RadioButton_disabled_dark cn1 RadioButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_light -- 94.97% fidelity (SSIM 0.9441) (-0.51 vs baseline)

    native CheckBox_selected_light cn1 CheckBox_selected_light
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_light -- 95.03% fidelity (SSIM 0.9579) (-1.10 vs baseline)

    native RadioButton_selected_light cn1 RadioButton_selected_light
    Left: native widget. Right: Codename One render.

  • Switch_selected_light -- 95.07% fidelity (SSIM 0.9598) (-2.46 vs baseline)

    native Switch_selected_light cn1 Switch_selected_light
    Left: native widget. Right: Codename One render.

  • Switch_selected_dark -- 95.15% fidelity (SSIM 0.9596) (-2.22 vs baseline)

    native Switch_selected_dark cn1 Switch_selected_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_light -- 95.17% fidelity (SSIM 0.9597) (-1.95 vs baseline)

    native RadioButton_disabled_light cn1 RadioButton_disabled_light
    Left: native widget. Right: Codename One render.

  • Switch_disabled_dark -- 95.24% fidelity (SSIM 0.9546) (+1.69 vs baseline)

    native Switch_disabled_dark cn1 Switch_disabled_dark
    Left: native widget. Right: Codename One render.

  • Button_normal_light -- 95.38% fidelity (SSIM 0.9444) (-3.16 vs baseline)

    native Button_normal_light cn1 Button_normal_light
    Left: native widget. Right: Codename One render.

  • Switch_normal_light -- 95.63% fidelity (SSIM 0.9545) (+1.53 vs baseline)

    native Switch_normal_light cn1 Switch_normal_light
    Left: native widget. Right: Codename One render.

  • Switch_normal_dark -- 95.84% fidelity (SSIM 0.9548) (+4.42 vs baseline)

    native Switch_normal_dark cn1 Switch_normal_dark
    Left: native widget. Right: Codename One render.

  • TextField_disabled_dark -- 95.96% fidelity (SSIM 0.9584) (+0.88 vs baseline)

    native TextField_disabled_dark cn1 TextField_disabled_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_dark -- 96.00% fidelity (SSIM 0.9432) (-2.67 vs baseline)

    native RaisedButton_normal_dark cn1 RaisedButton_normal_dark
    Left: native widget. Right: Codename One render.

  • Switch_disabled_light -- 96.15% fidelity (SSIM 0.9644) (+5.05 vs baseline)

    native Switch_disabled_light cn1 Switch_disabled_light
    Left: native widget. Right: Codename One render.

  • Button_disabled_light -- 96.45% fidelity (SSIM 0.9526) (-1.55 vs baseline)

    native Button_disabled_light cn1 Button_disabled_light
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_dark -- 96.62% fidelity (SSIM 0.9611) (-3.38 vs baseline)

    native ProgressBar_normal_dark cn1 ProgressBar_normal_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_light -- 96.92% fidelity (SSIM 0.9543) (-1.83 vs baseline)

    native RaisedButton_disabled_light cn1 RaisedButton_disabled_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_light -- 96.98% fidelity (SSIM 0.9545) (-1.62 vs baseline)

    native RaisedButton_normal_light cn1 RaisedButton_normal_light
    Left: native widget. Right: Codename One render.

  • TextField_disabled_light -- 96.98% fidelity (SSIM 0.9584) (-1.22 vs baseline)

    native TextField_disabled_light cn1 TextField_disabled_light
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_light -- 97.03% fidelity (SSIM 0.9694) (-2.97 vs baseline)

    native ProgressBar_normal_light cn1 ProgressBar_normal_light
    Left: native widget. Right: Codename One render.

  • FloatingActionButton_normal_dark -- 97.09% fidelity (SSIM 0.9522) (-1.91 vs baseline)

    native FloatingActionButton_normal_dark cn1 FloatingActionButton_normal_dark
    Left: native widget. Right: Codename One render.

  • FloatingActionButton_pressed_dark -- 97.09% fidelity (SSIM 0.9522) (-1.91 vs baseline)

    native FloatingActionButton_pressed_dark cn1 FloatingActionButton_pressed_dark
    Left: native widget. Right: Codename One render.

  • TextField_normal_dark -- 97.24% fidelity (SSIM 0.9508) (+1.81 vs baseline)

    native TextField_normal_dark cn1 TextField_normal_dark
    Left: native widget. Right: Codename One render.

  • TextField_normal_light -- 97.24% fidelity (SSIM 0.9506) (+1.60 vs baseline)

    native TextField_normal_light cn1 TextField_normal_light
    Left: native widget. Right: Codename One render.

  • FloatingActionButton_normal_light -- 97.73% fidelity (SSIM 0.9636) (-1.39 vs baseline)

    native FloatingActionButton_normal_light cn1 FloatingActionButton_normal_light
    Left: native widget. Right: Codename One render.

  • FloatingActionButton_pressed_light -- 97.73% fidelity (SSIM 0.9636) (-1.39 vs baseline)

    native FloatingActionButton_pressed_light cn1 FloatingActionButton_pressed_light
    Left: native widget. Right: Codename One render.

  • Toolbar_normal_light -- 98.34% fidelity (SSIM 0.9765) (+1.89 vs baseline)

    native Toolbar_normal_light cn1 Toolbar_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_normal_dark -- 98.57% fidelity (SSIM 0.9906) (+1.49 vs baseline)

    native Slider_normal_dark cn1 Slider_normal_dark
    Left: native widget. Right: Codename One render.

  • Slider_normal_light -- 99.50% fidelity (SSIM 0.9920) (-0.30 vs baseline)

    native Slider_normal_light cn1 Slider_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_disabled_dark -- 99.51% fidelity (SSIM 0.9918) (-0.28 vs baseline)

    native Slider_disabled_dark cn1 Slider_disabled_dark
    Left: native widget. Right: Codename One render.

  • Slider_disabled_light -- 99.54% fidelity (SSIM 0.9922) (-0.26 vs baseline)

    native Slider_disabled_light cn1 Slider_disabled_light
    Left: native widget. Right: Codename One render.

@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Android screenshot updates

Compared 139 screenshots: 135 matched, 4 updated.

  • ButtonTheme_dark — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    ButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ButtonTheme_dark.png in workflow artifacts.

  • DialogTheme_dark — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    DialogTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DialogTheme_dark.png in workflow artifacts.

  • DialogTheme_light — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    DialogTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DialogTheme_light.png in workflow artifacts.

  • PaletteOverrideTheme_dark — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    PaletteOverrideTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PaletteOverrideTheme_dark.png in workflow artifacts.

Native Android coverage

  • 📊 Line coverage: 9.08% (9021/99360 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.00% (44362/554765), branch 3.66% (1847/50413), complexity 3.97% (2126/53618), method 6.16% (1722/27959), class 10.05% (398/3961)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 241ms / native 133ms = 1.8x speedup
SIMD float-mul (64K x300) java 191ms / native 74ms = 2.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 205.000 ms
Base64 CN1 decode 251.000 ms
Base64 native encode 965.000 ms
Base64 encode ratio (CN1/native) 0.212x (78.8% faster)
Base64 native decode 750.000 ms
Base64 decode ratio (CN1/native) 0.335x (66.5% faster)
Image encode benchmark status skipped (SIMD unsupported)

- Switch.java: replace a non-ASCII U+2248 with ~ (Android port javac uses
  US-ASCII encoding and failed on it).
- scripts/javase/screenshots: refresh the 7 simulator goldens that shifted with
  the framework/theme changes (rendered on CI Linux to match the test env).
- scripts-fidelity.yml: TEMPORARY seed -- run the Android fidelity suite with
  FIDELITY_UPDATE_GOLDENS=1 + FIDELITY_UPDATE_BASELINE=1 so the native goldens
  and baseline are regenerated on CI's emulator density (the committed ones were
  rendered on a different local emulator, so 50/54 pairs "could not be compared").
  Reverted in a follow-up once the CI-density artifacts are committed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Apple Watch (watchOS / Core Graphics)

Compared 214 screenshots: 182 matched, 32 updated.

  • ButtonTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ButtonTheme_dark.png in workflow artifacts.

  • ButtonTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ButtonTheme_light.png in workflow artifacts.

  • ChatInput_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ChatInput_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatInput_dark.png in workflow artifacts.

  • ChatInput_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ChatInput_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatInput_light.png in workflow artifacts.

  • ChatView_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ChatView_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatView_dark.png in workflow artifacts.

  • ChatView_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ChatView_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatView_light.png in workflow artifacts.

  • CheckBoxRadioTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    CheckBoxRadioTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as CheckBoxRadioTheme_dark.png in workflow artifacts.

  • CheckBoxRadioTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    CheckBoxRadioTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as CheckBoxRadioTheme_light.png in workflow artifacts.

  • DialogTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    DialogTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DialogTheme_dark.png in workflow artifacts.

  • DialogTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    DialogTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DialogTheme_light.png in workflow artifacts.

  • FloatingActionButtonTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    FloatingActionButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as FloatingActionButtonTheme_dark.png in workflow artifacts.

  • FloatingActionButtonTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    FloatingActionButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as FloatingActionButtonTheme_light.png in workflow artifacts.

  • ListTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ListTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ListTheme_dark.png in workflow artifacts.

  • ListTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ListTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ListTheme_light.png in workflow artifacts.

  • MultiButtonTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    MultiButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as MultiButtonTheme_dark.png in workflow artifacts.

  • MultiButtonTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    MultiButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as MultiButtonTheme_light.png in workflow artifacts.

  • PaletteOverrideTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    PaletteOverrideTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PaletteOverrideTheme_dark.png in workflow artifacts.

  • PaletteOverrideTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    PaletteOverrideTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PaletteOverrideTheme_light.png in workflow artifacts.

  • PickerTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    PickerTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PickerTheme_dark.png in workflow artifacts.

  • PickerTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    PickerTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PickerTheme_light.png in workflow artifacts.

  • ShowcaseTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ShowcaseTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ShowcaseTheme_dark.png in workflow artifacts.

  • ShowcaseTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ShowcaseTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ShowcaseTheme_light.png in workflow artifacts.

  • SpanLabelTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    SpanLabelTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as SpanLabelTheme_dark.png in workflow artifacts.

  • SpanLabelTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    SpanLabelTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as SpanLabelTheme_light.png in workflow artifacts.

  • SwitchTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    SwitchTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as SwitchTheme_dark.png in workflow artifacts.

  • SwitchTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    SwitchTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as SwitchTheme_light.png in workflow artifacts.

  • TabsTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    TabsTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as TabsTheme_dark.png in workflow artifacts.

  • TabsTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    TabsTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as TabsTheme_light.png in workflow artifacts.

  • TextFieldTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    TextFieldTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as TextFieldTheme_dark.png in workflow artifacts.

  • TextFieldTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    TextFieldTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as TextFieldTheme_light.png in workflow artifacts.

  • ToolbarTheme_dark — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ToolbarTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ToolbarTheme_dark.png in workflow artifacts.

  • ToolbarTheme_light — updated screenshot. Screenshot differs (416x496 px, bit depth 8).

    ToolbarTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ToolbarTheme_light.png in workflow artifacts.

The native goldens + ratchet baseline are now the ones the seed run regenerated
on CI's own emulator (e.g. Tabs 377x100 vs the local 1039x277), so the fidelity
gate compares like-for-like instead of failing 50/54 pairs on size mismatch.
Removes the temporary FIDELITY_UPDATE_* seed so the job is a real one-way ratchet
again. CI baseline overall fidelity: 96.2%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Apple TV (tvOS / Metal)

Compared 138 screenshots: 105 matched, 33 updated.

  • ButtonTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ButtonTheme_dark
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 1344x756.
    Full-resolution PNG saved as ButtonTheme_dark.png in workflow artifacts.

  • ButtonTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ButtonTheme_light
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 1920x1080.
    Full-resolution PNG saved as ButtonTheme_light.png in workflow artifacts.

  • ChatInput_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ChatInput_dark
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 1920x1080.
    Full-resolution PNG saved as ChatInput_dark.png in workflow artifacts.

  • ChatInput_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ChatInput_light
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 1920x1080.
    Full-resolution PNG saved as ChatInput_light.png in workflow artifacts.

  • ChatView_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ChatView_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 1920x1080.
    Full-resolution PNG saved as ChatView_dark.png in workflow artifacts.

  • ChatView_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ChatView_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 1920x1080.
    Full-resolution PNG saved as ChatView_light.png in workflow artifacts.

  • CheckBoxRadioTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    CheckBoxRadioTheme_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 1920x1080.
    Full-resolution PNG saved as CheckBoxRadioTheme_dark.png in workflow artifacts.

  • CheckBoxRadioTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    CheckBoxRadioTheme_light
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 1920x1080.
    Full-resolution PNG saved as CheckBoxRadioTheme_light.png in workflow artifacts.

  • DialogTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    DialogTheme_dark
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 1344x756.
    Full-resolution PNG saved as DialogTheme_dark.png in workflow artifacts.

  • DialogTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    DialogTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 1344x756.
    Full-resolution PNG saved as DialogTheme_light.png in workflow artifacts.

  • FloatingActionButtonTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    FloatingActionButtonTheme_dark
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 1920x1080.
    Full-resolution PNG saved as FloatingActionButtonTheme_dark.png in workflow artifacts.

  • FloatingActionButtonTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    FloatingActionButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 1920x1080.
    Full-resolution PNG saved as FloatingActionButtonTheme_light.png in workflow artifacts.

  • graphics-partial-flush-clip-escape — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    graphics-partial-flush-clip-escape
    Preview info: JPEG preview quality 40; JPEG preview quality 40; downscaled to 1920x1080.
    Full-resolution PNG saved as graphics-partial-flush-clip-escape.png in workflow artifacts.

  • ListTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ListTheme_dark
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 1920x1080.
    Full-resolution PNG saved as ListTheme_dark.png in workflow artifacts.

  • ListTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ListTheme_light
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 1920x1080.
    Full-resolution PNG saved as ListTheme_light.png in workflow artifacts.

  • MultiButtonTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    MultiButtonTheme_dark
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 1344x756.
    Full-resolution PNG saved as MultiButtonTheme_dark.png in workflow artifacts.

  • MultiButtonTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    MultiButtonTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 1920x1080.
    Full-resolution PNG saved as MultiButtonTheme_light.png in workflow artifacts.

  • PaletteOverrideTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    PaletteOverrideTheme_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 1920x1080.
    Full-resolution PNG saved as PaletteOverrideTheme_dark.png in workflow artifacts.

  • PaletteOverrideTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    PaletteOverrideTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 1920x1080.
    Full-resolution PNG saved as PaletteOverrideTheme_light.png in workflow artifacts.

  • PickerTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    PickerTheme_dark
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 1920x1080.
    Full-resolution PNG saved as PickerTheme_dark.png in workflow artifacts.

  • PickerTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    PickerTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 1920x1080.
    Full-resolution PNG saved as PickerTheme_light.png in workflow artifacts.

  • ShowcaseTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ShowcaseTheme_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 1920x1080.
    Full-resolution PNG saved as ShowcaseTheme_dark.png in workflow artifacts.

  • ShowcaseTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ShowcaseTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 1920x1080.
    Full-resolution PNG saved as ShowcaseTheme_light.png in workflow artifacts.

  • SpanLabelTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    SpanLabelTheme_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 1920x1080.
    Full-resolution PNG saved as SpanLabelTheme_dark.png in workflow artifacts.

  • SpanLabelTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    SpanLabelTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 1920x1080.
    Full-resolution PNG saved as SpanLabelTheme_light.png in workflow artifacts.

  • SwitchTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    SwitchTheme_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 1920x1080.
    Full-resolution PNG saved as SwitchTheme_dark.png in workflow artifacts.

  • SwitchTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    SwitchTheme_light
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 1920x1080.
    Full-resolution PNG saved as SwitchTheme_light.png in workflow artifacts.

  • TabsTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    TabsTheme_dark
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 1344x756.
    Full-resolution PNG saved as TabsTheme_dark.png in workflow artifacts.

  • TabsTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    TabsTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 1344x756.
    Full-resolution PNG saved as TabsTheme_light.png in workflow artifacts.

  • TextFieldTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    TextFieldTheme_dark
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 1920x1080.
    Full-resolution PNG saved as TextFieldTheme_dark.png in workflow artifacts.

  • TextFieldTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    TextFieldTheme_light
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 1920x1080.
    Full-resolution PNG saved as TextFieldTheme_light.png in workflow artifacts.

  • ToolbarTheme_dark — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ToolbarTheme_dark
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 1920x1080.
    Full-resolution PNG saved as ToolbarTheme_dark.png in workflow artifacts.

  • ToolbarTheme_light — updated screenshot. Screenshot differs (3840x2160 px, bit depth 8).

    ToolbarTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 1920x1080.
    Full-resolution PNG saved as ToolbarTheme_light.png in workflow artifacts.

@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

iOS screenshot updates

Compared 136 screenshots: 103 matched, 33 updated.

  • ButtonTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ButtonTheme_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as ButtonTheme_dark.png in workflow artifacts.

  • ButtonTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ButtonTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as ButtonTheme_light.png in workflow artifacts.

  • ChatInput_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ChatInput_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ChatInput_dark.png in workflow artifacts.

  • ChatInput_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ChatInput_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ChatInput_light.png in workflow artifacts.

  • ChatView_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ChatView_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as ChatView_dark.png in workflow artifacts.

  • ChatView_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ChatView_light
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as ChatView_light.png in workflow artifacts.

  • CheckBoxRadioTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    CheckBoxRadioTheme_dark
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as CheckBoxRadioTheme_dark.png in workflow artifacts.

  • CheckBoxRadioTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    CheckBoxRadioTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as CheckBoxRadioTheme_light.png in workflow artifacts.

  • DialogTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    DialogTheme_dark
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as DialogTheme_dark.png in workflow artifacts.

  • DialogTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    DialogTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as DialogTheme_light.png in workflow artifacts.

  • FloatingActionButtonTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    FloatingActionButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as FloatingActionButtonTheme_dark.png in workflow artifacts.

  • FloatingActionButtonTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    FloatingActionButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as FloatingActionButtonTheme_light.png in workflow artifacts.

  • graphics-partial-flush-clip-escape — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-partial-flush-clip-escape
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-partial-flush-clip-escape.png in workflow artifacts.

  • ListTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ListTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ListTheme_dark.png in workflow artifacts.

  • ListTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ListTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ListTheme_light.png in workflow artifacts.

  • MultiButtonTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    MultiButtonTheme_dark
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 825x1789.
    Full-resolution PNG saved as MultiButtonTheme_dark.png in workflow artifacts.

  • MultiButtonTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    MultiButtonTheme_light
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 825x1789.
    Full-resolution PNG saved as MultiButtonTheme_light.png in workflow artifacts.

  • PaletteOverrideTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    PaletteOverrideTheme_dark
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 825x1789.
    Full-resolution PNG saved as PaletteOverrideTheme_dark.png in workflow artifacts.

  • PaletteOverrideTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    PaletteOverrideTheme_light
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as PaletteOverrideTheme_light.png in workflow artifacts.

  • PickerTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    PickerTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as PickerTheme_dark.png in workflow artifacts.

  • PickerTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    PickerTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as PickerTheme_light.png in workflow artifacts.

  • ShowcaseTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ShowcaseTheme_dark
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as ShowcaseTheme_dark.png in workflow artifacts.

  • ShowcaseTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ShowcaseTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as ShowcaseTheme_light.png in workflow artifacts.

  • SpanLabelTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SpanLabelTheme_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as SpanLabelTheme_dark.png in workflow artifacts.

  • SpanLabelTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SpanLabelTheme_light
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as SpanLabelTheme_light.png in workflow artifacts.

  • SwitchTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SwitchTheme_dark
    Preview info: JPEG preview quality 40; JPEG preview quality 40; downscaled to 825x1789.
    Full-resolution PNG saved as SwitchTheme_dark.png in workflow artifacts.

  • SwitchTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SwitchTheme_light
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as SwitchTheme_light.png in workflow artifacts.

  • TabsTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    TabsTheme_dark
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as TabsTheme_dark.png in workflow artifacts.

  • TabsTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    TabsTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as TabsTheme_light.png in workflow artifacts.

  • TextFieldTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    TextFieldTheme_dark
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as TextFieldTheme_dark.png in workflow artifacts.

  • TextFieldTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    TextFieldTheme_light
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as TextFieldTheme_light.png in workflow artifacts.

  • ToolbarTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ToolbarTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ToolbarTheme_dark.png in workflow artifacts.

  • ToolbarTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ToolbarTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ToolbarTheme_light.png in workflow artifacts.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 381 seconds

Build and Run Timing

Metric Duration
Simulator Boot 91000 ms
Simulator Boot (Run) 1000 ms
App Install 15000 ms
App Launch 1000 ms
Test Execution 508000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 119ms / native 3ms = 39.6x speedup
SIMD float-mul (64K x300) java 75ms / native 5ms = 15.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 460.000 ms
Base64 CN1 decode 263.000 ms
Base64 native encode 1566.000 ms
Base64 encode ratio (CN1/native) 0.294x (70.6% faster)
Base64 native decode 698.000 ms
Base64 decode ratio (CN1/native) 0.377x (62.3% faster)
Base64 SIMD encode 88.000 ms
Base64 encode ratio (SIMD/CN1) 0.191x (80.9% faster)
Base64 SIMD decode 109.000 ms
Base64 decode ratio (SIMD/CN1) 0.414x (58.6% faster)
Base64 encode ratio (SIMD/native) 0.056x (94.4% faster)
Base64 decode ratio (SIMD/native) 0.156x (84.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 26.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.115x (88.5% faster)
Image applyMask (SIMD off) 430.000 ms
Image applyMask (SIMD on) 168.000 ms
Image applyMask ratio (SIMD on/off) 0.391x (60.9% faster)
Image modifyAlpha (SIMD off) 235.000 ms
Image modifyAlpha (SIMD on) 303.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.289x (28.9% slower)
Image modifyAlpha removeColor (SIMD off) 363.000 ms
Image modifyAlpha removeColor (SIMD on) 464.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.278x (27.8% slower)

@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

JavaScript port screenshot updates

Compared 133 screenshots: 131 matched, 2 updated.

  • DialogTheme_dark — updated screenshot. Screenshot differs (375x667 px, bit depth 8).

    DialogTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DialogTheme_dark.png in workflow artifacts.

  • DialogTheme_light — updated screenshot. Screenshot differs (375x667 px, bit depth 8).

    DialogTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as DialogTheme_light.png in workflow artifacts.

@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Mac native screenshot updates

Compared 139 screenshots: 107 matched, 32 updated.

  • ButtonTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ButtonTheme_dark.png in workflow artifacts.

  • ButtonTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ButtonTheme_light.png in workflow artifacts.

  • ChatInput_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ChatInput_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatInput_dark.png in workflow artifacts.

  • ChatInput_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ChatInput_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatInput_light.png in workflow artifacts.

  • ChatView_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ChatView_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatView_dark.png in workflow artifacts.

  • ChatView_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ChatView_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ChatView_light.png in workflow artifacts.

  • CheckBoxRadioTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    CheckBoxRadioTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as CheckBoxRadioTheme_dark.png in workflow artifacts.

  • CheckBoxRadioTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    CheckBoxRadioTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as CheckBoxRadioTheme_light.png in workflow artifacts.

  • DialogTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    DialogTheme_dark
    Preview info: JPEG preview quality 40; JPEG preview quality 40.
    Full-resolution PNG saved as DialogTheme_dark.png in workflow artifacts.

  • DialogTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    DialogTheme_light
    Preview info: JPEG preview quality 40; JPEG preview quality 40.
    Full-resolution PNG saved as DialogTheme_light.png in workflow artifacts.

  • FloatingActionButtonTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    FloatingActionButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as FloatingActionButtonTheme_dark.png in workflow artifacts.

  • FloatingActionButtonTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    FloatingActionButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as FloatingActionButtonTheme_light.png in workflow artifacts.

  • ListTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ListTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ListTheme_dark.png in workflow artifacts.

  • ListTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ListTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ListTheme_light.png in workflow artifacts.

  • MultiButtonTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    MultiButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as MultiButtonTheme_dark.png in workflow artifacts.

  • MultiButtonTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    MultiButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as MultiButtonTheme_light.png in workflow artifacts.

  • PaletteOverrideTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    PaletteOverrideTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PaletteOverrideTheme_dark.png in workflow artifacts.

  • PaletteOverrideTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    PaletteOverrideTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PaletteOverrideTheme_light.png in workflow artifacts.

  • PickerTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    PickerTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PickerTheme_dark.png in workflow artifacts.

  • PickerTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    PickerTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as PickerTheme_light.png in workflow artifacts.

  • ShowcaseTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ShowcaseTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ShowcaseTheme_dark.png in workflow artifacts.

  • ShowcaseTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ShowcaseTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ShowcaseTheme_light.png in workflow artifacts.

  • SpanLabelTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    SpanLabelTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as SpanLabelTheme_dark.png in workflow artifacts.

  • SpanLabelTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    SpanLabelTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as SpanLabelTheme_light.png in workflow artifacts.

  • SwitchTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    SwitchTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as SwitchTheme_dark.png in workflow artifacts.

  • SwitchTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    SwitchTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as SwitchTheme_light.png in workflow artifacts.

  • TabsTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    TabsTheme_dark
    Preview info: JPEG preview quality 50; JPEG preview quality 50.
    Full-resolution PNG saved as TabsTheme_dark.png in workflow artifacts.

  • TabsTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    TabsTheme_light
    Preview info: JPEG preview quality 40; JPEG preview quality 40.
    Full-resolution PNG saved as TabsTheme_light.png in workflow artifacts.

  • TextFieldTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    TextFieldTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as TextFieldTheme_dark.png in workflow artifacts.

  • TextFieldTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    TextFieldTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as TextFieldTheme_light.png in workflow artifacts.

  • ToolbarTheme_dark — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ToolbarTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ToolbarTheme_dark.png in workflow artifacts.

  • ToolbarTheme_light — updated screenshot. Screenshot differs (1024x685 px, bit depth 8).

    ToolbarTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ToolbarTheme_light.png in workflow artifacts.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 146 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 53ms / native 3ms = 17.6x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 288.000 ms
Base64 CN1 decode 215.000 ms
Base64 native encode 889.000 ms
Base64 encode ratio (CN1/native) 0.324x (67.6% faster)
Base64 native decode 661.000 ms
Base64 decode ratio (CN1/native) 0.325x (67.5% faster)
Base64 SIMD encode 83.000 ms
Base64 encode ratio (SIMD/CN1) 0.288x (71.2% faster)
Base64 SIMD decode 48.000 ms
Base64 decode ratio (SIMD/CN1) 0.223x (77.7% faster)
Base64 encode ratio (SIMD/native) 0.093x (90.7% faster)
Base64 decode ratio (SIMD/native) 0.073x (92.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 36.000 ms
Image createMask (SIMD on) 29.000 ms
Image createMask ratio (SIMD on/off) 0.806x (19.4% faster)
Image applyMask (SIMD off) 191.000 ms
Image applyMask (SIMD on) 178.000 ms
Image applyMask ratio (SIMD on/off) 0.932x (6.8% faster)
Image modifyAlpha (SIMD off) 142.000 ms
Image modifyAlpha (SIMD on) 125.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.880x (12.0% faster)
Image modifyAlpha removeColor (SIMD off) 184.000 ms
Image modifyAlpha removeColor (SIMD on) 186.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.011x (1.1% slower)

shai-almog and others added 2 commits June 24, 2026 07:32
iOS fidelity native references now render (48 delivered, was 0). The earlier
"ParparVM can't render UIKit in a native method" conclusion was wrong: it was
three mundane MRC (non-ARC) memory bugs in NativeWidgetFactoryImpl.m --

1. knownKind: cached an AUTORELEASED +[NSSet setWithObjects:] in a static, which
   dangled once the autorelease pool drained between native calls; the 2nd call
   derefed freed memory. ParparVM turns that EXC_BAD_ACCESS into a bogus Java NPE
   (which read as "buildAndRender NPEs"). Fixed: -[alloc initWithObjects:] (+1).
2. The rendered NSData was autoreleased and built on the main queue (UIKit layout
   -- e.g. SF-Symbol buttons -- hangs off-main, so the build is dispatch_sync'd to
   main); when dispatch_sync returned, main's pool drained and freed it before the
   EDT's writeToFile. Fixed: -retain it across the boundary, -release after.
3. (UIKit build moved to the main thread to avoid the off-main layout hang.)

Report (RenderFidelityReport): lead with median / worst-pair / 25th-percentile /
distribution buckets instead of a single misleading mean; add a per-pair
percentage table (Fidelity, SSIM, mean-delta, delta-vs-baseline) sorted worst
first; list unscored pairs explicitly; render the side-by-side cards for every
pair worst-first.

Workflow: drop continue-on-error on the iOS job (no longer a blocker); reseed
per-environment goldens (FIDELITY_UPDATE_GOLDENS) while the committed baseline
remains the portable ratchet floor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… app

The off-screen UIKit factory render was bunk: it rasterized DETACHED widgets at
scale=1.0, so a 30pt button was 30px inside a 1087px tile (tiny, wrong size), and
UINavigationBar/UITabBar rendered blank without a window. Replaced it for iOS with
the approach Shai asked for:

- scripts/fidelity-app/ios-native-ref/NativeRef.swift: a standalone native iOS app
  that lays each reference UIKit widget out in a REAL UIWindow and captures it with
  drawHierarchy(afterScreenUpdates:) -- so nav/tab bars render correctly -- at CN1's
  pixel density (so the PNG overlays the CN1 render 1:1, no scaling). Built directly
  with swiftc (no Xcode project) by scripts/build-ios-native-ref.sh, which runs it on
  the simulator and copies the PNGs into the committed iOS goldens.
- run-ios-fidelity-tests.sh: iOS now compares the CN1 render against these COMMITTED
  goldens (generated offline, not same-run) instead of the broken factory native.
- ProcessScreenshots: tolerate a few px of cross-environment rounding (golden 1088 vs
  CN1 1087) by cropping both to their common top-left region before diffing -- a true
  1:1 overlay, never a scale.

Result: all 50 iOS pairs now compare against real, correctly-sized native widgets
(Toolbar was 0% blank -> a real centred-vs-left-aligned title diff). Seeded the iOS
ratchet baseline (mean 62.3%); the low scores are the genuine untuned-iOSModern-theme
gaps to drive up next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

iOS Metal screenshot updates

Compared 140 screenshots: 107 matched, 33 updated.

  • ButtonTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ButtonTheme_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as ButtonTheme_dark.png in workflow artifacts.

  • ButtonTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ButtonTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as ButtonTheme_light.png in workflow artifacts.

  • ChatInput_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ChatInput_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ChatInput_dark.png in workflow artifacts.

  • ChatInput_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ChatInput_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ChatInput_light.png in workflow artifacts.

  • ChatView_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ChatView_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as ChatView_dark.png in workflow artifacts.

  • ChatView_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ChatView_light
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as ChatView_light.png in workflow artifacts.

  • CheckBoxRadioTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    CheckBoxRadioTheme_dark
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as CheckBoxRadioTheme_dark.png in workflow artifacts.

  • CheckBoxRadioTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    CheckBoxRadioTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as CheckBoxRadioTheme_light.png in workflow artifacts.

  • DialogTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    DialogTheme_dark
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as DialogTheme_dark.png in workflow artifacts.

  • DialogTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    DialogTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as DialogTheme_light.png in workflow artifacts.

  • FloatingActionButtonTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    FloatingActionButtonTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as FloatingActionButtonTheme_dark.png in workflow artifacts.

  • FloatingActionButtonTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    FloatingActionButtonTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as FloatingActionButtonTheme_light.png in workflow artifacts.

  • graphics-partial-flush-clip-escape — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-partial-flush-clip-escape
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-partial-flush-clip-escape.png in workflow artifacts.

  • ListTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ListTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ListTheme_dark.png in workflow artifacts.

  • ListTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ListTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ListTheme_light.png in workflow artifacts.

  • MultiButtonTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    MultiButtonTheme_dark
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 825x1789.
    Full-resolution PNG saved as MultiButtonTheme_dark.png in workflow artifacts.

  • MultiButtonTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    MultiButtonTheme_light
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 825x1789.
    Full-resolution PNG saved as MultiButtonTheme_light.png in workflow artifacts.

  • PaletteOverrideTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    PaletteOverrideTheme_dark
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 825x1789.
    Full-resolution PNG saved as PaletteOverrideTheme_dark.png in workflow artifacts.

  • PaletteOverrideTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    PaletteOverrideTheme_light
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as PaletteOverrideTheme_light.png in workflow artifacts.

  • PickerTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    PickerTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as PickerTheme_dark.png in workflow artifacts.

  • PickerTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    PickerTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as PickerTheme_light.png in workflow artifacts.

  • ShowcaseTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ShowcaseTheme_dark
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as ShowcaseTheme_dark.png in workflow artifacts.

  • ShowcaseTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ShowcaseTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as ShowcaseTheme_light.png in workflow artifacts.

  • SpanLabelTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SpanLabelTheme_dark
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as SpanLabelTheme_dark.png in workflow artifacts.

  • SpanLabelTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SpanLabelTheme_light
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as SpanLabelTheme_light.png in workflow artifacts.

  • SwitchTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SwitchTheme_dark
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 825x1789.
    Full-resolution PNG saved as SwitchTheme_dark.png in workflow artifacts.

  • SwitchTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SwitchTheme_light
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as SwitchTheme_light.png in workflow artifacts.

  • TabsTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    TabsTheme_dark
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as TabsTheme_dark.png in workflow artifacts.

  • TabsTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    TabsTheme_light
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as TabsTheme_light.png in workflow artifacts.

  • TextFieldTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    TextFieldTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as TextFieldTheme_dark.png in workflow artifacts.

  • TextFieldTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    TextFieldTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as TextFieldTheme_light.png in workflow artifacts.

  • ToolbarTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ToolbarTheme_dark
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ToolbarTheme_dark.png in workflow artifacts.

  • ToolbarTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ToolbarTheme_light
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as ToolbarTheme_light.png in workflow artifacts.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 201 seconds

Build and Run Timing

Metric Duration
Simulator Boot 81000 ms
Simulator Boot (Run) 1000 ms
App Install 19000 ms
App Launch 3000 ms
Test Execution 451000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 297ms / native 4ms = 74.2x speedup
SIMD float-mul (64K x300) java 101ms / native 8ms = 12.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 763.000 ms
Base64 CN1 decode 372.000 ms
Base64 native encode 2639.000 ms
Base64 encode ratio (CN1/native) 0.289x (71.1% faster)
Base64 native decode 538.000 ms
Base64 decode ratio (CN1/native) 0.691x (30.9% faster)
Base64 SIMD encode 76.000 ms
Base64 encode ratio (SIMD/CN1) 0.100x (90.0% faster)
Base64 SIMD decode 53.000 ms
Base64 decode ratio (SIMD/CN1) 0.142x (85.8% faster)
Base64 encode ratio (SIMD/native) 0.029x (97.1% faster)
Base64 decode ratio (SIMD/native) 0.099x (90.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 35.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.086x (91.4% faster)
Image applyMask (SIMD off) 65.000 ms
Image applyMask (SIMD on) 54.000 ms
Image applyMask ratio (SIMD on/off) 0.831x (16.9% faster)
Image modifyAlpha (SIMD off) 332.000 ms
Image modifyAlpha (SIMD on) 502.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.512x (51.2% slower)
Image modifyAlpha removeColor (SIMD off) 385.000 ms
Image modifyAlpha removeColor (SIMD on) 411.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.068x (6.8% slower)

shai-almog and others added 8 commits June 24, 2026 09:03
The native and CN1 tiles both anchor the widget top-left, but their pixel sizes
can diverge -- a few px of cross-environment rounding (iOS offline goldens), or a
larger native-vs-CN1 tile-geometry gap that flakes between Android emulator runs
(e.g. CN1 320 vs native 377). Failing those as "size_mismatch" broke the gate.
Now both are cropped to their common top-left region and overlaid 1:1 (never a
scale); the structural metric still crops to each widget's content bbox, so an
honest extent difference scores lower rather than erroring. Only a degenerate
overlap (<8px) is an error.

TEMPORARY: FIDELITY_UPDATE_BASELINE=1 on both run steps to reseed the ratchet
baselines on CI under the new comparison (reverted once the baselines are
committed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The old score was the mean colour agreement over all widget-content pixels, so a
large flat region that happened to match -- e.g. a dark nav-bar fill against a
dark tile -- could carry the score into the high 80s even when the actual widget
(the title) was centred in one render and left-aligned at a totally different
font size in the other. "Mostly got points for being black."

Now fidelity = min(fillSim, structSim):
  - fillSim   = mean colour agreement over content pixels (the old term; catches
                wrong fill colours).
  - structSim = the same agreement WEIGHTED BY local-gradient salience SQUARED, so
                flat fills count for ~nothing and the strongest edges -- glyph
                strokes, crisp outlines, separators -- dominate. A mis-placed or
                mis-sized title lands its strokes on the other render's flat fill,
                collapsing this term.
A widget must now agree in BOTH fill AND structure/placement. Effect on the iOS
Toolbar that triggered this: 89.3% -> ~59% (dark) / 36% (light), matching the
independent SSIM (~56%), while genuinely-similar widgets (an off switch, disabled
buttons) stay in the mid-80s. This is stricter for Android too; the CI seed run
reseeds both ratchet baselines under it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per Shai's note that the native toolbar/widgets weren't using the modern look,
the native-reference app now uses the iOS 26 Liquid Glass options:
- buttons: UIButton.Configuration.glass() (tinted action), prominentGlass()
  (filled/CTA -> a real glass capsule), clearGlass() (borderless text button).
- UINavigationBar / UITabBar: standard + scrollEdge appearances configured with
  configureWithDefaultBackground() = the glass material, not the legacy opaque
  fill.
Regenerated the committed iOS goldens. (The glass translucency reads subtly over
the flat reference tile -- its blur only develops over scene content, which we do
not put behind the widget so the diff stays widget-vs-widget -- but the modern
configurations/appearances are now what the reference uses.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Liquid Glass only reveals itself over content behind it, so the glass widgets
(buttons, nav/tab bars) are now rendered over a single committed backdrop --
glass-backdrop.png, a simple smooth diagonal gradient. The SAME PNG is used by
both sides (the native NativeRef app bundles it; the CN1 FidelityDeviceRunner
loads it as the tile background for the glass component ids on iOS), so the only
difference left between the two renders is the glass itself, not the background.

A smooth gradient (no hard edges) is deliberate: it makes the frosted glass
clearly visible while adding almost no gradient "structure", so the
salience-weighted metric keeps scoring the widget difference rather than being
inflated by a matching backdrop. Non-glass widgets and all of Android stay on the
plain tile.

Regenerated the iOS goldens; the CI iOS run reseeds the baseline against them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…; Material 1.13.0

- Regenerate iOS native references on iOS 26 (real Liquid Glass), force 8-bit PNGs
- Slider.paintNativeSlider: iOS continuous-track + soft drop-shadow capsule thumb
- Toolbar circular glass commands, Tabs glass pill, dark-mode glass translucency, disabled fixes
- Honest geometric-mean fidelity metric (fillSim x ssim)
- Bump Android Material 1.12.0 -> 1.13.0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lider/tabs tuning

iOS: bigger toolbar glass circles + white dark glyphs; Button/RaisedButton cn1-pill;
checkbox unchecked plain circle; tabs centered + smaller icons + subtler dark selection;
switch thumb fills track (no ring); slider taller + narrower thumb + disabled translucency;
progressbar 2x height. Android: Material 1.13.0; switch off-thumb x inset; disabled-dark
button translucency; native pressed-state hotspot/state fix. Reseed iOS baseline (iOS 26).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1.13 needs AGP 8.1.1+); refresh JS+JavaSE theme goldens

- scripts-fidelity.yml iOS build: ARCHS=arm64 (x86_64 sim slice fails ParparVM SIMD neon module)
- Material 1.13.0 pulls dynamicanimation:1.1.0 requiring AGP 8.1.1; current build pins 8.1.0 -> revert to 1.12.0 (latest M3 the pipeline supports)
- Refresh 32 JS theme screenshot goldens + JavaSE ios-modern render for the theme changes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Native fidelity (iOS Modern, Metal)

62 pairs compared -- median 92.8%, worst 72.4% (Toolbar_normal_dark), 25th pct 90.4%, mean 92.6%.

Distribution -- >=99%: 0 | 95-99%: 23 | 90-95%: 25 | <90%: 14 | 16 not delivered/missing golden

Component State Appearance Fidelity SSIM mean delta vs base
Toolbar normal dark 72.4% 0.845 16.71 0.0
Toolbar normal light 78.7% 0.895 13.83 -0.3
Tabs normal dark 80.9% 0.881 17.07 -3.9
Tabs normal light 84.0% 0.885 10.83 -1.7
Switch disabled dark 85.4% 0.972 3.12 0.0
FlatButton normal light 86.2% 0.966 3.90 0.0
FlatButton normal dark 86.5% 0.963 3.62 0.0
RadioButton selected dark 87.8% 0.973 2.07 0.0
RadioButton selected light 87.8% 0.974 2.28 0.0
RaisedButton disabled dark 87.8% 0.954 4.42 0.0
RaisedButton disabled light 88.8% 0.954 4.17 0.0
CheckBox normal light 89.8% 0.987 1.13 0.0
RadioButton normal light 89.8% 0.987 1.13 0.0
Spinner normal dark 89.8% 0.870 3.98 0.0
Spinner normal light 90.0% 0.895 5.24 0.0
Switch selected light 90.4% 0.980 1.92 0.0
Switch normal dark 90.8% 0.978 2.07 0.0
Button normal dark 90.9% 0.959 3.95 0.0
Button disabled dark 91.4% 0.957 3.33 0.0
TabsGeom normal dark 91.5% 0.887 15.38 -1.7
Button normal light 91.5% 0.955 3.92 0.0
CheckBox normal dark 91.8% 0.986 0.81 0.0
RadioButton normal dark 91.8% 0.986 0.81 0.0
TabsGeom normal light 91.8% 0.876 11.25 -1.1
Slider disabled dark 92.4% 0.945 2.27 0.0
RaisedButton normal light 92.5% 0.959 3.06 0.0
RaisedButton normal dark 92.5% 0.962 2.89 0.0
CheckBox disabled light 92.6% 0.988 0.69 0.0
RadioButton disabled light 92.6% 0.988 0.69 0.0
Slider normal dark 92.7% 0.946 2.48 0.0
CheckBox disabled dark 92.8% 0.986 0.55 0.0
RadioButton disabled dark 92.8% 0.986 0.55 0.0
Button disabled light 93.4% 0.958 2.65 0.0
Switch selected dark 93.9% 0.980 1.43 0.0
Slider disabled light 94.0% 0.963 1.74 0.0
ProgressBar normal dark 94.4% 0.979 1.55 0.0
CheckBox selected light 94.7% 0.986 1.14 0.0
GlassPanelGrad normal dark 94.9% 0.977 20.61 0.0
Switch normal light 94.9% 0.984 0.87 0.0
Slider normal light 95.1% 0.958 1.58 0.0
GlassPanelGrad normal light 95.2% 0.978 19.06 0.0
TabOne normal light 95.3% 0.942 9.36 0.0
ProgressBar normal light 95.4% 0.984 1.37 0.0
CheckBox selected dark 95.6% 0.986 0.90 0.0
TabOne normal dark 95.7% 0.951 9.42 -0.2
GlassPanelPhoto normal light 96.1% 0.970 8.35 0.0
GlassPanelPhoto normal dark 96.2% 0.972 9.12 0.0
Switch disabled light 96.2% 0.990 0.61 0.0
Dialog normal dark 97.0% 0.948 2.20 0.0
Dialog normal light 97.1% 0.951 2.24 0.0
TextField normal light 97.4% 0.953 2.03 0.0
TextField disabled light 97.5% 0.955 1.97 0.0
TextField normal dark 97.5% 0.954 1.54 0.0
TextField disabled dark 97.6% 0.956 1.47 0.0
GlassPanelGrey normal light 98.4% 0.982 3.78 0.0
GlassPanelGrey normal dark 98.4% 0.982 3.62 0.0
GlassPanelRed normal light 98.4% 0.982 3.96 0.0
GlassPanelRed normal dark 98.6% 0.983 3.48 0.0
GlassIcon normal dark 98.6% 0.984 3.55 0.0
GlassText normal dark 98.6% 0.984 3.52 0.0
GlassIcon normal light 98.7% 0.986 3.54 0.0
GlassText normal light 98.7% 0.986 3.58 0.0

16 pair(s) not scored:

  • GlassCharB_normal_dark -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharB_normal_light -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharG_normal_dark -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharG_normal_light -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharK00_normal_dark -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharK00_normal_light -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharK40_normal_dark -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharK40_normal_light -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharK80_normal_dark -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharK80_normal_light -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharKC0_normal_dark -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharKC0_normal_light -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharKFF_normal_dark -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharKFF_normal_light -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharR_normal_dark -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)
  • GlassCharR_normal_light -- missing_actual (No CN1 render was delivered for this native golden (the test did not run, hung, or the suite crashed before reaching it).)

Side-by-side comparisons (worst first)

  • Toolbar_normal_dark -- 72.41% fidelity (SSIM 0.8454) (-0.02 vs baseline)

    native Toolbar_normal_dark cn1 Toolbar_normal_dark
    Left: native widget. Right: Codename One render.

  • Toolbar_normal_light -- 78.69% fidelity (SSIM 0.8953) (-0.25 vs baseline)

    native Toolbar_normal_light cn1 Toolbar_normal_light
    Left: native widget. Right: Codename One render.

  • Tabs_normal_dark -- 80.85% fidelity (SSIM 0.8813) (-3.86 vs baseline)

    native Tabs_normal_dark cn1 Tabs_normal_dark
    Left: native widget. Right: Codename One render.

  • Tabs_normal_light -- 84.01% fidelity (SSIM 0.8852) (-1.67 vs baseline)

    native Tabs_normal_light cn1 Tabs_normal_light
    Left: native widget. Right: Codename One render.

  • Switch_disabled_dark -- 85.42% fidelity (SSIM 0.9722) (no change)

    native Switch_disabled_dark cn1 Switch_disabled_dark
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_light -- 86.17% fidelity (SSIM 0.9659) (no change)

    native FlatButton_normal_light cn1 FlatButton_normal_light
    Left: native widget. Right: Codename One render.

  • FlatButton_normal_dark -- 86.54% fidelity (SSIM 0.9627) (no change)

    native FlatButton_normal_dark cn1 FlatButton_normal_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_dark -- 87.76% fidelity (SSIM 0.9729) (no change)

    native RadioButton_selected_dark cn1 RadioButton_selected_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_selected_light -- 87.76% fidelity (SSIM 0.9743) (no change)

    native RadioButton_selected_light cn1 RadioButton_selected_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_dark -- 87.82% fidelity (SSIM 0.9541) (no change)

    native RaisedButton_disabled_dark cn1 RaisedButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_disabled_light -- 88.77% fidelity (SSIM 0.9542) (no change)

    native RaisedButton_disabled_light cn1 RaisedButton_disabled_light
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_light -- 89.77% fidelity (SSIM 0.9866) (no change)

    native CheckBox_normal_light cn1 CheckBox_normal_light
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_light -- 89.77% fidelity (SSIM 0.9866) (no change)

    native RadioButton_normal_light cn1 RadioButton_normal_light
    Left: native widget. Right: Codename One render.

  • Spinner_normal_dark -- 89.80% fidelity (SSIM 0.8700) (no change)

    native Spinner_normal_dark cn1 Spinner_normal_dark
    Left: native widget. Right: Codename One render.

  • Spinner_normal_light -- 90.01% fidelity (SSIM 0.8950) (no change)

    native Spinner_normal_light cn1 Spinner_normal_light
    Left: native widget. Right: Codename One render.

  • Switch_selected_light -- 90.38% fidelity (SSIM 0.9798) (no change)

    native Switch_selected_light cn1 Switch_selected_light
    Left: native widget. Right: Codename One render.

  • Switch_normal_dark -- 90.82% fidelity (SSIM 0.9782) (no change)

    native Switch_normal_dark cn1 Switch_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_normal_dark -- 90.93% fidelity (SSIM 0.9586) (no change)

    native Button_normal_dark cn1 Button_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_disabled_dark -- 91.37% fidelity (SSIM 0.9574) (no change)

    native Button_disabled_dark cn1 Button_disabled_dark
    Left: native widget. Right: Codename One render.

  • TabsGeom_normal_dark -- 91.50% fidelity (SSIM 0.8867) (-1.65 vs baseline)

    native TabsGeom_normal_dark cn1 TabsGeom_normal_dark
    Left: native widget. Right: Codename One render.

  • Button_normal_light -- 91.52% fidelity (SSIM 0.9553) (no change)

    native Button_normal_light cn1 Button_normal_light
    Left: native widget. Right: Codename One render.

  • CheckBox_normal_dark -- 91.77% fidelity (SSIM 0.9856) (no change)

    native CheckBox_normal_dark cn1 CheckBox_normal_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_normal_dark -- 91.77% fidelity (SSIM 0.9856) (no change)

    native RadioButton_normal_dark cn1 RadioButton_normal_dark
    Left: native widget. Right: Codename One render.

  • TabsGeom_normal_light -- 91.81% fidelity (SSIM 0.8758) (-1.07 vs baseline)

    native TabsGeom_normal_light cn1 TabsGeom_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_disabled_dark -- 92.44% fidelity (SSIM 0.9448) (no change)

    native Slider_disabled_dark cn1 Slider_disabled_dark
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_light -- 92.46% fidelity (SSIM 0.9594) (no change)

    native RaisedButton_normal_light cn1 RaisedButton_normal_light
    Left: native widget. Right: Codename One render.

  • RaisedButton_normal_dark -- 92.52% fidelity (SSIM 0.9616) (no change)

    native RaisedButton_normal_dark cn1 RaisedButton_normal_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_light -- 92.58% fidelity (SSIM 0.9884) (no change)

    native CheckBox_disabled_light cn1 CheckBox_disabled_light
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_light -- 92.58% fidelity (SSIM 0.9884) (no change)

    native RadioButton_disabled_light cn1 RadioButton_disabled_light
    Left: native widget. Right: Codename One render.

  • Slider_normal_dark -- 92.74% fidelity (SSIM 0.9458) (no change)

    native Slider_normal_dark cn1 Slider_normal_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_disabled_dark -- 92.76% fidelity (SSIM 0.9855) (no change)

    native CheckBox_disabled_dark cn1 CheckBox_disabled_dark
    Left: native widget. Right: Codename One render.

  • RadioButton_disabled_dark -- 92.76% fidelity (SSIM 0.9855) (no change)

    native RadioButton_disabled_dark cn1 RadioButton_disabled_dark
    Left: native widget. Right: Codename One render.

  • Button_disabled_light -- 93.44% fidelity (SSIM 0.9576) (no change)

    native Button_disabled_light cn1 Button_disabled_light
    Left: native widget. Right: Codename One render.

  • Switch_selected_dark -- 93.93% fidelity (SSIM 0.9803) (no change)

    native Switch_selected_dark cn1 Switch_selected_dark
    Left: native widget. Right: Codename One render.

  • Slider_disabled_light -- 94.01% fidelity (SSIM 0.9626) (no change)

    native Slider_disabled_light cn1 Slider_disabled_light
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_dark -- 94.44% fidelity (SSIM 0.9788) (no change)

    native ProgressBar_normal_dark cn1 ProgressBar_normal_dark
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_light -- 94.70% fidelity (SSIM 0.9863) (no change)

    native CheckBox_selected_light cn1 CheckBox_selected_light
    Left: native widget. Right: Codename One render.

  • GlassPanelGrad_normal_dark -- 94.90% fidelity (SSIM 0.9774) (no change)

    native GlassPanelGrad_normal_dark cn1 GlassPanelGrad_normal_dark
    Left: native widget. Right: Codename One render.

  • Switch_normal_light -- 94.94% fidelity (SSIM 0.9844) (no change)

    native Switch_normal_light cn1 Switch_normal_light
    Left: native widget. Right: Codename One render.

  • Slider_normal_light -- 95.13% fidelity (SSIM 0.9583) (no change)

    native Slider_normal_light cn1 Slider_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelGrad_normal_light -- 95.23% fidelity (SSIM 0.9784) (no change)

    native GlassPanelGrad_normal_light cn1 GlassPanelGrad_normal_light
    Left: native widget. Right: Codename One render.

  • TabOne_normal_light -- 95.26% fidelity (SSIM 0.9419) (+0.01 vs baseline)

    native TabOne_normal_light cn1 TabOne_normal_light
    Left: native widget. Right: Codename One render.

  • ProgressBar_normal_light -- 95.41% fidelity (SSIM 0.9836) (no change)

    native ProgressBar_normal_light cn1 ProgressBar_normal_light
    Left: native widget. Right: Codename One render.

  • CheckBox_selected_dark -- 95.63% fidelity (SSIM 0.9857) (no change)

    native CheckBox_selected_dark cn1 CheckBox_selected_dark
    Left: native widget. Right: Codename One render.

  • TabOne_normal_dark -- 95.70% fidelity (SSIM 0.9508) (-0.22 vs baseline)

    native TabOne_normal_dark cn1 TabOne_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassPanelPhoto_normal_light -- 96.08% fidelity (SSIM 0.9696) (no change)

    native GlassPanelPhoto_normal_light cn1 GlassPanelPhoto_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelPhoto_normal_dark -- 96.17% fidelity (SSIM 0.9724) (no change)

    native GlassPanelPhoto_normal_dark cn1 GlassPanelPhoto_normal_dark
    Left: native widget. Right: Codename One render.

  • Switch_disabled_light -- 96.22% fidelity (SSIM 0.9896) (no change)

    native Switch_disabled_light cn1 Switch_disabled_light
    Left: native widget. Right: Codename One render.

  • Dialog_normal_dark -- 96.97% fidelity (SSIM 0.9479) (no change)

    native Dialog_normal_dark cn1 Dialog_normal_dark
    Left: native widget. Right: Codename One render.

  • Dialog_normal_light -- 97.09% fidelity (SSIM 0.9505) (no change)

    native Dialog_normal_light cn1 Dialog_normal_light
    Left: native widget. Right: Codename One render.

  • TextField_normal_light -- 97.35% fidelity (SSIM 0.9532) (no change)

    native TextField_normal_light cn1 TextField_normal_light
    Left: native widget. Right: Codename One render.

  • TextField_disabled_light -- 97.46% fidelity (SSIM 0.9551) (no change)

    native TextField_disabled_light cn1 TextField_disabled_light
    Left: native widget. Right: Codename One render.

  • TextField_normal_dark -- 97.46% fidelity (SSIM 0.9537) (no change)

    native TextField_normal_dark cn1 TextField_normal_dark
    Left: native widget. Right: Codename One render.

  • TextField_disabled_dark -- 97.56% fidelity (SSIM 0.9556) (no change)

    native TextField_disabled_dark cn1 TextField_disabled_dark
    Left: native widget. Right: Codename One render.

  • GlassPanelGrey_normal_light -- 98.38% fidelity (SSIM 0.9819) (no change)

    native GlassPanelGrey_normal_light cn1 GlassPanelGrey_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelGrey_normal_dark -- 98.42% fidelity (SSIM 0.9816) (no change)

    native GlassPanelGrey_normal_dark cn1 GlassPanelGrey_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassPanelRed_normal_light -- 98.43% fidelity (SSIM 0.9823) (no change)

    native GlassPanelRed_normal_light cn1 GlassPanelRed_normal_light
    Left: native widget. Right: Codename One render.

  • GlassPanelRed_normal_dark -- 98.57% fidelity (SSIM 0.9832) (no change)

    native GlassPanelRed_normal_dark cn1 GlassPanelRed_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassIcon_normal_dark -- 98.59% fidelity (SSIM 0.9841) (no change)

    native GlassIcon_normal_dark cn1 GlassIcon_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassText_normal_dark -- 98.59% fidelity (SSIM 0.9837) (no change)

    native GlassText_normal_dark cn1 GlassText_normal_dark
    Left: native widget. Right: Codename One render.

  • GlassIcon_normal_light -- 98.67% fidelity (SSIM 0.9860) (no change)

    native GlassIcon_normal_light cn1 GlassIcon_normal_light
    Left: native widget. Right: Codename One render.

  • GlassText_normal_light -- 98.69% fidelity (SSIM 0.9862) (no change)

    native GlassText_normal_light cn1 GlassText_normal_light
    Left: native widget. Right: Codename One render.

  • GlassCharB_normal_dark -- CN1 render not delivered.

  • GlassCharB_normal_light -- CN1 render not delivered.

  • GlassCharG_normal_dark -- CN1 render not delivered.

  • GlassCharG_normal_light -- CN1 render not delivered.

  • GlassCharK00_normal_dark -- CN1 render not delivered.

  • GlassCharK00_normal_light -- CN1 render not delivered.

  • GlassCharK40_normal_dark -- CN1 render not delivered.

  • GlassCharK40_normal_light -- CN1 render not delivered.

  • GlassCharK80_normal_dark -- CN1 render not delivered.

  • GlassCharK80_normal_light -- CN1 render not delivered.

  • GlassCharKC0_normal_dark -- CN1 render not delivered.

  • GlassCharKC0_normal_light -- CN1 render not delivered.

  • GlassCharKFF_normal_dark -- CN1 render not delivered.

  • GlassCharKFF_normal_light -- CN1 render not delivered.

  • GlassCharR_normal_dark -- CN1 render not delivered.

  • GlassCharR_normal_light -- CN1 render not delivered.

shai-almog and others added 2 commits June 25, 2026 00:23
…line)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pties; drop redundant FQN

The quality gate scans whole files the PR touches, surfacing the fidelity work's
intentional catch-and-default blocks. Enable EmptyCatchBlock allowCommentedBlocks
(its intended escape hatch), comment the bare catches, and shorten an unnecessary
com.codename1.ui.Font FQN in UIManager.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

… changes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
shai-almog and others added 28 commits June 28, 2026 06:28
…absGeom)

The single saturated-gradient glass backdrop made it impossible to separate tab
GEOMETRY from the glass BLEND -- tuning one broke the read on the other. Add
per-spec backdrops (solid hex / blue-green gradient / photo) and two isolation
tests so each concern is measured alone:
- GlassPanel{Grey,Red,Grad,Photo}: a bare glass rectangle (UIVisualEffectView /
  UIGlassEffect native vs a GlassPanel-UIID container in CN1) over four
  backgrounds, isolating the glass blend across solid/gradient/photo.
- TabsGeom: the tab bar over a flat grey backdrop so the glass is a uniform tint
  and only the tab GEOMETRY differs (86% vs the glass-confounded ~77%).

Wiring: backdrop key in fidelity-tests.yaml + FidelitySpecParser + ComponentSpec;
per-spec backdrop painting in FidelityDeviceRunner (CN1) and NativeRef.swift
(goldens); GlassPanel build case in Cn1WidgetRenderer; GlassPanel UIID + goldens
+ baseline entries committed.

These immediately exposed two real bugs to fix next: the CN1 GlassPanel renders
as a small circle instead of a filling rounded rect, and the Tabs pill is too
narrow with the "Featured" label clipping. Pure infra commit; tuning follows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ne icons

Using the new isolation tests to separate concerns:
- Stage 1 (glass fill): the GlassPanel (and the glass-blend read) was rendering as
  a CIRCLE -- cn1-round-border draws an ellipse, not a fixed-radius rect. Switched
  GlassPanel to border-radius:2.8mm. Glass-blend isolation jumped 77->92-96% across
  grey/red/gradient/photo (only photo-dark, 86, lags).
- Stage 2 (geometry, measured on TabsGeom over flat grey so glass is a uniform
  tint): widened the tab pill to native's width (Tab horizontal padding 3.3->3.8mm;
  pill now 774px vs native 772), pulled it to the tile top (TabsContainer top margin
  0.5->0mm; was 16px low), and reduced the selected-capsule vertical inset
  (0.35->0.18mm) so it covers the label instead of clipping it. Tab icons 4.6->4.1mm
  to match the native SF Symbol size (smaller star, no label clip). TabsGeom
  86->90.6; real Tabs light 77->83.

Baselines ratcheted up to lock the gains.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fallback

New optional framework feature: FontImage.createSFOrMaterial(char, Style, size)
renders the real Apple SF Symbol on iOS (UIImage systemImageNamed, tinted +
flattened to an RGBA bitmap, sized from the device scale) and falls back to the
Material icon font elsewhere / when a symbol is missing / when disabled. Gated by
the theme constant iosSFSymbolsBool. A small lookup table maps common Material
icon chars to SF names (star.fill, magnifyingglass, ellipsis, house.fill, etc.).

Plumbing: Display.createSFSymbolImage -> impl (default null) ->
IOSImplementation.createSFSymbolImage -> IOSNative.nativeCreateSFSymbol (native
ObjC in IOSNative.m, GLUIImage peer). The Tabs fidelity renderer calls the new
API for its icons.

NOTE: iosSFSymbolsBool defaults to FALSE in the iOS-modern theme for now. On the
offscreen fidelity metric the rendered SF Symbol currently scores ~2pts BELOW the
tuned Material star (89/91 -> 88 on TabsGeom) -- the native SF render's weight/AA
does not yet pixel-match UIKit's own tab-item icon rasterization, and the Material
font happened to match the golden well. The feature ships for real apps (which get
the authentic Apple glyph on-device); flip the constant on once the SF render is
tuned to beat Material on the metric.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ransform)

Instead of approximating Apple's Liquid Glass with a flat white tint + blur (which
can't be bright-over-plain AND colourful-over-busy at once), MEASURE the real
material and reproduce it. Added GlassChar* capture patches (gray ramp + RGB
primaries) rendered through a real UIVisualEffectView; fitting input->output gives
the exact colour transform (validated <1 LSB on solids AND the gradient):
  out_ch = clamp( (L + (in_ch - L) * SAT) * SCALE + OFFSET )   L = luma(in)
  LIGHT: SAT 1.95, SCALE 0.303, OFFSET 174.3
  DARK : SAT 2.50, SCALE 0.238, OFFSET 28.4

Implemented as Graphics.glassRegion -> impl.glassRegion (default falls back to a
plain blur). iOS applies the transform in the offscreen blur path
(glassMaterialInPlace). Component.internalPaintImpl calls it for backdrop-filter
components when the theme sets glassMaterialBool, picking light/dark params by the
component foreground luma. The glass widgets (GlassPanel/Toolbar/TitleArea/
TabsContainer) drop their flat tint (background transparent) -- the op now
produces the full frosted material.

Result: GlassPanel (pure material, isolated) is now 92-97% across grey/red/
gradient/photo in BOTH light and dark (dark was 86 / broken); real Tabs dark
72.7 -> 78.1. Remaining gap is edge feathering (CN1 blur feathers the rounded
panel edge; native has a crisp edge + faint stroke) and applying the transform
AFTER the blur over busy photos (currently before) -- follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reverse-engineered glass material (f760729) was drawn as a square,
rectangular patch and the bright light material vignetted at the edges --
the rounded/pill shape was lost when the glass UIIDs went background:transparent,
and the Gaussian blur faded to transparency at the patch border, revealing the
backdrop as a dark halo.

- glassRegion gains a cornerRadius param (Graphics/CodenameOneImplementation/
  IOSImplementation). Component derives it from the host border: RoundBorder ->
  capsule (-1), RoundRectBorder -> its corner radius (mm->px). The iOS op masks
  the blurred material to that rounded/pill shape AFTER the blur (a 1px AA
  coverage band), so Tabs reads as a pill and GlassPanel as a rounded rect.
- The iOS op now reads a region PADDED by the blur radius, blurs, then crops back
  to the panel rect (equivalent to a clamp-to-extent blur). Kills the edge
  vignette/feathering that was glaring on the light material.
- Different native glass surfaces use different materials: a UINavigationBar's
  default chrome background is far more transparent than a bare UIGlassEffect
  panel. Material params (sat/scale/offset) are now overridable per-UIID via
  theme constants -- ToolbarGlassScaleDark etc. -> global glassScaleDark ->
  measured panel default. ios-modern gives Toolbar/TitleArea a near-pass-through
  chrome material and the light tab pill a whiter floor.

iOS fidelity (vs f760729 start of this round):
  GlassPanelGrey 90.9->94.0, Red 91.7->94.5, Grad 90.8->93.8, Photo 88.6->91.2,
  Tabs 77.9->81.3 (dark pill 78.1->82.8), Toolbar 78->80.1, TabsGeom 89.7->91.3.
  MEAN 90.7->91.3. Baseline re-recorded; core quality gate clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The light Toolbar/TitleArea chrome read as a translucent colour gradient ("cloud")
rather than native's frosted glass -- too little white floor and sat:1.6 boosting
the backdrop colour blobs. Drop the light material to scale 0.42 / offset 125 /
sat 0.8: a higher white veil, flatter contrast and slight desaturation so the bar
lifts above the backdrop like the native frosted nav bar. Dark chrome unchanged
(already matched). Toolbar 80.1->80.6; nothing else moved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the smallest possible tab-bar reproduction -- a single text-only tab over
flat grey -- to isolate what actually breaks in the Tabs component, stripping
away the SF-vs-Material icon mismatch, multi-tab spacing and the busy-photo glass
confound. CN1 Tabs(one text tab) vs native UITabBar(one text item).

Finding (measured on the goldens):
- Multi-tab GEOMETRY is already correct: TabsGeom CN1 765x165 vs native 771x170.
- TabOne exposed a real centering bug: the single-tab tile was missing from the
  runner's `centered` list, so it landed top-LEFT (cx 138) instead of centered
  (native cx 544). Fixed -> TabOne 93.9 -> 95.4, cx now 542 ~= native 544.
- Remaining TabOne gap is the absent icon: CN1 content-sizes the bar (text-only
  -> h 87) while the native tab bar is a fixed height (h 170). This cannot be
  closed with shared Tab padding without overshooting the already-matching
  multi-tab, and CN1 has no fixed tab-bar-height knob -- a framework follow-up,
  and one that would NOT change real (icon-bearing) tabs, which already match.

So the "Tabs looks far off" perception is glass-over-photo + icon typeface, not
tab geometry. Glass tint, pill shape, text colour and centering all match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n ladder

Building the decomposition Shai asked for -- render each piece individually with
IDENTICAL authored geometry on both sides and drive it to pixel-exact before
adding the next element.

New isolation rungs (flat grey, capsule = radius h/2 both sides, fill tile -1mm):
- GlassText: glass capsule + one centred text label  -> isolates text + glass.
- GlassIcon: glass capsule + one centred icon         -> isolates the glyph.
NativeRef ios_glass_text/ios_glass_icon (UIGlassEffect + centred UILabel/SF
symbol), CN1 GlassText UIID + GlassTextLabel, runner routes them through the
glass-fill (1mm inset) path.

Root-cause fix (helps ALL glass): the glassRegion blur feathered the component
edge, making the glass read ~13px smaller per side than native's crisp panel
(e.g. GlassText capsule 1024x191 vs native 1050x216). Cause: CIGaussianBlur
fades to transparency at the buffer edge, and when the component sits within the
blur radius of the tile edge the fade reaches in. Fix: pad the blur buffer by
3*radius of EDGE-REPLICATED backdrop so the fade is fully contained outside the
component; crop the centre back out. Edges are now crisp and the capsule bbox
matches native within 1-2px.

iOS fidelity:
  GlassText 98.5, GlassIcon 98.0, GlassPanelGrey 94.0->98.4, Red 94.5->98.6,
  Grad 93.8->97.9, Photo 91.2->95.1, TabsGeom 91.3->92.9. MEAN 91.5->92.5.
Remaining glass residual is native's bright specular edge rim (233 vs flat 213
over ~2-4px) -- a Liquid Glass highlight, next. SF vs Material: SF matches a
single centred icon slightly better but loses on UIKit tab-item rasterisation,
so the shipped theme keeps Material (iosSFSymbolsBool false).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-on-glass)

The light tab bar had been over-whitened (TabsContainer offset 200) chasing an
earlier "too transparent" note, which washed the whole bar white and HID the
selected "Featured" sub-capsule -- no contrast. Measured over flat grey
(TabsGeom): native light outer bar 165 vs selected 216 (contrast 51); CN1 was
212/246 (contrast ~34). The tab bar glass is a TRANSLUCENT chrome like the nav
bar, not the opaque panel material, so the backdrop shows through the outer bar
and the brighter white selected capsule pops.

Retuned TabsContainer light glass to 0.45/140/1.4 (outer ~157 vs native 165,
selected ~220 vs 216) and DROPPED the dark override -- the panel-default dark
already matches (outer 110 vs native 116). Tabs light 81.6->83.2, dark 83.9;
TabsGeom 90.7->92.1. Baseline re-recorded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ecular rim

Per research + Shai's observation: native Liquid Glass is NOT a blurred see-through
hole, it's a refractive layer ON TOP -- it bends/lenses the backdrop near the edges
and carries a bright specular edge rim (the "glint"). Our glass had blur + colour
material + a crisp edge but neither effect, so it read as a flat hole cut into the bar.

Added both to glassRegion (new refract/specular params, plumbed Graphics ->
impl -> Component, resolved from glass{Refract,Specular}{Light,Dark} theme constants
with per-UIID override). New applyGlassOptics() uses a rounded-rect SDF for:
- edge refraction: displace the backdrop sample toward centre in the outer 60%,
  quarter-circle profile (1 - sqrt(1 - t^2)) -- magnifies/bends the backdrop like a
  real lens. No-op over flat backdrops, visible over busy content (as in iOS).
- specular rim: bright glint in the outer ~3px, brightest at the top.
- SDF anti-aliased shape mask (replaces the corner-only mask).
Bilinear sampler bases on the integer coord so refract=0 samples exactly (no softening).

The nav bar is edge-to-edge chrome, not a floating pill, so Toolbar/TitleArea set
Refract/Specular 0.

iOS fidelity: GlassText 98.5->99.0, GlassPanelPhoto 95.1->96.4 (refraction over the
photo), GlassPanelGrey/Red/Grad ~98.4-98.8, GlassIcon 98.4. MEAN 92.5->92.6. The
glass now reads as a layer on top, not a hole.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… glyphs

Shai's call was right: we already had Apple's exact glyphs available
(UIImage systemImageNamed), so the only reason SF tab icons looked wrong was a
SIZING BUG in createSFSymbolImage -- and a wrong icon size cascades into broken
tab layout. Two problems, both fixed in IOSNative.m:
- It rendered at the symbol's NATURAL bounding box at the point size, which is
  ~1.2-1.4x bigger than the requested size -> icons overflowed the (Material-tuned)
  tab cells, shoving "More" into the corner.
- SF glyphs have per-symbol heights (ellipsis short, star tall); rendering each at
  its natural height made tab cells different heights and misaligned the row.
Fix: composite each glyph at its NATURAL proportions, vertically centred, into a
canvas of UNIFORM height = the requested size px (downscale-only, never stretched).
Now SF icons are a drop-in uniform icon set, like UIKit lays out tab items.

Tab icon size is now theme-tunable (tabIconSizeMm, read by the renderer) and
calibrated to native: SF star 102x103 vs native 104x106. Shipped the theme with
iosSFSymbolsBool: true so the tabs use the real native glyphs.

iOS fidelity: with the EXACT native glyphs at matched size, Tabs 83.5->83.7,
TabsGeom 91.9 (== Material), GlassIcon 98.4->98.6, MEAN 92.6 -- i.e. Apple's real
glyphs now match at least as well as the tuned Material stand-ins, and look
correct. Baseline re-recorded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shai's observation + research (iOS 26 puts a Liquid Glass capsule selection
indicator BEHIND the selected tab, sitting on a more translucent bar): the glass
material/refraction/specular belongs on the SELECTION, not the outer bar. We had
it inverted -- glass on the bar, a flat white fill on the selection. Move the glass
hero to Tab.selected / SelectedTab (background transparent + backdrop-filter) so the
selected capsule is the frosted glass blob; the bar stays the translucent container.
Static metric is unchanged (brightness is similar), but the structure now matches
iOS and sets up the sliding-capsule selection animation.

Known follow-up: the glass material's light/dark pick is by component fg luma, which
is ambiguous for the accent-blue selected tab -- needs an appearance-based signal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First of the Liquid Glass tab ANIMATIONS we had glossed over. The selection
indicator is no longer a per-tab fill (or a Material underline) -- it's a SINGLE
glass capsule blob behind the selected tab that TWEENS between tabs on selection
change, like iOS 26.

Tabs.paintSelectionCapsule (gated by tabsSelectionCapsuleBool) reuses the proven
indicator Motion (indicatorAnimMotion / startIndicatorAnimation /
registerAnimatedInternal) that already slid the underline, so the capsule
interpolates its x/width frame-by-frame. It is painted BEHIND the tab content
(before super.paint) so the icon/label sit on top, and rendered with the Liquid
Glass material via Graphics.glassRegion when glassMaterialBool is set (translucent
rounded-capsule fallback otherwise). Dark/light is taken from the bar's fg
(the accent-blue selected-tab fg is ambiguous).

theme: tabsSelectionCapsuleBool on, Material underline off (iOS uses the capsule),
per-tab Tab.selected/SelectedTab backgrounds transparent so only the single capsule
shows. Resting state verified via the fidelity suite (capsule lands on the selected
tab; TabsGeom dark 92.9 / light 90.3); the slide follows from the reused motion.

Follow-ups (remaining animations): shrink-on-scroll, tap scale/squish, press settle.
Note: the live on-device glassRegion path still falls back to plain blur (offscreen
shows the full material) -- the moving capsule blurs but doesn't yet re-tint live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… spacing

Settled-state accuracy fix found via the deterministic screenshot diff: the
unselected tabs were shoved +17px right because tab cells were content-sized (the
longer "Featured" label widened its cell). Neither fill-rows (content-sized) nor
plain grid (sizes to the widest cell, overflows + scrolls) gives even cells.

New tabsEqualWidthBool: a NON-scrolling GridLayout so every cell = row width / tab
count, like a native UITabBar -- a long label can't widen its cell. Tab horizontal
padding trimmed (3.8->2.9mm) so the equal cells land on native's ~244px spacing.

Measured (TabsGeom light): Search/More icon centres 537/782 vs native 541/785
(was 557/803, +17) -- now even and matched within ~4px; the diff image confirms
icons/ellipsis now align. Remaining settled residual is a ~7px label/icon vertical
offset and the pill glass edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…=lens)

Shai (right again): the tab backgrounds were inverted vs native. Measured the CLEAN
glass at the top of the pill (above the icons): native light BAR=236 (bright frosted)
vs SELECTED=216 (a slightly darker translucent LENS); I had bar=197 (too translucent)
and selected=241 (too bright) -- the selection looked like the bar should and the bar
looked like the selection should.

Fix: the BAR (TabsContainer) is now the bright frosted surface (light 0.3/198 -> 236;
dark keeps the panel default ~58). The SELECTED capsule is a near-pass-through LENS
that reads a touch darker than the bar -- Tabs.paintSelectionCapsule material is now
theme-tunable (TabSel{Sat,Scale,Offset,Refract,Specular}{Light,Dark}) and set to lens
values. Measured after: light SELECTED 217/BAR 236 (native 216/236); dark SELECTED
88/BAR 58 (native 83/51) -- matched within a few LSB, relationship no longer inverted.
TabsGeom 91.5->92.7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…efraction

Two Shai-spotted issues on the photo backdrop:
1. Bar not translucent enough -- native shows the yellow/purple backdrop bleeding
   through the bar; ours was a uniform frost (scale 0.3 = no colour variance). Raised
   the bar material scale + saturation (light 0.62/sat1.8, dark 0.3/sat2.5) with the
   offset re-solved to keep the grey floor (bar 236/51, selected 216/83 still matched).
   Now light yellow blue=164 vs native 151; dark yellow 77 vs 73.
2. Selected capsule "hole + offset layer" -- the bar AND the lens both refracted
   (global 0.4), so the lens re-refracted the bar's already-displaced backdrop = two
   offset layers. Bar refraction now 0 (it is the flat frost); only the selected lens
   refracts, and subtly (TabSelRefract 0.4->0.2). Capsule reads clean now.

Tabs 83.1->84.2, TabsGeom 92.9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shai: the selected capsule looked like an inset pill with a bright ring, not in
sync with the bar. Root cause via a vertical luma scan: the capsule used the bar's
PADDED inner box (getInnerY/Height), so the TabsContainer 0.5mm padding showed as a
bright bar-frost band (236) above/below the darker lens (216) -- the "ring". Native's
selection reaches the pill's outer edge.

Fix: paintSelectionCapsule now spans the OUTER pill height (inner + top/bottom
padding) minus a hair (tabSelInsetMm, default 0.1mm). Also zeroed the lens refraction
+ specular (TabSel*) -- the refraction pulled the brighter bar into a bright edge ring
and the rim added a glint; the lens is now a flat near-pass-through tint, so its edge
is a clean single boundary like native. Capsule now fills the pill and reads as one
integrated lens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shai: "the pressed button is too small within the hole you left for it." Measured:
CN1 capsule w218 vs native w232, and inset from the pill's left edge (x172 vs 158).
The capsule was sized to the tab's own bounds (inset by the tab margin + bar padding)
instead of the whole cell.

Fix: new capsuleCellBounds() sizes the capsule to the full CELL -- midpoint-to-
midpoint between neighbours -- and hugs the pill's OUTER edge (minus the thin rim)
for the first/last tab, like a native UITabBar. Used for both the resting position
and the slide animation (startIndicatorAnimation), so they stay in sync; the Material
underline still tracks the tab's own bounds. The capsule now fills the Featured area
and reaches the pill edge instead of floating as a small inset pill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shai: "it's all too small -- should fill the area." Measured the whole pill, not
just the capsule: CN1 743x163 vs native 773x172. The capsule itself already matched
(242 vs 238) -- the BAR was undersized, so everything read small.

- Height: Tab vertical padding 0.78->1.0mm -> pill 171 (native 172).
- Width: TabsContainer horizontal end-padding 0.5->1.3mm. This widens the pill at its
  rounded ends WITHOUT moving the tab centres -- the pill grows symmetrically and the
  content shifts by the same amount, cancelling (this is how native's first/last cells
  get their extra width that equal cells don't). Pill 773 == native 773, centres
  542/783 vs native 541/785 still matched.

Pill geometry now matches native: size, edges, even tab spacing, and the selection
capsule fills its cell. Colours bleed through the bar as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… remains)

Shai: the selection reads as a grey "hole" with a gray area around it. Measured the
cause: the selected interior MATCHES native (~222), but the BAR over the busy photo
is too bright/washed -- nat (159,175,101) vs cn1 (198,198,137) -- so the correctly-
coloured selection has too little contrast and reads as recessed. Native's bar is
darker and more colourful there (it shows the backdrop), making the selection pop.

- Bar: scale 0.62->1.0, offset 157->108 (same grey floor 236, more backdrop pass-
  through/colour) and a subtle selection rim (TabSelSpecularLight 0->0.22) so the
  capsule reads as a raised lens rather than a flat grey hole.
- Root limit (documented): the bar can't be darkened to match the photo without
  breaking the matched grey (236) -- CN1's CIGaussianBlur yields a brighter, less
  colourful blurred backdrop than native's UIVisualEffectView (bar reads backdrop
  L~90 vs native L~60 at the same point). Over a flat backdrop (TabsGeom) where the
  blur is moot, bar/selection match exactly (236/216). Closing this needs matching
  the native blur, not a theme value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shai: a thick gray gap between the bar and the floating selected pill. Horizontal
luma scan over grey nailed it: native is a CRISP step (pill edge -> bar 236 ->
selection 216), but CN1 had a ~40px GRADIENT ramp at the pill end -- the selection
capsule's 30px blur smearing the bar->backdrop edge where it reached the pill end
(the capsule sits on the already-blurred bar, so it was re-blurring the edge).

- Capsule blur 30 -> 2.5px (tabSelBlurPx): it only re-tints the bar now, so its edge
  is crisp instead of a 40px feathered gray band.
- Inset the capsule from the pill edge (tabSelInsetMm 0.6) so the bright bar-frost rim
  (236) shows AROUND it like native, rather than the lens covering it to the edge.
- Softer selection rim (TabSelSpecularLight 0.22 -> 0.1) so it reads integrated, not a
  distinct floating pill.

Capsule edge is crisp + inset with the bar rim now, matching native's structure. The
remaining gap (bar over the busy photo slightly bright/less colourful) is the blur-
engine difference documented in c948835.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The selected tab painted a SECOND, smaller pill on top of the glass
selection capsule, leaving the capsule's material as a darker band
around it -- the "gap next to the pill" with no native counterpart.

Root cause: CN1 renders the active tab's RadioButton armed, so the
selected tab picked up Tab.pressed's translucent-white fill
(rgba(255,255,255,0.4) light / 0.22 dark) on top of the capsule.
Pixel-probe confirmed: bar 236 -> capsule material 216 -> a separate
~40% white pill reading 231 over it (102 over a forced-black capsule).

Fixes (theme-only, no CEF):
 - Tab.selected / SelectedTab / Tab.pressed paint NO background; the
   single sliding glass capsule is the only selection visual.
 - Light capsule scale 0.85 -> 0.915 so it lands on native's flat 216
   (was 200); specular 0 keeps it flat (a rim left a darker edge band).
 - Dark capsule scale/offset 0.5/6 -> 1.0/32 so it reads as a LIGHTER
   lens above the dark bar (native 83 over bar 51) instead of an
   inverted darker 31.

Result on the grey isolation tile: capsule now uniform 215 (light) /
83 (dark) edge-to-edge, matching native 216 / 83 -- no inner pill, no
ring. Tabs photo 83.8->85.5%, TabsGeom 92.8->93.6%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tar)

The Featured star read clearly smaller and lower than native's. Root
cause: the SF render shrank every glyph to a uniform slot = the nominal
`size`, so a tall glyph (star.fill, ~1.15x the nominal) was downscaled
while a UITabBar renders each symbol at its TRUE per-symbol extent (star
taller than the magnifier). Probing confirmed CN1's downscale-to-slot
flattened the star:magnifier size ratio that native preserves.

Make the SF icon slot theme-tunable so a native-style tab bar can give a
tall glyph a full-height slot instead of shrinking it:
 - IOSNative.nativeCreateSFSymbol + IOSImplementation.createSFSymbolImage:
   the int[] now also carries a uniform SLOT height (% of size) and a
   vertical BIAS (%) for the glyph in that slot. Defaults 100/50 reproduce
   the legacy centred, downscale-to-fit behaviour exactly, so no other SF
   icon (e.g. GlassIcon) changes.
 - theme: iosSFSlotPct 115 (slot tall enough that star.fill is NOT
   downscaled) + tabIconSizeMm 4.1 -> 3.75 so the natural star lands on
   native's 106px and the magnifier on 92px (both matched); Tab padding
   0.8/1.0 -> 0.65/1.35 (top/bottom, same total so pill height is
   unchanged) lifts the icon+label block to native's vertical position.

Result (same build): star top 9px-low -> 3px-low, magnifier/ellipsis/label
within ~1px, icon SIZES now match native. TabsGeom holds ~93%, Tabs photo
85.0->85.4; GlassIcon unaffected (98.9/99.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render the backdrop-filter glass material on a RUNNING iOS app, not just
offscreen fidelity tiles. glassScreenRegionX (METALView) ports the proven
offscreen recipe into the live Metal drain: 3*radius edge-replicated
padding, the affine colour material, a (triple box) blur, and the
rounded-rect SDF mask + edge refraction + specular rim. glassRegion's live
branch now queues a GlassRegion op (nativeGlassScreenRegion) carrying the
full material params instead of falling back to a plain blur.

Make the fidelity suite HONEST: it captures the real on-screen render
(Display.screenshot of the live form) instead of an offscreen
paintComponent re-render that masked the live-glass gap (a false green --
passing while the running app showed no glass). forceScreenRenderForCapture
now drives a frame on the simulator (was desktop-only), so the static-form
screenTexture is current and the live glass is actually captured.

Live-op correctness/perf:
- scale = contentScaleFactor / scaleValue (the fidelity app runs CN1
  logical == physical pixels, scaleValue=3); raw contentScaleFactor
  triple-scaled the screenTexture region and the blur radius.
- triple box blur (radius-independent) replaces a true-Gaussian kernel that
  was hundreds of ms per call and timed the suite out.

Theme: Toolbar/TitleArea glass tuned toward native's near-transparent
chrome (lower white/dark offset floor, blur 64 -> 32); RaisedButton ->
solid accent pill (matches the native prominent button).

Honest live fidelity: GlassPanels/Text/Icon 94-99%, RaisedButton ~92%,
median 93.2%, mean 92.7%. Baseline re-recorded from the live capture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FlatButton dark was a charcoal capsule (white text selected the dark
  "charcoal" glass material); the native flat glass button is a light cream
  pill with DARK text in BOTH appearances. Match the light variant. 82% -> 86.5%.
- Toolbar/TitleArea: blur 64 -> 32 (64px over the already-soft backdrop
  over-smoothed the bar into a washed band) and restore the dark saturation
  the previous pass cut; the bar colour now tracks the backdrop like the
  native translucent nav bar (mean delta 23 -> 17 dark, 17 -> 14 light).

Baseline re-recorded from the live capture. Median 93.2%, mean 92.7%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A native nav bar's glass fades into the content below; CN1's glass region
stopped at a hard rectangular edge, leaving a visible line at the bar's
bottom that no colour tuning could remove. glassApplyOptics now ramps the
glass alpha down over the bottom ~22% for cornerRadius==0 regions
(Toolbar/TitleArea), so the blurred bar blends into the backdrop beneath it
like native. Capsules (-1) and rounded panels (>0) keep their crisp shape.

Mean delta 17.2 -> 16.7 (dark), 14.5 -> 13.8 (light); the crop-based fidelity
% is structurally capped on this nav-bar-over-backdrop tile, but the render
now matches native's seamless bar bottom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The iOS-Modern (Liquid Glass) Tabs selection is now a genuine magnifying
glass DROP that morphs between tabs, matching the iOS 26 UITabBar effect,
instead of a flat translucent pill.

Effect (Tabs.paintSelectionCapsule -> Graphics.lensRegion -> per-port op):
a frosted glass capsule painted OVER the (dark) glyphs that magnifies with a
uniform flat centre + rim falloff, lifts/refracts the content, luminance-keys
a dark->accent TINT (so the selected blue exists only inside the drop and
travels with it), boosts saturation, and adds a subtle glare/edge-shadow.
The drop springs between tabs (springEaseTabs ease-out-back + settle) and
elongates while travelling, then settles to a clean pill.

iOS performance: the live lens is a Metal FRAGMENT SHADER (cn1_fs_lens) that
samples a GPU->GPU blit of the bar region and renders the warped/tinted drop
quad -- no per-frame CPU readback. The previous readback path (2x
waitUntilCompleted stalls + getBytes + texture upload every frame) capped the
morph at ~6fps; the shader runs it at frame rate. JavaSEPort.applyLensBuffer
is the CPU reference for the desktop simulator and stays numerically matched.

Morph-completion fix (Tabs, all platforms): a tab tap starts both the 550ms
indicator morph and the 200ms content-slide on one animation registration;
the shorter slide finishing used to deregister the animation and restart the
morph from a stale baseline, freezing it mid-travel. deregisterAnimatedInternal
now waits for BOTH motions, startIndicatorAnimation won't restart a morph
already heading to the same tab, and animate() paints a final settled frame so
the resting pill is never left stretched.

Tunable via theme constants (tabSelLens*/tabSel*/tabsAnimatedIndicatorDurationInt).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ead method

Add a "iOS 26 Liquid Glass tab selection morph" section to the Native Themes
developer guide: an animated gif of the morph plus a table of every tunable
theme constant (tabSelLens* / tabSel* / tabsAnimatedIndicatorDurationInt) with
its iOS-modern value and effect.

Also remove Tabs.tabCapsuleParam, a leftover unused private helper SpotBugs
flagged (UPM_UNCALLED_PRIVATE_METHOD).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: 2 alert(s) (2 errors, 0 warnings, 0 suggestions) (exit code 1) (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: 1 advisory match(es) — top: MORFOLOGIK_RULE_EN_US (1) (report)
  • Image references: 1 unused image(s) found (report)

Unused image preview:

  • img/ios-modern-tab-morph-fidelity.png

@shai-almog

Copy link
Copy Markdown
Collaborator Author

Review feedback, focused on the glass/material architecture, fidelity validation, and animation model:

The direction is strong: rendering CN1 and native widgets in comparable environments, adding isolation cases for glass, and pushing glass/lens behavior into port-backed graphics primitives are the right foundations. I do think a few design choices would be worth tightening before this becomes a broader theme/rendering surface.

  1. The glass/material decision is currently embedded in generic Component painting and keyed mostly by glassMaterialBool plus paint-time theme constants. That gives flexibility, but it also leaks an iOS-specific material model into every component that uses backdropFilterBlur. I would rather see a small typed material descriptor or recipe layer, for example plain blur, liquid-glass chrome, liquid-glass panel, selection lens, etc. Then Component/Style passes material intent to the port instead of reconstructing material behavior from many loosely related constants.

  2. The material tuning values are raw parameters resolved during paint: saturation, scale, offset, refraction, specular, per-UIID overrides. That will be hard to keep coherent as Toolbar glass, Button glass, panel glass, and tab lens glass evolve. A named recipe layer would let the theme override high-level choices while preserving bounded defaults and avoiding accidental divergence between similar glass surfaces.

  3. The implementation notes that backdrop blur runs on every paint. That is probably acceptable for static bars, but it is a risky default once glass is used in more places or during scroll/animation. I would like to see a clear caching/invalidation strategy or at least a documented policy: cache when backdrop and bounds are stable, bypass or degrade when scrolling if the port cannot keep up, and capture frame-time evidence for the live-screen Metal path.

  4. The fidelity comparator is thoughtfully designed, especially masking shared glass backdrops so the unchanged background cannot inflate the score. The next step should be to separate visual similarity from geometry fidelity. Cropping both images to the common top-left overlap makes the comparison robust, but it can hide size, position, and anchoring regressions. Please consider adding explicit geometry metrics to the report and gate: widget bbox x/y, bbox size ratio, center offset, and perhaps corner/radius agreement for pill/glass cases.

  5. Glass scoring is currently inferred from image content/backdrop matching. Since the spec already knows which tests are glass, normal, or isolation cases, it would be more robust to make that explicit in fidelity-tests.yaml, e.g. material: normal|glass|lens. Then the comparison mode is chosen from test intent rather than from corner/backdrop heuristics.

  6. The static fidelity matrix is useful, but the most complicated part of this PR is animated glass. The tabs morph should have deterministic animation-frame validation: sample frames at fixed progress values such as 0, 10, 25, 50, 75, 90, and 100, then compare both visual output and geometry metrics per frame. That would catch regressions where individual start/end screenshots look good but the motion path, overshoot, lens size, or tint timing feels wrong.

  7. The tab selection animation has many hand-tuned envelopes in the paint path: spring position, flight, moving, squash, grow, lift, lens magnify, aberration, overflow, and down-bias. The comments are helpful, but the behavior would be easier to reason about and test if extracted into a small TabSelectionMorph model. Given t, source cell, target cell, and theme tokens, it could return a plain data object with capsule rect, lens rect, magnify, aberration, tint strength, and bar-grow rect. That model can be unit-tested and reused by fidelity probes.

  8. I would reduce the public theme knobs for the morph. The current parameter surface is powerful, but too many independent constants make it easy to tune to screenshots while producing odd motion. A smaller set of high-level controls like duration, springiness, and lens intensity, backed by named presets, would be easier to maintain.

Overall: the PR is moving in the right direction, but I would like the glass/material process to become a typed rendering model with explicit validation of geometry and motion, not primarily a collection of paint-time constants plus static screenshots.

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.

1 participant