P4: HTML parity gallery generator + comprehensive docs
scripts/build_parity_gallery.py walks both golden directories and pairs Android↔iOS PNGs by filename convention into docs/parity-gallery.html — a self-contained HTML file with relative <img> paths that renders directly from gitea's raw-file view (no server needed). Current output: 34 screens × 71 Android + 58 iOS images, grouped per screen with sticky headers and per-screen anchor nav. docs/parity-gallery.md: full workflow guide — verify vs record, adding screens to both platforms, approving intentional drift, tool install, size budget, known limitations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
278
docs/parity-gallery.html
Normal file
278
docs/parity-gallery.html
Normal file
@@ -0,0 +1,278 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'><head><meta charset='utf-8'>
|
||||
<title>honeyDue parity gallery</title>
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #0d1117; color: #e6edf3; margin: 0; padding: 20px; }
|
||||
h1 { margin: 0 0 4px; font-size: 20px; }
|
||||
.meta { color: #8b949e; font-size: 13px; margin-bottom: 16px; }
|
||||
.nav { position: sticky; top: 0; background: #0d1117; padding: 8px 0; margin-bottom: 16px;
|
||||
border-bottom: 1px solid #30363d; font-size: 12px; z-index: 10; }
|
||||
.nav a { color: #79c0ff; margin-right: 10px; text-decoration: none; white-space: nowrap; }
|
||||
.nav a:hover { text-decoration: underline; }
|
||||
.grid-header { display: grid; grid-template-columns: 120px 1fr 1fr; gap: 12px;
|
||||
padding: 0 12px 8px; color: #8b949e; font-size: 12px; font-weight: 600;
|
||||
position: sticky; top: 38px; background: #0d1117; z-index: 9;
|
||||
border-bottom: 1px solid #30363d; }
|
||||
.screen { background: #161b22; border-radius: 8px; padding: 12px; margin-bottom: 20px; }
|
||||
.screen h2 { margin: 0 0 8px; font-size: 16px; color: #e6edf3; }
|
||||
.row { display: grid; grid-template-columns: 120px 1fr 1fr; gap: 12px;
|
||||
margin-bottom: 8px; align-items: start; }
|
||||
.label { font-size: 12px; color: #c9d1d9; padding-top: 4px; }
|
||||
.label .mode { color: #8b949e; font-weight: 400; }
|
||||
.row img { width: 100%; border: 1px solid #30363d; border-radius: 4px; display: block; }
|
||||
.missing { display: flex; align-items: center; justify-content: center;
|
||||
min-height: 200px; background: #21262d; border: 1px dashed #484f58;
|
||||
border-radius: 4px; color: #8b949e; font-size: 12px; }
|
||||
</style></head><body>
|
||||
<h1>honeyDue parity gallery</h1>
|
||||
<div class='meta'>71 Android · 58 iOS · 34 screens</div>
|
||||
<div class='nav'><a href='#add_residence'>add_residence</a> <a href='#add_task'>add_task</a> <a href='#add_task_with_residence'>add_task_with_residence</a> <a href='#all_tasks'>all_tasks</a> <a href='#contractors_list'>contractors_list</a> <a href='#documents_warranties'>documents_warranties</a> <a href='#feature_comparison'>feature_comparison</a> <a href='#forgot_password'>forgot_password</a> <a href='#home'>home</a> <a href='#join_residence'>join_residence</a> <a href='#login'>login</a> <a href='#notification_preferences'>notification_preferences</a> <a href='#onboarding_create_account'>onboarding_create_account</a> <a href='#onboarding_first_task'>onboarding_first_task</a> <a href='#onboarding_home_profile'>onboarding_home_profile</a> <a href='#onboarding_join_residence'>onboarding_join_residence</a> <a href='#onboarding_location'>onboarding_location</a> <a href='#onboarding_name_residence'>onboarding_name_residence</a> <a href='#onboarding_subscription'>onboarding_subscription</a> <a href='#onboarding_value_props'>onboarding_value_props</a> <a href='#onboarding_verify_email'>onboarding_verify_email</a> <a href='#onboarding_welcome'>onboarding_welcome</a> <a href='#profile_edit'>profile_edit</a> <a href='#profile_tab'>profile_tab</a> <a href='#register'>register</a> <a href='#reset_password'>reset_password</a> <a href='#residence_detail'>residence_detail</a> <a href='#residences'>residences</a> <a href='#residences_list'>residences_list</a> <a href='#task_suggestions'>task_suggestions</a> <a href='#task_templates_browser'>task_templates_browser</a> <a href='#theme_selection'>theme_selection</a> <a href='#verify_email'>verify_email</a> <a href='#verify_reset_code'>verify_reset_code</a></div>
|
||||
<div class='grid-header'><div class='label'></div><div>Android</div><div>iOS</div></div>
|
||||
<div class='screen' id='add_residence'>
|
||||
<h2>add_residence</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_residence_empty.add_residence_empty_light.png' alt='add_residence_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_residence_empty.add_residence_empty_dark.png' alt='add_residence_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='add_task'>
|
||||
<h2>add_task</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_light.png' alt='add_task_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_empty.add_task_empty_dark.png' alt='add_task_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='add_task_with_residence'>
|
||||
<h2>add_task_with_residence</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_light.png' alt='add_task_with_residence_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_add_task_with_residence_empty.add_task_with_residence_empty_dark.png' alt='add_task_with_residence_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='all_tasks'>
|
||||
<h2>all_tasks</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_light.png' alt='all_tasks_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_all_tasks_empty.all_tasks_empty_dark.png' alt='all_tasks_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='contractors_list'>
|
||||
<h2>contractors_list</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_light.png' alt='contractors_list_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_contractors_list_empty.contractors_list_empty_dark.png' alt='contractors_list_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='documents_warranties'>
|
||||
<h2>documents_warranties</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_light.png' alt='documents_warranties_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_documents_warranties_empty.documents_warranties_empty_dark.png' alt='documents_warranties_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='feature_comparison'>
|
||||
<h2>feature_comparison</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_light.png' alt='feature_comparison_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_feature_comparison_empty.feature_comparison_empty_dark.png' alt='feature_comparison_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='forgot_password'>
|
||||
<h2>forgot_password</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/forgot_password_empty_light.png' alt='forgot_password_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_light.png' alt='forgot_password_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/forgot_password_empty_dark.png' alt='forgot_password_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_forgot_password_empty.forgot_password_empty_dark.png' alt='forgot_password_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/forgot_password_populated_light.png' alt='forgot_password_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/forgot_password_populated_dark.png' alt='forgot_password_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='home'>
|
||||
<h2>home</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/home_empty_light.png' alt='home_empty_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/home_empty_dark.png' alt='home_empty_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/home_populated_light.png' alt='home_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/home_populated_dark.png' alt='home_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='join_residence'>
|
||||
<h2>join_residence</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_light.png' alt='join_residence_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_join_residence_empty.join_residence_empty_dark.png' alt='join_residence_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='login'>
|
||||
<h2>login</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/login_empty_light.png' alt='login_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_light.png' alt='login_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/login_empty_dark.png' alt='login_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_login_empty.login_empty_dark.png' alt='login_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/login_populated_light.png' alt='login_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/login_populated_dark.png' alt='login_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='notification_preferences'>
|
||||
<h2>notification_preferences</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_light.png' alt='notification_preferences_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_notification_preferences_empty.notification_preferences_empty_dark.png' alt='notification_preferences_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_create_account'>
|
||||
<h2>onboarding_create_account</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_empty_light.png' alt='onboarding_create_account_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_light.png' alt='onboarding_create_account_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_empty_dark.png' alt='onboarding_create_account_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_create_account_empty.onboarding_create_account_empty_dark.png' alt='onboarding_create_account_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_populated_light.png' alt='onboarding_create_account_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_populated_dark.png' alt='onboarding_create_account_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_first_task'>
|
||||
<h2>onboarding_first_task</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_light.png' alt='onboarding_first_task_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_first_task_empty.onboarding_first_task_empty_dark.png' alt='onboarding_first_task_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_home_profile'>
|
||||
<h2>onboarding_home_profile</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_empty_light.png' alt='onboarding_home_profile_empty_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_empty_dark.png' alt='onboarding_home_profile_empty_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_populated_light.png' alt='onboarding_home_profile_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_populated_dark.png' alt='onboarding_home_profile_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_join_residence'>
|
||||
<h2>onboarding_join_residence</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_empty_light.png' alt='onboarding_join_residence_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_light.png' alt='onboarding_join_residence_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_empty_dark.png' alt='onboarding_join_residence_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_join_residence_empty.onboarding_join_residence_empty_dark.png' alt='onboarding_join_residence_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_populated_light.png' alt='onboarding_join_residence_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_populated_dark.png' alt='onboarding_join_residence_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_location'>
|
||||
<h2>onboarding_location</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_location_empty_light.png' alt='onboarding_location_empty_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_location_empty_dark.png' alt='onboarding_location_empty_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_location_populated_light.png' alt='onboarding_location_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_location_populated_dark.png' alt='onboarding_location_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_name_residence'>
|
||||
<h2>onboarding_name_residence</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_empty_light.png' alt='onboarding_name_residence_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_light.png' alt='onboarding_name_residence_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_empty_dark.png' alt='onboarding_name_residence_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_name_residence_empty.onboarding_name_residence_empty_dark.png' alt='onboarding_name_residence_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_populated_light.png' alt='onboarding_name_residence_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_populated_dark.png' alt='onboarding_name_residence_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_subscription'>
|
||||
<h2>onboarding_subscription</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_empty_light.png' alt='onboarding_subscription_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_light.png' alt='onboarding_subscription_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_empty_dark.png' alt='onboarding_subscription_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_subscription_empty.onboarding_subscription_empty_dark.png' alt='onboarding_subscription_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_populated_light.png' alt='onboarding_subscription_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_populated_dark.png' alt='onboarding_subscription_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_value_props'>
|
||||
<h2>onboarding_value_props</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_empty_light.png' alt='onboarding_value_props_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_light.png' alt='onboarding_value_props_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_empty_dark.png' alt='onboarding_value_props_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_value_props_empty.onboarding_value_props_empty_dark.png' alt='onboarding_value_props_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_populated_light.png' alt='onboarding_value_props_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_populated_dark.png' alt='onboarding_value_props_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_verify_email'>
|
||||
<h2>onboarding_verify_email</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_empty_light.png' alt='onboarding_verify_email_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_light.png' alt='onboarding_verify_email_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_empty_dark.png' alt='onboarding_verify_email_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_verify_email_empty.onboarding_verify_email_empty_dark.png' alt='onboarding_verify_email_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_populated_light.png' alt='onboarding_verify_email_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_populated_dark.png' alt='onboarding_verify_email_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='onboarding_welcome'>
|
||||
<h2>onboarding_welcome</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_empty_light.png' alt='onboarding_welcome_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_light.png' alt='onboarding_welcome_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_empty_dark.png' alt='onboarding_welcome_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_onboarding_welcome_empty.onboarding_welcome_empty_dark.png' alt='onboarding_welcome_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_populated_light.png' alt='onboarding_welcome_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_populated_dark.png' alt='onboarding_welcome_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='profile_edit'>
|
||||
<h2>profile_edit</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_light.png' alt='profile_edit_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_edit_empty.profile_edit_empty_dark.png' alt='profile_edit_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='profile_tab'>
|
||||
<h2>profile_tab</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_light.png' alt='profile_tab_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_profile_tab_empty.profile_tab_empty_dark.png' alt='profile_tab_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='register'>
|
||||
<h2>register</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/register_empty_light.png' alt='register_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_light.png' alt='register_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/register_empty_dark.png' alt='register_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_register_empty.register_empty_dark.png' alt='register_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/register_populated_light.png' alt='register_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/register_populated_dark.png' alt='register_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='reset_password'>
|
||||
<h2>reset_password</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/reset_password_empty_light.png' alt='reset_password_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_light.png' alt='reset_password_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/reset_password_empty_dark.png' alt='reset_password_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_reset_password_empty.reset_password_empty_dark.png' alt='reset_password_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/reset_password_populated_light.png' alt='reset_password_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/reset_password_populated_dark.png' alt='reset_password_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='residence_detail'>
|
||||
<h2>residence_detail</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/residence_detail_empty_light.png' alt='residence_detail_empty_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/residence_detail_empty_dark.png' alt='residence_detail_empty_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/residence_detail_populated_light.png' alt='residence_detail_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='residences'>
|
||||
<h2>residences</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/residences_empty_light.png' alt='residences_empty_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/residences_empty_dark.png' alt='residences_empty_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/residences_populated_light.png' alt='residences_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/residences_populated_dark.png' alt='residences_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='residences_list'>
|
||||
<h2>residences_list</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_light.png' alt='residences_list_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_residences_list_empty.residences_list_empty_dark.png' alt='residences_list_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='task_suggestions'>
|
||||
<h2>task_suggestions</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_suggestions_empty.task_suggestions_empty_light.png' alt='task_suggestions_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_suggestions_empty.task_suggestions_empty_dark.png' alt='task_suggestions_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='task_templates_browser'>
|
||||
<h2>task_templates_browser</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_light.png' alt='task_templates_browser_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_task_templates_browser_empty.task_templates_browser_empty_dark.png' alt='task_templates_browser_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='theme_selection'>
|
||||
<h2>theme_selection</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_light.png' alt='theme_selection_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_theme_selection_empty.theme_selection_empty_dark.png' alt='theme_selection_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><div class='missing'>Android missing</div><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='verify_email'>
|
||||
<h2>verify_email</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/verify_email_empty_light.png' alt='verify_email_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_light.png' alt='verify_email_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/verify_email_empty_dark.png' alt='verify_email_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_email_empty.verify_email_empty_dark.png' alt='verify_email_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/verify_email_populated_light.png' alt='verify_email_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/verify_email_populated_dark.png' alt='verify_email_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<div class='screen' id='verify_reset_code'>
|
||||
<h2>verify_reset_code</h2>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/verify_reset_code_empty_light.png' alt='verify_reset_code_empty_light Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_light.png' alt='verify_reset_code_empty_light iOS'/></div>
|
||||
<div class='row'><div class='label'>empty<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/verify_reset_code_empty_dark.png' alt='verify_reset_code_empty_dark Android'/><img loading='lazy' src='../iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_verify_reset_code_empty.verify_reset_code_empty_dark.png' alt='verify_reset_code_empty_dark iOS'/></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>light</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/verify_reset_code_populated_light.png' alt='verify_reset_code_populated_light Android'/><div class='missing'>iOS missing</div></div>
|
||||
<div class='row'><div class='label'>populated<br><span class='mode'>dark</span></div><img loading='lazy' src='../composeApp/src/androidUnitTest/roborazzi/verify_reset_code_populated_dark.png' alt='verify_reset_code_populated_dark Android'/><div class='missing'>iOS missing</div></div>
|
||||
</div>
|
||||
<script>
|
||||
// Ctrl/Cmd-F friendly: expose screen names in the document title on anchor change.
|
||||
window.addEventListener('hashchange', () => {
|
||||
const s = location.hash.slice(1);
|
||||
document.title = s ? `${s} · parity` : 'honeyDue parity gallery';
|
||||
});
|
||||
</script>
|
||||
</body></html>
|
||||
181
docs/parity-gallery.md
Normal file
181
docs/parity-gallery.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Parity gallery — iOS ↔ Android snapshot regression
|
||||
|
||||
Every primary screen on both platforms is captured as a PNG golden and
|
||||
committed to the repo. A PR that drifts from a golden fails CI. The
|
||||
committed `docs/parity-gallery.html` pairs iOS and Android side-by-side in
|
||||
a scrollable HTML grid you can open locally or from gitea's raw-file view.
|
||||
|
||||
## Quick reference
|
||||
|
||||
```
|
||||
make verify-snapshots # PR gate; fast. Both platforms diff against goldens.
|
||||
make record-snapshots # Regenerate everything + optimize. Slow (~5 min).
|
||||
make optimize-goldens # Rerun zopflipng over existing PNGs. Idempotent.
|
||||
python3 scripts/build_parity_gallery.py # Rebuild docs/parity-gallery.html
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### Shared fixtures
|
||||
`composeApp/src/commonMain/kotlin/com/tt/honeyDue/testing/FixtureDataManager.kt`
|
||||
exposes `.empty()` and `.populated()` factories. Both platforms render the
|
||||
same screens against the same fixture graph — the only cross-platform
|
||||
differences left are actual UI code differences (by design). Fixtures use
|
||||
a fixed clock (`Fixtures.FIXED_DATE = LocalDate(2026, 4, 15)`) so dates
|
||||
never drift.
|
||||
|
||||
### Android capture (Roborazzi)
|
||||
- `composeApp/src/androidUnitTest/kotlin/com/tt/honeyDue/screenshot/ScreenshotTests.kt`
|
||||
declares one `@Test` per surface in `GallerySurfaces.kt`.
|
||||
- Each test captures 4 variants: `empty × light`, `empty × dark`,
|
||||
`populated × light`, `populated × dark`.
|
||||
- Runs in Robolectric — no emulator needed, no flake from animations.
|
||||
- Goldens: `composeApp/src/androidUnitTest/roborazzi/<screen>_<state>_<mode>.png`
|
||||
- Typical size: 30–80 KB per image.
|
||||
|
||||
### iOS capture (swift-snapshot-testing)
|
||||
- `iosApp/HoneyDueTests/SnapshotGalleryTests.swift` has 4 tests per screen.
|
||||
- Rendered at `displayScale: 2.0` (not the native 3.0) to cap per-image size.
|
||||
- Uses `FixtureDataManager.shared.empty()` / `.populated()` via SKIE.
|
||||
- Goldens: `iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/test_<name>.<variant>.png`
|
||||
- Typical size: 150–300 KB per image after `zopflipng` post-processing.
|
||||
|
||||
### Record-mode trigger
|
||||
Both platforms record only when explicitly requested:
|
||||
- Android: `./gradlew :composeApp:recordRoborazziDebug`
|
||||
- iOS: `SNAPSHOT_TESTING_RECORD=1 xcodebuild test …`
|
||||
|
||||
`make record-snapshots` does both, plus runs `scripts/optimize_goldens.sh`
|
||||
to shrink the output PNGs. No code edits required to switch between record
|
||||
and verify — the env var / gradle task controls everything.
|
||||
|
||||
## When to record vs verify
|
||||
|
||||
**Verify** is what CI runs on every PR. It is the gate. If verify fails,
|
||||
ask: *was this drift intentional?*
|
||||
|
||||
**Record** is what you run locally when a UI change is deliberate and you
|
||||
want to publish the new look as the new baseline. Commit the regenerated
|
||||
goldens alongside your code change so reviewers see both the code and the
|
||||
visual result in one PR.
|
||||
|
||||
Running record by mistake (on a branch where you didn't intend to change
|
||||
UI) will produce a large image-diff in `git status`. That diff is the
|
||||
signal — revert the goldens, investigate what unintentionally changed.
|
||||
|
||||
## Adding a screen to the gallery
|
||||
|
||||
### Android
|
||||
Add one entry to
|
||||
`composeApp/src/androidUnitTest/kotlin/com/tt/honeyDue/screenshot/GallerySurfaces.kt`:
|
||||
|
||||
```kotlin
|
||||
GallerySurface("my_new_screen") { MyNewScreen(onNavigateBack = {}, /* required params from fixtures */) },
|
||||
```
|
||||
|
||||
If the screen needs a specific model (`task`, `residence`, etc.) pass one
|
||||
from `Fixtures.*` — e.g. `Fixtures.tasks.first()`. If the screen renders
|
||||
differently in empty vs populated, the `LocalDataManager` provider wiring
|
||||
in `ScreenshotTests.kt` handles it automatically.
|
||||
|
||||
### iOS
|
||||
Add 4 test functions to `iosApp/HoneyDueTests/SnapshotGalleryTests.swift`:
|
||||
|
||||
```swift
|
||||
func test_myNewScreen_empty_light() { snap("my_new_screen_empty_light", empty: true, dark: false) { MyNewView() } }
|
||||
func test_myNewScreen_empty_dark() { snap("my_new_screen_empty_dark", empty: true, dark: true) { MyNewView() } }
|
||||
func test_myNewScreen_populated_light() { snap("my_new_screen_populated_light", empty: false, dark: false) { MyNewView() } }
|
||||
func test_myNewScreen_populated_dark() { snap("my_new_screen_populated_dark", empty: false, dark: true) { MyNewView() } }
|
||||
```
|
||||
|
||||
Then `make record-snapshots` to generate goldens, `git add` the PNGs
|
||||
alongside your test changes.
|
||||
|
||||
## Approving intentional UI drift
|
||||
|
||||
```bash
|
||||
# 1. Regenerate goldens against your new UI.
|
||||
make record-snapshots
|
||||
|
||||
# 2. Review the PNG diff — did only the intended screens change?
|
||||
git status composeApp/src/androidUnitTest/roborazzi/ iosApp/HoneyDueTests/__Snapshots__/
|
||||
git diff --stat composeApp/src/androidUnitTest/roborazzi/ iosApp/HoneyDueTests/__Snapshots__/
|
||||
|
||||
# 3. Stage and commit alongside the UI code change.
|
||||
git add <screen-file.kt> <SnapshotGalleryTests.swift changes> \
|
||||
composeApp/src/androidUnitTest/roborazzi/ \
|
||||
iosApp/HoneyDueTests/__Snapshots__/
|
||||
git commit -m "feat: <what changed>"
|
||||
```
|
||||
|
||||
Reviewers see the code diff AND the golden diff in one PR — makes intent
|
||||
obvious.
|
||||
|
||||
## Image size budget
|
||||
|
||||
Per-file soft budget: **400 KB**. Enforced by CI.
|
||||
|
||||
Android images are rarely this large. iOS images can exceed 400 KB for
|
||||
gradient-heavy screens (Onboarding welcome, organic blob backgrounds).
|
||||
If a new screen exceeds budget:
|
||||
1. Check whether the screen really needs a full-viewport gradient.
|
||||
2. If yes, consider rendering at `displayScale: 1.0` for just that test
|
||||
(the `snap` helper accepts an override).
|
||||
|
||||
## Tool installation
|
||||
|
||||
The optimizer script needs one of:
|
||||
```bash
|
||||
brew install zopfli # preferred — better compression
|
||||
brew install pngcrush # fallback
|
||||
```
|
||||
|
||||
Neither installed? `make record-snapshots` warns and skips optimization —
|
||||
goldens are still usable, just larger.
|
||||
|
||||
## HTML gallery
|
||||
|
||||
`docs/parity-gallery.html` is regenerated by
|
||||
`scripts/build_parity_gallery.py` whenever goldens change. It's a
|
||||
self-contained HTML file with relative `<img>` paths that resolve within
|
||||
the repo — so gitea's raw-file view renders it without any server.
|
||||
|
||||
To view locally:
|
||||
```bash
|
||||
python3 scripts/build_parity_gallery.py
|
||||
open docs/parity-gallery.html
|
||||
```
|
||||
|
||||
The gallery groups by screen name. Each row shows Android vs iOS for one
|
||||
{state, mode} combination, with sticky headers for quick navigation.
|
||||
|
||||
## Current coverage
|
||||
|
||||
Written to the output on each regeneration — check the top of
|
||||
`docs/parity-gallery.html` for the current count.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **iOS populated-state coverage is partial**. Swift Views today instantiate
|
||||
their ViewModels via `@StateObject viewModel = FooViewModel()`; the
|
||||
ViewModels read `DataManagerObservable.shared` directly rather than
|
||||
accepting an injected `IDataManager`. Until ViewModels gain a DI seam,
|
||||
populated-state snapshots require per-screen ad-hoc workarounds.
|
||||
Tracked as a follow-up.
|
||||
|
||||
- **Android detail-screen coverage is partial**. Screens that require a
|
||||
pre-selected model (`ResidenceDetailScreen(residence = ...)`,
|
||||
`ContractorDetailScreen(contractor = ...)`) silently skip rendering
|
||||
unless `GallerySurfaces.kt` passes a fixture item. Expanding these to
|
||||
full coverage is a follow-up PR — low-risk additions to
|
||||
`GallerySurfaces.kt`.
|
||||
|
||||
- **Cross-platform diff is visual, not pixel-exact**. SF Pro (iOS) vs
|
||||
SansSerif (Android) render different glyph shapes by design. Pixel-diff
|
||||
is only used within a platform — the HTML gallery is for side-by-side
|
||||
human review.
|
||||
|
||||
- **Roborazzi path mismatch**. The historical goldens lived at
|
||||
`composeApp/src/androidUnitTest/roborazzi/`. The Roborazzi Gradle block
|
||||
sets `outputDir` to match. If `verifyRoborazziDebug` ever reports
|
||||
"original file not found", confirm the `outputDir` hasn't drifted.
|
||||
161
scripts/build_parity_gallery.py
Executable file
161
scripts/build_parity_gallery.py
Executable file
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build docs/parity-gallery.html pairing iOS + Android goldens per screen.
|
||||
|
||||
The output is a single self-contained HTML file that gitea's raw-file view
|
||||
can render directly. Relative <img> paths resolve within the repo so the
|
||||
images load without any webserver.
|
||||
|
||||
Filename convention (both platforms):
|
||||
<screen>_<empty|populated>_<light|dark>.png
|
||||
|
||||
iOS snapshots live under
|
||||
iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests/
|
||||
with swift-snapshot-testing's `test_<name>.<nameArg>.png` prefix which we
|
||||
strip to align with Android's plain `<name>.png`.
|
||||
|
||||
Usage: python3 scripts/build_parity_gallery.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import glob
|
||||
import html
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ANDROID_DIR = "composeApp/src/androidUnitTest/roborazzi"
|
||||
IOS_DIR = "iosApp/HoneyDueTests/__Snapshots__/SnapshotGalleryTests"
|
||||
OUT = "docs/parity-gallery.html"
|
||||
|
||||
# swift-snapshot-testing names files "test_<func>.<name>.png" — strip prefix
|
||||
IOS_NAME_RE = re.compile(r"^test_[^.]+\.(.+)\.png$")
|
||||
|
||||
|
||||
def canonical_name(platform: str, path: str) -> str | None:
|
||||
"""Return the canonical <screen>_<state>_<mode> key or None if unparseable."""
|
||||
base = os.path.basename(path)
|
||||
if platform == "ios":
|
||||
m = IOS_NAME_RE.match(base)
|
||||
if not m:
|
||||
return None
|
||||
return m.group(1)
|
||||
return base[:-4] if base.endswith(".png") else None
|
||||
|
||||
|
||||
def load(platform: str, directory: str) -> dict[str, str]:
|
||||
out: dict[str, str] = {}
|
||||
full = os.path.join(REPO_ROOT, directory)
|
||||
if not os.path.isdir(full):
|
||||
return out
|
||||
for p in glob.glob(f"{full}/**/*.png", recursive=True):
|
||||
key = canonical_name(platform, p)
|
||||
if key is None:
|
||||
continue
|
||||
out[key] = os.path.relpath(p, os.path.join(REPO_ROOT, "docs"))
|
||||
return out
|
||||
|
||||
|
||||
def parse_key(key: str) -> tuple[str, str, str]:
|
||||
"""(screen, state, mode) — e.g. 'login_empty_dark' → ('login', 'empty', 'dark')."""
|
||||
m = re.match(r"^(.+)_(empty|populated)_(light|dark)$", key)
|
||||
if m:
|
||||
return m.group(1), m.group(2), m.group(3)
|
||||
return key, "?", "?"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
android = load("android", ANDROID_DIR)
|
||||
ios = load("ios", IOS_DIR)
|
||||
keys = sorted(set(android) | set(ios))
|
||||
screens = sorted({parse_key(k)[0] for k in keys})
|
||||
|
||||
out_path = os.path.join(REPO_ROOT, OUT)
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write(PAGE_HEAD)
|
||||
f.write(f"<div class='meta'>{len(android)} Android · {len(ios)} iOS · {len(screens)} screens</div>\n")
|
||||
f.write("<div class='nav'>")
|
||||
f.write(" ".join(f"<a href='#{html.escape(s)}'>{html.escape(s)}</a>" for s in screens))
|
||||
f.write("</div>\n")
|
||||
f.write(
|
||||
"<div class='grid-header'>"
|
||||
"<div class='label'></div>"
|
||||
"<div>Android</div>"
|
||||
"<div>iOS</div>"
|
||||
"</div>\n"
|
||||
)
|
||||
for screen in screens:
|
||||
f.write(f"<div class='screen' id='{html.escape(screen)}'>\n")
|
||||
f.write(f" <h2>{html.escape(screen)}</h2>\n")
|
||||
for state in ("empty", "populated"):
|
||||
for mode in ("light", "dark"):
|
||||
key = f"{screen}_{state}_{mode}"
|
||||
a = android.get(key)
|
||||
i = ios.get(key)
|
||||
a_cell = (
|
||||
f"<img loading='lazy' src='{html.escape(a)}' alt='{key} Android'/>"
|
||||
if a
|
||||
else "<div class='missing'>Android missing</div>"
|
||||
)
|
||||
i_cell = (
|
||||
f"<img loading='lazy' src='{html.escape(i)}' alt='{key} iOS'/>"
|
||||
if i
|
||||
else "<div class='missing'>iOS missing</div>"
|
||||
)
|
||||
f.write(
|
||||
f" <div class='row'>"
|
||||
f"<div class='label'>{state}<br><span class='mode'>{mode}</span></div>"
|
||||
f"{a_cell}{i_cell}"
|
||||
f"</div>\n"
|
||||
)
|
||||
f.write("</div>\n")
|
||||
f.write(PAGE_FOOT)
|
||||
|
||||
print(f"wrote {OUT}: {len(screens)} screens, {len(android)} Android + {len(ios)} iOS images")
|
||||
return 0
|
||||
|
||||
|
||||
PAGE_HEAD = """<!DOCTYPE html>
|
||||
<html lang='en'><head><meta charset='utf-8'>
|
||||
<title>honeyDue parity gallery</title>
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #0d1117; color: #e6edf3; margin: 0; padding: 20px; }
|
||||
h1 { margin: 0 0 4px; font-size: 20px; }
|
||||
.meta { color: #8b949e; font-size: 13px; margin-bottom: 16px; }
|
||||
.nav { position: sticky; top: 0; background: #0d1117; padding: 8px 0; margin-bottom: 16px;
|
||||
border-bottom: 1px solid #30363d; font-size: 12px; z-index: 10; }
|
||||
.nav a { color: #79c0ff; margin-right: 10px; text-decoration: none; white-space: nowrap; }
|
||||
.nav a:hover { text-decoration: underline; }
|
||||
.grid-header { display: grid; grid-template-columns: 120px 1fr 1fr; gap: 12px;
|
||||
padding: 0 12px 8px; color: #8b949e; font-size: 12px; font-weight: 600;
|
||||
position: sticky; top: 38px; background: #0d1117; z-index: 9;
|
||||
border-bottom: 1px solid #30363d; }
|
||||
.screen { background: #161b22; border-radius: 8px; padding: 12px; margin-bottom: 20px; }
|
||||
.screen h2 { margin: 0 0 8px; font-size: 16px; color: #e6edf3; }
|
||||
.row { display: grid; grid-template-columns: 120px 1fr 1fr; gap: 12px;
|
||||
margin-bottom: 8px; align-items: start; }
|
||||
.label { font-size: 12px; color: #c9d1d9; padding-top: 4px; }
|
||||
.label .mode { color: #8b949e; font-weight: 400; }
|
||||
.row img { width: 100%; border: 1px solid #30363d; border-radius: 4px; display: block; }
|
||||
.missing { display: flex; align-items: center; justify-content: center;
|
||||
min-height: 200px; background: #21262d; border: 1px dashed #484f58;
|
||||
border-radius: 4px; color: #8b949e; font-size: 12px; }
|
||||
</style></head><body>
|
||||
<h1>honeyDue parity gallery</h1>
|
||||
"""
|
||||
|
||||
PAGE_FOOT = """<script>
|
||||
// Ctrl/Cmd-F friendly: expose screen names in the document title on anchor change.
|
||||
window.addEventListener('hashchange', () => {
|
||||
const s = location.hash.slice(1);
|
||||
document.title = s ? `${s} · parity` : 'honeyDue parity gallery';
|
||||
});
|
||||
</script>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user