Prechádzať zdrojové kódy

docs(wc): 显式声明 wc 组件必须用 Lit 框架特性实现,禁手写原生 Web Component

用户反馈:wc 章节只写「Web Component」字样,担心被理解成原生 wc 手写。
现状核实:规则与示例(quote-calc.js)本就基于 LitElement,但规范未显式约束。

- web-component-library.md:新增第〇节「wc 必须用 Lit 框架特性开发」,
  说明 Lit 自身即构建在 Web Component 标准之上、产物仍是跨框架自定义元素;
  列出禁止的原生写法(extends HTMLElement / attachShadow / observedAttributes);
  铁律 0、依赖表、验收表、红线清单同步补条目;标题加注「Lit 实现的浏览器交互层」
- CLAUDE.md / README.md / SKILL.md:分层图与铁律同步一句话
- 技能脚手架 new.mjs 的 wc 模板注释同步
- scripts/check.mjs 增硬校验:注册自定义元素的 wc 文件必须 import lit +
  extends LitElement,且不得出现原生 wc 迹象(negative test 已验证可捕获)
LiuYuYang 4 hodín pred
rodič
commit
2e5d628458

+ 25 - 3
.claude/rules/web-component-library.md

@@ -1,4 +1,4 @@
-# Web Component 组件库规范(wc:仅浏览器交互层)
+# Web Component 组件库规范(wc:Lit 实现的浏览器交互层)
 
 > 组件库 = 一批**用 `customElements.define()` 注册的自定义元素**,内部封装 **DOM / 交互 / 样式**,
 > 逻辑**全部来自核心库 [core](./core-library.md)**。产物是标准 Web Component ——
@@ -7,6 +7,25 @@
 > 与页面内组件([components.md](./components.md))的区别:页面组件服务于「一个应用」;
 > **组件库组件服务于「多个应用 / 多个框架」**,因此契约更严、实现更克制。
 
+## 零、wc 必须用 Lit 框架特性开发(最重要的一条)
+
+> ⚠️ **本章所有 wc 组件必须用 Lit 框架特性(`LitElement` / `html` / `css` / Shadow DOM / `static properties` /
+> Lit 响应式)开发。禁止手写原生 Web Component。**
+>
+> - **Lit 本身就是 Web Component 的实现层**:`LitElement` 继承自 `ReactiveElement`(再继承 `HTMLElement`),
+>   `html` 产出 Shadow DOM 里的模板,`customElements.define()` 注册的是标准自定义元素。
+>   所以「用 Lit 写」与「产出标准 Web Component、跨框架可用」**不矛盾** —— 前者是实现方式,后者是产物形态。
+> - **✅ 写法**:`class X extends LitElement` + `static properties` + `static styles = css\`\`` + `render() { return html\`\`\` }`。
+> - **❌ 禁止的原生写法**(等价重造 Lit,且丢掉 Lit 的模板缓存 / 属性转换 / 响应式更新):
+>   - `class X extends HTMLElement`
+>   - `this.attachShadow({ mode: 'open' })` + 手动 `innerHTML` / `document.createElement`
+>   - 手动实现 `observedAttributes` / `attributeChangedCallback` / `connectedCallback` 里刷 DOM
+>   - 字符串拼接模板 + `this.shadowRoot.querySelector(...).textContent = ...`
+> - **「原生」只出现在宿主侧**:`plain.html` 里 `<script type="module">` 直接挂 `<quote-calc>` 是**验证跨框架**的宿主写法,组件本身仍是 Lit。宿主用什么框架,与组件用不用 Lit 无关。
+> - **例外(仅限非 UI 件)**:`browser-transport.js` 这类**不渲染 DOM 的 I/O 适配器**不是组件,可以不 import lit;但只要文件里 `customElements.define()` 注册了元素,就必须是 `LitElement`。
+>
+> `scripts/check.mjs` 对 `wc/` 下每个「注册了自定义元素」的文件做硬校验:必须 import lit、必须 `extends LitElement`、不得出现 `extends HTMLElement` / `attachShadow(` / `observedAttributes`。
+
 ## 一、定位与边界
 
 | 项 | 组件库(wc) | 页面内组件(page) |
@@ -15,7 +34,7 @@
 | 契约 | 语义属性 / DOM 事件 / slot / CSS 自定义属性,**稳定** | 可随应用演进 |
 | 业务逻辑 | ❌ 一律来自 core,组件只做**接线** | 可在页面 Service 中 |
 | 样式 | Shadow DOM 强隔离,主题走 CSS 变量 | 可宽松 |
-| 依赖 | `core` + `lit`(仅 `LitElement`/`html`/`css`) | `core` + 页面 `services/` |
+| 依赖 | `core` + `lit`(仅 `LitElement`/`html`/`css`;**必须用 Lit,禁原生 wc 手写**) | `core` + 页面 `services/` |
 
 ```
 fmode-code-agent(core,纯 ESM)
@@ -27,7 +46,8 @@ fmode-code-agent(core,纯 ESM)
 
 ## 二、铁律(违反即返工)
 
-1. **无装饰器**:与 [components.md 第〇节](./components.md) 一致,`static properties` + `customElements.define()`。组件库要让**任何无构建页面**直接跑,装饰器会让源码 `SyntaxError`。
+0. **用 Lit 写,不手写原生 wc**:组件类一律 `extends LitElement`,模板用 `html`、样式用 `css`(见第〇节)。禁止 `extends HTMLElement` / 手写 `attachShadow` / 手写 `observedAttributes`。
+1. **无装饰器**:与 [components.md 第〇节](./components.md) 一致,`static properties` + `customElements.define()`。组件库要让**任何无构建页面**直接跑,装饰器会让源码 `SyntaxError`。注意:无装饰器 ≠ 原生 wc —— 仍然用 Lit 的静态字段 API。
 2. **逻辑不进组件**:状态机 / 校验 / 协议都在 core;组件**只**做「DOM 事件 → 调 core → 用结果重渲染」。组件里能单测的逻辑,都应已在 core 里。
 3. **不 import 业务**:组件库不 import 任何应用的 `services/`、`routes/`、业务 model;外部世界只通过**属性**和**事件**进入。
 4. **不直接 `fetch`**:网络由**宿主注入**(见第五节)或由 core 的协议函数描述、宿主执行。
@@ -245,6 +265,7 @@ if (!customElements.get('designer-chat')) customElements.define('designer-chat',
 | 样式隔离 | 宿主页全局 `p { color: red }` **不影响**组件内部;`--dc-accent` 能改主题 |
 | 幂等 | 同名脚本 import 两次不报 `define` 冲突 |
 | 无装饰器 | `node --check` 全过;`grep` 无 `@property` 等 |
+| **Lit 实现** | `grep` 注册元素的文件均 `import ... from 'lit'` 且 `extends LitElement`;无 `extends HTMLElement` / `attachShadow(` / `observedAttributes` |
 
 ## 十、红线清单
 
@@ -256,6 +277,7 @@ if (!customElements.get('designer-chat')) customElements.define('designer-chat',
 - ❌ `customElements.define` 不判重 → 二次加载 `NotSupportedError`
 - ❌ 组件库内置 lit 副本 → 宿主多实例 lit,见 [pitfalls.md](./pitfalls.md)
 - ❌ 装饰器
+- ❌ 手写原生 wc(`extends HTMLElement` / `attachShadow()` / `observedAttributes` / 字符串模板刷 `innerHTML`)→ 必须 `extends LitElement` + `html` + `css`,见第〇节
 
 ## 相关
 

+ 10 - 4
CLAUDE.md

@@ -28,7 +28,7 @@
 | 11 | [modules/](./.claude/rules/modules/) | 功能模块 / 通用能力 / 业务模块 划分最佳实践 | 模块拆分 |
 | 12 | [routing-best-practices.md](./.claude/rules/routing-best-practices.md) | **子路径部署路由实战**:base 三处同源 / `new URL` 丢前缀 / 弃用 `@lit-labs/router` / CDN 刷新假成功 / 验收不能只 curl | 做路由 / 子路径部署 / 上线验收 |
 | 13 | [core-library.md](./.claude/rules/core-library.md) | **核心库(core)**:纯 ESM 零浏览器依赖、只 export 纯逻辑、Node 可测、无构建打包为单文件、版本管理 | 抽逻辑层 / 跨框架复用 / 写库 |
-| 14 | [web-component-library.md](./.claude/rules/web-component-library.md) | **组件库(wc)**:customElements 注册、属性/事件/Slot/`::part`、跨框架用法、依赖 core、样式隔离、无装饰器 | 写 Web Component / 组件跨框架复用 |
+| 14 | [web-component-library.md](./.claude/rules/web-component-library.md) | **组件库(wc)**:**必须用 Lit(LitElement/html/css)实现、禁原生 wc 手写**、customElements 注册、属性/事件/Slot/`::part`、跨框架用法、依赖 core、样式隔离、无装饰器 | 写 Web Component / 组件跨框架复用 |
 | 15 | [retrospective.md](./docs/retrospective.md) | 对标 Angular zoneless 的技术复盘 | 理解选型理由 |
 
 ## 2. 架构一句话
@@ -40,12 +40,18 @@
 ```
 [core] 纯 ESM 逻辑库    不认识 DOM / Lit,Node 可 import 单测
    ▲ import
-[wc]   Web Component 组件库  认识 DOM,不认识业务;跨框架 <my-x> 直接用
+[wc]   组件库(Lit 实现的 Web Component)  认识 DOM,不认识业务;跨框架 <my-x> 直接用
    ▲ import
 [page] Lit 无构建页面    组装 core 与 wc,持有路由 / 布局 / 服务
 ```
 
-**core 不认识 DOM,wc 不认识业务,page 组装一切。** 换框架还想复用逻辑 → 下沉 core;
+**core 不认识 DOM,wc 不认识业务,page 组装一切。**
+
+> ⚠️ **wc = 用 Lit 框架特性实现的标准 Web Component**:组件一律 `extends LitElement` + `html` + `css` + `static properties`。
+> **禁止手写原生 wc**(`extends HTMLElement` / `attachShadow()` / 手写 `observedAttributes`)—— Lit 基于 Web Component 标准,
+> 产物仍是跨框架可用的自定义元素。见 [web-component-library.md 第〇节](./.claude/rules/web-component-library.md)。
+
+换框架还想复用逻辑 → 下沉 core;
 换框架还想复用 UI → 上提 wc;只服务本项目 → 停在 page。
 (`fmode-code-agent` = core;`designer-chat` 组件 = wc;后台页 = page。)
 细则见 [architecture.md 一·五](./.claude/rules/architecture.md)。
@@ -110,6 +116,6 @@
 注入 I/O(`now`/`random`/`transport`)→ `node --test` 单测 → `src/index.js` 单一出口 → semver + Repose 版本目录。
 见 [core-library.md](./.claude/rules/core-library.md)。
 
-**抽 wc(组件库)**:逻辑一律来自 core → `static properties` + `customElements.define()`(**判重**)→
+**抽 wc(组件库)**:**用 Lit 框架特性写**(`extends LitElement` + `html` + `css`,**不手写原生 wc**)→ 逻辑一律来自 core → `static properties` + `customElements.define()`(**判重**)→
 只暴露语义属性 / `composed` 事件 / slot / CSS 变量 + `::part` → 标签加统一前缀 → 在**一个非 Lit 宿主**里冒烟。
 见 [web-component-library.md](./.claude/rules/web-component-library.md)。

+ 5 - 2
README.md

@@ -12,7 +12,7 @@
 | 规则总纲 | [`CLAUDE.md`](./CLAUDE.md) | AI 进入本仓/引用本仓时的第一入口,索引全部规则 |
 | 分层规则 | [`.claude/rules/`](./.claude/rules/) | 架构、目录、组件、服务、DI、路由、多标签、部署、坑位逐篇规范 |
 | **核心库规范** | [`.claude/rules/core-library.md`](./.claude/rules/core-library.md) | 纯 ESM 核心库:零浏览器依赖 / 只 export 纯逻辑 / Node 可测 / 无构建打包 / 版本管理 |
-| **组件库规范** | [`.claude/rules/web-component-library.md`](./.claude/rules/web-component-library.md) | Web Component 组件库:customElements 注册、属性/事件/Slot、**跨框架**用法、依赖核心库、样式隔离 |
+| **组件库规范** | [`.claude/rules/web-component-library.md`](./.claude/rules/web-component-library.md) | Web Component 组件库(**用 Lit 实现、禁原生 wc 手写**):customElements 注册、属性/事件/Slot、**跨框架**用法、依赖核心库、样式隔离 |
 | **路由实战** | [`.claude/rules/routing-best-practices.md`](./.claude/rules/routing-best-practices.md) | 子路径部署路由最佳实践:base 三处同源 / `new URL` 丢前缀 / 弃用 `@lit-labs/router` / CDN 刷新假成功 / 不能只 curl 验收 |
 | 模块划分 | [`.claude/rules/modules/`](./.claude/rules/modules/) | 功能模块 / 通用能力 / 业务模块 的拆分最佳实践 |
 | 源稿 | [`.claude/_lit-dev-manual.md`](./.claude/_lit-dev-manual.md) | 用户提供的《Lit 原生 ESM 无构建开发最佳实践》原始规范 |
@@ -32,13 +32,16 @@
 ```
 [core] 纯 ESM 核心库   零浏览器依赖、Node 可测 —— 跨前端框架最底层
    ▲ import
-[wc]   Web Component 组件库   仅浏览器交互、零业务逻辑 —— <my-x> 跨框架直接用
+[wc]   组件库(Lit 实现的 Web Component)   仅浏览器交互、零业务逻辑 —— <my-x> 跨框架直接用
    ▲ import
 [page] Lit 无构建页面   组合 core 与 wc,持有路由 / 布局 / 服务
 ```
 
 **core 不认识 DOM,wc 不认识业务,page 组装一切。**
 
+> **wc 用 Lit 写,不手写原生 wc**:组件一律 `extends LitElement` + `html` + `css`。Lit 本身构建在 Web Component 标准之上,
+> 产出的就是标准自定义元素,所以跨框架复用不受影响;禁止 `extends HTMLElement` / `attachShadow()` 这类原生写法。
+
 | 你想做的事 | 往哪写 | 规则篇 |
 |-----------|--------|--------|
 | 一段逻辑要在 **Node、浏览器、多个前端框架** 里都能用 | **core** | [core-library.md](./.claude/rules/core-library.md) |

+ 7 - 4
examples/core-wc/README.md

@@ -19,7 +19,7 @@ page/                    [page] 页面层 —— 组合
 test/
   quote.test.js          # core 单测(node:test,不依赖浏览器/jsdom)
 scripts/
-  check.mjs              # 静态体检:core 零 DOM / 无装饰器 / import 后缀 / 注册幂等
+  check.mjs              # 静态体检:core 零 DOM / 无装饰器 / wc 必须用 Lit 实现 / import 后缀 / 注册幂等
 ```
 
 ## 跑起来
@@ -37,8 +37,9 @@ node --test test/*.test.js
 node scripts/check.mjs
 ```
 
-断言四条红线:`core/` 不含 `window`/`document`/`fetch`、`core/` 不 import lit、
-全仓无装饰器、`customElements.define` 均有 `get()` 判重。
+断言五组红线:`core/` 不含 `window`/`document`/`fetch`、`core/` 不 import lit、
+全仓无装饰器、**`wc/` 组件必须用 Lit 实现**(`extends LitElement` + `import 'lit'`,
+无 `extends HTMLElement` / `attachShadow(` / `observedAttributes`)、`customElements.define` 均有 `get()` 判重。
 
 ### 3. 浏览器(页面 + 跨框架)
 
@@ -88,7 +89,9 @@ core 计算结果 / Lit 页面宿主 / 纯 HTML 宿主 / 重复 import 幂等 /
 
 - **core 零浏览器依赖**:`core/quote.js` 里没有 `window`/`document`/`fetch`,不 import lit;
   `now`/`id` 通过参数注入 → `node --test` 直接断言确定值。
-- **wc 只做接线**:`<quote-calc>` 不自己算钱、不自己 fetch;算钱调 core,网络由宿主注入 `transport`。
+- **wc 只做接线、且用 Lit 写**:`<quote-calc>` 不自己算钱、不自己 fetch;算钱调 core,网络由宿主注入 `transport`。
+  组件本身是 `LitElement`(Lit 框架特性:`static properties` / `html` / `css`),**不是手写原生 wc** ——
+  `plain.html` 里的「原生」只是**宿主**用原生 HTML 挂载它,恰好证明产物是跨框架可用的标准自定义元素。
 - **契约跨框架**:只暴露 `items`/`discount`/`disabled` 属性、`quote-done`/`quote-error` 事件
   (`composed: true` 穿透 Shadow DOM)、`<slot name="title">`、`--qc-accent` 变量。
 - **跨框架运行**:`page/index.html`(Lit 宿主)与 `wc/plain.html`(纯 HTML)用的是**同一个**

+ 45 - 7
examples/core-wc/scripts/check.mjs

@@ -2,10 +2,12 @@
 /**
  * scripts/check.mjs —— examples/core-wc 静态体检。
  *
- * 断言三件事(对应规则仓 core-library / web-component-library 的红线):
+ * 断言五件事(对应规则仓 core-library / web-component-library 的红线):
  *  1. core/ 零浏览器依赖、不 import lit;
  *  2. wc/ 无装饰器;
- *  3. 全仓 .js 语法通过 + 相对 import 都带 .js。
+ *  3. wc/ 组件必须用 Lit 实现(extends LitElement + import 'lit',禁原生 wc 手写);
+ *  4. 全仓 .js 语法通过 + 相对 import 都带 .js;
+ *  5. customElements.define 注册幂等。
  *
  * 用法:node scripts/check.mjs
  */
@@ -38,9 +40,14 @@ const files = walk(root);
 function strip(src) {
   return src
     .replace(/\/\*[\s\S]*?\*\//g, '')      // 块注释
-    .replace(/(^|[^:])\/\/[^\n]*/g, '$1')  // 行注释(避开 https://)
-    .replace(/'(?:[^'\\]|\\.)*'/g, "''")   // 单引号串
-    .replace(/"(?:[^"\\]|\\.)*"/g, '""');  // 双引号串
+    .replace(/(^|[^:])\/\/[^\n]*/g, '$1'); // 行注释(避开 https://)
+}
+
+/** 只去掉注释(保留字符串),用于「是否 import 某包」这类**看字面量**的判定。 */
+function stripComments(src) {
+  return src
+    .replace(/\/\*[\s\S]*?\*\//g, '')
+    .replace(/(^|[^:])\/\/[^\n]*/g, '$1');
 }
 
 // 1. core 零浏览器依赖
@@ -63,7 +70,38 @@ const DECORATOR = /^\s*@(customElement|property|state|consume|provide|query|quer
 const decoHits = files.filter((f) => DECORATOR.test(strip(readFileSync(f, 'utf8'))));
 check('全仓无装饰器语法', decoHits.length === 0, decoHits.join('\n    '));
 
-// 3. 语法 + 相对 import 带 .js
+// 3. wc/ 必须用 Lit 实现(禁原生 Web Component 手写)
+//
+// 判定口径:**注册了自定义元素的文件**就是「组件」,必须用 Lit 框架特性开发。
+// 不渲染 DOM 的纯 I/O 适配器(如 browser-transport.js,不 define)不在此列。
+const NATIVE_WC = [
+  [/\bextends\s+HTMLElement\b/, 'extends HTMLElement(原生 wc)'],
+  [/\battachShadow\s*\(/, 'attachShadow()(手写 Shadow DOM)'],
+  [/\bobservedAttributes\b/, 'observedAttributes(手写属性观察)'],
+  [/\battributeChangedCallback\b/, 'attributeChangedCallback(手写属性观察)'],
+];
+const wcComponentFiles = files.filter((f) => f.includes('/wc/') && /customElements\.define\(/.test(readFileSync(f, 'utf8')));
+check('wc/ 存在注册自定义元素的组件文件', wcComponentFiles.length > 0);
+
+const notLit = wcComponentFiles.filter((f) => !/from\s+['"]lit(-element)?['"]/.test(stripComments(readFileSync(f, 'utf8'))));
+check('wc/ 组件均 import lit', notLit.length === 0,
+  notLit.map((f) => `${f}: 缺少 import { LitElement, html, css } from 'lit'`).join('\n    '));
+
+const notLitElement = wcComponentFiles.filter((f) => !/\bextends\s+LitElement\b/.test(strip(readFileSync(f, 'utf8'))));
+check('wc/ 组件均 extends LitElement', notLitElement.length === 0,
+  notLitElement.map((f) => `${f}: 组件类必须继承 LitElement(见 web-component-library.md 第〇节)`).join('\n    '));
+
+let nativeHits = [];
+for (const f of wcComponentFiles) {
+  const src = strip(readFileSync(f, 'utf8'));
+  for (const [re, label] of NATIVE_WC) {
+    if (re.test(src)) nativeHits.push(`${f}: ${label}`);
+  }
+}
+check('wc/ 组件无原生 wc 写法(extends HTMLElement / attachShadow / observedAttributes)', nativeHits.length === 0,
+  nativeHits.join('\n    '));
+
+// 4. 语法 + 相对 import 带 .js
 let syntaxBad = [];
 const suffixBad = [];
 for (const f of files) {
@@ -85,7 +123,7 @@ for (const f of files) {
 check('全部 .js 通过 node --check', syntaxBad.length === 0, syntaxBad.join('\n    '));
 check('相对 import 带 .js 且裸标识符在 importmap 内', suffixBad.length === 0, suffixBad.join('\n    '));
 
-// 4. 注册幂等
+// 5. 注册幂等
 const defs = files.filter((f) => /customElements\.define\(/.test(readFileSync(f, 'utf8')));
 const guarded = defs.filter((f) => /customElements\.get\(/.test(readFileSync(f, 'utf8')));
 check('customElements.define 均有 get() 判重', defs.length > 0 && defs.length === guarded.length,

+ 4 - 3
skills/fmode-code-lit/SKILL.md

@@ -57,7 +57,7 @@ tags: [未来飞马, 智能体技能, 超级技能, 开发级, 前端, Lit, WebC
 ```
 [core] 纯 ESM 核心库   零浏览器依赖、Node 可测 — 跨前端框架最底层
    ▲ import
-[wc]   Web Component 组件库   仅浏览器交互、零业务 — <my-x> 跨框架直接用
+[wc]   组件库(Lit 实现的 Web Component)   仅浏览器交互、零业务 — <my-x> 跨框架直接用
    ▲ import
 [page] Lit 无构建页面   组合 core 与 wc,持有路由/布局/服务
 ```
@@ -71,7 +71,8 @@ tags: [未来飞马, 智能体技能, 超级技能, 开发级, 前端, Lit, WebC
 | 只做本应用的页面 | `src/routes/` | `new.mjs page <name>` | `components.md` |
 
 - **core 铁律**:不 `import lit`、不碰 `window`/`document`/`fetch`;`now`/`id`/`transport` 等不确定输入**注入**;`node --test` 可测。
-- **wc 铁律**:逻辑来自 core;网络由宿主注入 `transport`;只暴露语义属性 + `composed` 事件 + slot + CSS 变量;标签加统一前缀;`customElements.define` **判重**。
+- **wc 铁律**:**必须用 Lit 框架特性写**(`extends LitElement` + `html` + `css`,**禁手写原生 wc**:`extends HTMLElement` / `attachShadow()` / 手写 `observedAttributes`);逻辑来自 core;网络由宿主注入 `transport`;只暴露语义属性 + `composed` 事件 + slot + CSS 变量;标签加统一前缀;`customElements.define` **判重**。
+  > Lit 本身构建在 Web Component 标准之上,产物即标准自定义元素 → 「用 Lit 写」与「跨框架」不冲突。
 
 
 ## ⚠️ 无构建环境的硬约束(最容易踩)
@@ -141,7 +142,7 @@ bash scripts/deploy.sh <project>   # obsutil 同步 src/ → OBS → CDN 刷新
 | 页面 | `page-*.js`,标签 `<page-*>` |
 | 服务 | `*-service.js`,类 `*Service`,单例 `export const xService = new XService()` |
 | 核心库(core) | `lib/core/*.js`,纯函数/类,**无 `window`/`document`/`fetch`,不 import lit**;入口 `index.js` 单一出口 |
-| 组件库(wc) | `lib/wc/*.js`,`customElements.define`(**判重**),标签带统一前缀;逻辑来自 core |
+| 组件库(wc) | `lib/wc/*.js`,**`extends LitElement`(Lit 实现,禁原生 wc)**,`customElements.define`(**判重**),标签带统一前缀;逻辑来自 core |
 | 相对导入 | **必须带 `.js` 后缀** |
 | 全局状态 | `@lit-labs/signals` 的 signal + `SignalWatcher` |
 | 组件本地状态 | `static properties`(`state: true`) |

+ 4 - 0
skills/fmode-code-lit/scripts/new.mjs

@@ -128,6 +128,10 @@ export function ${pascal.charAt(0).toLowerCase() + pascal.slice(1)}(items, opts
   content = `/**
  * ${pascal} —— 组件库(Web Component):仅浏览器交互,逻辑来自 core。
  *
+ * ⚠️ 必须用 Lit 框架特性实现(extends LitElement + html + css),禁止手写原生 wc
+ *    (extends HTMLElement / attachShadow() / 手写 observedAttributes)。
+ *    Lit 本身基于 Web Component 标准,产物仍是跨框架可用的自定义元素。
+ *
  * 契约:语义属性 + composed 事件 + slot + CSS 变量 + ::part
  * 无装饰器:static properties + customElements.define(判重)
  * 规则:见 .claude/rules/web-component-library.md