Imported from xjxlx/skills (
code-html-compose/SKILL.md). Install upstream withnpx skills add xjxlx/skills --skill code-html-compose. Copyright stays with the author.
HTML → Jetpack Compose 高保真转换
所有面向用户的沟通、说明和代码注释使用中文。
适用范围
- 输入为含
index.html、CSS 和img/的 HTML 设计压缩包。 - 需要以像素、元素边界和模拟器截图验证 Compose 还原结果。
- 已有绝对定位基线,需要在验收通过后再安全重构为
Row、Column或列表。 - 页面同时包含固定视觉骨架、重复列表、弹窗、选中态、滚动或页面级导航逻辑。
不适用于只有截图、没有 HTML/CSS/图片资源的任务;先要求用户提供完整设计包。
页面角色与换算基准
- 参考清单中的
primary-page才是主页面视觉真源;当前项目主页面验收基准为1334px × 750px。 vertical-list-state只描述中间纵向列表的内容/滚动状态,popup-state只描述右上角套系弹窗和遮挡关系;状态片段可以有不同的 CSS 高度,不能被误当成新的整页布局,也不能替换主页面。- 多个 ZIP 即使都导出为完整 HTML 页面,角色也以用户明确指定为准:被指定为主页面的 ZIP 才参与根页面结构、尺寸和视觉验收;被指定为右侧列表、条目或图标参考的 ZIP 只能提取对应区域的条目、状态、资源和尺寸,不能把它的根背景、标题栏、左侧内容、页面高度或更多条目并入主页面。用户未指定角色时,先列出各 ZIP 的尺寸和结构证据,再确认角色,不能按页面高度自动选主页面。
- 先判定设计包的页面角色:若存在独立且有明确边界的居中面板,外层有全屏遮罩,遮罩下内容只是上下文,则按 Dialog 处理;只生成 Dialog 内部节点,背景上下文不生成、不参与结构验收,遮罩仅保留为 Dialog 宿主行为或统一底色。只有明确要求恢复底层页面时才提取背景。
- Android 目标视图的宽高统一静态导入项目的
AutoSizeConfig.DEFAULT_WIDTH和AutoSizeConfig.DEFAULT_HEIGHT,布局中直接使用DEFAULT_WIDTH、DEFAULT_HEIGHT;禁止在页面代码、示例或 Skill 规则中重复写死375、667等基准值。HTML 按 2 倍设计稿处理,固定使用DP_PER_PX=0.5(1dp=2px)。 - 数值对应关系由当前
DEFAULT_WIDTH、DEFAULT_HEIGHT和页面方向决定:HTML 坐标先按DP_PER_PX=0.5换算,再用这两个常量作为页面宽高基准;禁止把具体 Android 宽高数字写回页面代码,或横纵分别拉伸、裁切。 - 主页面不是
1334px × 750px时报告尺寸不匹配并停止;状态片段只做局部语义提取和行为校验,不参与主页面尺寸判定。
固定流程
- 在目标 Android 项目根目录确认工作区改动,并先根据
COMPOSE_ACTIVITY定位当前页面实际承载的 Activity(或 Activity-alias)。默认要求它有自己的MAIN+LAUNCHER;已有页面 Activity 不是 Launcher 时显式使用COMPOSE_ACTIVITY_MODE=existing,只允许复用该页面和真实导航入口,禁止创建 Activity、补写MAIN/LAUNCHER或用其他页面绕过检查。横向设计稿通过前,只能在已找到的 Activity 声明上写入或更新android:screenOrientation="landscape"。 - 安装脚本依赖:
npm ci --prefix <本技能>/scripts。 - 配置
PROJECT_ROOT、Compose 目标目录、包名、R、图片组件导入、参考角色清单和现有资源映射;完整变量见 配置参考。 - 先读取目标 Kotlin、调用方、状态数据和
.code-image资源元数据,并搜索项目中同类生产级 Compose 页面;记录实际宿主(普通页、Dialog 或 Popup)、状态 owner/callback、组件选型和资源/生命周期约定。生产文件只作为决策证据,不复制业务布局,再用node <本技能>/scripts/run.js <主页面设计包.zip>执行主页面基线;滚动/弹窗 ZIP 只通过参考清单关联,不能单独生成整页 Kotlin。 - 以
<PROJECT_ROOT>/.code-html-compose/内的original.png和验收报告为真源;它们是运行产物,不得提交或复制到技能仓库。
Launcher Activity 与屏幕方向前置检查
- 这里检查的是 Launcher 的
intent-filter标签,不是android:launchMode属性。 - 总入口和直接的 Compose 生成/验收入口都会读取
COMPOSE_ACTIVITY(验收命令行参数优先)并定位该 Activity;默认只认它自己的同一个intent-filter中同时声明android.intent.action.MAIN和android.intent.category.LAUNCHER。COMPOSE_ACTIVITY_MODE=existing可显式允许已有非 Launcher 页面,但只改变检查方式,不改变 Android 导出安全限制,也不自动提供启动路径。 - 目标 Activity 未配置或未找到时,输出明确提示并停止;禁止用项目中其他 Launcher Activity 替代、创建 Activity 或补写
MAIN/LAUNCHER。 - 横向设计稿在生成、编译、安装或启动前,更新该目标 Activity 的源
AndroidManifest.xml声明,确保存在android:screenOrientation="landscape";如果已有其他方向值,替换该值。不得修改其他 Activity。技能不得通过 ADB 修改模拟器的wm size、wm density、policy_control、accelerometer_rotation或user_rotation;需要横屏时只提示用户将模拟器旋转为横向。 build/、.gradle/和其他生成目录不参与发现,避免用过期合并 Manifest 掩盖项目源配置缺失。
必须遵守的判断
original.png是最终视觉真源;normalized.png只用于诊断,不能覆盖原始截图。- HTML 规范化每个确定性策略最多执行一次。未达标时保留最佳报告,禁止无限重试。
- Compose 先生成逐元素高保真基线;仅当结构通过率不少于 95%、局部抽查通过率不少于 80% 后,才能局部语义化重构。
- 先识别复合列表项:当 3 个及以上视觉条目沿横向或纵向共享相同的外框卡片尺寸(允许约 1dp/1–2px 栅格误差)、对齐轴、间距和字段槽位时,必须按列表建模;卡片内部标题、按钮、锁图标或高亮等状态差异不否定列表语义,连续的
01、02、03等编号是强信号。若最后一个条目只因落在设计稿/宿主视口边界而显示不全,必须仍作为一个使用完整卡片尺寸的 item 数据对象加入listOf,由列表宿主视口按设计稿可见宽度自然裁切;禁止另造页面级半截组件,也禁止把半截可见宽度写入 item 数据。把字段按条目边界聚合为数据类、listOf数据和 item Composable,禁止生成Number01、Number02、Number03这类复制粘贴的页面级组件。 - 强列表证据下,基线验收通过后必须完成数据驱动的
Column、LazyColumn、LazyRow或网格重构;容器类型由设计图的排列方向和是否存在可视视口决定。横向同态条目且尾项只在边界露出时,必须用LazyRow的可滚动视口承载完整 item,由容器自然裁切,不能把尾项做成半截组件。单独一行需要横向滑动且没有底部选中指示器时,优先使用LazyRow;每个选项下方需要跟随选中项移动的指示器时,使用PrimaryScrollableTabRow/ScrollableTabRow的selectedTabIndex与tabIndicatorOffset机制,点击回调必须更新同一选中状态,禁止用固定offset或独立线条手动推算指示器位置。重构后仍须保留每个条目的可观测边界,并重新执行结构和局部像素验收。 - 固定骨架和可重复入口必须先建立稳定锚点:返回、左侧导航、标题、目标栏等页面级节点不得成为筛选列表的子项;页面级区域优先用
ConstraintLayout和独立Guideline约束。需要适配手机和平板的外层面板,按设计稿比例使用 parent 的 percentageGuideline或Dimension.fillToConstraints,不要用固定size充当外层适配策略;内部固定 dp 仅用于已验收的资产和微调。重复入口先建数据列表并在已锚定的Row/Column内用forEach/forEachIndexed和固定Arrangement.spacedBy排列;只有设计要求独立起点、跨区域对齐或叠层时,才为每个入口建立独立Guideline。选中态只能改变入口状态,不能用前一个条目的测量结果、内容高度或自适应间距推导后续位置;禁止用页面级offset代替锚点,仅允许经过验收的 item 内微小光学修正;标题等文本的Arrangement/align必须与其设计区域的起点一致,左侧标题不得默认居中。 - 使用 inline
ConstraintLayoutDSL 时,先在同一个外层内容块顶部按固定顺序创建公共Guideline、refs 和其他 helper;再按页面区域分组,把每个区域独有的定位紧挨对应子 Composable 调用放置,并用中文区域注释标明归属。子 Composable 只能接收并使用HorizontalAnchor/VerticalAnchor等约束引用,禁止在ConstraintLayoutScope子函数中创建外层 Guideline。helper ID 按执行顺序生成,筛选、弹窗或条件列表引起的重组可能改变分散创建的顺序,导致约束引用错位。 - 统一约束命名:
createRefs()解构出的页面布局引用使用语义名并以Ref结尾(如titleRef、todayRef);传入子 Composable、表示布局 ID 的参数使用Res结尾(如titleRes),明确它是布局引用而不是 AndroidR资源。createGuidelineFromStart/End/Top/Bottom的变量使用对应方向后缀(如titleStart、todayTop、todayEnd),禁止用无方向的guide、line1等名称掩盖锚点方向。 - 页面颜色、
Shape和其他视觉常量按所属的大模块命名:模块前缀放在最前,再接组件、类型和用途,例如MODULE_CARD_BACKGROUND、MODULE_CARD_SHAPE、MODULE_COLOR_CARD_TITLE、MODULE_COLOR_ORANGE;没有明确模块归属时使用稳定的页面/区域前缀。禁止使用跨模块含义不清的COLOR_ORANGE、CARD_SHAPE等泛化名称,确保修改和排查时能直接看出常量归属。 - 生成的每个命名 Composable、辅助函数和自定义定位方法上方必须有中文 KDoc;参数较多时补充
@param,不能只写文件级说明代替方法说明。 - 像素级验收固定使用
semantic.designW=1334、semantic.designH=750;尺寸校验失败时立即停止,禁止通过DESIGN_WIDTH、DESIGN_HEIGHT或其他方式静默适配。 - 生成器固定按
DP_PER_PX=0.5把 HTML 坐标和尺寸换算为 Android dp;不得修改源坐标或用graphicsLayer整页缩放掩盖基线误差。 - 运行时尺寸适配先检查目标 Activity 的真实继承链和项目已有的 AutoSize;已完成全局适配时直接使用项目惯用的
dp/sp(或既有适配代理),不要再引入BoxWithConstraints、局部 Density 或整页缩放。Popup 的PopupPositionProvider为把Dp转成窗口像素而使用LocalDensity属于定位例外,不是尺寸适配。只有项目没有全局适配时,才按窗口宽高比例承载固定逻辑画布,并用设计背景色或背景图填充剩余空间,不能横纵分别拉伸或裁切。 - 横向设计稿验收前必须确保目标 Activity 的静态方向配置为
landscape,并要求模拟器当前已经横向显示;截图必须保持设备原始方向,禁止通过 ADB 修改窗口分辨率/density、锁定或设置旋转、使用rotate90或其他图像变换补偿方向。截图仍为竖屏时直接报错并停止验收;不得为了适配设计稿而覆盖模拟器分辨率,边界换算应使用当前横屏截图的实际尺寸。 - 只有验收基线需要为可观测视觉元素设置
testTag("e<domIndex>")并记录被覆盖层;集成到没有 UI 自动化或无障碍依赖的业务布局时,移除生成用testTag和testTagsAsResourceId,不要把验收标记当成布局的一部分。 - 只生成设计图中可见且有视觉证据的节点,不能凭名称补造箭头、指示器或装饰图。
文字裁切与内容高度
- 出现文字底部缺失时,先沿外层容器 →
Row/Column→ 数字/标签检查实际约束和测量高度;区分文字自身裁切、父容器裁切与相邻组件重叠。sp字号不等于文字组件的dp高度;固定高度中的Column会让后续文字受前面子项占用后的剩余高度限制。不要只增大lineHeight、开启字体留白或挪动下方内容就判定修复,也不要未经测量直接归因于includeFontPadding=false。 - 数字与说明标签等内容驱动区域,优先保留宽度、让高度随内容测量,移除无设计必要的固定
height/size和内部fillMaxHeight/weight高度分配。下方统计、进度条、按钮应在同一Column中按间距顺序排列,或约束到前一内容的底部;不能释放文字高度后仍用固定padding(top=...)定位后续内容。页面级固定骨架保持独立锚点;若总内容超过面板,按页面交互采用滚动或响应式布局,不能继续挤压文字。已明确固定高度的装饰、按钮或标题栏不一律改为内容撑高。 - 验证同时覆盖文字自身和相邻布局:读取
TextLayoutResult检查didOverflowHeight(必要时检查宽度溢出),再检查相邻 bounds 和真实宿主截图;“两个区域不重叠”不能证明文字完整。测试应记录窗口方向、可用尺寸、density 和 fontScale;出现maxHeight=0时先核对测试宿主约束。显式设计尺寸的组件测试只能证明该尺寸下的行为,不能替代真实设备与实际适配链的验证,也不能把测试用requiredSize/局部Density当作生产修复。
行为型页面规则
- 生成前必须画出状态模型:页面数据、当前套系、弹窗开关、当前列表项、滚动容器和点击后的变化;HTML/CSS 只负责外观,不能代替 Compose 状态。
- 固定骨架、纵向套系列表、横向书卡和右上角弹窗必须拆成有职责的 Composable。优先复用项目已有的 Dialog 宿主(如
ComposeDialog)和锚点Popup/PopupPositionProvider,让宿主负责遮罩、关闭和窗口定位;弹窗是页面状态,不是把弹窗 ZIP 追加到主页面坐标树;列表是数据 + item renderer,不是重复复制卡片。包含播放器或其他需释放资源的内容时,用与可见状态绑定的DisposableEffect在关闭/离开组合时清理资源。 - 优先复用现有页面的状态字段、回调和资源名;不凭设计文字臆造 API、导航、业务数据或图片。缺少行为证据时保留当前代码契约,并在报告中标注未验证行为。
- 有明确排列方向的连续内容(包括多行统计项)必须按方向使用
Column、Row、weight或列表容器;动态高度内容用verticalScroll或合适的列表承载,避免用Box的多层padding(top=...)定位造成重叠。复合项内部有明确方向时,不用Box叠加上下左右 padding 模拟行列布局;Box只用于单个复合项的背景、叠层或点击容器。不要把LazyColumn嵌入DropdownMenu这类 intrinsic measurement 容器。选中态、关闭、箭头旋转和筛选结果必须由同一状态源驱动。 - 套系选择只允许替换或过滤列表数据,必须保留根页面背景、返回、左侧导航、标题、目标栏和触发器;筛选状态与滚动偏移是两个独立变量。每个套系 item 使用完整且一致的外框高度,初始未滚动时也不得按状态片段或可见半截反推高度;尾项半截只能由列表 viewport 自然裁切。
- 行为验收至少覆盖:默认主页面、打开弹窗、选择一个套系、弹窗关闭后的筛选结果、纵向列表滚动/当前项定位;验收基线可用稳定
testTag观测宏观区域,正式业务布局则使用真实文本、点击回调和列表状态核对,不要求保留生成用标签。 - 涉及筛选、Tab、套系或弹窗选择的页面,切换前后必须核对返回、侧栏、标题和目标栏等固定骨架节点的 bounds;固定节点发生位置变化时,先检查外层 helper 的创建归属/顺序和状态是否存在二次同步,再考虑尺寸或 offset 调整。
资源
scripts/:DOM 解析、HTML 对比、Compose 基线生成、模拟器结构与局部像素校验及测试。references/configuration.md:运行命令、环境变量和产物边界。references/workflow.md:工作流细则与视觉还原约束。- 图片资源默认按完整
md5优先复用项目根目录累计的.code-image/image.json实际输出;需要复用时先用$code-image导入并--apply,本 Skill 只读取清单,不重新导入或改名。读取path/name/md5s历史数组或旧版单值md5,并按当前目标模块资源根目录过滤;旧版originalHash/outputPath/outputName只作迁移兼容。同一 Hash 多个有效路径按路径稳定选择首个;文件名不能替代内容确认。image.json的 Hash 是字节级身份,PNG/WebP、不同密度或重新压缩会造成视觉相同但 Hash 不同;历史 Hash 命中时仍需确认当前path存在且当前文件 Hash 位于同一记录的 Hash 集合内。Hash 未命中时必须在当前模块res中检索历史/语义别名,结合尺寸、透明边界和实际预览确认,确认后引用既有R资源,必要时通过COMPOSE_RESOURCE_MAP固化映射,不能把 Hash 未命中直接当成资源不存在。无可用清单、候选不存在或输出内容校验失败时才使用设计包的img/image图片,禁止跨模块或复用内容 Hash 不一致的输出文件。 - 若导出图片只表达纯色背景、圆角、阴影、描边、透明边缘或边缘噪声,即使命中 MD5 也不得放入生产布局;使用主题
Color、RoundedCornerShape、background及必要的代码阴影实现,并删除冗余ImageItem。只有真实纹理、插画、照片或无法可靠用代码表达的图标形状才保留图片。
运行产生的 run-*、compose-run-*、截图、JSON 报告、图片资源、node_modules/ 均只允许位于目标项目工作目录,不得发布到 GitHub。