PVQ-UI 项目规则(单一权威版)
状态:规范(最高优先级,覆盖各文档中零散的同类表述) 适用:所有
pvq-ui仓库的修改、以及任何消费pvq-ui的 PVQ 插件 关联:《PVQ-UI组件库开发方案.md》(架构总纲)、《组件提取对照表.md》(实施映射)、《库演进治理方案.md》(持续运营)、《PVQ-UI组件库调用手册.md》(插件实操)
0. 这份文档的地位
本仓库此前散落多处"铁律"(总纲 §12、演进方案 §9、Agent 记忆中的项目铁律等)。此文档是这些规则的收敛点:凡与本文冲突,以本文为准。
任何人对本仓库动手前,先读本文 + 调用手册。
1. 文档铁律(最高优先级)
来源:项目级铁律 + 总纲 §12 + 演进 §9
- 先文档 → 再代码 → 最后同步版本号。任何功能新增 / 修复 / 调整,都要先更新
doc/开发文档/(或doc/模块文档/),再根据文档改代码,最后同步CHANGELOG.md与readme.md。 - 文档是唯一真源(Single Source of Truth):代码必须服从文档,文档也要反映代码。两者长期不一致视为缺陷。
- 版本七处同步:一次升版必须同时改齐 7 个版本标记,缺一即视为不一致缺陷:
① 仓库根
pvq-ui-loader.php的DEFAULT_VERSION(各插件cp复制的真源,漏改则调用方不传版本时拉旧产物); ②site/pvq-ui-loader.php的DEFAULT_VERSION; ③assets/css/pvq-ui.css的@version; ④assets/js/pvq-ui.js的@version; ⑤site/inc/config.php的PVQ_UI_VERSION; ⑥site/inc/components.json的version; ⑦readme.md当前版本与示例代码。 另须新增CHANGELOG.md版本块、同步本模块文档 §1 的当前库版本说明与组件稳定性标记。注:
assets/css/pvq-ui-{admin,editor,frontend}.css头部的@version是各层独立版本号,不随发布版本走,不在此七处之列。 - 用户豁免:用户明确要求"不更新版本号"时,第 3 条可暂缓,但第 1、2 条仍生效。
2. 单一真源与防回潮规则
来源:总纲 §2 + 演进 §8
- 通用组件只存在于
pvq-ui。插件不得自带通用组件副本(按钮 / 卡片 / 输入 / 开关 / 选项卡 / 横幅 / 统计 / 帮助 / 范围 / 分隔 / 转圈 / 状态指示等)。 - 已进库的类在插件自有代码中零残留定义。迁移 / 改造后必须 grep 校验:如
.pvq-card、.pvq-btn--primary等在插件 CSS/HTML 中不得再被@规则定义(只可引用)。 - 组件清单唯一真源(JSON):
site/inc/components.json是库全部组件的机器可读唯一真源(组件 id / 名称 / 层 / 稳定性 / 附加说明 / 示例 HTML;字段语义见《组件库开发方案》§3.5——stability严格二值stable/experimental,layer须与 CSS 实际所在层一致,来源与版本写进note)。官网画廊(site/inc/gallery.php)数据驱动,遍历该 JSON 自动生成侧边栏与预览——新增组件只需在 JSON 加一条,画廊自动出现,杜绝手抄三遍漂移。模块文档doc/模块文档/设计令牌与组件清单.md是同一清单的人类可读 API 说明,引用 JSON、不再手抄示例;判断"某组件是否已存在"以 JSON 为准。 - 令牌只加不删:新增
--pvq-*令牌追加即可;语义变更 / 删除只发生在 MAJOR 版本,且须在CHANGELOG+ 文档说明迁移路径。
3. 命名与编码规则
来源:总纲 §2.4 + §6、提取对照表 §3
- BEM 双横线:
pvq-{block}/pvq-{block}__{element}/pvq-{block}--{modifier}。禁止单横线修饰符(如.pvq-wp-btn-primary)。受控例外(1.4.0 明文登记):勾选框 / 单选的变体写作
.pvq-cb-variant-{strike,ghost,ripple}、.pvq-rb-variant-{card,ghost,ripple,solid},即以「变体名」作 block 而非--modifier。这是既成事实(CSS / 组件清单 JSON / 文档三处一致),登记为受控例外而非改写——真 BEM 化(如.pvq-cb--strike)属破坏性改名,须留待 MAJOR 并附迁移说明。新增组件不得再引入此类命名。 - *类前缀统一 `.pvq-
**:禁止.pvq-wp-、.pvq-ts-` 等插件专属前缀污染库。 @keyframes统一pvq-前缀(如pvq-spin/pvq-pulse),禁止pvqWp*/pvq-ts*等。- 设计令牌优先:组件中颜色 / 圆角 / 阴影 / 动画一律用
--pvq-*变量,禁止硬编码色值(内网站点主题 override 时再覆盖变量)。落地细则(1.4.0 明确):- 字面值只允许出现在令牌定义处(
:root或组件自身的作用域令牌声明,如.pvq-table { --pvq-table-head-bg: … });组件规则体内一律引用var(--pvq-*)。 - 语义色的深浅派生用
color-mix(),不要写第二份 rgba:例如 12% 的危险色边框写color-mix(in srgb, var(--pvq-danger) 12%, transparent),而非rgba(196,84,84,0.12)。这样改--pvq-danger时派生色自动跟随(color-mix与等价rgba在 sRGB 下逐位相同,属同值迁移)。 - 受控例外(允许的字面值):无色相的中性叠加/遮挡——
#fff/#000及其半透明形式(按钮涟漪、遮罩、color-mix(..., #fff/#000)的混合端点、取色器色相条)。它们不随主题色变化,写死不影响换肤。 - 受控例外(防御性兜底):
var(--pvq-token, fallback)的兜底值允许保留,用于 base 层缺席时降级;兜底值必须与令牌定义一致,且不得依赖它生效(base 是各层依赖,正常都应命中令牌)。
- 字面值只允许出现在令牌定义处(
- 库不假设主题变量存在:库是纯令牌提供方;
pvq-wp激活主题时的--pvq-theme-*映射由它自行在作用域 override。 - JS 交互原生 + 事件委托:
pvq-ui.js用原生 JS、委托监听,禁止 jQuery 依赖、禁止 inlineonclick/onkeydown;库只管"视觉 + 派发事件",表单值同步等交给插件监听后处理。 - 页面生命周期事件纪律(v1.4.3 新增):
- 禁用
unload:Chrome 已将unload纳入 Permissions Policy 并逐步默认禁用——策略不允许时注册监听即报[Violation] Permissions policy violation: unload is not allowed in this document且回调不执行;更早就存在的代价是:只要注册了unload,页面即失去 bfcache 资格(后退变整页重载)。库内一律不得出现unload/beforeunload。 - 替代写法:释放类兜底用
pagehide(正常卸载与进入 bfcache 均触发,且不阻塞 bfcache);需要在"后退/前进恢复"时复位状态的用pageshow+if (e.persisted)。 - 状态残留必须可自愈:凡是需要成对释放的全局副作用(滚动锁、
body/<html>上的类与内联样式、计数器),都要能在pageshow(persisted) 时被强制复位,否则 bfcache 恢复后会出现"整页滚不动"这类只在用户侧偶发的故障。 - *禁止写 `document.body.style.
做状态标记**(既有类约定:.pvq-scroll-locked`):内联样式会覆盖主题/消费方对同一属性的设置,且一旦漏恢复就再也发现不了;类开关幂等,任何时刻移除一次即完全释放。
- 禁用
4. 版本化规则(支持"随开发慢慢完善")
来源:演进 §3
- 语义化版本:
MAJOR.MINOR.PATCHPATCH:修样式 bug / 微调,不破坏类名。MINOR:新增组件 / 新增修饰符 / 新增令牌(向后兼容,只加不删)。MAJOR:破坏性改名 / 删类 / 改令牌语义(须文档说明迁移)。
- CDN 路径含版本:CSS 四层在
https://<CDN_BASE>/pvq-ui/v{MAJOR.MINOR.PATCH}/css/pvq-ui.css(及-admin/-editor/-frontend),JS 在.../js/pvq-ui.js,组件清单在.../v{ver}/components.json。不提供latest别名:CDN 响应Cache-Control: immutable, max-age=31536000,别名目录一旦被覆盖就会重演"浏览器一年内取旧缓存"的事故,且与"插件显式锁版本"纪律冲突;要试用新版就显式指向该版本号。 - 版本在全站只定义一处(v1.4.0 起,推荐):规定 站点级在
wp-config.php定义一次PVQ_UI_VERSION,插件调用PVQ_UI_Loader::enqueue( $scenes )不传版本。 必须分清的两层「先到先得」(v1.4.2 明确;此前文档只写了第一层,导致漏判——见下):- 常量层:
PVQ_UI_VERSION(defined()守卫)→ 由第一个调用enqueue()者传入的版本决定。 - 类层(更隐蔽):类定义被
if ( ! class_exists( 'PVQ_UI_Loader' ) )包裹 → 由第一个requireloader 的插件的副本赢得类归属;其后所有副本的class_exists()为真,整份文件被跳过,它们的DEFAULT_VERSION/register()/converge()从未执行。而所有调用点都"不传版本"、看起来完全合规,从现象根本看不出是版本问题(实测:旧副本先加载时,新版插件复制的 loader 被静默忽略,全站停在旧版,Popover/Toast 等新组件"不存在")。 - ⇒ 只有站点常量能同时钉死两层。未定义常量时,全站版本实际由「最先加载的那份副本」决定。
- v1.4.2 加固(针对未定义常量的兜底):每份副本在文件作用域(
class_exists守卫之外,故必然执行——这是混合版本副本共存时唯一可靠的感知通道)登记「本副本自述版本」与「本副本的类未生效」事实;随后:- 无站点常量时:就地按各副本自述版本的最高者定义
PVQ_UI_VERSION。因为这一步在文件作用域完成,即便"最先加载的旧副本"赢得了类归属,其register()也会沿用它——加固不依赖"赢得类的是不是新版"(这是关键:若把加固只写在类方法里,旧副本赢得类时执行的仍是旧代码,加固等于没有)。 - 告警判据(只在真的被拉低时出声,健康站点零噪音):
站点生效版本 < 最高副本版本→_doing_it_wrong()+error_log一次性告警,列出各副本版本、落后项、未生效副本与修复命令。 - 纯「副本之间有差异但未影响生效版本」不在运行时告警,交由
scripts/check-loader-copies.php报阻塞项(开发期发现)。 站点常量已定义且不低于最高副本版本时行为完全不变、零输出。
- 无站点常量时:就地按各副本自述版本的最高者定义
- 好处:版本单点、与加载顺序无关;也避免"某插件硬编码旧版把全站拉回去"(这正是
converge()想解决但靠自身解决不了的问题)。 - 例外(临时隔离才用):需某插件单独锁版本时才在调用处显式传版本;因先到先得语义,多插件长期共存多版本不可靠,不作为常规手段(v1.4.2 起该显式版本仍被尊重,但会触发上述告警)。
- 存量不强制降级:站点常量一旦统一,全站同步到该版本即为"渐进统一"的终点。
- 常量层:
- 插件 loader 副本必须同步(复制分发的代价):
pvq-ui-loader.php每插件cp一份,故库升版或 loader 逻辑改动后,插件侧副本会过期(版本号过期只在"该插件是第一个调用者且不传版本"时才有害;逻辑过期则一律有害)。纪律:- 升版后跑
php scripts/check-loader-copies.php体检(只读、发现漂移非 0 退出;站点未定义PVQ_UI_VERSION也算阻塞项); - 需要修时跑
php scripts/sync-loaders.php --apply(默认干跑;留.bak、逐个php -l); - 判定规则:与真源逐字节一致→跳过;仅注释/
DEFAULT_VERSION差异→真源cp覆盖;衍生副本(自有文档头、if (...):语法等)→最小侵入只改版本行及其行前注释。 .bak约定:同步脚本写前留.bak(便于即时回退),验证通过后应清除(find <插件目录> -name '*.bak*' -delete);.bak不入库、不长期堆积。- ⚠️ 找副本必须跟随符号链接:本机多个插件是以符号链接安装的(
wp-content/plugins/pvq-weixinyuedu -> /home/muyu/pvq-weixinyuedu、pvq-workfolio -> /mnt/c/Users/muyu/Documents/code/Gastronomy/wordpress/pvq-workfolio)。默认find不跟随符号链接,会整份漏掉这类插件的副本——这正是 2026-09-19 那份 1.2.10 陈旧副本长期未被发现的原因。因此:find **-L** /www/wwwroot/wordpress/wp-content /home/muyu -maxdepth 8 -name 'pvq-ui-loader.php';体检脚本已改为直接遍历插件目录并realpath()解析,不依赖 find。 - 全站副本清单(2026-09-19 二次核实,共 4 份):①
/home/muyu/pvq-weixinyuedu/includes/(安装处为软链;该目录非 git 仓库);②/www/wwwroot/wordpress/wp-content/plugins/pvq-wp/includes/;③/www/wwwroot/wordpress/wp-content/plugins/pvq-threads-sync/includes/(衍生副本:自有文档头 + 冒号语法,已核实依赖逻辑与真源一致);④/mnt/c/Users/muyu/Documents/code/Gastronomy/wordpress/pvq-workfolio/includes/(安装处为软链;独立 git 仓库oh-af/pvq-workfolio;1.4.2 前为 1.2.10 陈旧副本且缺 1.2.9 的font-awesome-frontend逻辑)。① ② 与真源逐字节一致、③ 按纪律只改版本行、④ 因头部是标准头(非本地衍生)按整份cp覆盖。库自身site/pvq-ui-loader.php也必须与真源逐字节一致。新增插件后重跑体检,看是否多出第 5 份。
- 升版后跑
- override 收敛幂等:loader 内置收敛只解决"同版本冲突",多次挂载幂等;默认开启,可被
PVQ_UI_NO_CONVERGE关闭。
5. 组件分级规则
来源:演进 §5
stable:成熟、承诺不破坏性改(仅可加修饰符 / 不删类)。新插件放心引用。experimental:新提取 / 接口可能调整。文档标注,引用建议锁定版本。- 首版基线(wolai 提取的通用组件)即 stable;后续新提取先 experimental,经 1~2 个插件验证后转正。
6. 文件分层规则
来源:总纲 §3.1 + 演进 §7
- 四固定层:
pvq-ui(base) /pvq-ui-admin/pvq-ui-editor/pvq-ui-frontend。 - 新场景 → 新增
pvq-ui-<scene>.css,不塞进 base。 - 所有层共享 base 的
--pvq-*令牌,跨场景风格一致。 - 组件视觉依赖 FontAwesome:loader 保证
font-awesome句柄注册,pvq-ui-admin声明对其依赖。
7. 消费方接入规则
来源:总纲 §8/§10 + 调用手册
- 每个插件只需:复制
pvq-ui-loader.php→require_once→PVQ_UI_Loader::enqueue( $scenes )(不传版本,版本由站点级PVQ_UI_VERSION决定,见 §4.3)。不得各写一份收敛 / 注册逻辑。 - 插件自身样式声明对
pvq-ui/pvq-ui-<场景>的依赖,不复制库样式。 - 内网站点可用
PVQ_UI_URL等常量覆盖为本地副本。 - 插件不得硬编码库版本号字面量(如
enqueue( array('admin'), '1.2.10' ))——它会成为全站版本的"先到先得"钉子(§4.3);插件自有的版本常量(如用于界面展示的PVQ_WR_UI_VER)可以保留,但不要把它当作控制全站库版本的手段。
8. 提交 / 同步纪律
- 改动前先确认文档已更新(§1)。
- 改动后:bump 版本(§4)→ 按 §1.3 版本七处同步 → 同步
CHANGELOG.md(含组件加入 / stable 转正记录)→ 同步site/inc/components.json与模块文档的稳定性标记 → 同步readme.md。 - 迁移插件时:按总纲 §9 步骤,最后做 grep 防回潮校验(§2.2)。
9. 违规处理
- 发现插件自建通用组件副本 → 回流到库(先文档、后代码),删插件副本,grep 校验。
- 发现 inline handler / 非 BEM 命名 / 硬编码色值 → 改造时一并修正,不新增债务。
- 发现版本号三处不一致 → 立即对齐。