Browse Source

feat(fmode-code-lit): 补齐多页面开发五条死线(light DOM slot失效/host选择器/display-block/高度链/图表容器)+ capture-project 实现

liuyuyang 14 giờ trước cách đây
mục cha
commit
51966aecdc
1 tập tin đã thay đổi với 76 bổ sung và 57 xóa
  1. 76 57
      skills/fmode-code-lit/SKILL.md

+ 76 - 57
skills/fmode-code-lit/SKILL.md

@@ -1,81 +1,100 @@
 ---
-slug: fmode-skill-fmode-code-lit
-displayName: fmode-code-lit
-version: 1.0.0
 name: fmode-code-lit
-description: "数字人开发基础规则:Lit 原生 ESM 无构建前端(零编译/零打包,浏览器直跑)。分层 core/wc/page、signals 响应式、@lit/context DI、History 路由、多标签 KeepAlive;沉淀多页面开发 light DOM 五条死线、子路径路由丢前缀、CDN 假刷新等实战教训。触发词:lit、lit 项目、lit 组件、无构建、ESM、WebComponent、importmap、signals、多页面白屏、子路径部署、keepalive、前端部署。"
-tags: [FmodeAgent, FmodeCode, 开发级, code-lit, 数字人基础规则]
-platform: Both
-level: 开发级
-category: 开发规范
-icon: 🧱
-license: MPL-2.0
-author: Fmode (未来飞马)
-homepage: https://github.com/fmodecn/skill-code-lit
-npm: "@fmode/skill-code-lit"
+description: "Lit 原生 ESM 无构建前端。触发词:lit 组件、无构建、ESM。"
+schema_version: 1.0.0
+level: system
+category: development
+version: 1.0.0
+author: Yuyang001 (FmodeAgent)
+tags: [Lit, 无构建, 前端]
 ---
 
 # fmode-code-lit — Lit 原生 ESM 无构建前端
 
-> **这是方便数字人(FmodeAgent / FmodeCode)进行项目开发的基础规则技能。**
-> 它把踩过的坑沉淀成硬性约束,让 AI 在生成代码时不重犯同类错误。
-
-用无构建/无打包/纯原生 ESM + Lit 3.x 开发前端:源码即产物,浏览器直跑。分层(组件 / 服务 / 工具),
-`@lit-labs/signals` 响应式、`@lit/context` 依赖注入、自写 History 路由、多标签 KeepAlive。
-
-npm 包:`@fmode/skill-code-lit`(`npx @fmode/skill-code-lit@latest install`)。
-完整规则见 `references/RULES-INDEX.md` 与其索引的 `references/rules/`。
+用无构建/无打包/纯原生 ESM + Lit 3.x 开发前端:源码即产物,浏览器直跑;依赖走 FMODE Repose,业务码走 OBS+CDN。分层(组件/服务/工具)、signals 响应式、@lit/context DI、History 路由、多标签 KeepAlive。
 
 ## 触发
+新建/初始化 lit 项目 · 写 lit 组件 · 无构建前端 · 原生 ESM+importmap · WebComponent · signals · context DI · 多标签 KeepAlive · 部署 OBS+CDN。
 
-新建/初始化 lit 项目 · 写 lit 组件 · 无构建前端 · 原生 ESM + importmap · WebComponent · signals ·
-context DI · 多标签 KeepAlive · 多页面白屏排查 · 子路径部署 · 部署验收。
-
-## 三条铁律
-
-1. **源码即产物**:不引入任何构建/打包器(rollup / esbuild / vite / webpack),写好的 `.js` 直接上线。
-2. **依赖不落地项目**:`lit` 等运行时依赖由 importmap 从静态依赖仓解析,禁止拷进业务仓。
-3. **分层不可越界**:组件(渲染)→ 服务(业务与状态)→ 工具(无状态函数)。
+## 铁律
+1. 源码即产物:禁 rollup/esbuild/vite/webpack。
+2. 依赖不落地:lit 由 importmap 指向 FMODE Repose。
+3. 分层不越界:组件禁 fetch/复杂业务,服务禁 DOM。
 
 ## 无构建硬约束
+禁装饰器(@customElement/@property/@state/@consume/@provide),用静态字段+编程式 API。
 
-禁装饰器(`@customElement` / `@property` / `@state` / `@consume`),一律用
-`static properties` + `customElements.define()` + `ContextConsumer`。
+## 框架边界(用户定调,必守)
+- 新写项目默认可用无构建 Lit。
+- **项目明确要求 Angular:沿用 Angular 不改 Lit。**
 
-## 多页面开发铁律(light DOM 五条死线)
+## 用法
+scaffold.mjs 初始化 / install-rules.mjs 装 .claude/rules / new.mjs 生成组件服务页 / deploy.sh 部署。
 
-`createRenderRoot(){return this}` 是**渲染模型切换**,切完 `<slot>`、`:host`、`::slotted` 全部失效:
+## 关键约定
+组件 kebab-case.js 类 PascalCase · 页面 page-*.js · 服务 *-service.js 单例 · 相对导入带 .js · 全局 signal+SignalWatcher · DI 用 @lit/context · 子目录 <base href> · CDN History 回退 200 禁 302。
 
-1. light DOM 下**禁用 `<slot>`** —— 自检:`document.querySelector('main').children.length`,为 0 就是投影断了。
-2. 外壳**按路由自己产出页面元素**,不靠投影传内容。
-3. 真需传内容的组件(卡片)用 `references/light-slot.js` 的 `capture`(渲染前)/ `project`(updated 后)手动搬,`dataset.projected` 做幂等。
-4. 自定义元素必须 `display: block; min-width: 0`(例外元素写 `display: skip`);跑 `scripts/check-elements.mjs` 自检。
-5. 图表容器用 `height: 100% + min-height`,不用 `flex: 1`(父级无确定高度时算出来是 0)。
+## 部署
+依赖→FMODE Repose(.js text/javascript、CORS 放行、HTTP/2);业务码→obsutil sync src/→OBS(public-read)+刷 CDN;index.html no-cache。
 
-## 用法
+## 常见坑
+装饰器/CORS/MIME 非 JS/多版本混用/CDN 302/base href 缺失/漏 .js/signal 与 property 混用/老浏览器无 ImportMap。
 
-```bash
-# 安装到 ~/.claude/skills/
-npx @fmode/skill-code-lit@latest install
+## 多页面开发铁律(light DOM 五条死线)
 
-# 或直接用本目录脚本
-node scripts/scaffold.mjs ./my-admin --project my-admin   # 生成项目骨架
-node scripts/install-rules.mjs .                          # 把规则装入已有项目 .claude/rules/
-node scripts/new.mjs component user-card                  # 生成组件 / 服务 / 页面 / core / wc
-node scripts/check-elements.mjs src                       # 校验自定义元素 display 声明
-node scripts/smoke.mjs                                    # 自检
+> 来源:tongjisheji/report-review 首次交付 6 页全部错乱的复盘(2026-10-29)
+> **一句话**:`createRenderRoot(){return this}` 不是“让 Tailwind 生效的小开关”,是**渲染模型切换**。切完以后 `<slot>`、`:host`、`::slotted`、样式隔离全部失效,但代码看起来和普通 Lit 一样、编辑器也不报错。
+
+### 死线1:light DOM 下禁用 `<slot>`
+没有 shadow root 就没有投影。`<slot>` 退化成普通未知元素,`assignedSlot` 为 null,**子节点原封不动滞留在宿主元素上**——外壳和内容成了兄弟节点,谁也没包住谁,于是滚动区空、导航错、高度链断。
+**自检一行**:`document.querySelector('main').children.length`,为 0 就是投影断了。
+
+### 死线2:外壳接管路由渲染,不靠投影传内容
+页面元素由外壳**自己按路由产出**,让它天然成为 `<main>` 的子节点,父子关系由渲染函数保证:
+```js
+// app-shell.js
+_renderRoute() {
+  const { name, params } = parseRoute(this.path);
+  switch (name) {
+    case 'dashboard': return html`<page-dashboard></page-dashboard>`;
+    case 'project-detail': return html`<page-project-detail .code=${params.code||''}></page-project-detail>`;
+  }
+}
+// render 里:<main ...><div class="page-enter key-route-${this.active}">${this._renderRoute()}</div></main>
 ```
+`app-root` 退化成只负责挂载:`render() { return html\`<app-shell></app-shell>\` }`。
+
+### 死线3:真需传内容的组件(卡片),用 capture/project 手动搬
+light DOM 下只能自己搬节点。现成实现见 `references/light-slot.js`。**顺序不能错**:
+- `capture(host)` 在 **connectedCallback 中、渲染前** 调用,把 `childNodes` 存到 `host.__lightIncoming`
+- `project(host)` 在 **updated() 中、渲染后** 调用,把暂存的节点塞进 `[data-light-slot="body"]` 等容器
+- **必须靠 `dataset.projected` 做幂等**,否则每次重渲染都重复搬、节点来回跳
+- 渲染后再扫宿主子节点是致命的:会把组件自己刚生成的内容当成“外部传入”搬走,越搬越乱
+
+### 死线4:自定义元素必须显式 `display: block; min-width: 0`
+自定义元素是未知元素,浏览器默认按 inline 处理:不吃 height/width(`h-full`/`flex-1` 全失效)、不形成块级盒子(内部 flex/grid 失去参照)、相邻元素间出现空白间隙。
+```css
+app-shell, page-dashboard, page-analysis, data-table, kpi-card, empty-state, ... {
+  display: block; min-width: 0;   /* min-width:0 让 flex 子项可收缩,否则长表格撑破父容器 */
+}
+```
+**两个例外必须排除**:`section-card`、`chart-box` 的 class 自带 `flex h-full flex-col`,写死 `display: block` 会覆盖掉、把高度链断掉。
 
-## 关键约定
-
-组件 `kebab-case.js` 类 `PascalCase` · 页面 `page-*.js` · 服务 `*-service.js` 单例 · 相对导入带 `.js` ·
-全局 signal + `SignalWatcher` · DI 用 `@lit/context` · 子目录 `<base href>` · CDN History 回退 200 禁 302。
+### 死线5:图表容器用 `height:100% + min-height`,不用 `flex:1`
+```css
+chart-box { height: 100%; min-height: 260px; }   /* 父级无确定高度时 flex:1 算出来是 0,ECharts 拿到 0 高度直接不画 */
+```
 
-## 部署
+### 交付前自检清单
+1. **先跑通最小路径再铺量**:外壳 + 一个空白页 + 一次路由跳转,确认 DOM 父子关系正确,再往里填 6 个页面。规模越大定位成本越高。
+2. `node scripts/check-elements.mjs` 自动校验所有自定义元素都声明了 display —— 自动检查比文档写三遍有用。
+3. 长模板用 `node --check` 检查语法:**少一个闭标签,lit-html 会把后面所有兄弟节点当成它的子节点吞掉**,整页塌陷,且症状和渲染模型错误混在一起,排查方向严重跑偏。
+4. 症状≠根因:“样式全乱”先去查 DOM 结构,别一上来翻 Tailwind 配置(这次查错方向错了两轮)。
+5. **多页症状不一致时先找共同根因**:各页症状不同通常是同一个总根因的不同投影,不是 n 个独立 bug。最严重的那页反而最好的线索。
 
-依赖 → 静态依赖仓(`.js` 为 `text/javascript`、CORS 放行、HTTP/2);业务码 → `obsutil sync src/` + 刷 CDN;
-`index.html` no-cache 并先删后传。**验收不能只 curl 首页**:模块图爬取 + 深链接 + 真实浏览器。
+### 同批高频白屏(非 Lit 问题,但一起记)
+- **子路径白屏**:index.html 用绝对路径引资源,部署到 `/dev/XXX/` 子路径全 404。用 `__BASE__` 占位 + `document.write` 注入。
+- **改配置后白屏**:dev-server 进程还服务旧配置。**改配置必须重启,别信热更新。**
 
 ## 诚实失败
-
-生成前先探测目标目录;已存在文件不覆盖(除非 `--force`)。依赖版本无法解析时明确报错,不生成跑不起来的骨架。
+探测目录不覆盖已有文件;依赖解析失败明确报错。