fix(react): [EX-01] verify installed consumers and clean up callback failures

This commit is contained in:
Harvey Zhao committed 2026-09-14 05:17:51 +08:00
1 parent 3e39fc745f
commit e0d785662d
25 files changed
+3539 -98

No files matched your search

+3
View File
@@ -131,6 +131,8 @@ jobs:
package-manager-cache: false
- name: Verify canonical installed consumers
run: node scripts/package-runtime.mjs --canonical 2>&1 | tee refactor/.cache/ci/consumer-node-canonical.log
- name: Verify React installed consumers
run: yarn test:react-consumer 2>&1 | tee refactor/.cache/ci/react-consumer.log
- name: Run all three engines
run: yarn test:browser 2>&1 | tee refactor/.cache/ci/browser.log
- name: Verify iframe cached history and interrupted navigation
@@ -146,6 +148,7 @@ jobs:
refactor/.cache/ci/
refactor/.cache/browser/
refactor/.cache/iframe-history/
refactor/.cache/react-consumer-*/
refactor/.cache/performance/
refactor/.cache/packages/latest.json
refactor/.cache/packages/run-*/*.json
+55 -7
View File
@@ -1,12 +1,60 @@
# React + Vite
# React consumer example
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Run from the repository root with Node from .node-version and Yarn Classic
1.22.22. Install once with yarn install --frozen-lockfile; keep only the root
yarn.lock in version control.
Currently, two official plugins are available:
```sh
yarn dev:react
yarn typecheck:react
yarn lint:react
yarn build:react
yarn test:react-consumer
```
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
build:react writes to refactor/.cache/react-example-dist. It uses built
workspace package entrypoints. Rebuild a changed library with `yarn build <package>` before testing its integration. The consumer test packs the current
core, Danmuku and Document PiP distributions, installs them outside the workspace,
verifies their bytes, typechecks and builds the copied example, and exercises it
in Chromium, Firefox and WebKit. No package aliases to source files are used.
Reports and the consumer lock are in unique refactor/.cache/react-consumer-* folders.
## Expanding the ESLint configuration
## Component contract and ownership
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
- src/Player.tsx owns one Artplayer instance per effect. Existing props remain
option: `Partial<Option>`, optional getInstance(art), and native div attributes.
Stable option/callback identities retain the instance. Changing either destroys
the old instance before creating one.
- getInstance runs after construction, not media readiness. Subscribe to player
events for readiness. React StrictMode may call it again during development
setup/cleanup. Callback errors destroy the instance before propagating the
original error to React, including when cleanup also throws.
- Cleanup uses destroy(false): React owns the outer div. Retained player references
expose isDestroy; the callback does not receive a new null argument on unmount.
- src/player-options.ts retains this example's existing overrides: French language,
French/Indonesian dictionaries, and new Danmuku/Document PiP factories per instance.
These still override option.lang, i18n and plugins. Other options pass through.
React supplies the container. The url assertion preserves the historical Partial
input; the core validates it. Missing URLs are not promised to construct successfully.
- src/App.tsx supplies stable options and a callback. src/main.tsx mounts StrictMode;
index.html loads the TSX entry. tsconfig.json checks the actual TSX and declarations
with strict checks and skipLibCheck: false.
- Root development dependencies pin React/React DOM 19.1.1, their types
19.1.10/19.1.7, Vite 7.3.6, React Vite plugin 5.0.0 and TS 5.9.3.
These are example/test tooling, not Artplayer runtime dependencies.
## Test scope
Installed development and production builds cover StrictMode, stable/changed
props, sibling instances, optional callbacks, unmount/remount, callback exceptions,
native play/pause/seek, decoded canvas pixels, plugin registration and the App entry.
Video and XML responses are controlled fixtures; CDN availability is not asserted.
Window PiP interaction, full Danmuku scheduling, macOS/iOS Safari and device
capabilities remain in package/release acceptance tasks.
yarn test:react-consumer --before substitutes recorded pre-fix Player source
against the same installed candidate packages. It must fail at callback exception
cleanup. This is a wrapper-source regression control, not an old npm wrapper test:
this example is not a published wrapper package.
Maintenance details and frozen evidence belong to EX-01 in refactor/.
+2 -29
View File
@@ -1,29 +1,2 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])
// Reuse the repository's TypeScript-aware configuration for this TSX example.
export { default } from '../../eslint.config.js'
+1 -1
View File
@@ -8,6 +8,6 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+14 -15
View File
@@ -1,30 +1,29 @@
{
"name": "artplayer-react",
"private": true,
"version": "0.0.0",
"type": "module",
"version": "0.0.0",
"private": true,
"packageManager": "yarn@1.22.22",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "yarn typecheck && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"artplayer": "^5.3.0-beta.4",
"artplayer-plugin-danmuku": "^5.2.0",
"artplayer-plugin-document-pip": "^1.0.2",
"react": "^19.1.1",
"react-dom": "^19.1.1"
"react": "19.1.1",
"react-dom": "19.1.1"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^5.0.0",
"eslint": "^9.33.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
"vite": "^7.1.2"
"@types/react": "19.1.10",
"@types/react-dom": "19.1.7",
"@vitejs/plugin-react": "5.0.0",
"eslint": "9.39.2",
"typescript": "5.9.3",
"vite": "7.3.6"
}
}
+3 -1
View File
@@ -1,7 +1,9 @@
import Artplayer, { type Option } from 'artplayer'
import type Artplayer from 'artplayer'
import type { Option } from 'artplayer'
import Player from './Player'
function getInstance(art: Artplayer) {
// eslint-disable-next-line no-console
console.log(art)
}
+23 -27
View File
@@ -1,16 +1,11 @@
import type { Option } from 'artplayer'
import type { HTMLAttributes } from 'react'
import Artplayer from 'artplayer'
import { useEffect, useRef } from 'react'
import Artplayer, { type Option } from 'artplayer'
import { playerOptions } from './player-options'
// Test i18n
import fr from 'artplayer/i18n/fr'
import id from 'artplayer/i18n/id'
// Test plugins
import artplayerPluginDanmuku from 'artplayer-plugin-danmuku'
import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip'
interface PlayerProps extends React.HTMLAttributes<HTMLDivElement> {
option: Partial<Option>,
export interface PlayerProps extends HTMLAttributes<HTMLDivElement> {
option: Partial<Option>
getInstance?: (art: Artplayer) => void
}
@@ -18,24 +13,25 @@ export default function Player({ option, getInstance, ...rest }: PlayerProps) {
const containerRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
if (!containerRef.current) return
if (!containerRef.current)
return
const art = new Artplayer({
...option,
container: containerRef.current,
url: option.url as string,
i18n: { id, fr },
lang: 'fr',
plugins: [
artplayerPluginDocumentPip({}),
artplayerPluginDanmuku({
danmuku: 'https://artplayer.org/assets/sample/danmuku.xml',
}),
],
})
const art = new Artplayer(playerOptions(option, containerRef.current))
if (typeof getInstance === 'function') {
getInstance(art)
try {
if (typeof getInstance === 'function') {
getInstance(art)
}
}
catch (error) {
try {
art.destroy(false)
}
finally {
// Keep the consumer's error even if teardown also fails.
// eslint-disable-next-line no-unsafe-finally
throw error
}
}
return () => {
+23
View File
@@ -0,0 +1,23 @@
import type { Option } from 'artplayer'
import artplayerPluginDanmuku from 'artplayer-plugin-danmuku'
import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip'
import fr from 'artplayer/i18n/fr'
import id from 'artplayer/i18n/id'
// Preserve this example's existing overrides and create plugins per instance.
export function playerOptions(option: Partial<Option>, container: HTMLDivElement): Option {
return {
...option,
container,
// Partial<Option> is the historical wrapper API; Artplayer validates missing URLs.
url: option.url as string,
i18n: { id, fr },
lang: 'fr',
plugins: [
artplayerPluginDocumentPip({}),
artplayerPluginDanmuku({
danmuku: 'https://artplayer.org/assets/sample/danmuku.xml',
}),
],
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2020",
"jsx": "react-jsx",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["react", "react-dom"],
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": false
},
"include": ["src"]
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
// https://vite.dev/config/
export default defineConfig({
+12 -2
View File
@@ -47,7 +47,7 @@
"check:plan": "node refactor/scripts/plan.mjs --check",
"test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js test/declarations.test.js test/editor-types.test.js test/coverage.test.js test/performance-report.test.js test/media-gate.test.js test/ci-summary.test.js test/package-runtime.test.js test/library-build.test.js",
"test:baseline": "node --test refactor/scripts/*.test.mjs",
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn check:types && yarn typecheck && yarn test",
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn test",
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
"typecheck": "node scripts/typecheck.mjs",
@@ -108,12 +108,20 @@
"test:danmuku-mask-types-package": "node refactor/scripts/danmuku-mask-package-types.mjs",
"test:auto-thumbnail-types": "node --test refactor/scripts/auto-thumbnail-types.test.mjs",
"test:auto-thumbnail-types-package": "node refactor/scripts/auto-thumbnail-package-types.mjs",
"probe:auto-thumbnail-rendering": "node refactor/scripts/auto-thumbnail-rendering-probe.mjs"
"probe:auto-thumbnail-rendering": "node refactor/scripts/auto-thumbnail-rendering-probe.mjs",
"dev:react": "vite example/react.js --config example/react.js/vite.config.js",
"typecheck:react": "tsc -p example/react.js/tsconfig.json --noEmit",
"build:react": "yarn typecheck:react && vite build example/react.js --config example/react.js/vite.config.js --outDir ../../refactor/.cache/react-example-dist",
"test:react-consumer": "node refactor/scripts/react-consumer.mjs",
"lint:react": "eslint example/react.js --no-fix"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
"@antfu/eslint-config": "5.4.1",
"@playwright/test": "1.63.0",
"@types/react": "19.1.10",
"@types/react-dom": "19.1.7",
"@vitejs/plugin-react": "5.0.0",
"@vue/compiler-sfc": "3.5.28",
"@yarnpkg/lockfile": "1.1.0",
"c8": "12.0.0",
@@ -131,6 +139,8 @@
"linkedom": "0.18.13",
"pngjs": "7.0.0",
"prompts": "2.4.2",
"react": "19.1.1",
"react-dom": "19.1.1",
"semver": "7.7.4",
"servor": "4.0.2",
"svgo": "4.1.0",
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,95 @@
# EX-01 React 消费者、异常清理与实际打包验收
起点 `3e39fc745f4dc98eba678ca081e86c1d3cc965cd`。本任务对应 React 示例,
不将其他插件的组合、设备和分发任务一并标为完成。
## 已复现问题和实现
1. `index.html` 仍引用不存在的 `/src/main.jsx`,实际入口为 `main.tsx`。
改为实际入口,原 App 经 Vite 构建和三引擎实际加载媒体通过。
2. 原 Player effect 在调用 `getInstance` 后才返回清理函数。消费者回调抛错时,
React 无法取得该清理函数,已创建实例残留。最终 `--before` 控制使用原始
Player 源码及相同候选 tarball,在 Chromium 开发模式复现:错误边界已经
显示,注册表仍有 1 个播放器。修复在重新抛出原错误前调用 `destroy(false)`;
即使清理也抛错,保留最初的消费者异常。六组最终回归均无实例/DOM 残留。
3. 原 TSX 示例没有 tsconfig,局部 ESLint 配置仅覆盖 JS/JSX。补充严格 TSX
配置,复用仓库的 TS lint 配置,修复实际源码中的类型导入和配置格式。
文件职责:`Player.tsx` 负责 React effect、引用交付和资源归属;
`player-options.ts` 隔离示例特定配置和插件工厂;`App.tsx` 保持调用样例;
`main.tsx` 保持 StrictMode;README 说明真实维护入口、行为和验证边界。
没有为简单包装增加通用生命周期框架。
## 兼容性
- 保留 `option: Partial<Option>`、可选 `getInstance(art)` 和 div 属性。
- 保留 `[option, getInstance]` 依赖:同一引用不重建,任一引用改变则先清理后重建。
- 保留 callback 在构造之后同步交付实例,不改为等待 ready,不新增 null 回调。
- 保留 `destroy(false)`、React 外层容器归属及现有法语/词典/两个插件覆盖规则。
这些是示例原有行为;没有借通用化而静默改变用户传入 plugins/lang 的优先级。
- `url as string` 仅适配历史 Partial 输入,继续由核心验证无效/缺失 URL;
不把可编译的 `option={{}}` 宣称为可成功运行。
- 新导出的 PlayerProps 是补充类型入口;核心/插件公开声明和运行产物未修改。
React 的 [effect 文档](https://react.dev/reference/react/useEffect) 说明开发
StrictMode 的额外 setup/cleanup。真实受测 React 19.1.1 的开发模式抛错路径
交付两个相同原始错误,生产模式一个,逐模式精确断言;每个创建实例只销毁一次。
初版测试错误地统一要求一个异常,已纠正且保留失败报告,未放松清理断言。
## 工具、测试与 CI
根 devDependencies 新增固定 React/React DOM 19.1.1、@types/react 19.1.10、
@types/react-dom 19.1.7、@vitejs/plugin-react 5.0.0;复用 Vite 7.3.6、TS 5.9.3。
用于 TSX 示例、React 模式行为和 Vite 构建,不增加 Artplayer 生产依赖。
只更新根 yarn.lock;示例 manifest 同步已验证版本和实际使用的开发工具。
新增根命令 `dev:react`、`typecheck:react`、`lint:react`、`build:react`、
`test:react-consumer`。后者由 `refactor/scripts/react-consumer.mjs` 编排:
1. Yarn pack 核心/Danmuku/Document PiP,记录 SHA256;仓库外安装,再 frozen、
offline、force 安装并逐文件对照 tarball;缓存中的消费者锁是测试证据。
2. 复制真实示例及 `test/react/` 夹具,严格编译全部示例、真实浏览器 TSX 夹具和三个 @ts-expect-error
反例。检查所有声明只来自隔离消费者和 TS 标准库,禁止工作区类型逃逸。
初版 compiler host 使用仓库 cwd 导致 @types/react 逃逸,被验证器正确拒绝;
已将 host 的 currentDirectory 设为隔离消费者,保留此隔离检查。
3. Vite 构建 development/production,实际使用安装后的包,不 alias 生产源码。
4. 三引擎串行执行,每组 12 类检查:StrictMode/插件注册、稳定引用、回调变化、
option 变化、真实 play/pause/seek/非黑像素、兄弟实例身份保持、可选回调、
卸载清理、重挂载、回调异常、清理次生异常、原 App 媒体加载。
5. `--before` 使用固定 Git 版本中的原 Player,预期在回调异常清理处失败。
在现有 GitHub 三操作系统 browser-smoke 中加入此命令和 always 上传的证据目录;
ci:check 加入 React 类型/lint。这里已验证本地配置和 47 项 CI 回归,未执行远端
GitHub jobs,不能称远端三操作系统已通过。
## 验证结果和限制
最终证据见 [react-consumer-validation.json](../baselines/react-consumer-validation.json)。
Windows、Node 24.21.0、Yarn 1.22.22;Chromium 153.0.8010.12、Firefox 155.0、
WebKit 26.6:六组全部通过,无 retry/skip。旧包装精确复现 1 个残留实例。
严格隔离 TS 5.9.3 无诊断,三个无效输入被拒绝;全仓 typecheck 的 406 个生产
TS 文件和历史/准确类型矩阵通过。示例构建、lint、frozen 安装、严格工具链通过。
开发 React bundle 的 >500kB Vite 提示保留,没有提高阈值;生产共享 chunk
约 477.53kB,整体包体复盘仍属 MOD-03/REVIEW-01。
前期失败报告保留:`react-consumer-2B2QCR` 是类型逃逸,`dmu7pS` 是开发异常
计数断言错误;最终候选 `x9KPB2`,最终旧源码对照 `cgxKhm`。
这是原包装 API 在实际候选包中的消费验收,不是所有旧核心/旧插件组合验收。
受控 MP4/XML 不证明公网 CDN;插件注册不证明 PiP 真窗口或完整弹幕调度。
Playwright Windows WebKit 不等于 macOS/iOS Safari,未声称连接 Chrome 或真机。
这些包专项和发布复盘门槛继续保留。
## 回退
回退 EX-01 提交可恢复包装/HTML/工具配置及根锁;也会恢复已复现的回调泄漏和
入口错误。无生产包版本/产物变更,无推送、发布。候选生产内容改变后重跑此消费
脚本;不要将本次 tarball 哈希当成未来 npm 候选的免检依据。
## 扩大检查发现的独立 lint 问题
本任务受影响的 TSX/脚本 lint 通过,但最后全仓 yarn lint 被未改动的
packages/artplayer-plugin-danmuku-mask/package.json 字段排序阻断;已逐字对照
HEAD 确认该 manifest 不属于本任务改动。另有旧编辑器声明的 unused-disable
警告。全仓 lint 未计为通过;新增 ENG-LINT-01 独立修复,仍接入最终完成门槛。
日志 refactor/.cache/react-root-lint.log。
+12 -5
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 237 项,范围 22 个包及工作区/示例。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 238 项,范围 22 个包及工作区/示例。
状态:todo 62 / doing 15 / blocked 0 / done 160 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 62 / doing 15 / blocked 0 / done 161 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -23,7 +23,7 @@
| artplayer-plugin-chapter | 1.1.0 | PILOT-01, PKG-CHAPTER-01, PKG-CHAPTER-02, PKG-CHAPTER-03, PKG-CHAPTER-04, PKG-CHAPTER-05, PKG-CHAPTER-06 |
| artplayer-plugin-chromecast | 1.1.0 | PKG-CAST-01, PKG-CAST-02, PKG-CAST-03, PKG-CAST-04, PKG-CAST-05, PKG-CAST-06 |
| artplayer-plugin-danmuku | 5.3.0 | PKG-DANMUKU-01, PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-06, PKG-DANMUKU-07, PKG-DANMUKU-08, PKG-DANMUKU-09, PKG-DANMUKU-10, PKG-DANMUKU-11, PKG-DANMUKU-12 |
| artplayer-plugin-danmuku-mask | 1.1.0 | PKG-MASK-01, PKG-MASK-02, PKG-MASK-03, PKG-MASK-04, PKG-MASK-05, PKG-MASK-06 |
| artplayer-plugin-danmuku-mask | 1.1.0 | PKG-MASK-01, PKG-MASK-02, PKG-MASK-03, PKG-MASK-04, PKG-MASK-05, PKG-MASK-06, ENG-LINT-01 |
| artplayer-plugin-dash-control | 1.1.0 | PKG-DASH-01, PKG-DASH-02, PKG-DASH-03, PKG-DASH-04, PKG-DASH-05, PKG-DASH-06 |
| artplayer-plugin-document-pip | 1.1.0 | PKG-DPIP-01, PKG-DPIP-02, PKG-DPIP-03, PKG-DPIP-04, PKG-DPIP-05, PKG-DPIP-06 |
| artplayer-plugin-hls-control | 1.1.0 | PKG-HLS-01, PKG-HLS-02, PKG-HLS-03, PKG-HLS-04, PKG-HLS-SDK-01, PKG-HLS-05, PKG-HLS-06 |
@@ -386,7 +386,7 @@
| SITE-04 | artplayer-vitepress<br>交叉核对逐包持续维护的文档 | CORE-21, SITE-03, PKG-CHAPTER-04, PKG-AMBILIGHT-04, PKG-AUDIO-04, PKG-AUTO-THUMB-04, PKG-VTT-THUMB-04, PKG-HLS-04, PKG-DASH-04, PKG-MULTI-SUB-04, PKG-JASSUB-04, PKG-MASK-04, PKG-ASR-04, PKG-ADS-04, PKG-VAST-04, PKG-CAST-04, PKG-DPIP-04, PKG-CANVAS-04, PKG-IFRAME-04, PKG-TOOL-THUMB-04, PKG-DANMUKU-06, PKG-MB-08 | 已随实现更新的中文/英文 API、包内实现地图、旧 JS 示例及已知能力限制的全包核对 | 未把缺环境的能力写成已验证,静态核对不等待设备任务;最终 demo 仍由 EX-03 验收 | M | todo |
| SITE-05 | artplayer-vitepress<br>构建文档站和验证链接/示例 | SITE-04, EX-01, EX-02 | VitePress 构建、链接与嵌入 demo 检查 | 文档构建、链接、嵌入路径与声明注入通过;真实完整 demo 保留 EX-03 独立门槛;核对 ENG-PM-01 登记的搜索 peer 范围和真实搜索行为 | M | todo |
| SITE-06 | artplayer-vitepress<br>文档站交付验收 | SITE-05 | 维护指南和站点变更记录 | 未手改 generated 目录,旧 URL 可用、部署与检查分离 | M | todo |
| EX-01 | example/react.js<br>验证 React 消费者与 TS | CORE-22, ENG-07 | React 挂载/卸载/重挂载、引用和插件样例 | 真实 tarball + TS 消费通过,保留既有 React 集成 API | M | todo |
| EX-01 | example/react.js, workspace<br>验证 React 消费者与 TS | CORE-22, ENG-07 | React 挂载/卸载/重挂载、引用和插件样例 | 真实 tarball + TS 消费通过,保留既有 React 集成 API | M | done |
| EX-02 | example/vue.js<br>验证 Vue 消费者与更新卸载 | CORE-22, ENG-07 | Vue 实例/ref、参数更新、卸载及插件样例 | 旧 JS 组件用法无需修改,重复挂载不泄漏 | M | todo |
| EX-03 | workspace<br>验证全部原生 demo 与外部播放集成 | EX-01, EX-02, PKG-CHAPTER-06, PKG-AMBILIGHT-06, PKG-AUDIO-06, PKG-AUTO-THUMB-06, PKG-VTT-THUMB-06, PKG-HLS-06, PKG-DASH-06, PKG-MULTI-SUB-06, PKG-JASSUB-06, PKG-MASK-06, PKG-ASR-06, PKG-ADS-06, PKG-VAST-06, PKG-CAST-06, PKG-DPIP-06, PKG-DANMUKU-09, PKG-CANVAS-06, PKG-MB-10, PKG-IFRAME-06, PKG-TOOL-THUMB-06 | 8082 全 demo、HLS/DASH/FLV/MPEGTS/WebTorrent 集成记录 | 旧 URL/参数/脚本加载保持;网络/SDK 限制明确,不静默跳过;覆盖编辑器重复 Run 和 ESM/i18n/mobile/iframe,记录实际候选脚本及状态隔离证据;依据 BASE-04 路径台账逐项消除 not-run,处理 BASE-DEMO-01 | H | todo |
@@ -412,7 +412,7 @@
| REL-04 | workspace<br>提前演练回退与主线修复同步 | REL-08, ENG-07 | 分包旧版本/tag/依赖回退方案与 master 差异 | 隔离包演练可回退,主线修复同步流程可执行;正式每批再核对其实际回退产物 | H | todo |
| REL-05 | workspace<br>经授权发布候选并收集反馈 | REL-03, REL-04, REVIEW-03 | 候选 tag、完整产物 integrity、反馈与复现记录 | 实际发布授权/操作/版本可追溯;本计划不自动执行发布 | H | todo |
| REL-06 | workspace<br>经授权分批正式发布 | REL-05 | 正式分包版本/tag/站点文档及兼容公告 | 候选验收通过,安装/浏览器复核和回退入口就绪;候选反馈修复须独立建任务并复验三轮受影响结论,正式内容与最终验证产物一致 | H | todo |
| REL-07 | workspace<br>关闭重构里程碑并维护后续队列 | REL-06 | 最终任务/设计/证据归档和维护指南 | 全范围任务有结论、无未解释兼容缺口;遗留项有明确后续责任 | L | todo |
| REL-07 | workspace<br>关闭重构里程碑并维护后续队列 | REL-06, ENG-LINT-01 | 最终任务/设计/证据归档和维护指南 | 全范围任务有结论、无未解释兼容缺口;遗留项有明确后续责任 | L | todo |
## 8.1 多轮复盘
@@ -434,6 +434,12 @@
| --- | --- | --- | --- | --- | --- | --- |
| ENG-12 | workspace, artplayer<br>隔离库构建的 public 源码与分发内容 | ENG-03, CORE-21 | 禁用库的隐式 public 复制,拒绝误入包内的 TS 源码,清理生成污染并验证真实构建 | 三格式真实构建和严格 tarball 检查通过;已有声明和所有剩余成员字节不变;文档站使用独立构建 | M | done |
## 2 工程基础
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
| --- | --- | --- | --- | --- | --- | --- |
| ENG-LINT-01 | artplayer-plugin-danmuku-mask, workspace<br>修复 Mask manifest 排序并恢复全仓 lint | PKG-MASK-04 | 仅调整 manifest 字段顺序,独立验证全仓只读 lint 与元数据语义不变 | 根 lint 通过;解析后的 manifest 深度相等;不改包版本、依赖或公开入口 | L | todo |
## 完成证据与阻塞
- DOC-01: [记录](progress.md)
@@ -598,6 +604,7 @@
- PKG-TOOL-THUMB-02: [记录](baselines/thumbnail-behavior-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md)
- PKG-TOOL-THUMB-03: [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-input-checkpoint.md) [记录](baselines/thumbnail-input-checkpoint.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-lifecycle.md) [记录](baselines/thumbnail-lifecycle-validation.json)
- PKG-TOOL-THUMB-04: [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md) [记录](baselines/thumbnail-runtime-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-public-types.md) [记录](baselines/thumbnail-public-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-emitter.md) [记录](baselines/thumbnail-emitter-validation.json)
- EX-01: [记录](changes/2026-09-14-EX-01-react-consumer.md) [记录](baselines/react-consumer-validation.json) [记录](scripts/react-consumer.mjs)
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md) [记录](type-compatibility-policy.md) [记录](baselines/factory-compatibility-validation.json) [记录](changes/2026-09-13-PKG-FACTORY-01-compatible-types.md)
- CORE-25: [记录](changes/2026-09-13-CORE-25-defaults-ssr.md) [记录](baselines/defaults-ssr-validation.json)
- ENG-12: [记录](changes/2026-09-13-ENG-12-library-public.md) [记录](baselines/library-public-validation.json)
+15
View File
@@ -1,5 +1,20 @@
# 进度与证据
## EX-01 React 实际消费者完成
修复 React HTML 错误入口,以及 getInstance 抛错时实例泄漏;保留 Partial<Option>、
可选回调、div 属性、引用变化重建和示例插件/语言覆盖规则。拆出示例配置模块,
补齐严格 TSX、维护说明和根 Yarn 命令。实际打包安装核心/Danmuku/Document PiP,
逐文件对照并 frozen 重装;类型不允许从工作区补齐。最终开发/生产 × 三引擎六组
各 12 类检查通过,包括真实播放/暂停/seek/像素、兄弟实例、卸载重挂载和异常
清理。固定旧包装控制复现 1 个残留实例。全仓 406 个生产 TS 文件检查、示例
构建/lint、严格工具链和 frozen 安装通过;47 项 CI 回归通过。GitHub 增加消费
检查和失败证据上传,远端运行未验证。见[变更](changes/2026-09-14-EX-01-react-consumer.md)
和[证据](baselines/react-consumer-validation.json)。另有全仓 lint 被未改动 Mask manifest 排序阻断,已登记独立 ENG-LINT-01。
任务现为 238 项:161 done、15 doing、62 todo;未关闭插件专项任务。
下一步推进已就绪的 Vue 消费者 EX-02;Auto Thumbnail 首帧与 VAST 兼容选择仍未关闭。
## PKG-AUTO-THUMB-03 渲染/等待诊断检查点(仍 doing)
新增可重跑的 `yarn probe:auto-thumbnail-rendering`,用固定实际 ESM/时间轴媒体
+2
View File
@@ -253,3 +253,5 @@
| JASSUB-HYBRID-01 | open / 已复现 | Late hybrid frames mutate the new offscreen canvas or throw; terminal track calls recreate transferred canvases | PKG-JASSUB-09, PKG-JASSUB-05 |
| JASSUB-FIREFOX-OFFSCREEN-01 | open / 已复现 | Windows Firefox offscreen readback and asynchronous ImageBitmap creation can stall through seek/layout, including a minimal Worker control | PKG-JASSUB-09, PKG-JASSUB-05 |
| AUTO-THUMB-CANVAS-01 | resolved / 已复现 | Private thumbnail canvas retains sheet dimensions after completion or cancellation while callbacks retain the element | PKG-AUTO-THUMB-07 |
| EX-REACT-LIFE-01 | resolved / 已复现 | React getInstance exception leaks the constructed player | EX-01 |
| EX-REACT-ENTRY-01 | resolved / 源码/产物事实 | React HTML references absent main.jsx instead of the existing TSX entry | EX-01 |
+42
View File
@@ -5694,6 +5694,48 @@
"test/browser/auto-thumbnail-lifecycle.spec.js"
],
"resolutionRationale": "Six controlled regressions fail before and pass after. The frozen main native complete control fails dimensions; candidate main/legacy native lifecycle checks reset the retained canvas and decode final JPEGs after reset. No public options, types, sample positions or caller URLs changed; WebKit early-frame pixels remain separately open."
},
{
"id": "EX-REACT-LIFE-01",
"title": "React getInstance exception leaks the constructed player",
"confirmation": "reproduced",
"status": "resolved",
"owners": [
"EX-01"
],
"evidence": [
"refactor/changes/2026-09-14-EX-01-react-consumer.md",
"refactor/baselines/react-consumer-validation.json"
],
"resolutionEvidence": [
"refactor/changes/2026-09-14-EX-01-react-consumer.md",
"refactor/baselines/react-consumer-validation.json",
"refactor/scripts/react-consumer.mjs"
],
"compatibleResolution": "The old wrapper leaves one active instance after the React error boundary; the candidate cleans each instance exactly once and propagates the original error in six real browser profiles.",
"closureCriteria": "Actual installed consumer and original App entry pass without changing the wrapper props, default configuration or effect dependency contract.",
"resolutionRationale": "The old wrapper leaves one active instance after the React error boundary; the candidate cleans each instance exactly once and propagates the original error in six real browser profiles."
},
{
"id": "EX-REACT-ENTRY-01",
"title": "React HTML references absent main.jsx instead of the existing TSX entry",
"confirmation": "source-observed",
"status": "resolved",
"owners": [
"EX-01"
],
"evidence": [
"refactor/changes/2026-09-14-EX-01-react-consumer.md",
"refactor/baselines/react-consumer-validation.json"
],
"resolutionEvidence": [
"refactor/changes/2026-09-14-EX-01-react-consumer.md",
"refactor/baselines/react-consumer-validation.json",
"refactor/scripts/react-consumer.mjs"
],
"compatibleResolution": "The HTML now loads main.tsx; the unchanged App call builds and loads controlled media in all six installed consumer browser profiles.",
"closureCriteria": "Actual installed consumer and original App entry pass without changing the wrapper props, default configuration or effect dependency contract.",
"resolutionRationale": "The HTML now loads main.tsx; the unchanged App call builds and loads controlled media in all six installed consumer browser profiles."
}
]
}
+3
View File
@@ -84,6 +84,9 @@ export function validateCIWorkflow(source) {
consumerIndex = index + 1
}
assert(browser.steps.findIndex(step => step.run?.startsWith('yarn test:browser ')) > consumerIndex, 'Browser tools must run after canonical Node restoration')
const reactIndex = browser.steps.findIndex(step => step.run === 'yarn test:react-consumer 2>&1 | tee refactor/.cache/ci/react-consumer.log')
assert(reactIndex > consumerIndex && !Object.hasOwn(browser.steps[reactIndex], 'if'), 'Run React installed consumers after restoring canonical Node')
assert(browser.steps.some(step => step.uses?.startsWith('actions/upload-artifact@') && step.if === 'always()' && step.with.path.split('\n').includes('refactor/.cache/react-consumer-*/')), 'Retain React consumer failure evidence')
const pages = workflow.jobs.checks.steps.find(step => step.uses?.startsWith('actions/upload-pages-artifact@'))
assert.equal(pages?.if, 'inputs.pages-artifact && github.ref == \'refs/heads/master\' && matrix.os == \'ubuntu-latest\'', 'Only one trusted matrix leg can prepare Pages')
return { jobs: requiredJobs, systems, summary: summary.name }
+3
View File
@@ -34,6 +34,9 @@ for (const [name, mutate] of [
['wrong consumer Node version', w => w.jobs['browser-smoke'].steps.find(s => s.id === 'consumer-node-20').with['node-version'] = '24'],
['skipped installed consumer', w => w.jobs['browser-smoke'].steps.find(s => s.run?.includes('--expected-node 22.12.0')).if = 'false'],
['browser runtime not restored', w => w.jobs['browser-smoke'].steps.find(s => s.id === 'restore-canonical-node').with = { 'node-version': '22.12.0', 'package-manager-cache': false }],
['skipped React consumer', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:react-consumer')).if = 'false'],
['removed React consumer', w => w.jobs['browser-smoke'].steps = w.jobs['browser-smoke'].steps.filter(s => !s.run?.startsWith('yarn test:react-consumer'))],
['missing React evidence', w => w.jobs['browser-smoke'].steps.find(s => s.uses?.startsWith('actions/upload-artifact@')).with.path = 'refactor/.cache/ci/'],
['Pages uploaded by both matrix legs', w => w.jobs.checks.steps.find(s => s.uses?.startsWith('actions/upload-pages-artifact@')).if = 'inputs.pages-artifact && github.ref == \'refs/heads/master\''],
]) {
test(`CI rejects ${name}`, () => {
+206
View File
@@ -0,0 +1,206 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { chromium, firefox, webkit } from '@playwright/test'
import react from '@vitejs/plugin-react'
import ts from 'typescript'
import { build, preview } from 'vite'
import { packedFiles } from '../../scripts/package-check.mjs'
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
import { hash, readMember } from './releases.mjs'
assert.equal(process.version.slice(1), fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim())
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
const before = process.argv.includes('--before')
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/react-consumer-'))
const consumer = consumerDirectory()
const report = { task: 'EX-01', before, node: process.version, source: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: workspace, encoding: 'utf8' }).trim(), packages: [], browsers: [], passed: false }
console.log(`React consumer evidence: ${output}`)
try {
const dependencies = {}
for (const name of ['artplayer', 'artplayer-plugin-danmuku', 'artplayer-plugin-document-pip']) {
const archive = path.join(output, `${name}.tgz`)
fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name)))
dependencies[name] = `file:${archive.replaceAll('\\', '/')}`
report.packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)) })
}
const rootManifest = JSON.parse(fs.readFileSync(path.join(workspace, 'package.json'), 'utf8'))
for (const name of ['react', 'react-dom', '@types/react', '@types/react-dom']) dependencies[name] = rootManifest.devDependencies[name]
writeJson(path.join(consumer, 'package.json'), { name: 'react-artplayer-isolated-consumer', private: true, type: 'module', dependencies })
fs.writeFileSync(path.join(output, 'install.log'), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, 'consumer-yarn.lock'), lock)
fs.writeFileSync(path.join(output, 'frozen.log'), run([yarn, 'install', '--offline', '--frozen-lockfile', '--force', '--ignore-scripts', '--non-interactive'], consumer))
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
for (const pkg of report.packages) {
for (const member of packedFiles(pkg.archive)) {
assert.deepEqual(fs.readFileSync(path.join(consumer, 'node_modules', pkg.name, member.replace(/^package\//, ''))), readMember(pkg.archive, member))
}
}
const example = path.join(workspace, 'example/react.js')
fs.cpSync(path.join(example, 'src'), path.join(consumer, 'src'), { recursive: true })
fs.copyFileSync(path.join(example, 'index.html'), path.join(consumer, 'index.html'))
fs.copyFileSync(path.join(example, 'tsconfig.json'), path.join(consumer, 'tsconfig.json'))
if (before) {
const old = execFileSync('git', ['show', '3e39fc745f4dc98eba678ca081e86c1d3cc965cd:example/react.js/src/Player.tsx'], { cwd: workspace })
fs.writeFileSync(path.join(consumer, 'src/Player.tsx'), old)
report.beforeWrapper = hash(old)
}
for (const file of ['lifecycle.tsx', 'consumer.tsx']) fs.copyFileSync(path.join(workspace, 'test/react', file), path.join(consumer, file))
fs.writeFileSync(path.join(consumer, 'harness.html'), '<!doctype html><html><body><div id="root"></div><script type="module" src="/lifecycle.tsx"></script></body></html>')
fs.mkdirSync(path.join(consumer, 'public'))
const media = fs.readFileSync(path.join(workspace, 'test/browser/media/pattern.mp4'))
fs.writeFileSync(path.join(consumer, 'public/pattern.mp4'), media)
fs.copyFileSync(path.join(example, 'public/vite.svg'), path.join(consumer, 'public/vite.svg'))
report.media = { file: 'test/browser/media/pattern.mp4', sha256: hash(media) }
report.inputs = Object.fromEntries(['src/Player.tsx', 'src/player-options.ts', 'src/App.tsx', 'src/main.tsx', 'index.html', 'lifecycle.tsx', 'consumer.tsx'].map(file => [file, hash(fs.readFileSync(path.join(consumer, file)))]))
const config = ts.readConfigFile(path.join(consumer, 'tsconfig.json'), ts.sys.readFile)
assert.equal(config.error, undefined)
const parsed = ts.parseJsonConfigFileContent({ ...config.config, include: ['src', 'consumer.tsx', 'lifecycle.tsx'] }, ts.sys, consumer)
assert.deepEqual(parsed.errors, [])
const host = ts.createCompilerHost(parsed.options)
host.getCurrentDirectory = () => consumer
const program = ts.createProgram(parsed.fileNames, parsed.options, host)
const diagnostics = ts.getPreEmitDiagnostics(program).map(d => ({ code: d.code, file: d.file?.fileName, text: ts.flattenDiagnosticMessageText(d.messageText, '\n') }))
report.types = { version: ts.version, diagnostics }
assert.deepEqual(diagnostics, [])
const library = fs.realpathSync(path.dirname(ts.sys.getExecutingFilePath()))
for (const file of program.getSourceFiles()) {
const actual = fs.realpathSync(file.fileName)
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (path.dirname(actual) === library && /^lib\..*\.d\.ts$/.test(path.basename(actual))), `Types escaped consumer: ${actual}`)
}
for (const mode of ['development', 'production']) {
const outDir = path.join(output, mode)
await build({ root: consumer, configFile: false, plugins: [react()], mode, define: { 'process.env.NODE_ENV': JSON.stringify(mode) }, build: { outDir, emptyOutDir: false, rollupOptions: { input: { app: path.join(consumer, 'index.html'), harness: path.join(consumer, 'harness.html') } } } })
const server = await preview({ root: consumer, configFile: false, build: { outDir }, preview: { host: '127.0.0.1', port: 0, open: false } })
const address = server.httpServer.address()
const base = `http://127.0.0.1:${address.port}`
try {
for (const [engine, type] of Object.entries({ chromium, firefox, webkit })) {
const browser = await type.launch()
const row = { mode, engine, version: browser.version(), checks: [], passed: false }
report.browsers.push(row)
const page = await browser.newPage()
const unexpected = []
page.on('pageerror', error => unexpected.push(error.message))
await page.route('https://artplayer.org/assets/sample/danmuku.xml', route => route.fulfill({ contentType: 'text/xml', body: '<i><d p="0,1,25,16777215,0,0,0,0">React lifecycle sample</d></i>' }))
await page.route('https://artplayer.org/assets/sample/video.mp4', route => route.fulfill({ contentType: 'video/mp4', body: media }))
const snapshot = () => page.evaluate(() => window.probe.snapshot())
const wait = async (count) => {
await page.waitForFunction(count => window.probe?.snapshot().active === count, count)
return snapshot()
}
try {
await page.goto(`${base}/harness.html`)
let state = await wait(1)
assert.equal(state.created, mode === 'development' ? 2 : 1)
assert.equal(state.players, 1)
assert.equal(await page.locator('#player').getAttribute('data-consumer'), 'preserved')
await page.waitForFunction(() => window.probe.current().plugins.artplayerPluginDanmuku && window.probe.current().plugins.artplayerPluginDocumentPip)
row.checks.push('strict-mode-and-plugin-registration')
const original = state.created
await page.evaluate(() => window.probe.render())
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))))
assert.equal((await snapshot()).created, original)
row.checks.push('stable-props-retain-instance')
for (const mode of ['callback', 'option']) {
const prior = (await snapshot()).created
await page.evaluate(mode => window.probe.render(mode), mode)
await page.waitForFunction(prior => window.probe.snapshot().created === prior + 1, prior)
state = await wait(1)
assert(state.records.slice(0, -1).every(record => record.isDestroy && record.destroyed === 1))
assert.equal(state.players, 1)
row.checks.push(`${mode}-change-cleans-old-instance`)
}
await page.waitForFunction(() => window.probe.current().duration > 1)
await page.evaluate(() => window.probe.current().play())
await page.waitForFunction(() => window.probe.current().currentTime > 0.15)
row.media = await page.evaluate(async () => {
const art = window.probe.current()
art.pause()
const time = art.currentTime
const seeked = new Promise(resolve => art.video.addEventListener('seeked', resolve, { once: true }))
art.currentTime = 1
await seeked
const canvas = document.createElement('canvas')
canvas.width = 8
canvas.height = 8
const context = canvas.getContext('2d')
context.drawImage(art.video, 0, 0, 8, 8)
return { time, paused: art.playing === false, seek: art.currentTime, width: art.video.videoWidth, rgba: Array.from(context.getImageData(0, 0, 8, 8).data) }
})
assert(row.media.time > 0.15 && row.media.paused && Math.abs(row.media.seek - 1) < 0.1 && row.media.width > 0)
assert(row.media.rgba.some((value, index) => index % 4 !== 3 && value > 50))
row.checks.push('native-play-pause-seek-and-decoded-pixels')
await page.evaluate(() => window.probe.render('siblings'))
await wait(2)
const retainedId = await page.evaluate(() => window.probe.current().id)
await page.evaluate(() => window.probe.render())
state = await wait(1)
assert.equal(await page.evaluate(() => window.probe.current().id), retainedId)
assert.equal(state.players, 1)
row.checks.push('sibling-unmount-preserves-other-instance')
await page.evaluate(() => window.probe.render('optional'))
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))))
await wait(1)
row.checks.push('optional-getInstance')
await page.evaluate(() => window.probe.render('empty'))
state = await wait(0)
assert.equal(state.players, 0)
assert.equal(state.videos, 0)
assert(state.records.every(record => record.isDestroy && record.destroyed === 1))
row.checks.push('unmount-cleans-recorded-instances-and-dom')
await page.evaluate(() => window.probe.render())
await wait(1)
row.checks.push('remount-after-empty')
await page.evaluate(() => window.probe.render('throw'))
await page.locator('#caught').waitFor()
state = await snapshot()
row.callbackFailure = state
assert.equal(state.active, 0, 'Throwing getInstance leaked a player')
assert(state.records.every(record => record.isDestroy && record.destroyed === 1))
assert.equal(state.errors.length, mode === 'development' ? 2 : 1)
assert(state.errors.every(error => error.same && error.message === 'react-consumer-callback-failure'))
row.checks.push('callback-exception-cleanup-and-original-error')
await page.evaluate(() => window.probe.render('throw-cleanup'))
await page.waitForFunction(count => window.probe.snapshot().errors.length === count, mode === 'development' ? 4 : 2)
state = await snapshot()
assert.equal(state.active, 0)
assert.equal(state.players, 0)
assert(state.records.every(record => record.isDestroy && record.destroyed === 1))
assert(state.errors.every(error => error.same && error.message === 'react-consumer-callback-failure'))
row.checks.push('cleanup-error-preserves-primary-callback-error')
assert.deepEqual(unexpected, [])
await page.goto(base)
await page.locator('.art-video-player').waitFor()
await page.waitForFunction(() => document.querySelector('video')?.readyState >= 2)
row.checks.push('original-app-entry-loads-media')
assert.deepEqual(unexpected, [])
row.passed = true
}
catch (error) {
row.error = error.stack
await page.screenshot({ path: path.join(output, `${mode}-${engine}-failure.png`) })
throw error
}
finally {
await browser.close()
writeJson(path.join(output, 'report.json'), report)
}
}
}
finally {
await new Promise(resolve => server.httpServer.close(resolve))
}
}
report.passed = true
}
finally {
writeJson(path.join(output, 'report.json'), report)
removeConsumer(consumer)
}
console.log('React installed consumer types and six native browser profiles passed.')
+27 -4
View File
@@ -4206,17 +4206,22 @@
"phase": "6 文档与消费者",
"title": "验证 React 消费者与 TS",
"scope": [
"example/react.js"
"example/react.js",
"workspace"
],
"dependsOn": [
"CORE-22",
"ENG-07"
],
"status": "todo",
"status": "done",
"risk": "M",
"deliverable": "React 挂载/卸载/重挂载、引用和插件样例",
"acceptance": "真实 tarball + TS 消费通过,保留既有 React 集成 API",
"evidence": []
"evidence": [
"changes/2026-09-14-EX-01-react-consumer.md",
"baselines/react-consumer-validation.json",
"scripts/react-consumer.mjs"
]
},
{
"id": "EX-02",
@@ -4588,7 +4593,8 @@
"workspace"
],
"dependsOn": [
"REL-06"
"REL-06",
"ENG-LINT-01"
],
"status": "todo",
"risk": "L",
@@ -4884,6 +4890,23 @@
"changes/2026-09-14-PKG-AUTO-THUMB-09-alias.md",
"baselines/auto-thumbnail-alias-validation.json"
]
},
{
"id": "ENG-LINT-01",
"phase": "2 工程基础",
"title": "修复 Mask manifest 排序并恢复全仓 lint",
"scope": [
"artplayer-plugin-danmuku-mask",
"workspace"
],
"dependsOn": [
"PKG-MASK-04"
],
"status": "todo",
"risk": "L",
"deliverable": "仅调整 manifest 字段顺序,独立验证全仓只读 lint 与元数据语义不变",
"acceptance": "根 lint 通过;解析后的 manifest 深度相等;不改包版本、依赖或公开入口",
"evidence": []
}
]
}
+8
View File
@@ -2,6 +2,14 @@
Use the pinned Node/Yarn toolchain from `../refactor/toolchain-setup.md`.
`yarn test:react-consumer` installs packed core/Danmuku/Document PiP outside the
workspace and checks the actual React example's strict TSX, development and
production builds, and native lifecycle/playback in three engines. Its fixture
is in `test/react/`; unique reports and frozen consumer locks are retained under
`refactor/.cache/react-consumer-*`. The `--before` control intentionally fails at
the recorded old wrapper's callback-exception leak. See the example README for
the compatibility contract and the capabilities covered by other package tasks.
`yarn test:auto-thumbnail-types` checks the preserved npm 1.1.0 declaration,
accurate `/runtime` Promise types, exact rejected consumer lines, and generated
editor globals. It also runs in `test:auto-thumbnail` and `test:baseline`.
+21
View File
@@ -0,0 +1,21 @@
import type Artplayer from 'artplayer'
import type { Option } from 'artplayer'
import type { ComponentProps } from 'react'
import Player from './src/Player'
const option: Partial<Option> = { url: '/pattern.mp4' }
const props: ComponentProps<typeof Player> = {
option,
getInstance(art: Artplayer) { art.pause() },
style: { width: 640 },
className: 'consumer-container',
onClick(event) { event.currentTarget.dataset.clicked = 'yes' },
}
export const valid = <Player {...props} />
export const optional = <Player option={{}} />
// @ts-expect-error The historical callback receives a player, not a media element.
export const wrongCallback = <Player option={option} getInstance={(video: HTMLVideoElement) => video.pause()} />
// @ts-expect-error Native div attributes retain their original types.
export const wrongStyle = <Player option={option} style="width: 640px" />
// @ts-expect-error Player options still enforce field types.
export const wrongOption = <Player option={{ url: 123 }} />
+79
View File
@@ -0,0 +1,79 @@
import type { ReactNode } from 'react'
import Artplayer from 'artplayer'
import { Component, StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import Player from './src/Player'
const records: { art: Artplayer, destroyed: number }[] = []
const errors: { message: string, same: boolean }[] = []
const container = document.getElementById('root')
if (!container)
throw new Error('Missing React fixture container')
const root = createRoot(container)
const option = { url: '/pattern.mp4', muted: true }
const changedOption = { ...option, volume: 0.2 }
const failure = new Error('react-consumer-callback-failure')
function capture(art: Artplayer) {
const record = { art, destroyed: 0 }
records.push(record)
art.on('destroy', () => record.destroyed++)
}
function changedCapture(art: Artplayer) {
capture(art)
}
function fail(art: Artplayer) {
capture(art)
throw failure
}
function failWithCleanupError(art: Artplayer) {
const destroy = art.destroy.bind(art)
art.destroy = (...args) => {
destroy(...args)
throw new Error('secondary-cleanup-error')
}
fail(art)
}
class Boundary extends Component<{ children: ReactNode }, { failed: boolean }> {
state = { failed: false }
static getDerivedStateFromError() { return { failed: true } }
componentDidCatch(error: Error) { errors.push({ message: error.message, same: error === failure }) }
render() { return this.state.failed ? <div id="caught">caught</div> : this.props.children }
}
function render(mode = 'normal') {
root.render(
<StrictMode>
<Boundary key={mode.startsWith('throw') ? mode : 'healthy'}>
{mode !== 'empty' && (
<Player
key="primary"
id="player"
className="consumer-container"
data-consumer="preserved"
style={{ width: 640, height: 360 }}
option={mode === 'option' ? changedOption : option}
getInstance={mode === 'throw-cleanup' ? failWithCleanupError : mode === 'throw' ? fail : mode === 'callback' ? changedCapture : mode === 'optional' ? undefined : capture}
/>
)}
{mode === 'siblings' && <Player key="sibling" id="sibling" style={{ width: 320, height: 180 }} option={option} getInstance={capture} />}
</Boundary>
</StrictMode>,
)
}
const probe = {
render,
snapshot: () => ({
active: Artplayer.instances.length,
created: records.length,
records: records.map(({ art, destroyed }) => ({ id: art.id, isDestroy: art.isDestroy, destroyed })),
plugins: Artplayer.instances.map(art => Object.keys(art.plugins)),
errors,
videos: document.querySelectorAll('video').length,
players: document.querySelectorAll('.art-video-player').length,
}),
current: () => Artplayer.instances[0],
}
declare global {
interface Window { probe: typeof probe }
}
window.probe = probe
render()
+317 -6
View File
@@ -219,16 +219,130 @@
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/code-frame@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7"
integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==
dependencies:
"@babel/helper-validator-identifier" "^7.29.7"
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/compat-data@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629"
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
"@babel/core@^7.28.0":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7"
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/generator" "^7.29.7"
"@babel/helper-compilation-targets" "^7.29.7"
"@babel/helper-module-transforms" "^7.29.7"
"@babel/helpers" "^7.29.7"
"@babel/parser" "^7.29.7"
"@babel/template" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@jridgewell/remapping" "^2.3.5"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/generator@^7.29.7", "@babel/generator@^7.29.8":
version "7.29.8"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.8.tgz#4b0b887885422643339e09022148a4c4ebaa4979"
integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==
dependencies:
"@babel/parser" "^7.29.8"
"@babel/types" "^7.29.8"
"@jridgewell/gen-mapping" "^0.3.12"
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
"@babel/helper-compilation-targets@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042"
integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==
dependencies:
"@babel/compat-data" "^7.29.7"
"@babel/helper-validator-option" "^7.29.7"
browserslist "^4.24.0"
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-globals@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b"
integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==
"@babel/helper-module-imports@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396"
integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==
dependencies:
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-module-transforms@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae"
integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==
dependencies:
"@babel/helper-module-imports" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/helper-plugin-utils@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz#c0a0766f1a13617d8a17407d7ab8f9d486225ea4"
integrity sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==
"@babel/helper-string-parser@^7.27.1":
version "7.27.1"
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz"
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
"@babel/helper-string-parser@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f"
integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5":
version "7.28.5"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz"
integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
"@babel/helper-validator-identifier@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
"@babel/helper-validator-option@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
"@babel/helpers@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607"
integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==
dependencies:
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.29.7", "@babel/parser@^7.29.8":
version "7.29.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.8.tgz#9653716a2f10c677b98fbc63d4bfb000c302cf17"
integrity sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==
dependencies:
"@babel/types" "^7.29.8"
"@babel/parser@^7.25.3", "@babel/parser@^7.29.0":
version "7.29.0"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz"
@@ -236,6 +350,50 @@
dependencies:
"@babel/types" "^7.29.0"
"@babel/plugin-transform-react-jsx-self@^7.27.1":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz#c24424527858220624fd59a5b1eab4fa413c803a"
integrity sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-transform-react-jsx-source@^7.27.1":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz#5cf25a3689906b58e2f0a2f2b374789e6627b15f"
integrity sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/template@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700"
integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/traverse@^7.29.7":
version "7.29.8"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.8.tgz#4111014cdc71a0f95d9471907590baa0b8a6b28a"
integrity sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/generator" "^7.29.8"
"@babel/helper-globals" "^7.29.7"
"@babel/parser" "^7.29.8"
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.8"
debug "^4.3.1"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.29.7", "@babel/types@^7.29.8":
version "7.29.8"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863"
integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==
dependencies:
"@babel/helper-string-parser" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/types@^7.29.0":
version "7.29.0"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz"
@@ -793,7 +951,7 @@
dependencies:
"@sinclair/typebox" "^0.27.8"
"@jridgewell/gen-mapping@^0.3.5":
"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
version "0.3.13"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
@@ -801,6 +959,14 @@
"@jridgewell/sourcemap-codec" "^1.5.0"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/remapping@^2.3.5":
version "2.3.5"
resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1"
integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
@@ -819,7 +985,7 @@
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz"
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
@@ -1383,6 +1549,11 @@
dependencies:
playwright "1.63.0"
"@rolldown/pluginutils@1.0.0-beta.30":
version "1.0.0-beta.30"
resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.30.tgz#ac55a2215c78a54ba67276524ac3dc8c195316d0"
integrity sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw==
"@rollup/rollup-android-arm-eabi@4.57.1":
version "4.57.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz#add5e608d4e7be55bc3ca3d962490b8b1890e088"
@@ -1703,6 +1874,39 @@
dependencies:
tslib "^2.4.0"
"@types/babel__core@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
dependencies:
"@babel/parser" "^7.20.7"
"@babel/types" "^7.20.7"
"@types/babel__generator" "*"
"@types/babel__template" "*"
"@types/babel__traverse" "*"
"@types/babel__generator@*":
version "7.27.0"
resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9"
integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==
dependencies:
"@babel/types" "^7.0.0"
"@types/babel__template@*":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f"
integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==
dependencies:
"@babel/parser" "^7.1.0"
"@babel/types" "^7.0.0"
"@types/babel__traverse@*":
version "7.28.0"
resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74"
integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==
dependencies:
"@babel/types" "^7.28.2"
"@types/debug@^4.0.0":
version "4.1.12"
resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz"
@@ -1819,6 +2023,18 @@
resolved "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz"
integrity sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==
"@types/react-dom@19.1.7":
version "19.1.7"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.7.tgz#2863f2aa89e023592b981204ef92c5221b286410"
integrity sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==
"@types/react@19.1.10":
version "19.1.10"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.10.tgz#a05015952ef328e1b85579c839a71304b07d21d9"
integrity sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==
dependencies:
csstype "^3.0.2"
"@types/seedrandom@^2.4.28":
version "2.4.34"
resolved "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz"
@@ -1935,6 +2151,18 @@
resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
"@vitejs/plugin-react@5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.0.0.tgz#5cfe3d3665d3d04b0df7a8356ffd59359f5d0220"
integrity sha512-Jx9JfsTa05bYkS9xo0hkofp2dCmp1blrKjw9JONs5BTHOvJCgLbaPSuZLGSVJW6u2qe0tc4eevY0+gSNNi0YCw==
dependencies:
"@babel/core" "^7.28.0"
"@babel/plugin-transform-react-jsx-self" "^7.27.1"
"@babel/plugin-transform-react-jsx-source" "^7.27.1"
"@rolldown/pluginutils" "1.0.0-beta.30"
"@types/babel__core" "^7.20.5"
react-refresh "^0.17.0"
"@vitejs/plugin-vue@^5.2.1":
version "5.2.4"
resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz"
@@ -2392,6 +2620,11 @@ base64-js@^1.3.1:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
baseline-browser-mapping@^2.11.20:
version "2.11.23"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.23.tgz#304c980a35de0f460cf12985359d6e11494c1ab4"
integrity sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==
baseline-browser-mapping@^2.9.0:
version "2.9.19"
resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz"
@@ -2465,6 +2698,17 @@ braces@^3.0.3:
dependencies:
fill-range "^7.1.1"
browserslist@^4.24.0:
version "4.28.9"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.9.tgz#07ce6b449b90af880eb9bfb7cd39372cc4f71c8c"
integrity sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==
dependencies:
baseline-browser-mapping "^2.11.20"
caniuse-lite "^1.0.30001810"
electron-to-chromium "^1.5.420"
node-releases "^2.0.54"
update-browserslist-db "^1.3.2"
browserslist@^4.28.1:
version "4.28.1"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz"
@@ -2571,6 +2815,11 @@ caniuse-lite@^1.0.30001759:
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz"
integrity sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==
caniuse-lite@^1.0.30001810:
version "1.0.30001810"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz#4970b477dea3278374de9bc43aa8f5d39fc3cda2"
integrity sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==
ccount@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz"
@@ -3028,7 +3277,7 @@ cssom@^0.5.0:
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==
csstype@^3.1.3, csstype@^3.2.3:
csstype@^3.0.2, csstype@^3.1.3, csstype@^3.2.3:
version "3.2.3"
resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz"
integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==
@@ -3043,7 +3292,7 @@ dateformat@^3.0.3:
resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
debug@4, debug@^4.0.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3:
debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3:
version "4.4.3"
resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
@@ -3252,6 +3501,11 @@ electron-to-chromium@^1.5.263:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz"
integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==
electron-to-chromium@^1.5.420:
version "1.5.427"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz#f8693d109cf116d4a6efa98bc620b9fc7f265f67"
integrity sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==
emoji-regex-xs@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz"
@@ -4076,6 +4330,11 @@ function-bind@^1.1.2:
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
@@ -4831,7 +5090,7 @@ jsdoc-type-pratt-parser@~7.0.0:
resolved "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.0.0.tgz"
integrity sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA==
jsesc@^3.1.0:
jsesc@^3.0.2, jsesc@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz"
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
@@ -4881,7 +5140,7 @@ json-stringify-safe@^5.0.1:
resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
json5@^2.2.2:
json5@^2.2.2, json5@^2.2.3:
version "2.2.3"
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
@@ -5208,6 +5467,13 @@ lru-cache@^11.0.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.5.2.tgz#00e16665c90c620fba14a3c368732a976493f760"
integrity sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
dependencies:
yallist "^3.0.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"
@@ -6087,6 +6353,11 @@ node-releases@^2.0.27:
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz"
integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==
node-releases@^2.0.54:
version "2.0.55"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.55.tgz#c52faedb68001dcfe77495b5ed1ac5827a1788fc"
integrity sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==
nopt@^7.0.0, nopt@^7.2.1:
version "7.2.1"
resolved "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz"
@@ -6913,11 +7184,28 @@ quick-lru@^4.0.1:
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
react-dom@19.1.1:
version "19.1.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.1.tgz#2daa9ff7f3ae384aeb30e76d5ee38c046dc89893"
integrity sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==
dependencies:
scheduler "^0.26.0"
react-is@^18.0.0:
version "18.3.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react-refresh@^0.17.0:
version "0.17.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53"
integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==
react@19.1.1:
version "19.1.1"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.1.tgz#06d9149ec5e083a67f9a1e39ce97b06a03b644af"
integrity sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==
read-cmd-shim@4.0.0, read-cmd-shim@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz"
@@ -7201,6 +7489,11 @@ sax@1.6.1, sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.6.1.tgz#4c23cf608c0b693ab54b4b5888e92cfe977b9843"
integrity sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==
scheduler@^0.26.0:
version "0.26.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337"
integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==
scslre@^0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz"
@@ -7225,6 +7518,11 @@ semver@7.7.4, semver@^7.0.0, semver@^7.1.1, semver@^7.3.4, semver@^7.3.5, semver
resolved "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz"
integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
semver@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
servor@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/servor/-/servor-4.0.2.tgz#6d5e5c61fbb3d6f4bde72995dbe6745829395971"
@@ -7924,6 +8222,14 @@ update-browserslist-db@^1.2.0:
escalade "^3.2.0"
picocolors "^1.1.1"
update-browserslist-db@^1.3.2:
version "1.3.3"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz#197e21fb2561fa89f8b94fad605a2b296a18993a"
integrity sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==
dependencies:
escalade "^3.2.0"
picocolors "^1.1.1"
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
@@ -8231,6 +8537,11 @@ y18n@^5.0.5:
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"