docs(recommendation): 서브 배너 리뷰와 QA 증거를 보존한다

This commit is contained in:
Yu Sung
2026-10-30 12:19:58 +09:00
parent 7a42db66bc
commit 32ab7797e0
206 changed files with 21989 additions and 0 deletions
@@ -0,0 +1,25 @@
{
"borrowedSimulator": "B5F2BA0D-327C-4EE3-94EE-39710A0E1ACC",
"createdSimulators": [],
"ownedRoot": "/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa",
"apps": [
{
"id": "invalid.sodalive.gesturefix.Gesture",
"terminateExit": 3,
"terminateMessage": "An error was encountered processing the command (domain=NSPOSIXErrorDomain, code=3):\nSimulator device failed to terminate invalid.sodalive.gesturefix.Gesture.\nfound nothing to terminate\nUnderlying error (domain=NSPOSIXErrorDomain, code=3):\n\tThe request to terminate \"invalid.sodalive.gesturefix.Gesture\" failed. found nothing to terminate\n\tfound nothing to terminate",
"uninstallExit": 0
},
{
"id": "invalid.sodalive.gesturefix.GestureUITests.xctrunner",
"terminateExit": 3,
"terminateMessage": "An error was encountered processing the command (domain=NSPOSIXErrorDomain, code=3):\nSimulator device failed to terminate invalid.sodalive.gesturefix.GestureUITests.xctrunner.\nfound nothing to terminate\nUnderlying error (domain=NSPOSIXErrorDomain, code=3):\n\tThe request to terminate \"invalid.sodalive.gesturefix.GestureUITests.xctrunner\" failed. found nothing to terminate\n\tfound nothing to terminate",
"uninstallExit": 0
}
],
"ownedLaunchctlJobsAbsent": true,
"ownedAppsAbsent": true,
"ownedRootAndCachesAbsent": true,
"borrowedSimulatorStillBooted": true,
"parentRootUntouchedAndPresent": true,
"productionSourceSHA256": "b3e4c5333cf8249ccd7773b5cabd372efb5a5565323bd7778034e110386151fd"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -0,0 +1,466 @@
[
{
"time": 913597.4492290791,
"file": "0000.png"
},
{
"file": "0001.png",
"time": 913597.4755964761
},
{
"file": "0002.png",
"time": 913597.50850726
},
{
"time": 913597.5433671961,
"file": "0003.png"
},
{
"time": 913597.5739051071,
"file": "0004.png"
},
{
"file": "0005.png",
"time": 913597.609845889
},
{
"time": 913597.641820132,
"file": "0006.png"
},
{
"file": "0007.png",
"time": 913597.676881199
},
{
"file": "0008.png",
"time": 913597.7084638361
},
{
"file": "0009.png",
"time": 913597.7411874051
},
{
"file": "0010.png",
"time": 913597.775570434
},
{
"time": 913597.8080575881,
"file": "0011.png"
},
{
"file": "0012.png",
"time": 913597.84204845
},
{
"file": "0013.png",
"time": 913597.8778581431
},
{
"file": "0014.png",
"time": 913597.9616468931
},
{
"time": 913598.004829079,
"file": "0015.png"
},
{
"file": "0016.png",
"time": 913598.0422670391
},
{
"file": "0017.png",
"time": 913598.0752597761
},
{
"file": "0018.png",
"time": 913598.1080234471
},
{
"time": 913598.141075,
"file": "0019.png"
},
{
"file": "0020.png",
"time": 913598.175370774
},
{
"file": "0021.png",
"time": 913598.2073697711
},
{
"time": 913598.2420130861,
"file": "0022.png"
},
{
"file": "0023.png",
"time": 913598.2760424891
},
{
"time": 913598.306953603,
"file": "0024.png"
},
{
"time": 913598.3411505871,
"file": "0025.png"
},
{
"time": 913598.374023861,
"file": "0026.png"
},
{
"time": 913598.407951768,
"file": "0027.png"
},
{
"file": "0028.png",
"time": 913598.4401897051
},
{
"file": "0029.png",
"time": 913598.4735934021
},
{
"time": 913598.5078664881,
"file": "0030.png"
},
{
"file": "0031.png",
"time": 913598.5434376
},
{
"file": "0032.png",
"time": 913598.5772900771
},
{
"file": "0033.png",
"time": 913598.607618751
},
{
"file": "0034.png",
"time": 913598.641246484
},
{
"time": 913598.6736933991,
"file": "0035.png"
},
{
"file": "0036.png",
"time": 913598.7074893891
},
{
"file": "0037.png",
"time": 913598.743114041
},
{
"file": "0038.png",
"time": 913598.7739387951
},
{
"file": "0039.png",
"time": 913598.808327698
},
{
"time": 913598.841209955,
"file": "0040.png"
},
{
"file": "0041.png",
"time": 913598.875832265
},
{
"file": "0042.png",
"time": 913598.908611273
},
{
"file": "0043.png",
"time": 913598.9920293391
},
{
"time": 913599.0429295121,
"file": "0044.png"
},
{
"file": "0045.png",
"time": 913599.073915453
},
{
"time": 913599.1069630211,
"file": "0046.png"
},
{
"file": "0047.png",
"time": 913599.139273638
},
{
"file": "0048.png",
"time": 913599.171037629
},
{
"file": "0049.png",
"time": 913599.2040882771
},
{
"time": 913599.2386047101,
"file": "0050.png"
},
{
"file": "0051.png",
"time": 913599.2723036291
},
{
"time": 913599.3079158181,
"file": "0052.png"
},
{
"file": "0053.png",
"time": 913599.3431351511
},
{
"time": 913599.376199943,
"file": "0054.png"
},
{
"file": "0055.png",
"time": 913599.4078560281
},
{
"time": 913599.440783828,
"file": "0056.png"
},
{
"time": 913599.4710975741,
"file": "0057.png"
},
{
"file": "0058.png",
"time": 913599.5026518331
},
{
"time": 913599.5386346581,
"file": "0059.png"
},
{
"time": 913599.5707385921,
"file": "0060.png"
},
{
"file": "0061.png",
"time": 913599.602594416
},
{
"time": 913599.6393803691,
"file": "0062.png"
},
{
"file": "0063.png",
"time": 913599.6682303511
},
{
"file": "0064.png",
"time": 913599.7060550201
},
{
"time": 913599.736194105,
"file": "0065.png"
},
{
"file": "0066.png",
"time": 913599.7714051381
},
{
"time": 913599.802721531,
"file": "0067.png"
},
{
"time": 913599.873930242,
"file": "0068.png"
},
{
"time": 913599.893517905,
"file": "0069.png"
},
{
"file": "0070.png",
"time": 913599.913150748
},
{
"file": "0071.png",
"time": 913599.942140343
},
{
"time": 913599.97637692,
"file": "0072.png"
},
{
"time": 913600.0079948221,
"file": "0073.png"
},
{
"file": "0074.png",
"time": 913600.0425524261
},
{
"file": "0075.png",
"time": 913600.0739939511
},
{
"time": 913600.109444809,
"file": "0076.png"
},
{
"time": 913600.14165221,
"file": "0077.png"
},
{
"time": 913600.1751490401,
"file": "0078.png"
},
{
"time": 913600.207547056,
"file": "0079.png"
},
{
"time": 913600.241096996,
"file": "0080.png"
},
{
"file": "0081.png",
"time": 913600.2762579471
},
{
"file": "0082.png",
"time": 913600.306952715
},
{
"time": 913600.3412508611,
"file": "0083.png"
},
{
"time": 913600.3752544611,
"file": "0084.png"
},
{
"file": "0085.png",
"time": 913600.4240071401
},
{
"time": 913600.4467707721,
"file": "0086.png"
},
{
"time": 913600.4733675481,
"file": "0087.png"
},
{
"time": 913600.507895805,
"file": "0088.png"
},
{
"file": "0089.png",
"time": 913600.5426770151
},
{
"file": "0090.png",
"time": 913600.573082122
},
{
"file": "0091.png",
"time": 913600.6076171291
},
{
"file": "0092.png",
"time": 913600.6396793531
},
{
"file": "0093.png",
"time": 913600.675508002
},
{
"file": "0094.png",
"time": 913600.7075339471
},
{
"time": 913600.74025594,
"file": "0095.png"
},
{
"time": 913600.775323804,
"file": "0096.png"
},
{
"file": "0097.png",
"time": 913600.8072155941
},
{
"time": 913600.8411561421,
"file": "0098.png"
},
{
"file": "0099.png",
"time": 913600.8750394251
},
{
"file": "0100.png",
"time": 913600.90762452
},
{
"file": "0101.png",
"time": 913600.940233796
},
{
"time": 913600.9744563231,
"file": "0102.png"
},
{
"time": 913601.00685121,
"file": "0103.png"
},
{
"file": "0104.png",
"time": 913601.040866059
},
{
"time": 913601.074510779,
"file": "0105.png"
},
{
"file": "0106.png",
"time": 913601.1079004781
},
{
"file": "0107.png",
"time": 913601.1438779951
},
{
"file": "0108.png",
"time": 913601.1751519741
},
{
"time": 913601.2079005501,
"file": "0109.png"
},
{
"file": "0110.png",
"time": 913601.24195652
},
{
"time": 913601.277139337,
"file": "0111.png"
},
{
"time": 913601.307590552,
"file": "0112.png"
},
{
"file": "0113.png",
"time": 913601.3412412221
},
{
"time": 913601.3733143321,
"file": "0114.png"
},
{
"file": "0115.png",
"time": 913601.4083740071
}
]
@@ -0,0 +1,362 @@
[
{
"pid": 34695,
"test": "-[SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]",
"time": 913588.4641189601,
"event": "launch"
},
{
"event": "phase-inactive",
"pid": 34695,
"time": 913589.395679436,
"test": "-[SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]"
},
{
"event": "phase-active",
"time": 913589.412622352,
"pid": 34695,
"test": "-[SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]"
},
{
"pid": 34695,
"event": "record-automatic",
"time": 913597.4139605111,
"test": "-[SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]"
},
{
"test": "-[SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]",
"pid": 34695,
"time": 913602.1043090811,
"event": "tap-1"
},
{
"time": 913604.48309818,
"pid": 34735,
"event": "launch",
"test": "-[SubBannerGestureUITests testDirectionsWrapShortDragAndTap]"
},
{
"pid": 34735,
"event": "phase-inactive",
"time": 913605.391383399,
"test": "-[SubBannerGestureUITests testDirectionsWrapShortDragAndTap]"
},
{
"test": "-[SubBannerGestureUITests testDirectionsWrapShortDragAndTap]",
"event": "phase-active",
"time": 913605.4323043161,
"pid": 34735
},
{
"event": "tap-1",
"time": 913608.887246688,
"pid": 34735,
"test": "-[SubBannerGestureUITests testDirectionsWrapShortDragAndTap]"
},
{
"event": "tap-3",
"time": 913610.570216782,
"pid": 34735,
"test": "-[SubBannerGestureUITests testDirectionsWrapShortDragAndTap]"
},
{
"event": "tap-1",
"time": 913613.587584087,
"pid": 34735,
"test": "-[SubBannerGestureUITests testDirectionsWrapShortDragAndTap]"
},
{
"pid": 34767,
"test": "-[SubBannerGestureUITests testDisappearanceDuringAutomaticWrap]",
"event": "launch",
"time": 913615.994330842
},
{
"test": "-[SubBannerGestureUITests testDisappearanceDuringAutomaticWrap]",
"event": "phase-inactive",
"time": 913616.8966431321,
"pid": 34767
},
{
"pid": 34767,
"event": "phase-active",
"time": 913616.9213246681,
"test": "-[SubBannerGestureUITests testDisappearanceDuringAutomaticWrap]"
},
{
"test": "-[SubBannerGestureUITests testDisappearanceDuringAutomaticWrap]",
"time": 913625.4543779121,
"event": "record-hide",
"pid": 34767
},
{
"pid": 34767,
"test": "-[SubBannerGestureUITests testDisappearanceDuringAutomaticWrap]",
"time": 913627.272562311,
"event": "observed-mixed-hide"
},
{
"time": 913627.273564405,
"pid": 34767,
"test": "-[SubBannerGestureUITests testDisappearanceDuringAutomaticWrap]",
"event": "hide"
},
{
"event": "show",
"time": 913628.662756823,
"pid": 34767,
"test": "-[SubBannerGestureUITests testDisappearanceDuringAutomaticWrap]"
},
{
"test": "-[SubBannerGestureUITests testInactiveSceneCancelsAutomaticAdvance]",
"time": 913632.1317507351,
"pid": 34805,
"event": "launch"
},
{
"time": 913633.0326134231,
"pid": 34805,
"test": "-[SubBannerGestureUITests testInactiveSceneCancelsAutomaticAdvance]",
"event": "phase-inactive"
},
{
"time": 913633.0621602761,
"event": "phase-active",
"pid": 34805,
"test": "-[SubBannerGestureUITests testInactiveSceneCancelsAutomaticAdvance]"
},
{
"test": "-[SubBannerGestureUITests testInactiveSceneCancelsAutomaticAdvance]",
"time": 913636.61430926,
"pid": 34805,
"event": "phase-inactive"
},
{
"time": 913637.3474018341,
"pid": 34805,
"test": "-[SubBannerGestureUITests testInactiveSceneCancelsAutomaticAdvance]",
"event": "phase-background"
},
{
"event": "phase-inactive",
"time": 913642.895329179,
"pid": 34805,
"test": "-[SubBannerGestureUITests testInactiveSceneCancelsAutomaticAdvance]"
},
{
"pid": 34805,
"test": "-[SubBannerGestureUITests testInactiveSceneCancelsAutomaticAdvance]",
"event": "phase-active",
"time": 913643.185701646
},
{
"pid": 34837,
"test": "-[SubBannerGestureUITests testManualBoundaryFrames]",
"event": "launch",
"time": 913645.102787193
},
{
"event": "phase-inactive",
"pid": 34837,
"time": 913645.9961799241,
"test": "-[SubBannerGestureUITests testManualBoundaryFrames]"
},
{
"test": "-[SubBannerGestureUITests testManualBoundaryFrames]",
"time": 913646.027965715,
"event": "phase-active",
"pid": 34837
},
{
"time": 913649.4333553461,
"test": "-[SubBannerGestureUITests testManualBoundaryFrames]",
"pid": 34837,
"event": "record-manual"
},
{
"pid": 34837,
"test": "-[SubBannerGestureUITests testManualBoundaryFrames]",
"event": "record-forward",
"time": 913652.2542932191
},
{
"test": "-[SubBannerGestureUITests testMixedRatiosAndReplacement]",
"pid": 34870,
"time": 913657.008808529,
"event": "launch"
},
{
"time": 913657.9150553821,
"event": "phase-inactive",
"pid": 34870,
"test": "-[SubBannerGestureUITests testMixedRatiosAndReplacement]"
},
{
"test": "-[SubBannerGestureUITests testMixedRatiosAndReplacement]",
"event": "phase-active",
"time": 913657.9402682821,
"pid": 34870
},
{
"time": 913664.836766332,
"test": "-[SubBannerGestureUITests testMixedRatiosAndReplacement]",
"pid": 34870,
"event": "replace"
},
{
"event": "tap-9",
"time": 913665.2533201941,
"pid": 34870,
"test": "-[SubBannerGestureUITests testMixedRatiosAndReplacement]"
},
{
"time": 913667.64497189,
"test": "-[SubBannerGestureUITests testReplacementDuringAutomaticWrap]",
"pid": 34894,
"event": "launch"
},
{
"event": "phase-inactive",
"time": 913668.5523268571,
"pid": 34894,
"test": "-[SubBannerGestureUITests testReplacementDuringAutomaticWrap]"
},
{
"test": "-[SubBannerGestureUITests testReplacementDuringAutomaticWrap]",
"event": "phase-active",
"pid": 34894,
"time": 913668.589080931
},
{
"time": 913677.1188918981,
"test": "-[SubBannerGestureUITests testReplacementDuringAutomaticWrap]",
"pid": 34894,
"event": "record-replace"
},
{
"event": "observed-mixed-replace",
"test": "-[SubBannerGestureUITests testReplacementDuringAutomaticWrap]",
"time": 913678.958359765,
"pid": 34894
},
{
"test": "-[SubBannerGestureUITests testReplacementDuringAutomaticWrap]",
"time": 913678.959777174,
"pid": 34894,
"event": "replace"
},
{
"time": 913680.7371332,
"event": "tap-9",
"test": "-[SubBannerGestureUITests testReplacementDuringAutomaticWrap]",
"pid": 34894
},
{
"pid": 34933,
"test": "-[SubBannerGestureUITests testSingleAndEmpty]",
"event": "launch",
"time": 913683.2259921201
},
{
"event": "phase-inactive",
"pid": 34933,
"time": 913684.1276096171,
"test": "-[SubBannerGestureUITests testSingleAndEmpty]"
},
{
"pid": 34933,
"test": "-[SubBannerGestureUITests testSingleAndEmpty]",
"event": "phase-active",
"time": 913684.1499581131
},
{
"time": 913687.586987999,
"pid": 34933,
"event": "tap-1",
"test": "-[SubBannerGestureUITests testSingleAndEmpty]"
},
{
"test": "-[SubBannerGestureUITests testSingleAndEmpty]",
"time": 913690.6921521671,
"event": "launch",
"pid": 34951
},
{
"time": 913691.5782256341,
"test": "-[SubBannerGestureUITests testSingleAndEmpty]",
"event": "phase-inactive",
"pid": 34951
},
{
"time": 913691.5995834051,
"event": "phase-active",
"pid": 34951,
"test": "-[SubBannerGestureUITests testSingleAndEmpty]"
},
{
"time": 913696.0071675461,
"pid": 34968,
"test": "-[SubBannerGestureUITests testSwipeDoesNotTap]",
"event": "launch"
},
{
"pid": 34968,
"time": 913696.9234373821,
"event": "phase-inactive",
"test": "-[SubBannerGestureUITests testSwipeDoesNotTap]"
},
{
"event": "phase-active",
"pid": 34968,
"test": "-[SubBannerGestureUITests testSwipeDoesNotTap]",
"time": 913696.949425647
},
{
"event": "tap-2",
"test": "-[SubBannerGestureUITests testSwipeDoesNotTap]",
"time": 913701.720210077,
"pid": 34968
},
{
"event": "launch",
"pid": 34991,
"test": "-[SubBannerGestureUITests testSwipeReleasePositionsAndRepeatedInput]",
"time": 913704.0864146911
},
{
"event": "phase-inactive",
"test": "-[SubBannerGestureUITests testSwipeReleasePositionsAndRepeatedInput]",
"time": 913704.9734107051,
"pid": 34991
},
{
"pid": 34991,
"event": "phase-active",
"time": 913705.0040099691,
"test": "-[SubBannerGestureUITests testSwipeReleasePositionsAndRepeatedInput]"
},
{
"test": "-[SubBannerGestureUITests testSwipeReleasePositionsAndRepeatedInput]",
"event": "tap-1",
"time": 913712.636718675,
"pid": 34991
},
{
"pid": 35015,
"event": "launch",
"time": 913715.053948565,
"test": "-[SubBannerGestureUITests testVerticalParentScrollDoesNotTap]"
},
{
"time": 913715.943832408,
"pid": 35015,
"test": "-[SubBannerGestureUITests testVerticalParentScrollDoesNotTap]",
"event": "phase-inactive"
},
{
"pid": 35015,
"event": "phase-active",
"test": "-[SubBannerGestureUITests testVerticalParentScrollDoesNotTap]",
"time": 913715.967804307
}
]
@@ -0,0 +1 @@
b3e4c5333cf8249ccd7773b5cabd372efb5a5565323bd7778034e110386151fd /Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift
@@ -0,0 +1 @@
b3e4c5333cf8249ccd7773b5cabd372efb5a5565323bd7778034e110386151fd /Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift
@@ -0,0 +1 @@
b3e4c5333cf8249ccd7773b5cabd372efb5a5565323bd7778034e110386151fd /Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift
@@ -0,0 +1,5 @@
PASS: actual APIs/repositories, two exact GETs, requestPlain, anonymous/whitespace auth, original bearer, adjacent follow POST/body/header
PASS: actual two VMs reject non200 valid/invalid JSON, success false, decode/transport errors; preserve error UI and loading
PASS: actual DTOs/VM success, >2^53 and Int.max, enum, required array, 23+ full order, placement/eligibility, legacy banner, subbanner-only/filtered-empty, failure retains data
PASS: HTTP(S)+host, absolute URI/blacklist, malformed/blank/auto-encoded rejection, exact escaped URI, all 8 link combinations, completion-based iOS/web attempts, Android never
PASS: sub-banner production-code checks; native Combine, Moya transport double; no live requests
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -0,0 +1,282 @@
[
{
"time": 913652.291601978,
"file": "0000.png"
},
{
"time": 913652.311797108,
"file": "0001.png"
},
{
"time": 913652.3391558371,
"file": "0002.png"
},
{
"time": 913652.376711928,
"file": "0003.png"
},
{
"file": "0004.png",
"time": 913652.4078878481
},
{
"file": "0005.png",
"time": 913652.4395461461
},
{
"file": "0006.png",
"time": 913652.4726654671
},
{
"time": 913652.538570735,
"file": "0007.png"
},
{
"time": 913652.586738405,
"file": "0008.png"
},
{
"file": "0009.png",
"time": 913652.60548328
},
{
"file": "0010.png",
"time": 913652.663007152
},
{
"file": "0011.png",
"time": 913652.682149627
},
{
"file": "0012.png",
"time": 913652.7373864851
},
{
"file": "0013.png",
"time": 913652.805418989
},
{
"time": 913652.904987868,
"file": "0014.png"
},
{
"time": 913652.9665794891,
"file": "0015.png"
},
{
"file": "0016.png",
"time": 913652.987309437
},
{
"time": 913653.0076745551,
"file": "0017.png"
},
{
"file": "0018.png",
"time": 913653.039906566
},
{
"time": 913653.0749919331,
"file": "0019.png"
},
{
"time": 913653.109595312,
"file": "0020.png"
},
{
"file": "0021.png",
"time": 913653.138689461
},
{
"file": "0022.png",
"time": 913653.1710794021
},
{
"file": "0023.png",
"time": 913653.2016720751
},
{
"file": "0024.png",
"time": 913653.2382149621
},
{
"time": 913653.268156755,
"file": "0025.png"
},
{
"file": "0026.png",
"time": 913653.3231254701
},
{
"file": "0027.png",
"time": 913653.3458972421
},
{
"time": 913653.3699691831,
"file": "0028.png"
},
{
"time": 913653.4018004481,
"file": "0029.png"
},
{
"file": "0030.png",
"time": 913653.4518237461
},
{
"time": 913653.472333822,
"file": "0031.png"
},
{
"file": "0032.png",
"time": 913653.520925558
},
{
"file": "0033.png",
"time": 913653.5443326001
},
{
"file": "0034.png",
"time": 913653.5703751601
},
{
"file": "0035.png",
"time": 913653.6053882821
},
{
"file": "0036.png",
"time": 913653.6348647911
},
{
"file": "0037.png",
"time": 913653.6737473761
},
{
"file": "0038.png",
"time": 913653.701857235
},
{
"file": "0039.png",
"time": 913653.7411189
},
{
"file": "0040.png",
"time": 913653.798327717
},
{
"file": "0041.png",
"time": 913653.8416348731
},
{
"time": 913653.8685095541,
"file": "0042.png"
},
{
"file": "0043.png",
"time": 913653.904678173
},
{
"time": 913653.9346417851,
"file": "0044.png"
},
{
"time": 913653.9724434691,
"file": "0045.png"
},
{
"file": "0046.png",
"time": 913654.0039547511
},
{
"file": "0047.png",
"time": 913654.03900551
},
{
"time": 913654.072691896,
"file": "0048.png"
},
{
"file": "0049.png",
"time": 913654.1242488661
},
{
"file": "0050.png",
"time": 913654.145452633
},
{
"time": 913654.172808056,
"file": "0051.png"
},
{
"file": "0052.png",
"time": 913654.2075264001
},
{
"time": 913654.242221885,
"file": "0053.png"
},
{
"time": 913654.276169833,
"file": "0054.png"
},
{
"time": 913654.306327088,
"file": "0055.png"
},
{
"file": "0056.png",
"time": 913654.341744952
},
{
"file": "0057.png",
"time": 913654.3735942411
},
{
"file": "0058.png",
"time": 913654.407528787
},
{
"file": "0059.png",
"time": 913654.4408740861
},
{
"file": "0060.png",
"time": 913654.475743551
},
{
"time": 913654.5077910591,
"file": "0061.png"
},
{
"file": "0062.png",
"time": 913654.5393077991
},
{
"time": 913654.5721815861,
"file": "0063.png"
},
{
"file": "0064.png",
"time": 913654.605473313
},
{
"file": "0065.png",
"time": 913654.6419051881
},
{
"file": "0066.png",
"time": 913654.6744255021
},
{
"file": "0067.png",
"time": 913654.705513195
},
{
"file": "0068.png",
"time": 913654.740565455
},
{
"file": "0069.png",
"time": 913654.7747490801
}
]
@@ -0,0 +1,4 @@
fb9c6a4fa0ada92335df184de26eba1f560d243ae7ee0a22df92a3ab0841bba0 work/tests/SubBannerGestureHarness.swift
17ca9e2b84ede28fcd69043585a0ae8fc684e889c86196431ddb67904ae49f94 work/tests/SubBannerGestureUITests.swift
d1ace05fd9a0e703aec1eeada0630951b69aff0094e51d2e2824c6f059c51cb0 work/tests/run-sub-banner-gesture-tests.sh
90d49c05bd5b2ef8872842fd0dcbb372687ad363fd8293417d61e320c33ada8c work/tests/generate-sub-banner-gesture-project.rb
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -0,0 +1,478 @@
[
{
"file": "0000.png",
"time": 913625.5146856791
},
{
"file": "0001.png",
"time": 913625.543152694
},
{
"time": 913625.578383538,
"file": "0002.png"
},
{
"file": "0003.png",
"time": 913625.6081350951
},
{
"file": "0004.png",
"time": 913625.642946815
},
{
"file": "0005.png",
"time": 913625.675942062
},
{
"file": "0006.png",
"time": 913625.7072436911
},
{
"time": 913625.7405944001,
"file": "0007.png"
},
{
"file": "0008.png",
"time": 913625.772961818
},
{
"file": "0009.png",
"time": 913625.8083248751
},
{
"file": "0010.png",
"time": 913625.839961951
},
{
"file": "0011.png",
"time": 913625.875382437
},
{
"file": "0012.png",
"time": 913625.907734352
},
{
"time": 913625.9399363911,
"file": "0013.png"
},
{
"file": "0014.png",
"time": 913625.9756715071
},
{
"time": 913626.0076115291,
"file": "0015.png"
},
{
"time": 913626.040202938,
"file": "0016.png"
},
{
"time": 913626.0763464931,
"file": "0017.png"
},
{
"time": 913626.1074425881,
"file": "0018.png"
},
{
"file": "0019.png",
"time": 913626.142858917
},
{
"time": 913626.174399673,
"file": "0020.png"
},
{
"file": "0021.png",
"time": 913626.2089162191
},
{
"time": 913626.245077291,
"file": "0022.png"
},
{
"file": "0023.png",
"time": 913626.273724116
},
{
"time": 913626.308378434,
"file": "0024.png"
},
{
"time": 913626.342996428,
"file": "0025.png"
},
{
"file": "0026.png",
"time": 913626.375832002
},
{
"file": "0027.png",
"time": 913626.4081797751
},
{
"file": "0028.png",
"time": 913626.4584254951
},
{
"time": 913626.47900169,
"file": "0029.png"
},
{
"file": "0030.png",
"time": 913626.505358272
},
{
"file": "0031.png",
"time": 913626.539463017
},
{
"file": "0032.png",
"time": 913626.573096725
},
{
"file": "0033.png",
"time": 913626.608633795
},
{
"time": 913626.6417559021,
"file": "0034.png"
},
{
"file": "0035.png",
"time": 913626.6760256
},
{
"time": 913626.707807358,
"file": "0036.png"
},
{
"file": "0037.png",
"time": 913626.7404586211
},
{
"file": "0038.png",
"time": 913626.7742304481
},
{
"time": 913626.8069071311,
"file": "0039.png"
},
{
"time": 913626.841651942,
"file": "0040.png"
},
{
"time": 913626.875099051,
"file": "0041.png"
},
{
"file": "0042.png",
"time": 913626.906885443
},
{
"file": "0043.png",
"time": 913626.9402810431
},
{
"file": "0044.png",
"time": 913626.9741933261
},
{
"file": "0045.png",
"time": 913627.0088141051
},
{
"file": "0046.png",
"time": 913627.04069364
},
{
"file": "0047.png",
"time": 913627.0738882851
},
{
"time": 913627.106783885,
"file": "0048.png"
},
{
"file": "0049.png",
"time": 913627.142314809
},
{
"file": "0050.png",
"time": 913627.1741426891
},
{
"file": "0051.png",
"time": 913627.207692999
},
{
"time": 913627.2382389171,
"file": "0052.png"
},
{
"time": 913627.2715684241,
"file": "0053.png"
},
{
"file": "0054.png",
"time": 913627.3016557251
},
{
"file": "0055.png",
"time": 913627.3565241981
},
{
"time": 913627.3890422831,
"file": "0056.png"
},
{
"time": 913627.410564169,
"file": "0057.png"
},
{
"file": "0058.png",
"time": 913627.44241933
},
{
"time": 913627.47227425,
"file": "0059.png"
},
{
"time": 913627.503578981,
"file": "0060.png"
},
{
"time": 913627.53656089,
"file": "0061.png"
},
{
"time": 913627.5706346061,
"file": "0062.png"
},
{
"time": 913627.6059774631,
"file": "0063.png"
},
{
"file": "0064.png",
"time": 913627.642300406
},
{
"time": 913627.672786911,
"file": "0065.png"
},
{
"file": "0066.png",
"time": 913627.7059342221
},
{
"file": "0067.png",
"time": 913627.7404805991
},
{
"file": "0068.png",
"time": 913627.772288126
},
{
"file": "0069.png",
"time": 913627.806091586
},
{
"file": "0070.png",
"time": 913627.8423069571
},
{
"time": 913627.872227921,
"file": "0071.png"
},
{
"file": "0072.png",
"time": 913627.904815815
},
{
"time": 913627.9391412591,
"file": "0073.png"
},
{
"file": "0074.png",
"time": 913627.974508933
},
{
"time": 913628.0064958821,
"file": "0075.png"
},
{
"time": 913628.0413351611,
"file": "0076.png"
},
{
"file": "0077.png",
"time": 913628.072915519
},
{
"file": "0078.png",
"time": 913628.1060072781
},
{
"file": "0079.png",
"time": 913628.141235796
},
{
"file": "0080.png",
"time": 913628.1745066891
},
{
"file": "0081.png",
"time": 913628.20653584
},
{
"file": "0082.png",
"time": 913628.240295661
},
{
"time": 913628.274166323,
"file": "0083.png"
},
{
"time": 913628.306417725,
"file": "0084.png"
},
{
"file": "0085.png",
"time": 913628.341532737
},
{
"time": 913628.3915037521,
"file": "0086.png"
},
{
"file": "0087.png",
"time": 913628.411066954
},
{
"time": 913628.455057331,
"file": "0088.png"
},
{
"file": "0089.png",
"time": 913628.474613611
},
{
"time": 913628.503860494,
"file": "0090.png"
},
{
"file": "0091.png",
"time": 913628.5366643631
},
{
"file": "0092.png",
"time": 913628.573629267
},
{
"file": "0093.png",
"time": 913628.604441763
},
{
"time": 913628.6594196,
"file": "0094.png"
},
{
"file": "0095.png",
"time": 913628.6832831871
},
{
"file": "0096.png",
"time": 913628.7330405031
},
{
"time": 913628.77642887,
"file": "0097.png"
},
{
"file": "0098.png",
"time": 913628.8043427521
},
{
"file": "0099.png",
"time": 913628.840021964
},
{
"file": "0100.png",
"time": 913628.8716845771
},
{
"time": 913628.905594829,
"file": "0101.png"
},
{
"file": "0102.png",
"time": 913628.93960048
},
{
"file": "0103.png",
"time": 913628.9711876811
},
{
"time": 913629.0029976941,
"file": "0104.png"
},
{
"file": "0105.png",
"time": 913629.0400746231
},
{
"file": "0106.png",
"time": 913629.071553146
},
{
"file": "0107.png",
"time": 913629.103350113
},
{
"file": "0108.png",
"time": 913629.140292271
},
{
"time": 913629.168505785,
"file": "0109.png"
},
{
"time": 913629.207598084,
"file": "0110.png"
},
{
"file": "0111.png",
"time": 913629.236739716
},
{
"time": 913629.272875122,
"file": "0112.png"
},
{
"file": "0113.png",
"time": 913629.307721958
},
{
"file": "0114.png",
"time": 913629.3432565581
},
{
"file": "0115.png",
"time": 913629.3731304981
},
{
"file": "0116.png",
"time": 913629.407447105
},
{
"file": "0117.png",
"time": 913629.4423861081
},
{
"time": 913629.477664105,
"file": "0118.png"
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@@ -0,0 +1,270 @@
[
{
"file": "0000.png",
"time": 913649.4789123321
},
{
"file": "0001.png",
"time": 913649.5409981351
},
{
"file": "0002.png",
"time": 913649.5719159461
},
{
"time": 913649.604001975,
"file": "0003.png"
},
{
"file": "0004.png",
"time": 913649.637579345
},
{
"file": "0005.png",
"time": 913649.6715946691
},
{
"time": 913649.7373082781,
"file": "0006.png"
},
{
"file": "0007.png",
"time": 913649.773161266
},
{
"file": "0008.png",
"time": 913649.837708718
},
{
"file": "0009.png",
"time": 913649.897244942
},
{
"file": "0010.png",
"time": 913649.916900538
},
{
"file": "0011.png",
"time": 913650.0185351061
},
{
"file": "0012.png",
"time": 913650.040369812
},
{
"file": "0013.png",
"time": 913650.0959979381
},
{
"file": "0014.png",
"time": 913650.115852643
},
{
"time": 913650.1387285291,
"file": "0015.png"
},
{
"time": 913650.1890770551,
"file": "0016.png"
},
{
"time": 913650.209703319,
"file": "0017.png"
},
{
"file": "0018.png",
"time": 913650.257690393
},
{
"file": "0019.png",
"time": 913650.2955483911
},
{
"file": "0020.png",
"time": 913650.321425457
},
{
"file": "0021.png",
"time": 913650.352865361
},
{
"time": 913650.3855440071,
"file": "0022.png"
},
{
"file": "0023.png",
"time": 913650.422998685
},
{
"time": 913650.4526552961,
"file": "0024.png"
},
{
"file": "0025.png",
"time": 913650.485515214
},
{
"time": 913650.5055894121,
"file": "0026.png"
},
{
"file": "0027.png",
"time": 913650.5543494751
},
{
"time": 913650.5751487471,
"file": "0028.png"
},
{
"file": "0029.png",
"time": 913650.620362143
},
{
"file": "0030.png",
"time": 913650.640958155
},
{
"file": "0031.png",
"time": 913650.6845618271
},
{
"time": 913650.7045911091,
"file": "0032.png"
},
{
"file": "0033.png",
"time": 913650.734892515
},
{
"time": 913650.774539203,
"file": "0034.png"
},
{
"file": "0035.png",
"time": 913650.8017526851
},
{
"file": "0036.png",
"time": 913650.8394169301
},
{
"file": "0037.png",
"time": 913650.8681549091
},
{
"file": "0038.png",
"time": 913650.926757905
},
{
"time": 913650.972167773,
"file": "0039.png"
},
{
"time": 913651.03541228,
"file": "0040.png"
},
{
"time": 913651.109038961,
"file": "0041.png"
},
{
"time": 913651.1392424961,
"file": "0042.png"
},
{
"time": 913651.175997302,
"file": "0043.png"
},
{
"file": "0044.png",
"time": 913651.207577649
},
{
"time": 913651.2412511851,
"file": "0045.png"
},
{
"file": "0046.png",
"time": 913651.272701774
},
{
"file": "0047.png",
"time": 913651.307512223
},
{
"file": "0048.png",
"time": 913651.343124047
},
{
"time": 913651.3754428921,
"file": "0049.png"
},
{
"time": 913651.4083072931,
"file": "0050.png"
},
{
"file": "0051.png",
"time": 913651.441592662
},
{
"file": "0052.png",
"time": 913651.4762968901
},
{
"file": "0053.png",
"time": 913651.506200829
},
{
"time": 913651.5413629961,
"file": "0054.png"
},
{
"file": "0055.png",
"time": 913651.5741396641
},
{
"time": 913651.607966713,
"file": "0056.png"
},
{
"file": "0057.png",
"time": 913651.640467989
},
{
"file": "0058.png",
"time": 913651.675139843
},
{
"time": 913651.707729437,
"file": "0059.png"
},
{
"time": 913651.740942755,
"file": "0060.png"
},
{
"time": 913651.7733378131,
"file": "0061.png"
},
{
"time": 913651.805534414,
"file": "0062.png"
},
{
"file": "0063.png",
"time": 913651.8411757961
},
{
"file": "0064.png",
"time": 913651.876669439
},
{
"time": 913651.9073031051,
"file": "0065.png"
},
{
"time": 913651.941832795,
"file": "0066.png"
}
]
@@ -0,0 +1,69 @@
Test Case '-[GestureUITests.SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]' started.
t = 17.06s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]' passed (17.665 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testDirectionsWrapShortDragAndTap]' started.
t = 6.17s Synthesize event
t = 6.54s Press "1 / 3" Button[0.20, 0.50] for 0.1 seconds, then drag to "1 / 3" Button[0.80, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 6.62s Synthesize event
t = 7.74s Synthesize event
t = 8.22s Press "3 / 3" Button[0.80, 0.50] for 0.1 seconds, then drag to "3 / 3" Button[0.20, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 8.30s Synthesize event
t = 9.34s Press "1 / 3" Button[0.50, 0.50] for 0.1 seconds, then drag to "1 / 3" Button[0.41, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 9.42s Synthesize event
t = 10.10s Press "1 / 3" Button[0.50, 0.50] for 0.1 seconds, then drag to "1 / 3" Button[0.46, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 10.18s Synthesize event
t = 10.88s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testDirectionsWrapShortDragAndTap]' passed (11.506 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testDisappearanceDuringAutomaticWrap]' started.
t = 14.44s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testDisappearanceDuringAutomaticWrap]' passed (16.138 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testInactiveSceneCancelsAutomaticAdvance]' started.
Test Case '-[GestureUITests.SubBannerGestureUITests testInactiveSceneCancelsAutomaticAdvance]' passed (12.944 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testManualBoundaryFrames]' started.
t = 6.14s Synthesize event
t = 6.46s Press "1 / 3" Button[0.20, 0.50] for 0.1 seconds, then drag to "1 / 3" Button[0.80, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 6.59s Synthesize event
t = 8.96s Synthesize event
t = 9.29s Press "3 / 3" Button[0.80, 0.50] for 0.1 seconds, then drag to "3 / 3" Button[0.20, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 9.42s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testManualBoundaryFrames]' passed (11.906 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testMixedRatiosAndReplacement]' started.
t = 6.18s Press "1 / 3" Button[0.80, 0.50] for 0.1 seconds, then drag to "1 / 3" Button[0.20, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 6.26s Synthesize event
t = 7.32s Press "2 / 3" Button[0.20, 0.50] for 0.1 seconds, then drag to "2 / 3" Button[0.80, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 7.41s Synthesize event
t = 8.39s Press "1 / 3" Button[0.20, 0.50] for 0.1 seconds, then drag to "1 / 3" Button[0.80, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 8.47s Synthesize event
t = 9.63s Synthesize event
t = 10.06s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testMixedRatiosAndReplacement]' passed (10.653 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testReplacementDuringAutomaticWrap]' started.
t = 14.86s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testReplacementDuringAutomaticWrap]' passed (15.490 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testSingleAndEmpty]' started.
t = 6.12s Synthesize event
t = 6.47s Press "1 / 1" Button[0.80, 0.50] for 0.1 seconds, then drag to "1 / 1" Button[0.20, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 6.55s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testSingleAndEmpty]' passed (12.696 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testSwipeDoesNotTap]' started.
t = 6.14s Press "1 / 3" Button[0.80, 0.50] for 0.1 seconds, then drag to "1 / 3" Button[0.20, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 6.23s Synthesize event
t = 7.37s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testSwipeDoesNotTap]' passed (8.157 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testSwipeReleasePositionsAndRepeatedInput]' started.
t = 6.05s Press "1 / 3" Button[0.90, 0.50] for 0.2 seconds, then drag to "1 / 3" Button[0.60, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 6.14s Synthesize event
t = 8.18s Press "2 / 3" Button[0.70, 0.50] for 0.0 seconds, then drag to "2 / 3" Button[0.15, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 8.26s Synthesize event
t = 9.13s Press "3 / 3" Button[0.60, 0.50] for 0.1 seconds, then drag to "3 / 3" Button[0.10, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 9.21s Synthesize event
t = 10.32s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testSwipeReleasePositionsAndRepeatedInput]' passed (10.950 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testVerticalParentScrollDoesNotTap]' started.
t = 6.10s Press "1 / 3" Button[0.50, 0.90] for 0.1 seconds, then drag to "1 / 3" Button[0.50, 0.10] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 6.18s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testVerticalParentScrollDoesNotTap]' passed (9.099 seconds).
Executed 11 tests, with 0 failures (0 unexpected) in 137.204 (137.216) seconds
Executed 11 tests, with 0 failures (0 unexpected) in 137.204 (137.217) seconds
Executed 11 tests, with 0 failures (0 unexpected) in 137.204 (137.219) seconds
** TEST SUCCEEDED **
@@ -0,0 +1,181 @@
import SwiftUI
import Kingfisher
struct SubBannerCarousel: View {
let items: [SubBanner]
let onTap: (SubBanner) -> Void
@Environment(\.scenePhase) private var scenePhase
@State private var displayIndex = 1
@State private var itemsRevision = 0
@State private var containerWidth: CGFloat = 0
@State private var imageRatios: [String: CGFloat] = [:]
@State private var lastKnownRatio: CGFloat?
@GestureState private var isSwiping = false
@GestureState private var dragOffset: CGFloat = 0
private static let autoScrollNanoseconds: UInt64 = 5_000_000_000
private static let transitionDuration: TimeInterval = 0.35
private var safeDisplayIndex: Int {
items.count > 1 ? min(max(displayIndex, 0), items.count + 1) : 0
}
private var safeIndex: Int {
items.count > 1 ? (safeDisplayIndex + items.count - 1) % items.count : 0
}
private var pages: [(id: String, item: SubBanner)] {
let originals = items.map { (id: "item-\($0.id)", item: $0) }
guard items.count > 1, let first = items.first, let last = items.last else { return originals }
return [(id: "leading-\(last.id)", item: last)] + originals + [(id: "trailing-\(first.id)", item: first)]
}
private var currentRatio: CGFloat? {
guard !items.isEmpty else { return nil }
return imageRatios[items[safeIndex].imageUrl]
}
private var height: CGFloat {
guard !items.isEmpty, let ratio = currentRatio ?? lastKnownRatio else { return 0 }
return containerWidth / ratio
}
var body: some View {
GeometryReader { geometry in
HStack(spacing: 0) {
ForEach(pages, id: \.id) { page in
Button {
onTap(page.item)
} label: {
KFImage(page.item.imageURL)
.placeholder { Color.gray800 }
.onSuccess { result in
let size = result.image.size
guard size.width > 0, size.height > 0 else { return }
imageRatios[page.item.imageUrl] = size.width / size.height
}
.startLoadingBeforeViewAppear()
.resizable()
.scaledToFit()
.frame(width: geometry.size.width, height: height)
.background(Color.gray800)
}
.buttonStyle(SubBannerTapStyle(drag: dragGesture(width: geometry.size.width)))
.accessibilityLabel(Text("\(safeIndex + 1) / \(items.count)"))
.accessibilityHidden(pages[safeDisplayIndex].id != page.id)
}
}
.offset(x: -CGFloat(safeDisplayIndex) * geometry.size.width + dragOffset)
.animation(.easeOut(duration: Self.transitionDuration), value: displayIndex)
.frame(width: geometry.size.width, height: height, alignment: .leading)
.contentShape(Rectangle())
.clipped()
.overlay(alignment: .topTrailing) {
if !items.isEmpty && height > 0 {
pageIndicator
.padding(SodaSpacing.s14)
}
}
.onAppear { containerWidth = geometry.size.width }
.onChange(of: geometry.size.width) { containerWidth = $0 }
}
.frame(height: height)
.onChange(of: currentRatio) { ratio in
if let ratio { lastKnownRatio = ratio }
}
.onChange(of: items) { _ in
itemsRevision += 1
normalize(to: items.count > 1 ? 1 : 0)
if items.isEmpty { lastKnownRatio = nil }
}
.onDisappear {
itemsRevision += 1
normalize(to: items.count > 1 ? safeIndex + 1 : 0)
}
.task(id: scenePhase == .active && items.count > 1 ? items : []) {
guard scenePhase == .active, items.count > 1 else { return }
while !Task.isCancelled {
do {
try await Task.sleep(nanoseconds: Self.autoScrollNanoseconds)
} catch {
return
}
guard !Task.isCancelled else { return }
if !isSwiping { move(by: 1) }
}
}
.accessibilityAdjustableAction { direction in
switch direction {
case .increment: move(by: 1)
case .decrement: move(by: -1)
@unknown default: break
}
}
}
private var pageIndicator: some View {
HStack(spacing: 0) {
Text(String(format: "%02d", safeIndex + 1))
.foregroundColor(.white)
Text("/" + String(format: "%02d", items.count))
.foregroundColor(.gray400)
}
.appFont(.body3)
.lineLimit(1)
.fixedSize(horizontal: true, vertical: false)
.padding(.horizontal, SodaSpacing.s6)
.padding(.vertical, SodaSpacing.s4)
.background(Color.gray900)
.clipShape(Capsule())
.accessibilityHidden(true)
}
private func dragGesture(width: CGFloat) -> some Gesture {
DragGesture(minimumDistance: SodaSpacing.s12, coordinateSpace: .global)
.updating($dragOffset) { value, offset, _ in
if items.count > 1, abs(value.translation.width) > abs(value.translation.height) {
offset = value.translation.width
}
}
.updating($isSwiping) { _, swiping, _ in swiping = true }
.onEnded { value in
guard abs(value.translation.width) > abs(value.translation.height) else { return }
if value.translation.width < -width * 0.2 {
move(by: 1)
} else if value.translation.width > width * 0.2 {
move(by: -1)
}
}
}
private func move(by step: Int) {
guard items.count > 1, (1...items.count).contains(displayIndex) else { return }
let next = displayIndex + step
displayIndex = next
guard next == 0 || next == items.count + 1 else { return }
let revision = itemsRevision
DispatchQueue.main.asyncAfter(deadline: .now() + Self.transitionDuration) {
guard itemsRevision == revision, displayIndex == next else { return }
normalize(to: next == 0 ? items.count : 1)
}
}
private func normalize(to index: Int) {
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) { displayIndex = index }
}
}
private struct SubBannerTapStyle<Drag: Gesture>: PrimitiveButtonStyle {
let drag: Drag
func makeBody(configuration: Configuration) -> some View {
configuration.label
.contentShape(Rectangle())
.simultaneousGesture(drag.exclusively(before: TapGesture().onEnded { configuration.trigger() }))
.accessibilityAction { configuration.trigger() }
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -0,0 +1,466 @@
[
{
"time": 913677.182000758,
"file": "0000.png"
},
{
"file": "0001.png",
"time": 913677.208739417
},
{
"time": 913677.243329994,
"file": "0002.png"
},
{
"file": "0003.png",
"time": 913677.2766025431
},
{
"time": 913677.3236773841,
"file": "0004.png"
},
{
"time": 913677.364179086,
"file": "0005.png"
},
{
"file": "0006.png",
"time": 913677.385782319
},
{
"time": 913677.4096495911,
"file": "0007.png"
},
{
"file": "0008.png",
"time": 913677.4411729571
},
{
"time": 913677.4755007031,
"file": "0009.png"
},
{
"time": 913677.507597494,
"file": "0010.png"
},
{
"time": 913677.541143598,
"file": "0011.png"
},
{
"time": 913677.575468246,
"file": "0012.png"
},
{
"time": 913677.607664886,
"file": "0013.png"
},
{
"file": "0014.png",
"time": 913677.63882929
},
{
"file": "0015.png",
"time": 913677.670936167
},
{
"time": 913677.704879379,
"file": "0016.png"
},
{
"file": "0017.png",
"time": 913677.7407021441
},
{
"time": 913677.770956119,
"file": "0018.png"
},
{
"file": "0019.png",
"time": 913677.805286434
},
{
"file": "0020.png",
"time": 913677.840920433
},
{
"time": 913677.8730100831,
"file": "0021.png"
},
{
"time": 913677.9581180731,
"file": "0022.png"
},
{
"file": "0023.png",
"time": 913678.00875871
},
{
"time": 913678.0434123791,
"file": "0024.png"
},
{
"file": "0025.png",
"time": 913678.073637673
},
{
"time": 913678.1048476781,
"file": "0026.png"
},
{
"file": "0027.png",
"time": 913678.143166751
},
{
"file": "0028.png",
"time": 913678.1782429271
},
{
"file": "0029.png",
"time": 913678.2079582481
},
{
"file": "0030.png",
"time": 913678.239294811
},
{
"time": 913678.272196469,
"file": "0031.png"
},
{
"time": 913678.306947525,
"file": "0032.png"
},
{
"file": "0033.png",
"time": 913678.3415255671
},
{
"file": "0034.png",
"time": 913678.372522235
},
{
"time": 913678.407802038,
"file": "0035.png"
},
{
"time": 913678.440060911,
"file": "0036.png"
},
{
"file": "0037.png",
"time": 913678.4746635171
},
{
"time": 913678.504926466,
"file": "0038.png"
},
{
"time": 913678.540071622,
"file": "0039.png"
},
{
"file": "0040.png",
"time": 913678.57196552
},
{
"file": "0041.png",
"time": 913678.608095773
},
{
"file": "0042.png",
"time": 913678.640771551
},
{
"file": "0043.png",
"time": 913678.674382659
},
{
"file": "0044.png",
"time": 913678.705199606
},
{
"time": 913678.740146241,
"file": "0045.png"
},
{
"time": 913678.771469746,
"file": "0046.png"
},
{
"file": "0047.png",
"time": 913678.8079382931
},
{
"time": 913678.837268878,
"file": "0048.png"
},
{
"time": 913678.8705325951,
"file": "0049.png"
},
{
"time": 913678.957523811,
"file": "0050.png"
},
{
"file": "0051.png",
"time": 913679.0235937741
},
{
"time": 913679.0449015361,
"file": "0052.png"
},
{
"file": "0053.png",
"time": 913679.0738721241
},
{
"file": "0054.png",
"time": 913679.1065804161
},
{
"file": "0055.png",
"time": 913679.140883742
},
{
"file": "0056.png",
"time": 913679.173805641
},
{
"time": 913679.209024106,
"file": "0057.png"
},
{
"file": "0058.png",
"time": 913679.2407425691
},
{
"time": 913679.2749094791,
"file": "0059.png"
},
{
"time": 913679.307120681,
"file": "0060.png"
},
{
"file": "0061.png",
"time": 913679.3397686051
},
{
"time": 913679.3735209911,
"file": "0062.png"
},
{
"file": "0063.png",
"time": 913679.407494329
},
{
"time": 913679.4407483081,
"file": "0064.png"
},
{
"time": 913679.4731188971,
"file": "0065.png"
},
{
"time": 913679.5077481561,
"file": "0066.png"
},
{
"file": "0067.png",
"time": 913679.557752116
},
{
"time": 913679.5799938161,
"file": "0068.png"
},
{
"file": "0069.png",
"time": 913679.6070735101
},
{
"file": "0070.png",
"time": 913679.640295565
},
{
"time": 913679.6770715461,
"file": "0071.png"
},
{
"file": "0072.png",
"time": 913679.707296167
},
{
"file": "0073.png",
"time": 913679.740801326
},
{
"file": "0074.png",
"time": 913679.7748053831
},
{
"time": 913679.8080684431,
"file": "0075.png"
},
{
"file": "0076.png",
"time": 913679.8412805101
},
{
"file": "0077.png",
"time": 913679.875727502
},
{
"file": "0078.png",
"time": 913679.9071129421
},
{
"file": "0079.png",
"time": 913679.9597379351
},
{
"time": 913679.97902312,
"file": "0080.png"
},
{
"file": "0081.png",
"time": 913680.0047622471
},
{
"file": "0082.png",
"time": 913680.042046424
},
{
"file": "0083.png",
"time": 913680.0745555841
},
{
"file": "0084.png",
"time": 913680.1082944991
},
{
"file": "0085.png",
"time": 913680.142798252
},
{
"time": 913680.173877244,
"file": "0086.png"
},
{
"time": 913680.2076699181,
"file": "0087.png"
},
{
"time": 913680.240965635,
"file": "0088.png"
},
{
"time": 913680.276695547,
"file": "0089.png"
},
{
"file": "0090.png",
"time": 913680.306939149
},
{
"file": "0091.png",
"time": 913680.339200414
},
{
"file": "0092.png",
"time": 913680.3754256241
},
{
"file": "0093.png",
"time": 913680.40727143
},
{
"time": 913680.4413317901,
"file": "0094.png"
},
{
"file": "0095.png",
"time": 913680.4747653401
},
{
"time": 913680.5317274261,
"file": "0096.png"
},
{
"file": "0097.png",
"time": 913680.5543979681
},
{
"file": "0098.png",
"time": 913680.5904828061
},
{
"file": "0099.png",
"time": 913680.6107395231
},
{
"time": 913680.6469458761,
"file": "0100.png"
},
{
"time": 913680.6707490981,
"file": "0101.png"
},
{
"file": "0102.png",
"time": 913680.724500303
},
{
"time": 913680.7592690381,
"file": "0103.png"
},
{
"file": "0104.png",
"time": 913680.7851261741
},
{
"file": "0105.png",
"time": 913680.805062163
},
{
"file": "0106.png",
"time": 913680.8376410251
},
{
"time": 913680.8706479521,
"file": "0107.png"
},
{
"file": "0108.png",
"time": 913680.9038749371
},
{
"file": "0109.png",
"time": 913680.9381074051
},
{
"file": "0110.png",
"time": 913680.987470016
},
{
"file": "0111.png",
"time": 913681.0077511901
},
{
"file": "0112.png",
"time": 913681.0417480101
},
{
"time": 913681.0748786661,
"file": "0113.png"
},
{
"time": 913681.107035094,
"file": "0114.png"
},
{
"file": "0115.png",
"time": 913681.1391082171
}
]
@@ -0,0 +1,42 @@
{
"devicesAndConfigurations" : [
{
"device" : {
"architecture" : "x86_64",
"deviceId" : "B5F2BA0D-327C-4EE3-94EE-39710A0E1ACC",
"deviceName" : "SubBannerGestureQA-20261030",
"modelName" : "iPhone SE (3rd generation)",
"osBuildNumber" : "22D8075",
"osVersion" : "18.3.1",
"platform" : "iOS Simulator"
},
"expectedFailures" : 0,
"failedTests" : 0,
"passedTests" : 11,
"skippedTests" : 0,
"testPlanConfiguration" : {
"configurationId" : "1",
"configurationName" : "Test Scheme Action"
}
}
],
"environmentDescription" : "Gesture · Built with macOS 26.0",
"expectedFailures" : 0,
"failedTests" : 0,
"finishTime" : 1793292296.135,
"passedTests" : 11,
"result" : "Passed",
"skippedTests" : 0,
"startTime" : 1793292150.956,
"statistics" : [
],
"testFailures" : [
],
"title" : "Test - Gesture",
"topInsights" : [
],
"totalTestCount" : 11
}
@@ -0,0 +1 @@
PASS: production carousel/model/UIKit opener iOS16.6 typecheck with installed Kingfisher and real theme/font files
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

@@ -0,0 +1,97 @@
# P3-T1: 실제 터치 오탭과 경계 순환 수정
## 결과와 범위
- 제품 변경은 `SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift` 한 파일이다. 기존 main `BannerCarousel`, 두 호출부, 모델/API/opener와 production project는 수정하지 않았다.
- 실제 XCTest 좌표 터치 RED를 제품 변경 전에 확인했다. 최종 독립 native app/UI-test target은 **11 tests, 0 failures**, `xcodebuild` exit 0이다. 소스 문자열이나 상태 직접 호출을 제스처 증거로 사용하지 않았다.
- `check-sub-banners.sh`와 `--ui` 모두 exit 0이다. 테스트용 앱, runner와 소유 root/cache를 정리했고 빌려 쓴 simulator와 parent root는 유지했다.
## 원인과 최소 수정
1. 기본 Button과 simultaneous drag의 경합에서 `@GestureState isSwiping`이 release 시 초기화되어 이전 배너 action이 실행됐다. `PrimitiveButtonStyle` 안에서 `drag.exclusively(before: TapGesture())`로 상호 배제한다. 외부 부모 ScrollView에는 simultaneous로 참여하고 접근성 activation은 `configuration.trigger()`를 유지한다. 고정 시간 탭 억제나 high-priority gesture를 사용하지 않았다.
2. 인식기를 이동하는 라벨에 붙인 뒤 로컬 좌표로는 112pt 이동이 페이지 임계값에 못 미치는 실제 회귀가 발생했다. `.global` 좌표계로 바꾼 같은 테스트가 통과했다. 17pt·34pt의 인식된 짧은 드래그는 tap 없이 제자리를 유지한다.
3. 실제 경계 이동에서 애니메이션을 끄던 분기를 제거했다. `[last] + originals + [first]`에 별도 clone identity와 display index를 부여하고 0.35초 같은 방향 전환 후 같은 이미지 위치로만 무애니메이션 정렬한다. 정렬 callback은 revision/index로 보호하며 목록 교체·disappear가 revision을 무효화한다. 경계 정렬 전 display index를 더 넘기지 않는다.
API, 전체 목록, 색상/간격/font tokens, 링크 전달, 실제 페이지 수, URL별 원본 비율, 5초 간격과 scene task cancellation은 유지했다. `isSwiping`은 자동 전환 보류에만 쓰며 tap 판정에 사용하지 않는다.
## 하네스와 실행 환경
- 도구: Xcode 26.0 / Swift 5 mode, x86_64 simulator, Ruby `xcodeproj` 1.27.0. 별도 의존성 설치 없음.
- 사용자 소유 기기: `B5F2BA0D-327C-4EE3-94EE-39710A0E1ACC`, iPhone SE 3, 실행 결과의 OS 18.3.1 / 22D8075. 모든 run은 `-parallel-testing-enabled NO`, 별도 device 생성 없음.
- 자체 root: `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa` (검증 후 삭제).
- 앱은 현재 production component/model과 실제 `Color.swift`, `Spacing.swift`, `ColorExtension.swift`, `FontModifier.swift`를 원본 경로에서 컴파일한다. 독립 앱에 필요한 `LanguageOption` enum만 테스트 대역이다.
- 기존 `/Users/klaus/Library/Developer/Xcode/DerivedData/SodaLive-clzdtvqrsfonoxcijatzkrqdzrsu/Build/Products/Debug-iphonesimulator/Kingfisher.o`와 module을 읽기 전용으로 링크한다. 생성 이미지 3장을 실제 Kingfisher memory cache에 미리 저장한다. 모든 downloader 요청은 로컬 URLProtocol에서 차단하므로 live request가 없다.
- 부모 ScrollView 안에서 actual component를 렌더한다. `XCUIApplication`, `coordinate.press(forDuration:thenDragTo:)`, `.tap()`으로 simulator 입력을 보낸다. 화면의 callback 횟수/마지막 ID와 기존 버튼 접근성 label을 assert한다. 입력 전용 scenario는 harness environment만 `.inactive`; 자동/백그라운드 scenario는 실제 scene phase다.
- `work/tests/run-sub-banner-gesture-tests.sh`만 명시적으로 native tests를 실행한다. 기존 `check-*.sh` 자동 검사에는 추가하지 않았다. runner가 출력한 root는 호출자가 증거 추출 후 정리하는 방식이다.
## 실행 명령과 RED/GREEN
제품 수정 전 최초 RED (runner가 위 root를 생성):
```bash
bash work/tests/run-sub-banner-gesture-tests.sh \
-only-testing:GestureUITests/SubBannerGestureUITests/testSwipeDoesNotTap \
-only-testing:GestureUITests/SubBannerGestureUITests/testManualBoundaryFrames \
-only-testing:GestureUITests/SubBannerGestureUITests/testAutomaticFiveSecondWrapFrames
```
**결과:** exit 65. 좌표 drag 뒤 페이지 2 또는 3 도달 assertion은 성공했으나 `taps=1;last=1`이 `taps=0;last=0`과 달라 두 테스트가 실패했다. [native-tests.log](red/native-tests.log), [test-summary.json](red/test-summary.json). 빌드/도구 오류를 RED로 세지 않았다.
최종 GREEN:
```bash
SB_RUN_ROOT=/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa \
SB_RUN_NAME=final-confirmed bash work/tests/run-sub-banner-gesture-tests.sh
```
**결과:** exit 0, 11/11 PASS, 테스트 수행 137.219초. [native-tests.log](green/native-tests.log), [test-summary.json](green/test-summary.json).
| 실제 실행 시나리오 | 확인 결과 |
|---|---|
| 정상 tap, left/right, 양방향 wrap, 직후 tap | drag callback 0, 현재 ID를 tap하면 정확히 1회. clone의 접근성 버튼 중복 없음 |
| 17pt·34pt subthreshold drag, 112pt 이동, 여러 release 위치/hold duration, 연속 입력 | 짧은 drag는 callback/페이지 이동 없음. 충분한 이동과 반복 좌우·wrap 입력은 기대 페이지 도달 |
| 배너 위에서 세로 drag | callback 0, 부모 콘텐츠 row의 실제 화면 Y가 30pt 이상 감소. [vertical-scroll.png](green/vertical-scroll.png) |
| single / empty / 2:1 및 3:1 ratio / 목록 교체 | single tap 유지, drag callback 없음, empty 높이/불필요 영역 없음, 원본 비율 프레임 확인, 교체 항목 ID 9 tap |
| 실제 두 번의 5초 전환 / scene inactive / delayed wrap 중 replace·hide | 자동 1→2→1, 실제 Home 버튼으로 background 6초 후 같은 페이지, 중간 프레임에서 교체·이탈 후 정상 tap/재표시 |
집중 검사 (각 명령에 `TMPDIR=$SB_RUN_ROOT/tmp`, `NODE_COMPILE_CACHE=$SB_RUN_ROOT/node`, `BUN_RUNTIME_TRANSPILER_CACHE_PATH=$SB_RUN_ROOT/bun` 지정):
```bash
bash work/tests/check-sub-banners.sh
bash work/tests/check-sub-banners.sh --ui
```
두 명령 exit 0. [focused.log](green/focused.log), [ui-typecheck.log](green/ui-typecheck.log). `--ui`는 실제 Kingfisher와 theme/font를 사용하는 iOS16.6 target typecheck다.
## 경계 애니메이션: 실제 렌더 프레임
`CADisplayLink` 30fps에서 실제 UIWindow `drawHierarchy` PNG를 저장했다. 375×667 PNG signature/크기를 확인했고 보존한 중간/종료 이미지를 직접 열어 확인했다. 최종 전체 캡처는 자동 116, reverse 67, forward 70, replace 116, hide 119 frames다. 보존 PNG는 각 시퀀스의 시작/중간/종료 일부이며 모든 샘플의 timestamp·색 픽셀 수는 [frame-analysis.json](frame-analysis.json)에 있다. 전체 frame manifest에는 보존하지 않은 샘플 이름도 포함한다.
| 증거 | 관찰 |
|---|---|
| 원본 자동 경계 [0049](red/automatic/0049.png) → [0050](red/automatic/0050.png) | 117 samples 중 양 이미지가 섞인 프레임 0. 약 **31.9ms** 사이에 green 359px → red 359px로 통째 교체 |
| 수정 자동 경계 [0063](green/automatic/0063.png) | 10개 중간 프레임. red가 오른쪽에서 들어오며 green을 왼쪽으로 밀어냄 |
| 수정 수동 reverse [0029](green/manual/0029.png), forward [0032](green/forward/0032.png) | 각각 21/22개 중간 프레임. 들어오는 이미지 영역이 같은 방향으로 단조 증가. 3개 목록에서 중간 green 배너를 역주행하지 않음 |
| replace/hide [events.json](green/events.json) | 실제 red+green 중간 PNG를 감지한 뒤 각각 **1.42ms / 1.00ms**에 목록 교체/제거 실행. 고정 시각을 전환 중 증거로 간주하지 않음 |
| 정렬 후 종료 프레임 | 자동/양방향 수동 모두 마지막 5개 배너 영역 hash 동일. [manual-settled.png](green/manual-settled.png) |
초기 좁은 자동 캡처 창은 전환이 끝난 뒤만 잡았으므로 경계 RED로 쓰지 않았다. 원본 snapshot을 별도 project source로 명시해 8~12초 창으로 재실행하여 위 경계 RED를 확보했다. production은 되돌리거나 다시 편집하지 않았다:
```bash
SB_RUN_ROOT=/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa \
SB_RUN_NAME=red-wide-window \
SB_CAROUSEL_SOURCE="$PWD/docs/20261029_추천_서브_배너/reviews/evidence/native-final/production-source-snapshot/SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift" \
bash work/tests/run-sub-banner-gesture-tests.sh \
-only-testing:GestureUITests/SubBannerGestureUITests/testAutomaticFiveSecondWrapFrames
```
이 테스트는 순환 도달만 assert하므로 원본에서도 exit 0이다. **애니메이션 RED는 위 연속 렌더 샘플의 직접 교체 현상**이며 테스트 PASS를 애니메이션 PASS로 오인하지 않는다. [baseline-replay.log](red/baseline-replay.log)에는 같은 snapshot의 오탭 재실행 실패도 포함한다.
## 시행 중 발견하고 해결한 검증 문제
- tap recognizer만 둔 첫 시도는 34pt drag에서 callback이 발생하여 폐기했다. exclusive recognition으로 해결했다.
- exclusive 인식기를 라벨로 옮긴 뒤 로컬 좌표 112pt 이동 회귀를 실제 입력에서 확인했다. 2초 대기 후에도 실패했으며 전역 좌표 수정으로 동일 테스트가 통과했다.
- 부모 scroll의 초기 preference 측정은 실제 화면이 스크롤되어도 0이었다. 실제 row frame 변위를 assert하도록 수정했다. UI-test의 element/query `.count` 컴파일 오류 1건도 수정했으며 이것은 제품 RED가 아니다.
- 캡처가 중단되어도 오래된 manifest를 사용하지 않도록 매 frame 현재 metadata를 저장했다. 좁은 창 누락과 움직임이 거의 끝난 프레임의 hash 차이를 제품 결함으로 세지 않고, 실제 양 끝 이미지 존재와 완전 정착 후 hash를 검사했다.
## 소스 동결과 정리
| 단계 | SubBannerCarousel SHA-256 |
|---|---|
| 제품 수정 전 native touch RED 및 원본 replay compiled source | `7e25db3076d545ffc61ea3a1c5585e7b8a617964ef406d7570286d75a75e010a` |
| 최종 native 실행 직전 / 직후 / compiled source / cleanup 후 | `b3e4c5333cf8249ccd7773b5cabd372efb5a5565323bd7778034e110386151fd` |
[final-confirmed-before.sha256](green/final-confirmed-before.sha256), [after](green/final-confirmed-after.sha256), [compiled](green/final-confirmed-compiled.sha256), [production snapshot](green/production-SubBannerCarousel.swift), [harness.sha256](green/harness.sha256). 최종 native 증거 이후 production 편집 없음. 현 소스와 snapshot `cmp` exit 0.
[owned-processes.json](owned-processes.json)에 소유 앱 ID와 관찰된 app PID를 기록했다. runner PID 숫자는 xcodebuild 로그에 제공되지 않아 미확인으로 구분한다. 종료 시 launchctl의 해당 app/runner job은 이미 없었고, 두 앱 uninstall exit 0, 소유 root/cache 부재, parent simulator 여전히 Booted, `sub-banner-gesture.Fyv2wD` 존재를 확인했다. [cleanup.json](cleanup.json).
## 미실행 범위
- 전체 main-app build, 전체 16개 check suite, 실제 서비스/외부 링크 launch, 실기기는 이번 범위 밖이다. 기존 native/native-final evidence는 수정하지 않았다.
- iOS16.6은 typecheck이며 실제 touch는 iOS18.3.1 simulator 한 기기다. VoiceOver/Switch Control/hardware keyboard 자체 조작, Reduce Motion, 동시 다중 터치는 실행하지 않았다. 접근성 Button 노출과 action은 유지했지만 전체 보조공학 검증으로 확대 주장하지 않는다.
- LSP 도구는 없으며 native compile/test 및 `--ui` typecheck로 대체했다. 독립 리뷰/추가 agent는 사용자 명시 금지에 따라 실행하지 않았다. commit/push/worktree/의존성 설치 없음.
@@ -0,0 +1,97 @@
{
"appIDs": [
"invalid.sodalive.gesturefix.Gesture",
"invalid.sodalive.gesturefix.GestureUITests.xctrunner"
],
"observedAppPIDs": [
21851,
21916,
23640,
23701,
24930,
25062,
25139,
25194,
25284,
25315,
25378,
25425,
25464,
25511,
26259,
26350,
26454,
27649,
27725,
27783,
27870,
27924,
28018,
28049,
28115,
28139,
28175,
28221,
28244,
29139,
29652,
29722,
29797,
29837,
30488,
30568,
30662,
30730,
30838,
30891,
31026,
31226,
31274,
31298,
31330,
31376,
32123,
32169,
32253,
32294,
32337,
32375,
32435,
32479,
32503,
32525,
32574,
32610,
33668,
33711,
33727,
34228,
34695,
34735,
34767,
34805,
34837,
34870,
34894,
34933,
34951,
34968,
34991,
35015
],
"finalAppPIDs": [
34695,
34735,
34767,
34805,
34837,
34870,
34894,
34933,
34951,
34968,
34991,
35015
],
"note": "XCTest runner managed and exited by xcodebuild; its numeric PID is not emitted in logs. No owned launchctl jobs remain before teardown."
}
@@ -0,0 +1,42 @@
{
"devicesAndConfigurations" : [
{
"device" : {
"architecture" : "x86_64",
"deviceId" : "B5F2BA0D-327C-4EE3-94EE-39710A0E1ACC",
"deviceName" : "SubBannerGestureQA-20261030",
"modelName" : "iPhone SE (3rd generation)",
"osBuildNumber" : "22D8075",
"osVersion" : "18.3.1",
"platform" : "iOS Simulator"
},
"expectedFailures" : 0,
"failedTests" : 0,
"passedTests" : 11,
"skippedTests" : 0,
"testPlanConfiguration" : {
"configurationId" : "1",
"configurationName" : "Test Scheme Action"
}
}
],
"environmentDescription" : "Gesture · Built with macOS 26.0",
"expectedFailures" : 0,
"failedTests" : 0,
"finishTime" : 1793293222.713,
"passedTests" : 11,
"result" : "Passed",
"skippedTests" : 0,
"startTime" : 1793293069.964,
"statistics" : [
],
"testFailures" : [
],
"title" : "Test - Gesture",
"topInsights" : [
],
"totalTestCount" : 11
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -0,0 +1,470 @@
[
{
"file" : "0000.png",
"time" : 913495.54207174003
},
{
"file" : "0001.png",
"time" : 913495.5738208961
},
{
"file" : "0002.png",
"time" : 913495.61067922611
},
{
"time" : 913495.64156974305,
"file" : "0003.png"
},
{
"time" : 913495.6749411471,
"file" : "0004.png"
},
{
"file" : "0005.png",
"time" : 913495.70521654701
},
{
"time" : 913495.73877406307,
"file" : "0006.png"
},
{
"file" : "0007.png",
"time" : 913495.7714096501
},
{
"time" : 913495.80537242105,
"file" : "0008.png"
},
{
"time" : 913495.84002024506,
"file" : "0009.png"
},
{
"file" : "0010.png",
"time" : 913495.87342155003
},
{
"file" : "0011.png",
"time" : 913495.91759019205
},
{
"time" : 913495.93913621211,
"file" : "0012.png"
},
{
"file" : "0013.png",
"time" : 913495.9718883941
},
{
"time" : 913496.01687527308,
"file" : "0014.png"
},
{
"file" : "0015.png",
"time" : 913496.04254880711
},
{
"file" : "0016.png",
"time" : 913496.07467698609
},
{
"file" : "0017.png",
"time" : 913496.10751351411
},
{
"file" : "0018.png",
"time" : 913496.14147810102
},
{
"time" : 913496.172093114,
"file" : "0019.png"
},
{
"file" : "0020.png",
"time" : 913496.20843185706
},
{
"time" : 913496.2405724331,
"file" : "0021.png"
},
{
"time" : 913496.27334535203,
"file" : "0022.png"
},
{
"time" : 913496.30913038005,
"file" : "0023.png"
},
{
"file" : "0024.png",
"time" : 913496.34009797906
},
{
"file" : "0025.png",
"time" : 913496.37281471805
},
{
"file" : "0026.png",
"time" : 913496.455701945
},
{
"time" : 913496.50706537208,
"file" : "0027.png"
},
{
"file" : "0028.png",
"time" : 913496.54125389305
},
{
"file" : "0029.png",
"time" : 913496.5738092541
},
{
"file" : "0030.png",
"time" : 913496.6069761751
},
{
"file" : "0031.png",
"time" : 913496.64345990401
},
{
"file" : "0032.png",
"time" : 913496.67478215508
},
{
"file" : "0033.png",
"time" : 913496.70709522709
},
{
"file" : "0034.png",
"time" : 913496.74192454701
},
{
"file" : "0035.png",
"time" : 913496.77307740704
},
{
"time" : 913496.80924846802,
"file" : "0036.png"
},
{
"file" : "0037.png",
"time" : 913496.84352450003
},
{
"file" : "0038.png",
"time" : 913496.87277250108
},
{
"file" : "0039.png",
"time" : 913496.9077881251
},
{
"file" : "0040.png",
"time" : 913496.95619421406
},
{
"file" : "0041.png",
"time" : 913496.9842686241
},
{
"time" : 913497.00723700109,
"file" : "0042.png"
},
{
"time" : 913497.041060513,
"file" : "0043.png"
},
{
"time" : 913497.07430782309,
"file" : "0044.png"
},
{
"file" : "0045.png",
"time" : 913497.10735687101
},
{
"file" : "0046.png",
"time" : 913497.14334790211
},
{
"time" : 913497.17468008504,
"file" : "0047.png"
},
{
"time" : 913497.2073035331,
"file" : "0048.png"
},
{
"file" : "0049.png",
"time" : 913497.2407012051
},
{
"file" : "0050.png",
"time" : 913497.27261283004
},
{
"file" : "0051.png",
"time" : 913497.30841093604
},
{
"time" : 913497.36005954805,
"file" : "0052.png"
},
{
"file" : "0053.png",
"time" : 913497.37658144103
},
{
"file" : "0054.png",
"time" : 913497.40616276604
},
{
"file" : "0055.png",
"time" : 913497.44372079405
},
{
"time" : 913497.47105816507,
"file" : "0056.png"
},
{
"file" : "0057.png",
"time" : 913497.50395031006
},
{
"time" : 913497.54083171708,
"file" : "0058.png"
},
{
"file" : "0059.png",
"time" : 913497.573211696
},
{
"file" : "0060.png",
"time" : 913497.60865372606
},
{
"time" : 913497.64094713808,
"file" : "0061.png"
},
{
"time" : 913497.67296079407,
"file" : "0062.png"
},
{
"file" : "0063.png",
"time" : 913497.70777579001
},
{
"time" : 913497.74030354002,
"file" : "0064.png"
},
{
"file" : "0065.png",
"time" : 913497.77367360005
},
{
"file" : "0066.png",
"time" : 913497.80816427805
},
{
"file" : "0067.png",
"time" : 913497.85291612009
},
{
"time" : 913497.8853847651,
"file" : "0068.png"
},
{
"time" : 913497.9067391901,
"file" : "0069.png"
},
{
"time" : 913497.95591217605,
"file" : "0070.png"
},
{
"time" : 913497.98578011105,
"file" : "0071.png"
},
{
"file" : "0072.png",
"time" : 913498.00512788806
},
{
"file" : "0073.png",
"time" : 913498.05205323
},
{
"file" : "0074.png",
"time" : 913498.08894538402
},
{
"time" : 913498.11515654309,
"file" : "0075.png"
},
{
"file" : "0076.png",
"time" : 913498.15184456704
},
{
"file" : "0077.png",
"time" : 913498.17095839209
},
{
"file" : "0078.png",
"time" : 913498.21903711604
},
{
"file" : "0079.png",
"time" : 913498.25633848202
},
{
"time" : 913498.28123657801,
"file" : "0080.png"
},
{
"time" : 913498.31629434205,
"file" : "0081.png"
},
{
"file" : "0082.png",
"time" : 913498.339072
},
{
"file" : "0083.png",
"time" : 913498.38532137405
},
{
"time" : 913498.41876535304,
"file" : "0084.png"
},
{
"time" : 913498.4510205501,
"file" : "0085.png"
},
{
"time" : 913498.48649811209,
"file" : "0086.png"
},
{
"file" : "0087.png",
"time" : 913498.51935760211
},
{
"time" : 913498.55137622508,
"file" : "0088.png"
},
{
"time" : 913498.58565475,
"file" : "0089.png"
},
{
"time" : 913498.61504772911,
"file" : "0090.png"
},
{
"file" : "0091.png",
"time" : 913498.65260026406
},
{
"file" : "0092.png",
"time" : 913498.68563959002
},
{
"time" : 913498.71734139905,
"file" : "0093.png"
},
{
"time" : 913498.75264153909,
"file" : "0094.png"
},
{
"file" : "0095.png",
"time" : 913498.78606353805
},
{
"time" : 913498.81542745605,
"file" : "0096.png"
},
{
"file" : "0097.png",
"time" : 913498.85264388402
},
{
"file" : "0098.png",
"time" : 913498.88223846711
},
{
"time" : 913498.9185269681,
"file" : "0099.png"
},
{
"file" : "0100.png",
"time" : 913498.938407565
},
{
"time" : 913498.9707252091,
"file" : "0101.png"
},
{
"time" : 913499.00930571207,
"file" : "0102.png"
},
{
"time" : 913499.0412945631,
"file" : "0103.png"
},
{
"file" : "0104.png",
"time" : 913499.07240307203
},
{
"time" : 913499.10760145809,
"file" : "0105.png"
},
{
"file" : "0106.png",
"time" : 913499.14348651504
},
{
"file" : "0107.png",
"time" : 913499.17313443404
},
{
"file" : "0108.png",
"time" : 913499.20785826608
},
{
"time" : 913499.24082741106,
"file" : "0109.png"
},
{
"time" : 913499.27446252503,
"file" : "0110.png"
},
{
"file" : "0111.png",
"time" : 913499.3080595911
},
{
"time" : 913499.34256857308,
"file" : "0112.png"
},
{
"file" : "0113.png",
"time" : 913499.37193783605
},
{
"time" : 913499.41522673611,
"file" : "0114.png"
},
{
"time" : 913499.44977159204,
"file" : "0115.png"
},
{
"time" : 913499.48206619604,
"file" : "0116.png"
}
]
@@ -0,0 +1,18 @@
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log:Test Case '-[GestureUITests.SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]' started.
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log:Test Case '-[GestureUITests.SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]' passed (17.427 seconds).
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log:Test Case '-[GestureUITests.SubBannerGestureUITests testManualBoundaryFrames]' started.
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log:/Users/klaus/Develop/sodalive/iOS/SodaLive/work/tests/SubBannerGestureUITests.swift:21: error: -[GestureUITests.SubBannerGestureUITests testManualBoundaryFrames] : XCTAssertEqual failed: ("taps=1;last=1") is not equal to ("taps=0;last=0")
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log:Test Case '-[GestureUITests.SubBannerGestureUITests testManualBoundaryFrames]' failed (8.351 seconds).
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log:Test Case '-[GestureUITests.SubBannerGestureUITests testSwipeDoesNotTap]' started.
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log:/Users/klaus/Develop/sodalive/iOS/SodaLive/work/tests/SubBannerGestureUITests.swift:21: error: -[GestureUITests.SubBannerGestureUITests testSwipeDoesNotTap] : XCTAssertEqual failed: ("taps=1;last=1") is not equal to ("taps=0;last=0")
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log:Test Case '-[GestureUITests.SubBannerGestureUITests testSwipeDoesNotTap]' failed (7.549 seconds).
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log: Executed 3 tests, with 2 failures (0 unexpected) in 33.327 (33.331) seconds
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log: Executed 3 tests, with 2 failures (0 unexpected) in 33.327 (33.332) seconds
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log: Executed 3 tests, with 2 failures (0 unexpected) in 33.327 (33.334) seconds
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-boundary.log:** TEST FAILED **
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-wide-window.log:Test Case '-[GestureUITests.SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]' started.
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-wide-window.log:Test Case '-[GestureUITests.SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]' passed (16.185 seconds).
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-wide-window.log: Executed 1 test, with 0 failures (0 unexpected) in 16.185 (16.187) seconds
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-wide-window.log: Executed 1 test, with 0 failures (0 unexpected) in 16.185 (16.188) seconds
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-wide-window.log: Executed 1 test, with 0 failures (0 unexpected) in 16.185 (16.190) seconds
/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sub-banner-touch.n2JrMa/red-wide-window.log:** TEST SUCCEEDED **
@@ -0,0 +1,18 @@
Test Case '-[GestureUITests.SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]' started.
t = 16.64s Synthesize event
Test Case '-[GestureUITests.SubBannerGestureUITests testAutomaticFiveSecondWrapFrames]' passed (17.854 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testManualBoundaryFrames]' started.
t = 8.72s Synthesize event
t = 9.15s Press "1 / 3" Button[0.20, 0.50] for 0.1 seconds, then drag to "1 / 3" Button[0.80, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 9.46s Synthesize event
/Users/klaus/Develop/sodalive/iOS/SodaLive/work/tests/SubBannerGestureUITests.swift:20: error: -[GestureUITests.SubBannerGestureUITests testManualBoundaryFrames] : XCTAssertEqual failed: ("taps=1;last=1") is not equal to ("taps=0;last=0")
Test Case '-[GestureUITests.SubBannerGestureUITests testManualBoundaryFrames]' failed (12.351 seconds).
Test Case '-[GestureUITests.SubBannerGestureUITests testSwipeDoesNotTap]' started.
t = 9.09s Press "1 / 3" Button[0.80, 0.50] for 0.1 seconds, then drag to "1 / 3" Button[0.20, 0.50] with velocity of 500.00 pixels per second, and then hold for 0.0 seconds
t = 9.32s Synthesize event
/Users/klaus/Develop/sodalive/iOS/SodaLive/work/tests/SubBannerGestureUITests.swift:20: error: -[GestureUITests.SubBannerGestureUITests testSwipeDoesNotTap] : XCTAssertEqual failed: ("taps=1;last=1") is not equal to ("taps=0;last=0")
Test Case '-[GestureUITests.SubBannerGestureUITests testSwipeDoesNotTap]' failed (11.294 seconds).
Executed 3 tests, with 2 failures (0 unexpected) in 41.500 (41.508) seconds
Executed 3 tests, with 2 failures (0 unexpected) in 41.500 (41.513) seconds
Executed 3 tests, with 2 failures (0 unexpected) in 41.500 (41.517) seconds
** TEST FAILED **
@@ -0,0 +1 @@
b3e4c5333cf8249ccd7773b5cabd372efb5a5565323bd7778034e110386151fd /Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift
@@ -0,0 +1 @@
b3e4c5333cf8249ccd7773b5cabd372efb5a5565323bd7778034e110386151fd /Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift
@@ -0,0 +1 @@
7e25db3076d545ffc61ea3a1c5585e7b8a617964ef406d7570286d75a75e010a /Users/klaus/Develop/sodalive/iOS/SodaLive/docs/20261029_추천_서브_배너/reviews/evidence/native-final/production-source-snapshot/SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift
@@ -0,0 +1,61 @@
{
"devicesAndConfigurations" : [
{
"device" : {
"architecture" : "x86_64",
"deviceId" : "B5F2BA0D-327C-4EE3-94EE-39710A0E1ACC",
"deviceName" : "SubBannerGestureQA-20261030",
"modelName" : "iPhone SE (3rd generation)",
"osBuildNumber" : "22D8075",
"osVersion" : "18.3.1",
"platform" : "iOS Simulator"
},
"expectedFailures" : 0,
"failedTests" : 2,
"passedTests" : 1,
"skippedTests" : 0,
"testPlanConfiguration" : {
"configurationId" : "1",
"configurationName" : "Test Scheme Action"
}
}
],
"environmentDescription" : "Gesture · Built with macOS 26.0",
"expectedFailures" : 0,
"failedTests" : 2,
"finishTime" : 1793289744.783,
"passedTests" : 1,
"result" : "Failed",
"skippedTests" : 0,
"startTime" : 1793289548.885,
"statistics" : [
],
"testFailures" : [
{
"failureText" : "XCTAssertEqual failed: (\"taps=1;last=1\") is not equal to (\"taps=0;last=0\")",
"targetName" : "GestureUITests",
"testIdentifier" : 2,
"testIdentifierString" : "SubBannerGestureUITests/testManualBoundaryFrames()",
"testIdentifierURL" : "test://com.apple.xcode/Gesture/GestureUITests/SubBannerGestureUITests/testManualBoundaryFrames",
"testName" : "testManualBoundaryFrames()"
},
{
"failureText" : "XCTAssertEqual failed: (\"taps=1;last=1\") is not equal to (\"taps=0;last=0\")",
"targetName" : "GestureUITests",
"testIdentifier" : 3,
"testIdentifierString" : "SubBannerGestureUITests/testSwipeDoesNotTap()",
"testIdentifierURL" : "test://com.apple.xcode/Gesture/GestureUITests/SubBannerGestureUITests/testSwipeDoesNotTap",
"testName" : "testSwipeDoesNotTap()"
}
],
"title" : "Test - Gesture",
"topInsights" : [
{
"category" : "Common Failures",
"impact" : "100% of failures",
"text" : "2 runs of 2 tests failed with XCTAssertEqual failed: (\"taps=1;last=1\") is not equal to (\"taps=0;last=0\")"
}
],
"totalTestCount" : 3
}
@@ -0,0 +1 @@
7e25db3076d545ffc61ea3a1c5585e7b8a617964ef406d7570286d75a75e010a /Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift
@@ -0,0 +1 @@
7e25db3076d545ffc61ea3a1c5585e7b8a617964ef406d7570286d75a75e010a /Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/V2/Component/Banner/SubBannerCarousel.swift