fix(h5): reduce report swipe conflicts and limit edge overscroll

This commit is contained in:
xsl
2026-10-13 00:24:52 +08:00
parent f1e64565b9
commit 3efa28b311
10 changed files with 858 additions and 757 deletions
@@ -0,0 +1,13 @@
# 报告页滑动防冲突
仅优化报告详情卡的手势,不修改报告数据、算法或基础信息页的滑动参数。
- 图片区域可横向跟手切换;说明文字不触发横滑,页签仍可点击或键盘切换。
- 方向判定由 8px、横向略大于纵向,改为移动超过 12px 且横向位移大于纵向的 1.5 倍才进入横滑。其余明确移动交给纵向阅读,一次手势锁定后不重新判方向。
- 横滑过程中冻结卡片当前高度,包括图片/字体延迟加载导致的尺寸更新;松手或取消后再恢复当前页签高度,并沿用平滑高度过渡。
- 首尾保留阻尼反馈,但回弹位移最大为 16 CSS px,不允许拉出大片空白。
- 保留浏览器原生纵向滚动、图片点击放大、取消复位与无障碍页签行为。不尝试拦截微信系统返回手势。
验证:`node h5/test/report-touch-layout.mjs` 检查真实移动触摸横滑、斜滑、文字区不切页、图片与文字区竖向滚动、拖动期间高度不变、边缘位移上限和原有布局/交互;`node h5/test/profile-interaction.mjs` 回归共用滑动组件的基础信息页。
本地上述两项测试均通过。发布沿用 Git bundle 快进同步至 `/home/ubuntu/juzi`,重启 `juzi-h5.service`,再经公网运行同一报告交互测试并检查服务健康及静态资源一致性。不代用户推送 origin,不包含未追踪的 `new_feedback/`。最终手机微信体验仍需真机复验。
+12 -24
View File
@@ -21,7 +21,7 @@
"19": "customer-feedback-fourth.js",
"20": "build_fixtures.py",
"21": "disposeUnusedWeightTensors",
"22": ".loadFromDisk",
"22": "k",
"23": "facepp-skinanalyze(旷视皮肤分析专业版调用技能)",
"24": "巨子测肤 · 1'me 语音播报内容设计文档 V1.0",
"25": "mobile-select.iife.min.js",
@@ -40,13 +40,13 @@
"38": "FaceLandmark68Net",
"39": "UI交互界面调整",
"40": "第四轮反馈修改与验收",
"41": "SsdMobilenetv1",
"41": "vizCompose",
"42": "layer_at",
"43": "TinyYolov2Base",
"44": "start-local.sh",
"45": "PredictAllAgeAndGenderWithFaceAlignmentTask",
"46": "PredictAllFaceExpressionsWithFaceAlignmentTask",
"47": "recommendation.js",
"47": "readPhotoFile",
"48": "已修改",
"49": "mobile-select/README.md",
"50": "call_skinanalyze_3view.sh",
@@ -63,21 +63,21 @@
"61": "报告布局与跟手交互调整",
"62": "提交前 Review(2026-10-11)",
"63": "e2e-browser.mjs",
"64": "start",
"64": "play",
"65": "微信登录接入与公网部署说明",
"66": "normalize",
"66": ".runAndExtendWithFaceDetections",
"67": "ui-20261009.mjs",
"68": "analyze-timeout.js",
"69": "misans/README.md",
"70": "scan-placeholder.mjs",
"71": "age",
"71": "报告顶部评分与年龄排版",
"72": "build_report_copy.py",
"73": "camera-geometry.js",
"74": "report-copy.js",
"75": "prepare_h5_assets.py",
"76": "showScreen",
"77": ".runAndExtendWithFaceDetections",
"78": "Point",
"76": "报告页滑动防冲突-2026-10-13.md",
"77": "drawFaceExpressions",
"78": ".toString",
"79": "登录反馈与加载优化(2026-10-12)",
"80": "线上微信登录部署记录",
"81": "wechat-production.mjs",
@@ -96,12 +96,9 @@
"94": "第三轮反馈:PSD 对齐、验收及上线记录",
"95": "login-performance.mjs",
"96": "production-loop.mjs",
"97": "PredictSingleAgeAndGenderWithFaceAlignmentTask",
"98": "PredictSingleFaceExpressionsWithFaceAlignmentTask",
"99": "DetectSingleFaceLandmarksTask",
"100": "ComputeSingleFaceDescriptorTask",
"101": "LabeledFaceDescriptors",
"102": "Box",
"100": "extractAllFacesAndComputeResults",
"102": "extractFaceTensors",
"103": "第四轮反馈/傅艳NEW与项思炼的会话 2026年10月12日.md",
"104": "export_fourth_feedback_assets.py",
"105": "capture-quality-browser.mjs",
@@ -113,19 +110,10 @@
"111": "i",
"113": "swipe-track.js",
"114": "optimize_delivery_images.py",
"115": "FaceProcessor",
"116": "convLayer",
"117": "startCamera",
"118": "AgeGenderNet",
"119": "FaceLandmarks",
"120": ".forwardInput",
"121": "browser",
"122": ".forwardInput",
"123": "评分圈内居中、基础信息页固定与正脸语音修复",
"124": "TinyYolov2",
"125": "报告等待页实拍背景-2026-10-12.md",
"126": "FaceExpressionNet",
"127": "FaceLandmark68NetBase",
"128": "drawFaceExpressions",
"129": "ComputeAllFaceDescriptorsTask"
"127": "height"
}
+1 -1
View File
@@ -1 +1 @@
{"0": "38915c7bbf20dd63", "1": "cb6ea50bd4e4a5ca", "2": "3427a6e8a9c7cb3e", "3": "1f737ca8edb978f2", "4": "56bf12952b0de42b", "5": "e8ddef85a868abc9", "6": "b1e7fae56fe55ee1", "7": "03b56b13a08b5f83", "8": "a689fcb62e7754af", "9": "3370a263a4641111", "10": "2b037735e1f5b0f6", "11": "4a490abd5e503318", "12": "aeca274694e158d2", "13": "7e3b558a587a4ebe", "14": "bf75bfc99d4f9e34", "15": "523e783b24871b14", "16": "95add6a975c0cb72", "17": "e042d7fe7351bcb9", "18": "f93d5d85e671fcee", "19": "39abb1eef19df707", "20": "aecf2fce44ba49c5", "21": "a4ff643a50f0c3df", "22": "00dab8704da757dc", "23": "90fac00055070ce2", "24": "f85b5b1634224c86", "25": "46e8db3e6e3f37ea", "26": "10c2b0fcd8bec2c1", "27": "eab427ce9a5219e9", "28": "d1b8d101d84f0ee2", "29": "c608f1c6492e1cb1", "30": "73c13793fca09f56", "31": "fbbcfc059751cc6a", "32": "f6bcf56c4589316b", "33": "75f750c5b7d9f63d", "34": "f024ff7942935690", "35": "241be5abeab1da6f", "36": "a7d375041779bad4", "37": "cb8f26249485324a", "38": "9a25b1230b5fe649", "39": "fdd95375b4a5ddb3", "40": "5e546676ab96de03", "41": "5d43ff2eaff3e484", "42": "916170b9f5d7346b", "43": "04c475f726d2508f", "44": "852c1bb2446edd9c", "45": "92ab3e5134ed8685", "46": "65718282b3c32109", "47": "e594ecaa8e7d1538", "48": "cf2f2424bc00436f", "49": "1f976711cda158dc", "50": "bbb2b57d72922b16", "51": "eb6a182204867275", "52": "2ec3c8c088568ba9", "53": "68c45056378341a7", "54": "c58d41e8e580fdc6", "55": "68c52c80f38f8b2c", "56": "9ee2ad45dc27851d", "57": "f2a40b5f1b29f669", "58": "504ce5bc46e6237e", "59": "891f59cda3708718", "60": "a505b7de6257a4e8", "61": "5209ce2a267bf33b", "62": "1366ae24c7e5897d", "63": "c256b43f75d7809e", "64": "a01bccd7f7c24c78", "65": "bdee3c4a37df52f8", "66": "f05da4095366f6d4", "67": "3e126d8deaf6e200", "68": "6b91f3679541de79", "69": "e511846eeba03076", "70": "be0c8310abda2467", "71": "b234c5e4ac7f632d", "72": "c84f196a7bd35861", "73": "2b887bf601ccfb9f", "74": "ae2a154311e1f53a", "75": "69fdd23278518884", "76": "71cbe5098f4c1b6f", "77": "2ee521497bb8506a", "78": "aa921a06e1bd1a1d", "79": "0746e33eaf6cd1f9", "80": "315325cde1352e98", "81": "37c559741825be74", "82": "200f25fbad7af8fb", "83": "1284897282eb47a3", "84": "d8bb296741d3d05c", "85": "4bc370be8afd7db1", "86": "386c6fcc7be918e0", "87": "e5fa7717989eda80", "88": "8b5ab5232cef3457", "89": "9e3363b80eeceeea", "90": "dd1c246e19a2d032", "91": "83b0fd3b14363e4d", "92": "fced244d93677d90", "93": "8bd61518dd267641", "94": "713d04996c4627df", "95": "59c31f858c7b6970", "96": "4195478d5c528371", "97": "01f68dfc34a29d32", "98": "8c471c7baeaaca54", "99": "23f1d406f6c779ae", "100": "7ac475ea9bc280f8", "101": "f8b38aeaaab11ec4", "102": "074575463a8da45a", "103": "6ffbe07b521a061b", "104": "e114a7aebaaca2f1", "105": "af50c66d16c81bba", "106": "8bc72c179793e1c2", "107": "d9c195d9ccdb137e", "108": "e0fc0a42e33bda91", "109": "4afb1715f62865b1", "110": "52f28eb30e80851f", "111": "2dfe251d5e9bd2fe", "113": "0d99f86b9e41c4bb", "114": "b9d7c266afa84437", "115": "57344ed1fd5c260f", "116": "0dda68823cc5a1d7", "117": "cc191db3bbde70d7", "118": "14fdf4e4c496519f", "119": "a64c13723656b663", "120": "5fec2bbca3634aec", "121": "ce0e68dab611f022", "122": "15f58f7494ac2b87", "123": "b091e3eb82b456eb", "124": "29790cf710874dd0", "125": "e4f29216745ec758", "126": "e858d75e995c92e5", "127": "e2c998de34fdd08d", "128": "a0bc88eb14b70170", "129": "3dd68cce72af5314"}
{"0": "e93608cf288712b8", "1": "cb6ea50bd4e4a5ca", "2": "fd2eb9185c61f1fe", "3": "1f737ca8edb978f2", "4": "56bf12952b0de42b", "5": "7eecef0f27ae2903", "6": "23e6fea2f88a24d1", "7": "ca1cdb7d0ae4e71e", "8": "a689fcb62e7754af", "9": "5b3557ee5c2bf900", "10": "2b037735e1f5b0f6", "11": "4a490abd5e503318", "12": "aeca274694e158d2", "13": "7e3b558a587a4ebe", "14": "bf75bfc99d4f9e34", "15": "6dfa695990b6977c", "16": "95add6a975c0cb72", "17": "e042d7fe7351bcb9", "18": "f93d5d85e671fcee", "19": "39abb1eef19df707", "20": "aecf2fce44ba49c5", "21": "6079f456ac9d4b2e", "22": "02495fa16ac642c3", "23": "90fac00055070ce2", "24": "f85b5b1634224c86", "25": "46e8db3e6e3f37ea", "26": "a8588d8e77a6ae93", "27": "eab427ce9a5219e9", "28": "037cf5fc8fd22dd0", "29": "c608f1c6492e1cb1", "30": "73c13793fca09f56", "31": "fbbcfc059751cc6a", "32": "f6bcf56c4589316b", "33": "75f750c5b7d9f63d", "34": "f024ff7942935690", "35": "241be5abeab1da6f", "36": "a7d375041779bad4", "37": "cb8f26249485324a", "38": "9a25b1230b5fe649", "39": "fdd95375b4a5ddb3", "40": "5e546676ab96de03", "41": "a005abe41943c7cd", "42": "916170b9f5d7346b", "43": "110bce64414980d4", "44": "852c1bb2446edd9c", "45": "92ab3e5134ed8685", "46": "65718282b3c32109", "47": "4e91cfdf55362604", "48": "cf2f2424bc00436f", "49": "1f976711cda158dc", "50": "bbb2b57d72922b16", "51": "eb6a182204867275", "52": "2ec3c8c088568ba9", "53": "68c45056378341a7", "54": "c58d41e8e580fdc6", "55": "68c52c80f38f8b2c", "56": "9ee2ad45dc27851d", "57": "f2a40b5f1b29f669", "58": "d8c090d6837ee23a", "59": "891f59cda3708718", "60": "a505b7de6257a4e8", "61": "5209ce2a267bf33b", "62": "1366ae24c7e5897d", "63": "c256b43f75d7809e", "64": "0adfe3c7d77dcbe1", "65": "bdee3c4a37df52f8", "66": "46204fb2e4d50c53", "67": "3e126d8deaf6e200", "68": "6b91f3679541de79", "69": "e511846eeba03076", "70": "be0c8310abda2467", "71": "589a2d5a76f3880a", "72": "c84f196a7bd35861", "73": "2b887bf601ccfb9f", "74": "ae2a154311e1f53a", "75": "69fdd23278518884", "76": "3c4c278d6026bc3a", "77": "b4e08c87a078a832", "78": "71f300fdfae6e5a6", "79": "86f177bf0a7dd07c", "80": "315325cde1352e98", "81": "37c559741825be74", "82": "200f25fbad7af8fb", "83": "1284897282eb47a3", "84": "2fb17901aa87e0e7", "85": "4bc370be8afd7db1", "86": "386c6fcc7be918e0", "87": "e5fa7717989eda80", "88": "8b5ab5232cef3457", "89": "9e3363b80eeceeea", "90": "dd1c246e19a2d032", "91": "83b0fd3b14363e4d", "92": "fced244d93677d90", "93": "5a9014c4a7f87699", "94": "713d04996c4627df", "95": "59c31f858c7b6970", "96": "4195478d5c528371", "99": "23f1d406f6c779ae", "100": "e42f384dfedcff89", "102": "4dba2da8e6a57bef", "103": "6ffbe07b521a061b", "104": "e114a7aebaaca2f1", "105": "af50c66d16c81bba", "106": "8bc72c179793e1c2", "107": "d9c195d9ccdb137e", "108": "e0fc0a42e33bda91", "109": "4afb1715f62865b1", "110": "45259f492e4f2efb", "111": "b34758de9b00285f", "113": "0d99f86b9e41c4bb", "114": "b9d7c266afa84437", "116": "d1ec12eee057e765", "117": "02c78dd73c398bfa", "123": "b091e3eb82b456eb", "124": "c96276581a8c98d4", "125": "e4f29216745ec758", "127": "746b316833e9c203"}
+86 -114
View File
@@ -1,16 +1,16 @@
# Graph Report - juzi (2026-10-13)
## Corpus Check
- 105 files · ~4,905,190 words
- 107 files · ~4,905,535 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 1452 nodes · 2901 edges · 129 communities (96 shown, 33 thin omitted)
- 1457 nodes · 2904 edges · 117 communities (92 shown, 25 thin omitted)
- Extraction: 93% EXTRACTED · 7% INFERRED · 0% AMBIGUOUS · INFERRED: 210 edges (avg confidence: 0.57)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `6f83f58a`
- Built from commit: `f1e64565`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@@ -37,7 +37,7 @@
- customer-feedback-fourth.js
- build_fixtures.py
- disposeUnusedWeightTensors
- .loadFromDisk
- k
- facepp-skinanalyze(旷视皮肤分析专业版调用技能)
- 巨子测肤 · 1'me 语音播报内容设计文档 V1.0
- mobile-select.iife.min.js
@@ -54,13 +54,13 @@
- FaceLandmark68Net
- UI交互界面调整
- 第四轮反馈修改与验收
- SsdMobilenetv1
- vizCompose
- layer_at
- TinyYolov2Base
- start-local.sh
- PredictAllAgeAndGenderWithFaceAlignmentTask
- PredictAllFaceExpressionsWithFaceAlignmentTask
- recommendation.js
- readPhotoFile
- 已修改
- mobile-select/README.md
- call_skinanalyze_3view.sh
@@ -74,18 +74,18 @@
- 报告布局与跟手交互调整
- 提交前 Review(2026-10-11)
- e2e-browser.mjs
- start
- play
- 微信登录接入与公网部署说明
- normalize
- .runAndExtendWithFaceDetections
- ui-20261009.mjs
- analyze-timeout.js
- misans/README.md
- scan-placeholder.mjs
- age
- 报告顶部评分与年龄排版
- build_report_copy.py
- showScreen
- .runAndExtendWithFaceDetections
- Point
- 报告页滑动防冲突-2026-10-13.md
- drawFaceExpressions
- .toString
- 登录反馈与加载优化(2026-10-12)
- 线上微信登录部署记录
- wechat-production.mjs
@@ -103,12 +103,9 @@
- 第三轮反馈:PSD 对齐、验收及上线记录
- login-performance.mjs
- production-loop.mjs
- PredictSingleAgeAndGenderWithFaceAlignmentTask
- PredictSingleFaceExpressionsWithFaceAlignmentTask
- DetectSingleFaceLandmarksTask
- ComputeSingleFaceDescriptorTask
- LabeledFaceDescriptors
- Box
- extractAllFacesAndComputeResults
- extractFaceTensors
- 第四轮反馈/傅艳NEW与项思炼的会话 2026年10月12日.md
- 第三轮反馈20261012/傅艳NEW与项思炼的会话 2026年10月12日.md
- 基础信息记忆与肤质拖动
@@ -116,21 +113,12 @@
- submitPhotos
- doCapture
- i
- FaceProcessor
- convLayer
- startCamera
- AgeGenderNet
- FaceLandmarks
- .forwardInput
- browser
- .forwardInput
- 评分圈内居中、基础信息页固定与正脸语音修复
- TinyYolov2
- 报告等待页实拍背景-2026-10-12.md
- FaceExpressionNet
- FaceLandmark68NetBase
- drawFaceExpressions
- ComputeAllFaceDescriptorsTask
- height
## God Nodes (most connected - your core abstractions)
1. `__awaiter()` - 59 edges
@@ -147,31 +135,31 @@
## Surprising Connections (you probably didn't know these)
- `actualLines()` --indirect_call--> `el()` [INFERRED]
h5/test/customer-feedback-third.mjs → h5/js/admin.js
- `browser()` --indirect_call--> `key()` [INFERRED]
h5/js/account-client.js → h5/js/profile-store.js
- `init()` --indirect_call--> `key()` [INFERRED]
h5/js/account-client.js → h5/js/profile-store.js
- `loadRecords()` --indirect_call--> `it()` [INFERRED]
h5/js/admin.js → h5/vendor/faceapi/face-api.js
- `bind()` --indirect_call--> `a()` [INFERRED]
h5/js/admin.js → h5/vendor/faceapi/face-api.js
- `checkStill()` --indirect_call--> `d()` [INFERRED]
h5/js/app.js → h5/vendor/faceapi/face-api.js
- `detectFrame()` --indirect_call--> `d()` [INFERRED]
h5/js/app.js → h5/vendor/faceapi/face-api.js
## Import Cycles
- None detected.
## Communities (129 total, 33 thin omitted)
## Communities (117 total, 25 thin omitted)
### Community 0 - "face-api.js"
Cohesion: 0.02
Nodes (44): bgrToRgbTensor(), cn(), computeReshapedDimensions(), createBrowserEnv(), createFaceDetectionNet(), createFileSystem(), createNodejsEnv(), createSsdMobilenetv1() (+36 more)
Nodes (41): bgrToRgbTensor(), cn(), computeReshapedDimensions(), createBrowserEnv(), createFileSystem(), createNodejsEnv(), decodeBoxesLayer(), Dimensions() (+33 more)
### Community 1 - "线上闭环测试(不含真实微信授权)"
Cohesion: 0.25
Nodes (7): 发现并修复, 可复现命令, 已通过的检查, 线上闭环测试(不含真实微信授权), 结论, 话术及仓库同步, 验证边界
### Community 2 - "app.js"
Cohesion: 0.11
Nodes (22): cancelSkinDrag(), esc(), goSkin(), openHistory(), renderHistoryList(), renderReport(), renderViz(), restartScanCycle() (+14 more)
Cohesion: 0.13
Nodes (22): cancelSkinDrag(), esc(), goSkin(), loadAppFont(), loginAllowed(), loginBrowser(), loginSucceeded(), loginWechat() (+14 more)
### Community 3 - "skin-score.js"
Cohesion: 0.12
@@ -182,24 +170,24 @@ Cohesion: 0.17
Nodes (10): assert, context, face, fs, geometry, original, path, source (+2 more)
### Community 5 - "t"
Cohesion: 0.11
Nodes (17): bi(), Ef(), er(), FaceMatch(), ia(), Ie(), j(), Kt() (+9 more)
Cohesion: 0.09
Nodes (31): be(), bi(), Bo(), ce(), cl(), de(), e(), Ef() (+23 more)
### Community 6 - "n"
Cohesion: 0.10
Nodes (54): a(), ae(), b(), ba(), be(), bf(), ce(), cf() (+46 more)
Cohesion: 0.14
Nodes (37): a(), ae(), bf(), cf(), d(), drawContour(), dt(), ea() (+29 more)
### Community 7 - "test/profile-store.js"
Cohesion: 0.14
Nodes (13): assert, data, empty, now, other, Profile, quota, regions (+5 more)
Cohesion: 0.07
Nodes (29): browser(), init(), read(), rememberKind(), request(), age(), clean(), create() (+21 more)
### Community 8 - "admin.js"
Cohesion: 0.20
Nodes (29): ageText(), api(), bind(), captureQualityHtml(), closeDetail(), collectFilters(), dimsHtml(), doRetry() (+21 more)
### Community 9 - "__awaiter"
Cohesion: 0.12
Nodes (16): __awaiter(), extendWithAge(), extendWithFaceDescriptor(), extendWithFaceExpressions(), extendWithGender(), extractAllFacesAndComputeResults(), extractSingleFaceAndComputeResult(), FaceRecognitionNet() (+8 more)
Cohesion: 0.13
Nodes (12): __awaiter(), ComposableTask(), fetchImage(), fetchJson(), fetchNetWeights(), fetchOrThrow(), __generator(), getModelUris() (+4 more)
### Community 10 - "wechat-browser.mjs"
Cohesion: 0.22
@@ -218,8 +206,8 @@ Cohesion: 0.12
Nodes (15): files, analyze-fail, analyzing, countdown-321, hint-far, hint-front, hint-hold, hint-near (+7 more)
### Community 15 - "extractFaces"
Cohesion: 0.15
Nodes (23): height(), awaitMediaLoaded(), bufferToImage(), createCanvas(), createCanvasFromMedia(), extractFaces(), extractFaceTensors(), extractImagePatches() (+15 more)
Cohesion: 0.17
Nodes (18): awaitMediaLoaded(), bufferToImage(), createCanvas(), createCanvasFromMedia(), extractFaces(), extractImagePatches(), getContext2dOrThrow(), getEnv() (+10 more)
### Community 16 - "巨子测肤 · 1'me 在线肌肤测试 — UI 设计规范"
Cohesion: 0.15
@@ -242,12 +230,12 @@ Cohesion: 0.40
Nodes (9): build_case1(), build_case2(), build_truth(), col(), dump(), main(), parse_value(), set_path() (+1 more)
### Community 21 - "disposeUnusedWeightTensors"
Cohesion: 0.09
Nodes (25): disposeUnusedWeightTensors(), extractorsFactory$3(), extractorsFactory$5(), extractorsFactory$7(), extractorsFactory$9(), extractParamsFromWeigthMap(), extractParamsFromWeigthMap$1(), extractParamsFromWeigthMap$2() (+17 more)
Cohesion: 0.05
Nodes (39): conv(), conv$1(), convDown(), convLayer$1(), convNoRelu(), denseBlock3(), denseBlock4(), depthwiseSeparableConv() (+31 more)
### Community 22 - ".loadFromDisk"
Cohesion: 0.29
Nodes (3): ComposableTask(), getModelUris(), TinyFaceDetector()
### Community 22 - "k"
Cohesion: 0.10
Nodes (21): b(), ba(), ca(), da(), fa(), gn(), k(), kn() (+13 more)
### Community 23 - "facepp-skinanalyze(旷视皮肤分析专业版调用技能)"
Cohesion: 0.20
@@ -262,16 +250,16 @@ Cohesion: 0.20
Nodes (16): _arrayLikeToArray(), _arrayWithHoles(), _arrayWithoutHoles(), _classCallCheck(), _createClass(), _defineProperties(), h(), _iterableToArray() (+8 more)
### Community 26 - "NeuralNetwork"
Cohesion: 0.07
Nodes (24): createFaceRecognitionNet(), createMtcnn(), createTinyFaceDetector(), createTinyYolov2(), extractConvParamsFactory(), extractFCParamsFactory(), extractorsFactory(), extractorsFactory$1() (+16 more)
Cohesion: 0.05
Nodes (30): AgeGenderNet(), createFaceDetectionNet(), createFaceRecognitionNet(), createMtcnn(), createSsdMobilenetv1(), createTinyFaceDetector(), createTinyYolov2(), extractConvParamsFactory() (+22 more)
### Community 27 - "gen_voice.py"
Cohesion: 0.30
Nodes (11): ndarray, Path, gen_countdown(), main(), normalize_clip(), 倒计时单独按字生成再拼接,避免「三、二、一」整句被读成播报腔、与提示音气质漂移。 与提示音共用同一 speaker + instruct;句间留约…, 裁掉过长首尾静音,保留 ≤150ms 边沿,避免提示显迟钝。, 响度归一 −16 LUFS + 转 MP3 48k CBR mono 22050Hz。 (+3 more)
### Community 28 - "C"
Cohesion: 0.08
Nodes (34): ao(), Bo(), C(), cd(), co(), Do(), eo(), fo() (+26 more)
Cohesion: 0.11
Nodes (25): ao(), C(), cd(), co(), Do(), eo(), fo(), Ft() (+17 more)
### Community 29 - "face-api.js 本地化依赖(h5/vendor/faceapi/)"
Cohesion: 0.29
@@ -301,9 +289,9 @@ Nodes (4): 1meAI测肤旷视UI问题反馈, UI交互界面调整, 交互, **设
Cohesion: 0.22
Nodes (8): PSD 来源, 前端参数及限制, 后台校准留痕, 已确认并实现, 生产部署及只读验收, 皱纹(紧塑力)原文, 第四轮反馈修改与验收, 验证
### Community 41 - "SsdMobilenetv1"
### Community 41 - "vizCompose"
Cohesion: 0.18
Nodes (9): decodeBoxesLayer(), depthwiseConvLayer(), getCenterCoordinatesAndSizesLayer(), getStridesForLayerIdx(), mobileNetV1(), outputLayer(), pointwiseConvLayer(), predictionLayer() (+1 more)
Nodes (11): renderReport(), renderViz(), setDim(), vizCollectMarks(), vizCompose(), vizFaceCrop(), vizLoadImage(), vizMapURL() (+3 more)
### Community 42 - "layer_at"
Cohesion: 0.18
@@ -313,9 +301,9 @@ Nodes (11): list, bounds(), layer_at(), main(), save_image(), composite(), main(
Cohesion: 0.50
Nodes (3): HOST, PORT, start-local.sh script
### Community 47 - "recommendation.js"
Cohesion: 0.38
Nodes (6): assert, check(), missing, raw, SS, UI
### Community 47 - "readPhotoFile"
Cohesion: 0.40
Nodes (5): checkStill(), faceLuma(), hideCamStatus(), readPhotoFile(), showCamStatus()
### Community 48 - "已修改"
Cohesion: 0.22
@@ -339,7 +327,7 @@ Nodes (5): diffCtx(), loadJSON(), main(), okNum(), okStr()
### Community 58 - ".forwardInput"
Cohesion: 0.19
Nodes (10): extendWithFaceLandmarks(), extractBoundingBoxes(), iou(), NetInput(), nonMaxSuppression(), nonMaxSuppression$1(), scale(), stage1() (+2 more)
Nodes (8): FaceRecognitionNet(), iou(), nonMaxSuppression(), nonMaxSuppression$1(), scale(), stage1(), stage2(), stage3()
### Community 61 - "报告布局与跟手交互调整"
Cohesion: 0.33
@@ -353,18 +341,14 @@ Nodes (4): 仍需公网/业务确认, 已发现并修复, 提交前 Review(202
Cohesion: 0.29
Nodes (4): EXPECTED, fails, pageErrors, pwRoot
### Community 64 - "start"
Cohesion: 0.29
Nodes (7): decode(), drain(), load(), preload(), start(), stopCur(), unlock()
### Community 64 - "play"
Cohesion: 0.22
Nodes (9): busyWithFlow(), decode(), drain(), load(), play(), preload(), start(), stopCur() (+1 more)
### Community 65 - "微信登录接入与公网部署说明"
Cohesion: 0.29
Nodes (7): 公网服务器配置, 公网真机验收清单, 反向代理与后台保护, 已完成的链路, 微信登录接入与公网部署说明, 数据、迁移与备份, 本机开发环境限制(不代表线上状态)
### Community 66 - "normalize"
Cohesion: 0.20
Nodes (9): conv(), denseBlock3(), denseBlock4(), depthwiseSeparableConv(), FaceFeatureExtractor(), mainBlock(), normalize(), reductionBlock() (+1 more)
### Community 68 - "analyze-timeout.js"
Cohesion: 0.20
Nodes (7): assert, end, fs, path, source, start, vm
@@ -373,24 +357,16 @@ Nodes (7): assert, end, fs, path, source, start, vm
Cohesion: 0.29
Nodes (3): errors, samples, unexpectedWrites
### Community 71 - "age"
Cohesion: 0.43
Nodes (4): age(), clean(), create(), key()
### Community 77 - "drawFaceExpressions"
Cohesion: 0.12
Nodes (10): DrawBox(), drawDetections(), drawFaceExpressions(), DrawFaceLandmarks(), FaceExpressions(), isWithFaceDetection(), isWithFaceExpressions(), isWithFaceLandmarks() (+2 more)
### Community 76 - "showScreen"
Cohesion: 0.22
Nodes (11): loadAppFont(), loginAllowed(), loginBrowser(), loginSucceeded(), loginWechat(), openOverlay(), prepareScanArtwork(), renderAccountStatus() (+3 more)
### Community 77 - ".runAndExtendWithFaceDetections"
Cohesion: 0.18
Nodes (8): DetectAllFacesTask(), DrawBox(), drawDetections(), DrawFaceLandmarks(), extendWithFaceDetection(), isWithFaceDetection(), isWithFaceLandmarks(), resizeResults()
### Community 78 - "Point"
Cohesion: 0.22
Nodes (5): FaceLandmarks5(), getCenterPoint(), Point(), pt(), vt()
### Community 78 - ".toString"
Cohesion: 0.12
Nodes (11): FaceLandmarks5(), FaceMatch(), getCenterPoint(), la(), Point(), pr(), pt(), vt() (+3 more)
### Community 79 - "登录反馈与加载优化(2026-10-12)"
Cohesion: 0.22
Cohesion: 0.33
Nodes (5): 体验变化, 公网部署后复测, 慢的原因与处理, 登录反馈与加载优化(2026-10-12), 验证
### Community 80 - "线上微信登录部署记录"
@@ -410,7 +386,7 @@ Cohesion: 0.29
Nodes (5): 10.09 新版 H5 实施与验收, 按确认项实现, 文档未定义部分的实施口径, 素材再生成, 验证命令
### Community 84 - "微信登录联调:2026-10-12"
Cohesion: 0.33
Cohesion: 0.22
Nodes (6): 后续真机确认与体验优化, 微信登录联调:2026-10-12, 本轮修正, 白名单与凭据, 真实微信验收与排查, 自动验证
### Community 85 - "test/accounts.js"
@@ -434,8 +410,8 @@ Cohesion: 0.33
Nodes (5): 公网部署与复测, 客户反馈修改(2026-10-12), 皱纹数据说明, 逐项处理, 验证
### Community 93 - ".runAndExtendWithFaceDetection"
Cohesion: 0.23
Nodes (6): allFacesMtcnn(), allFacesSsdMobilenetv1(), allFacesTinyYolov2(), DetectAllFaceLandmarksTask(), detectAllFaces(), DetectSingleFaceTask()
Cohesion: 0.33
Nodes (5): allFacesMtcnn(), allFacesSsdMobilenetv1(), allFacesTinyYolov2(), detectAllFaces(), DetectSingleFaceTask()
### Community 94 - "第三轮反馈:PSD 对齐、验收及上线记录"
Cohesion: 0.33
@@ -445,6 +421,14 @@ Nodes (5): 原稿与导出可追溯性, 已完成, 本地验证, 生产部署及
Cohesion: 0.50
Nodes (3): artifacts, errors, samples
### Community 100 - "extractAllFacesAndComputeResults"
Cohesion: 0.09
Nodes (12): ComputeAllFaceDescriptorsTask(), ComputeSingleFaceDescriptorTask(), extendWithAge(), extendWithFaceDescriptor(), extendWithFaceExpressions(), extendWithGender(), extractAllFacesAndComputeResults(), extractSingleFaceAndComputeResult() (+4 more)
### Community 102 - "extractFaceTensors"
Cohesion: 0.10
Nodes (11): Box(), DetectAllFaceLandmarksTask(), extendWithFaceDetection(), extendWithFaceLandmarks(), extractFaceTensors(), FaceLandmarks(), isDimensions(), isTensor3D() (+3 more)
### Community 107 - "基础信息记忆与肤质拖动"
Cohesion: 0.33
Nodes (4): 回归命令, 基础信息记忆与肤质拖动, 肤质交互, 范围
@@ -458,54 +442,42 @@ Cohesion: 0.24
Nodes (12): bindBackdropClose(), closeLoginSuccess(), closeOverlay(), handleCamHidden(), leaveCamera(), photosByView(), popHistorySilently(), requestAnalyze() (+4 more)
### Community 110 - "doCapture"
Cohesion: 0.17
Nodes (22): armNudge(), busyWithFlow(), captureFromVideo(), captureHaptic(), captureStageHint(), checkStill(), doCapture(), dropHint() (+14 more)
Cohesion: 0.15
Nodes (24): armNudge(), beginDetect(), captureFromVideo(), captureHaptic(), captureStageHint(), detectFrame(), detTick(), doCapture() (+16 more)
### Community 111 - "i"
Cohesion: 0.08
Nodes (30): bn(), ca(), cl(), dn(), ee(), et(), fa(), fn() (+22 more)
Cohesion: 0.12
Nodes (18): bn(), dn(), ee(), et(), fn(), i(), je(), ln() (+10 more)
### Community 116 - "convLayer"
Cohesion: 0.36
Nodes (9): boxPredictionLayer(), convLayer(), fullyConnectedLayer(), ONet(), PNet(), prelu(), qh(), RNet() (+1 more)
Cohesion: 0.14
Nodes (17): boxPredictionLayer(), convLayer(), convWithBatchNorm(), depthwiseConvLayer(), depthwiseSeparableConv$1(), fullyConnectedLayer(), getStridesForLayerIdx(), leaky() (+9 more)
### Community 117 - "startCamera"
Cohesion: 0.15
Nodes (17): applyMirror(), beginDetect(), detectFrame(), detTick(), effMirror(), enableFallback(), getCamStream(), handleCamVisible() (+9 more)
### Community 120 - ".forwardInput"
Cohesion: 0.48
Nodes (6): conv$1(), convDown(), convLayer$1(), convNoRelu(), residual(), residualDown()
### Community 121 - "browser"
Cohesion: 0.80
Nodes (5): browser(), init(), read(), rememberKind(), request()
### Community 122 - ".forwardInput"
Cohesion: 0.47
Nodes (3): convWithBatchNorm(), depthwiseSeparableConv$1(), leaky()
Cohesion: 0.29
Nodes (8): applyMirror(), effMirror(), getCamStream(), handleCamVisible(), onCamTrackEnded(), renderCamStep(), startCamera(), waitVideoStable()
### Community 123 - "评分圈内居中、基础信息页固定与正脸语音修复"
Cohesion: 0.40
Nodes (4): 发布, 范围, 评分圈内居中、基础信息页固定与正脸语音修复, 验证
### Community 128 - "drawFaceExpressions"
Cohesion: 0.50
Nodes (3): drawFaceExpressions(), FaceExpressions(), isWithFaceExpressions()
### Community 127 - "height"
Cohesion: 0.38
Nodes (5): height(), FaceLandmark68NetBase(), getSizesForScale(), normalize$1(), rescaleAndNormalize()
## Knowledge Gaps
- **318 isolated node(s):** `source`, `document_size`, `pages`, `assets`, `layers` (+313 more)
- **320 isolated node(s):** `source`, `document_size`, `pages`, `assets`, `layers` (+315 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **33 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **25 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `height()` connect `extractFaces` to `app.js`, `customer-feedback.js`, `scan-placeholder.mjs`, `TinyYolov2Base`, `profile-interaction.mjs`, `showScreen`, `.forwardInput`, `FaceLandmark68NetBase`?**
- **Why does `height()` connect `height` to `app.js`, `customer-feedback.js`, `scan-placeholder.mjs`, `extractFaceTensors`, `__awaiter`, `profile-interaction.mjs`, `extractFaces`, `.forwardInput`?**
_High betweenness centrality (0.062) - this node is a cross-community bridge._
- **Why does `v()` connect `n` to `report-ui.js`, `face-api.js`, `app.js`, `C`?**
- **Why does `v()` connect `n` to `report-ui.js`, `vizCompose`, `C`, `face-api.js`?**
_High betweenness centrality (0.045) - this node is a cross-community bridge._
- **Why does `k()` connect `n` to `face-api.js`, `app.js`, `t`, `lib/accounts.js`, `i`, `C`?**
- **Why does `k()` connect `k` to `face-api.js`, `t`, `n`, `vizCompose`, `lib/accounts.js`?**
_High betweenness centrality (0.039) - this node is a cross-community bridge._
- **Are the 24 inferred relationships involving `n()` (e.g. with `newRecordId()` and `ae()`) actually correct?**
_`n()` has 24 INFERRED edges - model-reasoned connections that need verification._
@@ -514,4 +486,4 @@ _Questions this graph is uniquely positioned to answer:_
- **Are the 9 inferred relationships involving `i()` (e.g. with `da()` and `f()`) actually correct?**
_`i()` has 9 INFERRED edges - model-reasoned connections that need verification._
- **What connects `source`, `document_size`, `pages` to the rest of the system?**
_318 weakly-connected nodes found - possible documentation gaps or missing edges._
_320 weakly-connected nodes found - possible documentation gaps or missing edges._
File diff suppressed because one or more lines are too long
+685 -597
View File
File diff suppressed because it is too large Load Diff
+18 -8
View File
@@ -40,8 +40,8 @@
"semantic_hash": ""
},
"h5/js/app.js": {
"mtime": 1791819697.652488,
"ast_hash": "e0e2ef463d0c51e4c3b5148fb32d3a11",
"mtime": 1791822027.245803,
"ast_hash": "4034c98d93fbaa0534de20e7ac47cf41",
"semantic_hash": ""
},
"h5/js/head-pose.js": {
@@ -1920,13 +1920,13 @@
"semantic_hash": ""
},
"h5/js/swipe-track.js": {
"mtime": 1791815201.6350212,
"ast_hash": "65957645bd9401e303978c7e0bcb1273",
"mtime": 1791822192.248705,
"ast_hash": "da9340563e73df85502b1e8860b769e5",
"semantic_hash": ""
},
"h5/test/report-touch-layout.mjs": {
"mtime": 1791821449.3654912,
"ast_hash": "4dcabb75f011480c0569ce74479b0451",
"mtime": 1791822203.777799,
"ast_hash": "8c83b7333fc0f8e3964fbaf156e32f2b",
"semantic_hash": ""
},
"h5/test/scan-placeholder.mjs": {
@@ -1960,8 +1960,18 @@
"semantic_hash": ""
},
"docs/\u8bc4\u5206\u5708\u5185\u5c45\u4e2d\u4e0e\u6b63\u8138\u8bed\u97f3\u4fee\u590d-2026-10-12.md": {
"mtime": 1791819897.8782563,
"ast_hash": "2879d154d155aad6b87effd5a4c499e1",
"mtime": 1791821522.1000154,
"ast_hash": "9bfcfa97c4b84e30916c1d9a56f1e512",
"semantic_hash": ""
},
"docs/\u62a5\u544a\u9875\u6ed1\u52a8\u9632\u51b2\u7a81-2026-10-13.md": {
"mtime": 1791822192.248705,
"ast_hash": "cd09aff1f65d7a9dee76b2d7c6455cbe",
"semantic_hash": ""
},
"docs/\u62a5\u544a\u9876\u90e8\u8bc4\u5206\u4e0e\u5e74\u9f84\u6392\u7248-2026-10-13.md": {
"mtime": 1791821522.1000154,
"ast_hash": "517105aaf52427102a325e0450142446",
"semantic_hash": ""
}
}
+6 -1
View File
@@ -1513,12 +1513,17 @@
panels[i].setAttribute('role', 'tabpanel'); panels[i].setAttribute('aria-labelledby', tab.id);
});
function height(at) {
// Fonts/images may resize while dragging. Keep the current card height
// until release so the rest of the report does not move under the finger.
if (swipe && swipe.isDragging()) return;
position = at;
var lower = Math.floor(at), upper = Math.min(panels.length - 1, Math.ceil(at)), ratio = at - lower;
viewport.style.height = (panels[lower].offsetHeight * (1 - ratio) + panels[upper].offsetHeight * ratio) + 'px';
}
var swipe = SwipeTrack.create(viewport, track, { count: panels.length, gestureTarget: card,
onMove: height, onCancel: height, onSelect: function (i) { setDetailTab(card, String(i)); } });
gestureSelector: '.viz-frame', startThreshold: 12, directionRatio: 1.5, maxOverscroll: 16,
onStart: function () { viewport.style.height = viewport.getBoundingClientRect().height + 'px'; },
onCancel: height, onSelect: function (i) { setDetailTab(card, String(i)); } });
detailSwipes.set(card, { swipe: swipe, height: height });
setDetailTab(card, '0', true);
if (typeof ResizeObserver !== 'undefined') {
+12 -5
View File
@@ -21,6 +21,7 @@ window.SwipeTrack = (function () {
target.addEventListener('pointerdown', function (e) {
if (drag.id != null || e.isPrimary === false || e.button !== 0 || !viewport.clientWidth) return;
suppressUntil = 0; // A fresh tap is intentional, not the preceding drag's click.
if (options.gestureSelector && (!e.target.closest || !e.target.closest(options.gestureSelector))) return;
drag.id = e.pointerId; drag.startX = e.clientX; drag.startY = e.clientY;
drag.offset = track.getBoundingClientRect().left - viewport.getBoundingClientRect().left;
drag.width = viewport.clientWidth; drag.lastX = e.clientX; drag.lastAt = e.timeStamp; drag.velocity = 0;
@@ -31,9 +32,14 @@ window.SwipeTrack = (function () {
if (e.pointerId !== drag.id || drag.vertical) return;
var dx = e.clientX - drag.startX, dy = e.clientY - drag.startY;
if (!drag.horizontal) {
if (Math.abs(dy) > 8 && Math.abs(dy) >= Math.abs(dx)) { drag.vertical = true; return; }
if (Math.abs(dx) <= 8 || Math.abs(dx) <= Math.abs(dy)) return;
drag.horizontal = true; viewport.classList.add('dragging'); track.classList.add('dragging');
var threshold = options.startThreshold || 8, ratio = options.directionRatio || 1;
if (Math.max(Math.abs(dx), Math.abs(dy)) <= threshold) return;
// Ambiguous diagonal movement belongs to page scrolling. Once locked,
// neither direction can steal this gesture before the finger lifts.
if (Math.abs(dy) * ratio >= Math.abs(dx)) { drag.vertical = true; return; }
drag.horizontal = true;
if (options.onStart) options.onStart(index);
viewport.classList.add('dragging'); track.classList.add('dragging');
try { target.setPointerCapture(e.pointerId); } catch (_) {}
}
if (e.cancelable) e.preventDefault();
@@ -41,8 +47,9 @@ window.SwipeTrack = (function () {
if (elapsed > 0) drag.velocity = (e.clientX - drag.lastX) / elapsed;
drag.lastX = e.clientX; drag.lastAt = e.timeStamp;
var offset = drag.offset + dx, min = -(options.count - 1) * drag.width;
if (offset > 0) offset *= .25;
else if (offset < min) offset = min + (offset - min) * .25;
var edgeLimit = options.maxOverscroll == null ? Infinity : options.maxOverscroll;
if (offset > 0) offset = Math.min(edgeLimit, offset * .25);
else if (offset < min) offset = min - Math.min(edgeLimit, (min - offset) * .25);
track.style.transform = 'translate3d(' + offset + 'px,0,0)';
if (options.onMove) options.onMove(Math.max(0, Math.min(options.count - 1, -offset / drag.width)));
});
+21 -3
View File
@@ -65,18 +65,30 @@ try {
const touch = (type, x, y, timestamp) => cdp.send('Input.dispatchTouchEvent', {
type, ...(timestamp ? { timestamp } : {}), touchPoints: /touchEnd|touchCancel/.test(type) ? [] : [{ x, y, id: 1 }]
});
async function drag(card, dx, { dy = 0, cancel = false, pause = 0, flick = false, verify = false } = {}) {
const frame = card.locator('.dpanel.active .viz-frame');
async function drag(card, dx, { dy = 0, cancel = false, pause = 0, flick = false, verify = false, text = false } = {}) {
const frame = card.locator('.dpanel.active ' + (text ? '.ditem-text' : '.viz-frame'));
await frame.scrollIntoViewIfNeeded(); await page.waitForTimeout(400);
const box = await frame.boundingBox(), x = box.x + box.width / 2, y = Math.min(500, box.y + box.height / 2);
const box = await frame.boundingBox(), x = box.x + box.width / 2, y = box.y + box.height / 2;
const before = await offset(card), previous = await selected(card), now = flick ? Date.now() / 1000 : null;
const scrollBefore = await page.locator('#s-report').evaluate(el => el.scrollTop);
const beforeHeight = await card.locator('.detail-viewport').evaluate(el => el.getBoundingClientRect().height);
await touch('touchStart', x, y, now);
await touch('touchMove', x + dx / 2, y + dy / 2, now && now + .02);
await touch('touchMove', x + dx, y + dy, now && now + .04);
const liveOffset = await offset(card), limit = await card.locator('.detail-viewport').evaluate(el => el.clientWidth * (el.querySelectorAll('.dpanel').length - 1));
assert(liveOffset <= 17 && liveOffset >= -limit - 17, 'edge resistance caps overscroll at 16px');
if (verify) {
assert(Math.abs(await offset(card) - before - dx) < 3, 'report follows finger before release');
assert.equal(await selected(card), previous, 'selection commits only on release');
assert.equal(await card.locator('.detail-panels.dragging').count(), 1);
assert(Math.abs(await card.locator('.detail-viewport').evaluate(el => el.getBoundingClientRect().height) - beforeHeight) < 1, 'card height stays fixed during horizontal dragging');
await card.locator('.dpanel.active .ditem-text').evaluate(el => {
const growth = document.createElement('span');
growth.dataset.testGrowth = ''; growth.style.cssText = 'display:block;height:80px'; el.append(growth);
});
await page.waitForTimeout(100);
assert(Math.abs(await card.locator('.detail-viewport').evaluate(el => el.getBoundingClientRect().height) - beforeHeight) < 1, 'height stays fixed even when panel content resizes while held');
await card.locator('[data-test-growth]').evaluate(el => el.remove());
if (process.env.H5_ARTIFACT_DIR) {
fs.mkdirSync(process.env.H5_ARTIFACT_DIR, { recursive: true });
await page.screenshot({ path: path.join(process.env.H5_ARTIFACT_DIR, 'report-drag-' + await card.locator('.dtab').first().textContent() + '.png') });
@@ -85,6 +97,9 @@ try {
if (pause) await page.waitForTimeout(pause);
await touch(cancel ? 'touchCancel' : 'touchEnd', 0, 0, now && now + .05);
await page.waitForTimeout(400);
if (dy <= -80 && Math.abs(dx) < 10) {
assert(await page.locator('#s-report').evaluate(el => el.scrollTop) > scrollBefore + 10, 'vertical gesture actually scrolls the report');
}
assert.equal(await card.locator('.detail-panels.dragging').count(), 0);
assert.equal(await page.locator('#p-zoom.show').count(), 0, 'drag never opens zoom');
}
@@ -105,6 +120,9 @@ try {
await drag(card, 25, { pause: 160 }); assert.equal(await selected(card), '1', 'short slow drag snaps back');
await drag(card, 100, { cancel: true }); assert.equal(await selected(card), '1', 'cancellation restores selection');
await drag(card, 2, { dy: -90 }); assert.equal(await selected(card), '1', 'vertical scrolling preserves tab');
await drag(card, 65, { dy: -60 }); assert.equal(await selected(card), '1', 'ambiguous diagonal scrolling does not switch tabs');
await drag(card, 100, { text: true }); assert.equal(await selected(card), '1', 'horizontal dragging on analysis text does not switch tabs');
await drag(card, 2, { dy: -90, text: true }); assert.equal(await selected(card), '1', 'reading text keeps vertical scrolling and current tab');
await drag(card, 40, { flick: true }); assert.equal(await selected(card), '0', 'short fast flick goes back');
await drag(card, 100); assert.equal(await selected(card), '0', 'first edge cannot overshoot');
await card.locator('.dtab').last().click(); await page.waitForTimeout(400); await aligned(card);