agentsclimarketplace

Figma ios listview recognition

Skill mythkiven/figma-ios-codegen/.cursor/skills/figma-ios-listview-recognition

Figma to iOS UIKit codegen: deterministic data package + Agent skills (Cursor/Claude) for baseline Swift UI.

Install
npx -y skills add mythkiven/figma-ios-codegen --skill figma-ios-listview-recognition

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 25 days oldThe repository was created 25 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

识别 Figma 节点是否需要用 UICollectionView 实现。 通过 3 个条件(节点名称/子节点相似度/SYMBOL)判定, 返回识别结果供代码生成使用。 Use when analyzing Figma nodes to determine if they should be implemented as UICollectionView.

SKILL.md

24.9 KB, as published. Nobody here has run it

Figma ListView 识别与生成

⛔ 硬规则(红线,违反即视为交付失败)

这一节优先级高于下文所有"识别条件"。任何与本节冲突的判定,以本节为准。

红线 1:audit.json.list_containers / index.json.by_role.list_container 全部必须 UICollectionView

两处来源等价

  • {data_dir}/audit.jsonlist_containers[](推荐,自带 must_use/forbidden 提示)
  • {data_dir}/index.jsonby_role.list_container[]

强制规则

  • 这两处列出的每一个 node_id,在生成代码里必须有对应的 UICollectionView(不是 UIScrollView,不是 UIStackView,不是手摆 UIView)
  • 即使该节点的父节点 export.is_export_asset == true也禁止把父节点整组切图——切图会吃掉列表,列表的语义子节点必须保留为 cell(详见 figma-ios-vector-vs-code 红线规则
  • 即使该节点 children 全是 IMAGE 节点,也仍然是列表 cell——cell 内容是图就用 UIImageView 当 cell 内容,不要把整个列表退化成 N 张 ImageView

自检脚本(生成完代码后必跑):

data_dir=<data_dir>
out_file=<your_generated.swift>

python3 - <<PY
import json, sys
audit = json.load(open(f"$data_dir/audit.json"))
src_raw = open("$out_file").read()

# 剔除文件顶部连续的"节点树注释块"(常见形态:Figma 代码生成器会在文件开头 dump
# 一整棵树的 `// ├ 341:xxx  INSTANCE  ...`,如果不剔除,每个 node_id 的"第一次出现"
# 其实都在注释里,后续窗口扫描会串到其他节点的代码上造成误判)。
# 规则:从文件起始连续的 `空行` 或 `//` 开头行视为注释块,遇到第一个非注释非空行停止。
lines = src_raw.splitlines(keepends=True)
i = 0
while i < len(lines) and (not lines[i].strip() or lines[i].lstrip().startswith("//")):
    i += 1
src = "".join(lines[i:])
fail = []

# 1) list_containers:必须 UICollectionView(同构列表红线)
for it in audit.get("list_containers", []):
    nid = it["node_id"]
    if nid not in src:
        fail.append(f"❌ list_container {nid} '{it['name']}' 未在代码中出现(可能被父切图吃掉)")
        continue
    window = src.split(nid, 1)[1][:2000]
    if "UICollectionView" not in window and "CollectionView" not in window:
        fail.append(f"❌ list_container {nid} '{it['name']}' 没用 UICollectionView(scroll_axis={it['scroll_axis']}, cell_count={it['cell_count']})")

# 2) list_keyword_hits(scroll_intent=True):子节点异构仍要 UIScrollView,禁止降到 UIView
for it in audit.get("list_keyword_hits", []):
    if not it.get("scroll_intent"):
        continue  # 集合语义不检查
    nid = it["node_id"]
    if nid not in src:
        continue  # 节点可能属于 list_containers(已在 1 校验)
    window = src.split(nid, 1)[1][:2000]
    has_scroll = any(k in window for k in ("UICollectionView", "CollectionView", "UIScrollView", "ScrollView"))
    if not has_scroll:
        fail.append(
            f"❌ 滚动语义节点 {nid} '{it['name']}'(matched={it['matched_keyword']!r})"
            f"没用 UICollectionView 或 UIScrollView,禁止降到 UIView"
        )

# 3) chip_group_hits(红线 2):父异构 + 子段连续 ≥4 同构 INSTANCE/COMPONENT/FRAME,必 UICollectionView
#    注意:不能简单用 "cell_id 附近出现 UICollectionView" 判断。
#    同一文件里如果有其他 CV(例如 categoryCollectionView),cell_id 的位置会撞到那个 CV
#    导致假阴性。下面用两条判据:
#      (a) 反模式正则:[UIView]/[UIButton]/[UILabel]/[UIImageView] 数组声明后 1500 char 内
#          覆盖了 run 中过半 cell id → 典型的"chip 被做成 view 数组"事故(如 rankChipViews: [UIView])
#      (b) 距离兜底:每个 cell id 距离任何 UICollectionView/Cell/register 声明 >600 char
#          → 大概率根本没用 UICollectionView 承载
import re
_chip_anti_array = re.compile(
    r"(?:lazy\s+var|var|let)\s+(\w+)\s*:\s*\[UI(?:View|Button|Label|ImageView)\]"
)
_chip_cv_sign = re.compile(
    r"UICollectionView(?:Cell|FlowLayout|DataSource|Delegate)?|register\s*\("
)
_cv_region_starts = [m.start() for m in _chip_cv_sign.finditer(src)]
for it in audit.get("chip_group_hits", []):
    run_ids = it.get("run_node_ids") or []
    present = [nid for nid in run_ids if nid in src]
    if not present:
        fail.append(
            f"❌ chip_group parent={it['parent_id']} '{it['parent_name']}' 的 "
            f"{it['run_length']} 个 cell 全部未在代码中出现(可能被整组切图吃掉)"
        )
        continue

    # 判据 (a):反模式正则
    caught = False
    for m in _chip_anti_array.finditer(src):
        tail = src[m.end(): m.end()+1500]
        hits = [nid for nid in run_ids if nid in tail]
        if len(hits) >= max(2, len(run_ids)//2):
            fail.append(
                f"❌ chip_group {it['parent_id']} '{it['parent_name']}' 命中反模式 "
                f"`{m.group()}`(覆盖 {len(hits)}/{len(run_ids)} 个 cell)— "
                f"chip 群必须 UICollectionView + cell 承载,不能做成 view 数组"
            )
            caught = True
            break
    if caught:
        continue

    # 判据 (b):距离兜底
    too_far = False
    for nid in present:
        nid_pos = src.find(nid)
        nearest = min((abs(nid_pos - s) for s in _cv_region_starts), default=10**9)
        if nearest > 600:
            too_far = True
            break
    if too_far:
        fail.append(
            f"❌ chip_group {it['parent_id']} '{it['parent_name']}' "
            f"(run_length={it['run_length']}, layout={it['layout_hint']}) 的 cell "
            f"离任何 UICollectionView 声明都 >600 char,大概率没用 CV 承载 — "
            f"对照 must_use:{it['must_use']}"
        )

if fail:
    print("\n".join(fail))
    sys.exit(1)
print("✅ list_containers / scroll_intent / chip_group_hits 节点都已用滚动容器")

# 注:判据 (b) 的 600 char 阈值是经验值;如真实代码里 cell id 挨着某个无关 CV 声明(偶发的假阴性),
# 请在代码评审中人工确认;大多数情况下此脚本能钉住 rankChipViews 这类事故形态。
PY

违反处置:脚本输出 fail → 必须重写对应节点,禁止以"视觉一致"为由放过。滚动语义一旦命中,组件类型降级到 UIView 即视为交付失败。

红线 2:异构容器内的 chip/tag 子集(≥4 个 INSTANCE/COMPONENT/相似 FRAME)也要 UICollectionView

脚本 _role.is_list_container 判定要求"父节点的全部子节点同构",但很多设计稿的列表是藏在异构容器里的——比如父节点同时有"标题 + 段位 chip 群 + 选中态指示",脚本就不会把父节点标成 list_container。

强制规则

  • 父节点的子节点中存在连续 ≥4 个 INSTANCE/COMPONENT 或同构 FRAME(同 size、同 type、x 等间距) → 把这一段单独抽出来当 cell 群 → 用一个 UICollectionView (LeftAlignedFlowLayout) 承载
  • 例:341:315 'Group 4' 的子节点 [Rectangle, Component 1, Component 5, Component 2, Component 3, Component 4, Component 6, Component 7, TEXT, TEXT, TEXT] 中,连续 7 个 Component N 就是一个 chip 群,必须 UICollectionView,不能各写一个 lazy var

📌 此判定已由 figma-ios-preload-data 预扫并写进 audit.json.chip_group_hits,LLM 不需要自己全局扫 design.json 找 chip 群——只要把 chip_group_hits 当必读清单即可。每条 entry 带 parent_id / run_node_ids / scroll_axis / wrap / layout_hint / must_use / forbiddenlayout_hint=LeftAlignedFlow 表示多行折行(scrollEnabled=false + 随外层 UIScrollView 滚)、FlowLayout 表示单轴排布。下方自检脚本 §3 会反查代码是否落地。

红线 3:列表关键词的"三档降级"(区分滚动语义 vs 集合语义)⭐️

audit.json.list_keyword_hits 列出了所有名字含列表/滚动/集合关键字的节点。每条 entry 带一个 scroll_intent 字段,由 figma-ios-preload-data 根据关键字自动分类,下游 禁止 无视:

① 滚动语义关键字scroll_intent=true): 可滚动 / 垂直滚动 / 水平滚动 / 垂直列表 / 水平列表 / 列表 / 滚动 / scroll / list

→ 这类关键字命中时,节点必须可以滚动,按以下三档降级选组件:

场景组件备注
A. 子节点同构(同 type + 相似 size + 命名规律)UICollectionView_role.is_list_container 已自动标,见 list_containers
B. 子节点异构(如 垂直滚动 容器里塞了 4 个不同表单分区)UIScrollView + contentView ⚠️禁止直接降级到 UIView——设计师用"滚动"命名就是要能滚
C. 节点名无滚动语义 内容总高 ≤ 可用区域UIView仅在两个条件同时满足时允许(本档不适用滚动关键字命中场景)

② 集合语义关键字scroll_intent=false): 标签组 / 标签区 / 集合 / tag集合 / collection

→ 多行自适应,本身不一定滚动,按以下两档选组件:

场景组件备注
A. 子节点同构(5 个 chip 等)UICollectionView(LeftAlignedFlow)scrollEnabled=false,多行自适应
B. 子节点异构UIView 允许若异构容器里仍有连续 ≥4 个同构 INSTANCE/COMPONENT(红线 2),那一段仍然要独立 UICollectionView

已修复的历史事故:把 341:313 "垂直滚动" 做成了 UIView——因为它子节点异构(4 个不同表单分区)。正确做法是 UIScrollView + contentView,小屏上内容仍可滚动可见。

判定的 4 维度相似度见下方 §「相似度判定(4 维度)」。


⚡ QUICK_REF(Agent 优先读此处)

核心原则:识别 Figma 节点是否需要用 UICollectionView 实现。

本 SKILL 职责

  • ✅ 识别节点是否为列表/集合场景
  • ✅ 返回识别结果(component, direction, scrollEnabled, confidence)
  • ❌ 不负责代码生成(由 figma-ios-uicollectionview-codegen SKILL 处理)

⭐️ 数据包优先:先查 _role.is_list_container

figma-ios-preload-data 阶段 1 已经做了同构子节点的几何判定,把列表容器标记到了:

  • design.json[node]._role.is_list_container = true
  • design.json[node]._layout_hint.list = {scroll_axis: "vertical"|"horizontal", cell_template_node_id, cell_count}
  • index.json.by_role.list_container = [node_id, ...]

判断顺序

  1. 优先查 _role.is_list_container:为 true 时直接当列表容器,cell_template_node_id 就是 cell 模板
  2. 命中后再用下面的"识别条件"细化 scrollEnabledLeftAlignedFlowLayout vs FlowLayout
  3. 没命中 _role.is_list_container 时,仍按下面的 A/B/C 条件兜底(脚本判定要求严格,名称含"可滚动/列表/标签组"等关键词的容器可能不被打 list_container 但仍要走 UICollectionView)

识别条件(OR 逻辑,满足任一即可)

主路径:先吃 audit.json.list_containers / chip_group_hits / list_keyword_hits_role.is_list_container(见上文红线与「数据包优先」)。下表 A/B/C 与后文长段伪代码仅作 audit 未命中时的兜底,不要绕过 audit 重算一遍。

条件说明置信度示例
A. 节点名称关键词"可滚动"/"列表"/"集合"/"标签组"0.95"可滚动" → UICollectionView
B. 子节点 ≥ 4 且相似4 维度判定,满足 ≥ 2 个维度0.805 个相似标签 → UICollectionView
C. 包含 SYMBOL/INSTANCE即使只有 1 个,也可能是重复内容0.85"可滚动" + SYMBOL → UICollectionView

相似度判定(4 维度)

维度判定标准示例
1. 类型相同type 一致或都是容器5 个 FRAME
2. 尺寸相似width/height 误差 ≤ 20%宽度 60-80
3. 命名规律数字递增/相同前缀/重复名"品类"、"品类备份2"
4. 位置规律x/y 坐标等间距(误差 ≤ 10%)x: 0, 100, 200

判定规则:满足 2 个或以上维度 → 认为"具有相同特征"


生成组件对照表

滚动语义关键字(scroll_intent=true)

Figma 特征子节点同构子节点异构
可滚动UICollectionView (horizontal)UIScrollView + contentView
垂直滚动 / 垂直列表 / 列表UICollectionView (vertical)UIScrollView + contentView
水平滚动 / 水平列表UICollectionView (horizontal)UIScrollView + contentView
滚动 / scroll / listUICollectionView(按 axis 推断)UIScrollView + contentView

集合语义关键字(scroll_intent=false)

Figma 特征子节点同构子节点异构
标签组 / 标签区 / 集合 / collectionUICollectionView (LeftAlignedFlow, scrollEnabled=false)UIView 允许(但连续 ≥4 个同构子集仍要单独抽 UICollectionView)

其他推断

触发条件组件
含 SYMBOL/INSTANCE 子节点UICollectionView
≥4 个相似子节点(详见相似度判定)UICollectionView

识别结果格式

# 成功识别,返回 dict:
{
    'component': 'UICollectionView',
    'direction': 'horizontal' | 'vertical',
    'scrollEnabled': True | False,
    'layoutType': 'FlowLayout' | 'LeftAlignedFlow',  # 可选
    'confidence': 0.80 | 0.85 | 0.95,
    'reason': '识别原因',
    'needModel': True,
    'homogeneous': True | False,  # 可选,是否同构内容
    'warning': 'TODO 提示'  # 可选
}

# 未识别,返回 None

使用场景

当 Agent 需要判断一个 Figma 节点是否应该用 UICollectionView 实现时,调用本 SKILL。

输入

  • 数据包 design.json[node_id](数据来自 figma-ios-preload-data 阶段 1)
  • 节点 ID 和名称

输出

  • 识别结果 dict(如上格式)
  • 或 None(不需要 UICollectionView)

后续流程

  • 如果返回 dict → 调用 figma-ios-uicollectionview-codegen SKILL 生成代码
  • 如果返回 None → 按其他规则处理节点

识别规则(统一决策树)

核心识别条件(OR 逻辑)

满足任一条件即判定为 UICollectionView:

A. 节点名称包含关键词(优先级最高,置信度 0.95)
B. 子节点 ≥ 4 且相似度 ≥ 2(4 维度判定,置信度 0.80)
C. 包含 SYMBOL/INSTANCE 节点(可能是重复内容,置信度 0.85)


识别函数

def recognize_listview(node_name, node_data):
    """
    统一识别 ListView 类型(统一用 UICollectionView)
    
    返回: {'component', 'direction', 'scrollEnabled', 'confidence', 'reason', ...} 或 None
    """
    name_lower = node_name.lower()
    children = node_data.get('children', [])

    # ========== 优先级 1:可滚动容器关键词 ==========

    scroll_keywords = {
        '可滚动': {'direction': 'horizontal', 'scrollEnabled': True},
        '垂直滚动': {'direction': 'vertical', 'scrollEnabled': True},
        '水平滚动': {'direction': 'horizontal', 'scrollEnabled': True},
        '列表': {'direction': 'vertical', 'scrollEnabled': True},
        'list': {'direction': 'vertical', 'scrollEnabled': True}
    }

    for keyword, config in scroll_keywords.items():
        if keyword in name_lower:
            return {
                'component': 'UICollectionView',
                'direction': config['direction'],
                'scrollEnabled': config['scrollEnabled'],
                'confidence': 0.95,
                'reason': f'节点命名包含"{keyword}"',
                'needModel': True
            }

    # ========== 优先级 2:标签集合关键词 ==========

    collection_keywords = ['标签组', '集合', 'tag集合', 'collection']

    for keyword in collection_keywords:
        if keyword in name_lower:
            return {
                'component': 'UICollectionView',
                'direction': 'vertical',
                'scrollEnabled': False,
                'layoutType': 'LeftAlignedFlow',
                'confidence': 0.95,
                'reason': f'节点命名包含"{keyword}"',
                'needModel': True,
                'homogeneous': True
            }

    # ========== 优先级 3:包含 SYMBOL/INSTANCE ==========
    
    for child_id in children:
        child = get_node(child_id)
        if child and child.get('type') in ['SYMBOL', 'INSTANCE', 'COMPONENT']:
            direction = 'horizontal' if '水平' in name_lower or '可滚动' in name_lower else 'vertical'
            
            return {
                'component': 'UICollectionView',
                'direction': direction,
                'scrollEnabled': True,
                'confidence': 0.85,
                'reason': f'包含 SYMBOL/INSTANCE 节点(可能是重复内容)',
                'needModel': True,
                'warning': 'TODO(阶段3): 接入接口文档后替换为真实数据源(当前为阶段 2 mock)'
            }

    # ========== 优先级 4:子节点 ≥ 4 且相似度 ≥ 2 ==========
    
    if len(children) >= 4:
        similarity_result = check_children_similarity(children, node_data)
        similarity_score = similarity_result.get('score', 0)
        
        if similarity_score >= 2:
            is_tag_collection = similarity_result.get('is_small_items', False)
            
            return {
                'component': 'UICollectionView',
                'direction': 'vertical',
                'scrollEnabled': not is_tag_collection,
                'layoutType': 'LeftAlignedFlow' if is_tag_collection else 'FlowLayout',
                'confidence': 0.80,
                'reason': f'发现 {len(children)} 个相似子节点(相似度: {similarity_score}/4)',
                'needModel': True,
                'homogeneous': True
            }

    return None

相似度判定函数(4 维度)

def check_children_similarity(children, node_data):
    """
    检查子节点相似度(返回满足的维度数 0-4)
    
    返回: {
        'score': int (0-4),
        'is_small_items': bool (判断是否为小标签)
    }
    """
    if len(children) < 2:
        return {'score': 0, 'is_small_items': False}
    
    score = 0
    child_nodes = [get_node(c) for c in children if get_node(c)]
    
    if len(child_nodes) < 2:
        return {'score': 0, 'is_small_items': False}
    
    # 维度 1: 类型相同
    types = [n.get('type') for n in child_nodes]
    container_types = {'FRAME', 'INSTANCE', 'COMPONENT'}
    
    if len(set(types)) == 1 or all(t in container_types for t in types):
        score += 1
    
    # 维度 2: 尺寸相似(容错 20%)
    widths = [n.get('width', 0) for n in child_nodes if n.get('width', 0) > 0]
    heights = [n.get('height', 0) for n in child_nodes if n.get('height', 0) > 0]
    
    size_similar = False
    if len(widths) >= 2 and len(heights) >= 2:
        if is_similar_range(widths, 0.2) and is_similar_range(heights, 0.2):
            score += 1
            size_similar = True
    
    # 维度 3: 命名模式相似
    names = [n.get('name', '') for n in child_nodes]
    if has_naming_pattern(names):
        score += 1
    
    # 维度 4: 位置排列规律
    xs = [n.get('x', 0) for n in child_nodes]
    ys = [n.get('y', 0) for n in child_nodes]
    
    if is_regular_spacing(xs) or is_regular_spacing(ys):
        score += 1
    
    # 判断是否为小标签(width < 150 且 height < 50)
    is_small_items = False
    if size_similar and widths and heights:
        avg_width = sum(widths) / len(widths)
        avg_height = sum(heights) / len(heights)
        if avg_width < 150 and avg_height < 50:
            is_small_items = True
    
    return {'score': score, 'is_small_items': is_small_items}


def is_similar_range(values, tolerance=0.2):
    """检查数值是否在相似范围内(容错比例)"""
    if not values or len(values) < 2:
        return False
    
    avg = sum(values) / len(values)
    if avg == 0:
        return all(v == 0 for v in values)
    
    return all(abs(v - avg) / avg <= tolerance for v in values)


def has_naming_pattern(names):
    """检查命名是否有规律(数字递增/相同前缀/重复名)"""
    if not names or len(names) < 2:
        return False
    
    # 规律 1: 数字递增
    import re
    numbers = [int(m.group(1)) for name in names if (m := re.search(r'(\d+)', name))]
    if len(numbers) == len(names) >= 2:
        if numbers == sorted(numbers) and len(set(numbers)) == len(numbers):
            return True
    
    # 规律 2: 相同前缀(至少 2 个字符)
    import os
    common_prefix = os.path.commonprefix(names)
    if len(common_prefix) >= 2:
        return True
    
    # 规律 3: 相同名称(重复节点)
    if len(set(names)) == 1:
        return True
    
    return False


def is_regular_spacing(positions):
    """检查位置是否等间距分布"""
    if len(positions) < 2:
        return False
    
    sorted_pos = sorted(positions)
    spacings = [sorted_pos[i+1] - sorted_pos[i] for i in range(len(sorted_pos)-1)]
    
    return is_similar_range(spacings, tolerance=0.1)

排除规则

不生成 UICollectionView 的场景

  1. 节点数量 < 4 且不含关键词

    • 例:性别选择器(3 个固定按钮)
    • 结果:生成独立按钮
  2. 包含右箭头图标(即使 ≥4 个节点)

    • 判断:节点名包含 icon_nextarrow
    • 结果:单独处理为导航按钮

实际案例

案例 1:品类水平滚动(当前案例)

Figma 结构

96:92 "可滚动" (FRAME)
  └─ 96:91 "Property 1=水平" (SYMBOL)

识别流程

1. 检查节点名称: "可滚动" ✅ → 满足条件 A
   置信度: 0.95
   方向: horizontal(默认)
   
2. 检查子节点: 包含 SYMBOL (96:91) ✅ → 满足条件 C
   置信度: 0.85
   
3. 综合判定: 取最高置信度 = 0.95

生成结果:UICollectionView(horizontal, scrollEnabled=true)


案例 2:标签集合(5 个相似标签)

场景 A:设计师添加了编组"标签组"

条件 A: 节点命名包含"标签组" ✅
置信度: 0.95

场景 B:设计师没有添加编组

条件 B: 子节点 ≥ 4 且相似度判定
  - 节点数量:5 ≥ 4 ✅
  - 维度 1(类型):都是 FRAME ✅
  - 维度 2(尺寸):width 60-80, height 28 ✅
  - 维度 4(位置):x 等间距分布 ✅
  相似度: 3/4 ≥ 2 ✅
  
置信度: 0.80

生成结果:UICollectionView(LeftAlignedFlow, scrollEnabled=false)


案例 3:性别选择器(3 个固定按钮)

节点数量:3(< 4)
样式不一致:有边框 vs 无边框

结果:生成 3 个独立按钮(不是 CollectionView)
理由:节点数量 < 4,不满足自动识别条件

与其他 Skill 的协作

协作流程

本 SKILL(识别)
    ↓ 返回识别结果 dict
figma-ios-uicollectionview-codegen(代码生成)
    ↓ 生成 Swift 代码
figma-ios-snapkit-layout(布局约束)

职责划分

  • 本 SKILL:识别节点是否需要 UICollectionView
  • figma-ios-uicollectionview-codegen:生成完整代码(Model + Mock + CollectionView + Cell + 交互)
  • figma-ios-snapkit-layout:生成 SnapKit 约束代码
  • figma-ios-rxswift-interaction-pattern:生成 RxSwift 交互代码

设计师协作建议

为了提高识别准确率,建议设计师在 Figma 中使用关键词命名:

推荐操作(1 分钟):

  1. 选中所有列表元素
  2. Cmd+G 创建编组
  3. 重命名为关键词
UI 场景推荐命名scroll_intent识别置信度
通用滚动区可滚动 / 滚动✅ true0.95
垂直列表 / 滚动垂直滚动 / 垂直列表 / 列表✅ true0.95
水平列表 / 滚动水平滚动 / 水平列表✅ true0.95
标签集合标签组 / 标签区 / 集合❌ false0.95

效果:识别准确率从 ~80%(自动判定)提升至 99%(关键词匹配)

关键字单一来源:完整列表见 figma-ios-preload-data/src/normalize/audit.pyLIST_KEYWORDS_SCROLLLIST_KEYWORDS_COLLECTION。新增关键字时,先改那里,再在本文档同步。

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.