diff --git a/.gitignore b/.gitignore
index 676d6bda..e7e4ce2c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,9 @@
!/qiming-dev-inject/
!/qiming-mcp-proxy/
!/qiming-noVNC/
+!/qiming-run_code_rmcp/
+!/qiming-vite-plugin-design-mode/
+!/qiming-xagi-frontend-templates/
!/qimingclaw/
!/qimingcode/
diff --git a/qiming-run_code_rmcp/.cursor/rules/code-execution.mdc b/qiming-run_code_rmcp/.cursor/rules/code-execution.mdc
new file mode 100644
index 00000000..6771acee
--- /dev/null
+++ b/qiming-run_code_rmcp/.cursor/rules/code-execution.mdc
@@ -0,0 +1,109 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# 代码执行流程
+
+本项目的核心功能是执行JavaScript、TypeScript和Python代码,并捕获执行结果。以下是代码执行的主要流程:
+
+## 代码执行模型
+
+项目使用模块化的代码执行模型,定义在[src/model/code_run_model.rs](mdc:src/model/code_run_model.rs)中:
+
+```rust
+/// 执行结果,包含js/python 执行结果,和打印的log日志
+#[derive(Debug, Serialize, Deserialize)]
+pub struct CodeScriptExecutionResult {
+ //js/python 执行结果
+ pub result: Option,
+ //js/python 打印的log日志
+ pub logs: Vec,
+ // 是否执行成功,ture:默认值,执行成功
+ #[serde(skip_serializing)]
+ pub success: bool,
+ //如果执行错误的话,错误信息
+ #[serde(skip_serializing)]
+ pub error: Option,
+}
+
+/// 代码执行器
+pub struct CodeExecutor;
+
+impl CodeExecutor {
+ /// 执行代码
+ pub async fn execute(
+ code: &str,
+ language: LanguageScript,
+ ) -> Result {
+ Self::execute_with_params(code, language, None).await
+ }
+
+ /// 执行代码并传递参数
+ pub async fn execute_with_params(
+ code: &str,
+ language: LanguageScript,
+ params: Option,
+ ) -> Result {
+ match language {
+ LanguageScript::Js => JsRunner::new().run_with_params(code, params).await,
+ LanguageScript::Ts => TsRunner::new().run_with_params(code, params).await,
+ LanguageScript::Python => PythonRunner::new().run_with_params(code, params).await,
+ }
+ }
+}
+```
+
+## 语言特定执行器
+
+### JavaScript执行器
+
+JavaScript代码通过[JsRunner](mdc:src/deno_runner/js_runner.rs)执行:
+
+1. 准备JavaScript代码,添加日志捕获和handler函数执行逻辑
+2. 使用代码哈希值缓存处理过的代码
+3. 使用Deno执行代码
+4. 解析执行结果
+
+### TypeScript执行器
+
+TypeScript代码通过[TsRunner](mdc:src/deno_runner/ts_runner.rs)执行:
+
+1. 准备TypeScript代码,添加日志捕获和handler函数执行逻辑
+2. 使用代码哈希值缓存处理过的代码
+3. 使用Deno执行代码
+4. 解析执行结果
+
+### Python执行器
+
+Python代码通过[PythonRunner](mdc:src/python_runner/python_runner.rs)执行:
+
+1. 分析Python代码的依赖
+2. 准备Python代码,添加日志捕获和handler/main函数执行逻辑
+3. 使用代码哈希值缓存处理过的代码
+4. 使用uv在隔离环境中执行Python代码
+5. 解析执行结果
+
+## 参数传递
+
+参数通过JSON格式传递给脚本:
+
+1. JavaScript/TypeScript:参数直接传递给handler函数
+2. Python:参数通过环境变量`INPUT_JSON`传递,然后在脚本中解析并传递给handler或main函数
+
+## 结果处理
+
+执行结果通过[CodeScriptExecutionResult](mdc:src/model/code_run_model.rs)结构体表示,包含:
+
+- result: 执行结果(通过handler/main函数返回)
+- logs: 执行过程中的日志
+- success: 执行是否成功
+- error: 执行错误(如果有)
+
+## 缓存机制
+
+为了提高性能,项目使用[CodeFileCache](mdc:src/cache)缓存处理过的代码:
+
+1. 计算代码的哈希值
+2. 检查是否存在对应的缓存
+3. 如果存在,直接使用缓存;否则处理代码并缓存结果
diff --git a/qiming-run_code_rmcp/.cursor/rules/dependencies.mdc b/qiming-run_code_rmcp/.cursor/rules/dependencies.mdc
new file mode 100644
index 00000000..2a94ea5c
--- /dev/null
+++ b/qiming-run_code_rmcp/.cursor/rules/dependencies.mdc
@@ -0,0 +1,72 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# 项目依赖
+
+本项目使用多个Rust库来实现其功能。以下是主要依赖项及其用途:
+
+## Cargo.toml
+
+```toml
+[dependencies]
+tokio = { version = "1.32", features = ["full"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+log = "0.4"
+env_logger = "0.10"
+anyhow = "1.0"
+tempfile = "3.8"
+regex = "1.9"
+clap = { version = "4.4", features = ["derive"] }
+thiserror = "1.0"
+```
+
+## 主要依赖项
+
+### 异步运行时
+
+- [tokio](https://crates.io/crates/tokio): 异步运行时,用于支持异步/await语法和并发执行
+
+### 序列化/反序列化
+
+- [serde](https://crates.io/crates/serde): 通用序列化/反序列化框架
+- [serde_json](https://crates.io/crates/serde_json): JSON序列化/反序列化支持
+
+### 错误处理
+
+- [anyhow](https://crates.io/crates/anyhow): 简化错误处理的库
+- [thiserror](https://crates.io/crates/thiserror): 用于定义自定义错误类型
+
+### 日志记录
+
+- [log](https://crates.io/crates/log): 日志记录接口
+- [env_logger](https://crates.io/crates/env_logger): 基于环境变量的日志记录器
+
+### 命令行参数解析
+
+- [clap](https://crates.io/crates/clap): 命令行参数解析库,支持派生宏
+
+### 其他工具
+
+- [rmcp](https://crates.io/crates/rmcp): Model Context Protocol (MCP) SDK
+- [tempfile](https://crates.io/crates/tempfile): 临时文件创建和管理
+- [regex](https://crates.io/crates/regex): 正则表达式支持
+
+## 外部依赖
+
+除了Rust库外,本项目还依赖以下外部工具:
+
+1. [Deno](https://deno.land/): 现代JavaScript和TypeScript运行时,用于执行JavaScript代码
+2. [uv](https://github.com/astral-sh/uv): Python包管理器和隔离环境工具,用于安全执行Python代码
+
+## 安装外部依赖
+
+```bash
+# 安装Deno
+curl -fsSL https://deno.land/x/install/install.sh | sh
+
+# 安装uv
+curl -fsSL https://astral.sh/uv/install.sh | sh
+```
diff --git a/qiming-run_code_rmcp/.cursor/rules/error-handling.mdc b/qiming-run_code_rmcp/.cursor/rules/error-handling.mdc
new file mode 100644
index 00000000..73812684
--- /dev/null
+++ b/qiming-run_code_rmcp/.cursor/rules/error-handling.mdc
@@ -0,0 +1,101 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# 错误处理
+
+本项目使用[anyhow](mdc:https:/crates.io/crates/anyhow)和[thiserror](mdc:https:/crates.io/crates/thiserror)库进行错误处理,错误类型定义在[error.rs](mdc:src/error.rs)中。
+
+## 错误类型
+
+```rust
+#[derive(Error, Debug)]
+pub enum AppError {
+ #[error("Failed to execute code: {0}")]
+ ExecutionError(String),
+
+ #[error("Failed to parse output: {0}")]
+ ParseError(String),
+
+ #[error("Failed to create temporary file: {0}")]
+ IoError(#[from] std::io::Error),
+
+ #[error("Failed to parse JSON: {0}")]
+ JsonError(#[from] serde_json::Error),
+
+ #[error("Missing required field: {0}")]
+ MissingField(String),
+
+ #[error("Unsupported language: {0}")]
+ UnsupportedLanguage(String),
+
+ #[error("Handler function not found")]
+ HandlerNotFound,
+
+ #[error("MCP error: {0}")]
+ McpError(String),
+
+ #[error("{0}")]
+ Other(String),
+}
+
+pub type AppResult = Result;
+```
+
+## 错误处理流程
+
+1. 在代码执行过程中使用`anyhow::Result`类型传递错误
+2. 使用`?`操作符传播错误
+3. 使用`context`方法添加错误上下文
+4. 在`main`函数中处理所有未捕获的错误
+
+## 示例
+
+```rust
+// 读取代码文件
+fn get_code(args: &CodeArgs) -> Result {
+ if let Some(file) = &args.file {
+ fs::read_to_string(file).context("Failed to read code file")
+ } else if let Some(code) = &args.code {
+ Ok(code.clone())
+ } else {
+ anyhow::bail!("Either file or code must be provided")
+ }
+}
+
+// 执行JavaScript代码
+fn execute_js(code: &str, show_logs: bool) -> Result {
+ // Prepare the JavaScript code with wrapped handler
+ let wrapped_code = prepare_js_code(code, show_logs);
+
+ // Create a temporary file
+ let mut temp_file = NamedTempFile::new()?;
+ write!(temp_file.as_file_mut(), "{}", wrapped_code)?;
+
+ // Execute with Deno
+ let output = Command::new("deno")
+ .arg("run")
+ .arg("--allow-all")
+ .arg(temp_file.path())
+ .output()
+ .context("Failed to execute Deno")?;
+
+ // Parse the output
+ parse_execution_output(&output.stdout, &output.stderr)
+}
+```
+
+## 错误输出
+
+执行结果中的错误通过`ExecutionResult`结构体的`error`字段返回,并在`print_result`函数中输出:
+
+```rust
+fn print_result(result: ExecutionResult) {
+ // ...
+
+ if let Some(error) = result.error {
+ eprintln!("Error: {}", error);
+ }
+}
+```
diff --git a/qiming-run_code_rmcp/.cursor/rules/examples.mdc b/qiming-run_code_rmcp/.cursor/rules/examples.mdc
new file mode 100644
index 00000000..9e8a1966
--- /dev/null
+++ b/qiming-run_code_rmcp/.cursor/rules/examples.mdc
@@ -0,0 +1,138 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# 示例代码
+
+本项目包含JavaScript和Python的示例代码,用于测试代码执行功能。
+
+## JavaScript示例
+
+[examples/test_js.js](mdc:examples/test_js.js)是一个简单的JavaScript示例,包含:
+
+```javascript
+// Sample JavaScript file with handler function
+
+// Log some debug information
+console.log("Initializing JavaScript test...");
+
+// Define a simple add function
+function add(a, b) {
+ console.log(`Adding ${a} + ${b}`);
+ return a + b;
+}
+
+// Some processing
+const numbers = [1, 2, 3, 4, 5];
+console.log("Processing numbers:", numbers);
+
+// Handler function that will be called to get the result
+function handler() {
+ console.log("Handler function called");
+
+ // Calculate sum
+ const sum = numbers.reduce((acc, num) => add(acc, num), 0);
+ console.log("Final calculation completed");
+
+ return `The sum of [${numbers.join(", ")}] is ${sum}`;
+}
+```
+
+执行这个示例将输出:
+
+```
+--- Logs ---
+Initializing JavaScript test...
+Processing numbers: [1,2,3,4,5]
+Handler function called
+Adding 0 + 1
+Adding 1 + 2
+Adding 3 + 3
+Adding 6 + 4
+Adding 10 + 5
+Final calculation completed
+------------
+Result: The sum of [1, 2, 3, 4, 5] is 15
+```
+
+## Python示例
+
+[examples/test_python.py](mdc:examples/test_python.py)是一个简单的Python示例,包含:
+
+```python
+#!/usr/bin/env python3
+# Sample Python file with handler function
+
+import time
+
+# Log some debug information
+print("Initializing Python test...")
+
+# Define a simple function
+def multiply(a, b):
+ print(f"Multiplying {a} * {b}")
+ return a * b
+
+# Some processing
+numbers = [1, 2, 3, 4, 5]
+print(f"Processing numbers: {numbers}")
+
+# Handler function that will be called to get the result
+def handler():
+ print("Handler function called")
+
+ # Calculate product
+ product = 1
+ for num in numbers:
+ product = multiply(product, num)
+
+ print("Final calculation completed")
+
+ return f"The product of {numbers} is {product}"
+
+# This part won't be executed when the code is run through the MCP runner
+if __name__ == "__main__":
+ print("Running directly, calling handler...")
+ result = handler()
+ print(f"Result: {result}")
+```
+
+执行这个示例将输出:
+
+```
+--- Logs ---
+Handler function called
+Multiplying 1 * 1
+Multiplying 1 * 2
+Multiplying 2 * 3
+Multiplying 6 * 4
+Multiplying 24 * 5
+Final calculation completed
+------------
+Result: The product of [1, 2, 3, 4, 5] is 120
+```
+
+## 如何创建自己的示例
+
+创建自己的示例代码时,需要遵循以下规则:
+
+1. 必须包含一个名为`handler`的函数,作为执行结果的返回点
+2. `handler`函数的返回值将作为执行结果
+3. 使用`console.log`(JavaScript)或`print`(Python)输出日志
+4. 可以定义其他函数和变量,它们将在执行过程中被使用
+
+示例:
+
+```javascript
+// JavaScript示例
+function handler() {
+ return "Hello from JavaScript!";
+}
+```
+
+```python
+# Python示例
+def handler():
+ return "Hello from Python!"
+```
diff --git a/qiming-run_code_rmcp/.cursor/rules/fixtures-guide.mdc b/qiming-run_code_rmcp/.cursor/rules/fixtures-guide.mdc
new file mode 100644
index 00000000..e8f6a68c
--- /dev/null
+++ b/qiming-run_code_rmcp/.cursor/rules/fixtures-guide.mdc
@@ -0,0 +1,98 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# 测试脚本示例指南
+
+本项目包含多种语言的测试脚本示例,位于`fixtures`目录下。这些示例展示了如何编写可被本工具执行的JavaScript、TypeScript和Python脚本。
+
+## JavaScript示例
+
+### 基本执行
+
+[test_js.js](mdc:fixtures/test_js.js) - 基本的JavaScript执行示例:
+- 展示了如何使用`console.log`输出日志
+- 定义了`handler`函数作为执行结果的返回点
+
+### 参数传递
+
+[test_js_params.js](mdc:fixtures/test_js_params.js) - 带参数的JavaScript执行示例:
+- 展示了如何接收和处理传入的参数
+- 演示了如何根据参数执行不同的逻辑
+
+### 函数示例
+
+[rfunction_test1.js](mdc:fixtures/rfunction_test1.js) - 函数式JavaScript示例:
+- 展示了如何定义和使用函数
+- 演示了如何处理和返回复杂数据结构
+
+## TypeScript示例
+
+### 基本执行
+
+[test_ts.ts](mdc:fixtures/test_ts.ts) - 基本的TypeScript执行示例:
+- 展示了TypeScript的类型定义
+- 定义了带类型的`handler`函数
+
+### 参数传递
+
+[test_ts_params.ts](mdc:fixtures/test_ts_params.ts) - 带参数的TypeScript执行示例:
+- 展示了如何定义参数类型
+- 演示了TypeScript中的类型安全参数处理
+
+## Python示例
+
+### 基本执行
+
+[test_python.py](mdc:fixtures/test_python.py) - 基本的Python执行示例:
+- 使用`handler`函数作为执行结果的返回点
+- 展示了如何使用Python进行基本计算和日志输出
+
+### 参数传递
+
+[test_python_params.py](mdc:fixtures/test_python_params.py) - 带参数的Python执行示例:
+- 展示了如何接收和处理传入的参数
+- 演示了如何返回包含多个字段的结果
+
+### 类型示例
+
+[test_python_types.py](mdc:fixtures/test_python_types.py) - 不同返回类型的Python示例:
+- 展示了如何返回不同类型的值(字符串、数字、布尔值、列表、字典等)
+- 演示了不同类型在JSON序列化中的处理方式
+
+### 函数示例
+
+[rfunction_python.py](mdc:fixtures/rfunction_python.py) - 函数式Python示例:
+- 展示了如何定义和使用Python函数
+- 演示了如何处理和返回复杂数据结构
+
+## 使用示例
+
+以下是如何使用这些示例脚本的命令:
+
+```bash
+# 执行基本JavaScript示例
+cargo run -- --show-logs js -f fixtures/test_js.js
+
+# 执行带参数的JavaScript示例
+cargo run -- --show-logs js -f fixtures/test_js_params.js -p '{"name":"User"}'
+
+# 执行基本TypeScript示例
+cargo run -- --show-logs ts -f fixtures/test_ts.ts
+
+# 执行带参数的TypeScript示例
+cargo run -- --show-logs ts -f fixtures/test_ts_params.ts -p '{"a":10, "b":20, "name":"User"}'
+
+# 执行基本Python示例
+cargo run -- --show-logs python -f fixtures/test_python.py
+
+# 执行带参数的Python示例
+cargo run -- --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
+
+# 执行不同类型的Python示例
+cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"string"}'
+cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"number"}'
+cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"list"}'
+cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"dict"}'
+```
diff --git a/qiming-run_code_rmcp/.cursor/rules/mcp-integration.mdc b/qiming-run_code_rmcp/.cursor/rules/mcp-integration.mdc
new file mode 100644
index 00000000..c3cd4b97
--- /dev/null
+++ b/qiming-run_code_rmcp/.cursor/rules/mcp-integration.mdc
@@ -0,0 +1,146 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# MCP SDK集成
+
+本项目支持Model Context Protocol (MCP) SDK集成,允许通过标准化协议执行代码。MCP相关功能主要在[mcp.rs](mdc:src/mcp.rs)中实现。
+
+## 核心数据结构
+
+```rust
+#[derive(Serialize, Deserialize, Debug)]
+pub struct CodeExecutionParams {
+ pub code: String,
+ pub language: String,
+ pub show_logs: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct CodeExecutionResult {
+ pub logs: Vec,
+ pub result: Option,
+ pub error: Option,
+}
+```
+
+## JavaScript执行
+
+MCP中的JavaScript执行使用以下包装代码:
+
+```javascript
+// MCP SDK integration for JavaScript
+// Note: MCP SDK is not required for basic functionality
+
+// Log capture setup
+const logs = [];
+const originalConsoleLog = console.log;
+console.log = function() {
+ const message = Array.from(arguments).map(arg =>
+ typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg)
+ ).join(' ');
+ logs.push(message);
+ if (show_logs) {
+ originalConsoleLog.apply(console, arguments);
+ }
+};
+
+async function mcpExecute() {
+ try {
+ // User code here
+
+ // Execute handler
+ let result = null;
+ if (typeof handler === 'function') {
+ result = await handler();
+ }
+
+ return {
+ logs: logs,
+ result: result !== undefined ? String(result) : null,
+ error: null
+ };
+ } catch (error) {
+ return {
+ logs: logs,
+ result: null,
+ error: error.toString()
+ };
+ }
+}
+
+// Execute and return results
+mcpExecute().then(result => {
+ originalConsoleLog(JSON.stringify(result));
+});
+```
+
+## Python执行
+
+MCP中的Python执行使用以下包装代码:
+
+```python
+# MCP SDK integration for Python
+# Note: MCP SDK is not required for basic functionality
+import sys
+import json
+import io
+from contextlib import redirect_stdout
+
+# Store logs
+logs = []
+
+# Create custom stdout to capture logs
+class LogCapture(io.StringIO):
+ def write(self, text):
+ if text.strip():
+ logs.append(text.rstrip())
+ if show_logs:
+ sys.__stdout__.write(text)
+
+def mcp_execute():
+ try:
+ // User code here
+
+ // Execute handler function and capture result
+ result = None
+ with redirect_stdout(LogCapture()):
+ if 'handler' in globals() and callable(handler):
+ result = handler()
+
+ return {
+ "logs": logs,
+ "result": str(result) if result is not None else None,
+ "error": None
+ }
+ except Exception as e:
+ return {
+ "logs": logs,
+ "result": None,
+ "error": str(e)
+ }
+
+# Execute and print results
+result = mcp_execute()
+print(json.dumps(result))
+```
+
+## 执行流程
+
+MCP执行流程由[execute_with_mcp](mdc:src/mcp.rs#L138)函数处理:
+
+1. 创建临时文件并写入包装后的代码
+2. 根据语言类型选择执行命令(Deno或uv)
+3. 执行代码并捕获输出
+4. 解析JSON格式的执行结果
+5. 如果无法解析JSON,则返回原始输出
+
+## 使用方法
+
+在命令行中使用`--use-mcp`参数启用MCP SDK集成:
+
+```bash
+cargo run -- --use-mcp js -c "function handler() { return 'Hello from MCP JS'; }"
+cargo run -- --use-mcp python -f examples/test_python.py
+```
diff --git a/qiming-run_code_rmcp/.cursor/rules/project-overview.mdc b/qiming-run_code_rmcp/.cursor/rules/project-overview.mdc
new file mode 100644
index 00000000..aba9dab5
--- /dev/null
+++ b/qiming-run_code_rmcp/.cursor/rules/project-overview.mdc
@@ -0,0 +1,50 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# 项目概述
+
+这是一个使用Rust开发的命令行工具,用于执行JavaScript、TypeScript和Python代码。该工具支持以下功能:
+
+1. 执行JavaScript代码(使用Deno运行时)
+2. 执行TypeScript代码(使用Deno运行时)
+3. 执行Python代码(使用uv提供隔离环境)
+4. 捕获并区分脚本中的日志输出和执行结果
+5. 支持JavaScript/TypeScript的handler函数和Python的handler/main函数作为执行结果的返回点
+6. 通过环境变量传递参数给脚本
+7. 可以通过参数控制是否显示日志输出
+8. 支持MCP SDK集成
+
+## 主要文件
+
+- [src/main.rs](mdc:src/main.rs) - 应用程序入口点,处理命令行参数和执行流程
+- [src/lib.rs](mdc:src/lib.rs) - 库入口点,导出模块和类型
+- [src/model/code_run_model.rs](mdc:src/model/code_run_model.rs) - 代码执行模型和结果处理
+- [src/deno_runner/js_runner.rs](mdc:src/deno_runner/js_runner.rs) - JavaScript代码执行器
+- [src/deno_runner/ts_runner.rs](mdc:src/deno_runner/ts_runner.rs) - TypeScript代码执行器
+- [src/python_runner/python_runner.rs](mdc:src/python_runner/python_runner.rs) - Python代码执行器
+- [src/cache/](mdc:src/cache) - 代码缓存管理
+- [fixtures/](mdc:fixtures) - 测试脚本示例
+
+## 使用示例
+
+```bash
+# 执行JavaScript文件
+cargo run -- --show-logs js -f fixtures/test_js.js
+
+# 执行TypeScript文件
+cargo run -- --show-logs ts -f fixtures/test_ts.ts
+
+# 执行Python文件
+cargo run -- --show-logs python -f fixtures/test_python.py
+
+# 带参数执行Python文件
+cargo run -- --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
+
+# 直接执行JavaScript代码
+cargo run -- js -c "function handler(input) { return 'Hello from JS: ' + input.name; }" -p '{"name":"User"}'
+
+# 直接执行Python代码
+cargo run -- python -c "def handler(args): return 'Hello from Python: ' + args.get('name', 'Guest')" -p '{"name":"User"}'
+```
diff --git a/qiming-run_code_rmcp/.cursor/rules/python-execution.mdc b/qiming-run_code_rmcp/.cursor/rules/python-execution.mdc
new file mode 100644
index 00000000..850496be
--- /dev/null
+++ b/qiming-run_code_rmcp/.cursor/rules/python-execution.mdc
@@ -0,0 +1,143 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# Python代码执行
+
+本项目使用`uv`在隔离环境中执行Python代码,提供安全可靠的执行环境。以下是Python代码执行的详细流程:
+
+## Python运行器
+
+Python代码通过[PythonRunner](mdc:src/python_runner/python_runner.rs)执行,实现了`RunCode`特性:
+
+```rust
+pub struct PythonRunner;
+
+impl RunCode for PythonRunner {
+ async fn run(&self, code: &str) -> Result {
+ // 实现代码...
+ }
+
+ async fn run_with_params(&self, code: &str, params: Option) -> Result {
+ // 实现代码...
+ }
+}
+```
+
+## 依赖解析
+
+Python代码的依赖通过[python_dependencies.rs](mdc:src/python_runner/dependencies/python_dependencies.rs)解析:
+
+1. 使用Pest解析器分析Python代码中的import语句
+2. 支持常规import语句和importlib动态导入
+3. 提取所有导入的模块名称
+
+```rust
+pub fn parse_import(python_code: &str) -> Result> {
+ // 使用Pest解析器分析Python代码
+ let pairs = ImportParser::parse(Rule::file, &input)?;
+
+ // 提取模块名称
+ let mut imported_modules = Vec::new();
+ // 处理各种import语句...
+
+ Ok(imported_modules)
+}
+```
+
+## 代码准备
+
+Python代码在执行前需要进行包装,添加日志捕获和函数执行逻辑:
+
+```rust
+fn prepare_python_code(&self, code: &str, show_logs: bool) -> String {
+ // 创建包装代码,包括:
+ // 1. 日志捕获
+ // 2. 参数解析
+ // 3. 执行handler或main函数
+ // 4. 结果格式化
+}
+```
+
+包装代码主要功能:
+1. 捕获标准输出作为日志
+2. 从环境变量`INPUT_JSON`解析参数
+3. 优先执行`handler`函数,如果不存在则执行`main`函数
+4. 将执行结果和日志格式化为JSON
+
+## 参数传递
+
+参数通过环境变量`INPUT_JSON`传递给Python脚本:
+
+```rust
+// 将参数序列化为JSON字符串
+let params_json = match params {
+ Some(p) => serde_json::to_string(&p)?,
+ None => "{}".to_string(),
+};
+
+// 通过环境变量传递参数
+execute_command.env("INPUT_JSON", ¶ms_json);
+```
+
+在Python脚本中,参数通过以下方式获取:
+
+```python
+# 从环境变量获取输入参数
+args = {}
+try:
+ input_json = os.environ.get('INPUT_JSON')
+ if input_json:
+ args = json.loads(input_json)
+except Exception as e:
+ print(f"解析输入参数失败: {e}")
+```
+
+## 执行流程
+
+1. 计算代码哈希值,检查缓存
+2. 如果缓存存在,直接使用;否则:
+ - 分析代码依赖
+ - 准备包装后的代码
+ - 保存到缓存
+3. 使用`uv`命令在隔离环境中执行代码:
+ ```rust
+ let mut execute_command = Command::new("uv");
+ execute_command
+ .arg("run")
+ .arg("-s") // 明确指定作为脚本运行
+ .arg("--isolated") // 在隔离环境中运行
+ .arg("-p")
+ .arg("python3") // 指定Python解释器
+ .env("INPUT_JSON", ¶ms_json) // 通过环境变量传递参数
+ .arg(&temp_path)
+ ```
+4. 解析执行结果,提取日志和返回值
+
+## 结果处理
+
+执行结果通过JSON格式返回,包含三个字段:
+- `logs`: 执行过程中的日志数组
+- `result`: 执行结果(可能是字符串、数字、布尔值、对象等)
+- `error`: 执行错误(如果有)
+
+```json
+{
+ "logs": ["日志1", "日志2", ...],
+ "result": "执行结果",
+ "error": null
+}
+```
+
+## 支持的返回类型
+
+Python脚本可以返回多种类型的值:
+1. 字符串
+2. 数字
+3. 布尔值
+4. 列表/数组
+5. 字典/对象
+6. None/null
+
+所有复杂类型(列表、字典)都会被序列化为JSON字符串。
diff --git a/qiming-run_code_rmcp/.devcontainer/devcontainer.json b/qiming-run_code_rmcp/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..afab381e
--- /dev/null
+++ b/qiming-run_code_rmcp/.devcontainer/devcontainer.json
@@ -0,0 +1,43 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/rust
+{
+ "name": "Rust",
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+ // "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye",
+ // 使用你自定义的Dockerfile
+ "build": {
+ "dockerfile": "../Dockerfile",
+ "context": ".."
+ },
+ "features": {
+ "ghcr.io/devcontainers/features/go:1": {},
+ "ghcr.io/devcontainers-community/features/deno:1": {},
+ "ghcr.io/va-h/devcontainers-features/uv:1": {},
+ "ghcr.io/devcontainers-extra/features/curl-apt-get:1": {},
+ "ghcr.io/devcontainers-extra/features/wget-apt-get:1": {}
+ }
+
+ // Use 'mounts' to make the cargo cache persistent in a Docker Volume.
+ // "mounts": [
+ // {
+ // "source": "devcontainer-cargo-cache-${devcontainerId}",
+ // "target": "/usr/local/cargo",
+ // "type": "volume"
+ // }
+ // ]
+
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ // "features": {},
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "rustc --version",
+
+ // Configure tool-specific properties.
+ // "customizations": {},
+
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
diff --git a/qiming-run_code_rmcp/.gitignore b/qiming-run_code_rmcp/.gitignore
new file mode 100644
index 00000000..3187ccbd
--- /dev/null
+++ b/qiming-run_code_rmcp/.gitignore
@@ -0,0 +1,6 @@
+/target
+.idea
+.vscode
+.DS_Store
+
+temp/
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/CLAUDE.md b/qiming-run_code_rmcp/CLAUDE.md
new file mode 100644
index 00000000..2a0663ec
--- /dev/null
+++ b/qiming-run_code_rmcp/CLAUDE.md
@@ -0,0 +1,108 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+This is a Rust-based MCP (Model Context Protocol) server that executes JavaScript, TypeScript, and Python code in isolated environments. The project provides both a command-line tool and a library for code execution with proper sandboxing and result capture.
+
+## Key Commands
+
+### Building and Running
+```bash
+# Build the project
+cargo build
+
+# Run with cargo
+cargo run -- js -f fixtures/test_js.js
+cargo run -- ts -f fixtures/test_ts.ts
+cargo run -- python -f fixtures/test_python.py
+
+# Install as binary
+cargo install --path . --bin script_runner
+
+# Run tests
+cargo test
+
+# Clear cache
+cargo run -- clear-cache all
+```
+
+### Common Development Commands
+```bash
+# Execute JavaScript with logs
+cargo run -- --show-logs js -f fixtures/test_js.js
+
+# Execute with parameters
+cargo run -- --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
+
+# Execute inline code
+cargo run -- js -c "function handler(input) { return 'Hello: ' + input.name; }" -p '{"name":"User"}'
+
+# Use MCP integration
+cargo run -- --use-mcp python -f fixtures/test_python.py
+```
+
+## Architecture
+
+### Core Components
+- **src/main.rs** - CLI entry point with command parsing using clap
+- **src/lib.rs** - Library interface exporting public APIs
+- **src/model/code_run_model.rs** - Core execution models and `CodeExecutor` struct
+- **src/cache/** - Code caching system using blake3 hashing
+- **src/mcp/** - MCP protocol server implementation
+
+### Language Runners
+- **src/deno_runner/js_runner.rs** - JavaScript execution via Deno
+- **src/deno_runner/ts_runner.rs** - TypeScript execution via Deno
+- **src/python_runner/python_runner.rs** - Python execution via uv with dependency analysis
+- **src/python_runner/dependencies/** - Python dependency parsing and management
+
+### Execution Flow
+1. Code is processed and cached using blake3 hashing
+2. Language-specific runner prepares the execution environment
+3. Code is wrapped with logging capture and handler function execution
+4. Results are captured and returned as `CodeScriptExecutionResult`
+
+## Important Patterns
+
+### Handler Function Convention
+Scripts must expose a `handler` function (JS/TS) or `handler`/`main` function (Python) that returns the final result:
+```javascript
+function handler(input) {
+ return "Result: " + input.param;
+}
+```
+
+### Parameter Passing
+- JS/TS: Parameters passed directly to handler function
+- Python: Parameters passed via `INPUT_JSON` environment variable
+
+### Error Handling
+- Uses `anyhow::Result` for error propagation
+- Custom `app_error` module for application-specific errors
+- Execution results include error information in `CodeScriptExecutionResult`
+
+### Dependencies
+- **Deno** for JavaScript/TypeScript execution
+- **uv** for Python environment isolation
+- **rmcp** crate for MCP protocol implementation
+- **tokio** for async runtime
+- **serde** for JSON serialization
+
+## Development Notes
+
+### Testing
+- Test fixtures in `fixtures/` directory for all supported languages
+- Integration tests in `src/tests/`
+- Use `--show-logs` flag during development to see execution output
+
+### Caching
+- Code processing results are cached based on content hash
+- Use `--clear-cache` to invalidate cache when debugging
+- Cache files stored in system temp directory
+
+### MCP Integration
+- Can run as standalone MCP server via `script_runner` binary
+- Supports SSE and HTTP transports
+- Tools: `run_javascript`, `run_typescript`, `run_python`
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/Cargo.lock b/qiming-run_code_rmcp/Cargo.lock
new file mode 100644
index 00000000..c78f31c9
--- /dev/null
+++ b/qiming-run_code_rmcp/Cargo.lock
@@ -0,0 +1,1429 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "addr2line"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
+dependencies = [
+ "anstyle",
+ "once_cell",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
+
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "backtrace"
+version = "0.3.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bitflags"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+
+[[package]]
+name = "blake3"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "cc"
+version = "1.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
+
+[[package]]
+name = "env_filter"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "io-uring"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "jiff"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
+dependencies = [
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.172"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
+dependencies = [
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "pastey"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec"
+
+[[package]]
+name = "pest"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
+dependencies = [
+ "memchr",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "portable-atomic"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
+[[package]]
+name = "ref-cast"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "rmcp"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528d42f8176e6e5e71ea69182b17d1d0a19a6b3b894b564678b74cd7cab13cfa"
+dependencies = [
+ "async-trait",
+ "base64",
+ "chrono",
+ "futures",
+ "pastey",
+ "pin-project-lite",
+ "rmcp-macros",
+ "schemars",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "rmcp-macros"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3f81daaa494eb8e985c9462f7d6ce1ab05e5299f48aafd76cdd3d8b060e6f59"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "serde_json",
+ "syn",
+]
+
+[[package]]
+name = "run_code_rmcp"
+version = "0.0.35"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "blake3",
+ "clap",
+ "env_logger",
+ "libc",
+ "log",
+ "once_cell",
+ "pest",
+ "pest_derive",
+ "pin-project",
+ "regex",
+ "rmcp",
+ "schemars",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "thiserror",
+ "tokio",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustix"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "schemars"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
+dependencies = [
+ "chrono",
+ "dyn-clone",
+ "ref-cast",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "socket2"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
+dependencies = [
+ "fastrand",
+ "getrandom",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio"
+version = "1.47.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "io-uring",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "slab",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasi"
+version = "0.14.2+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags",
+]
diff --git a/qiming-run_code_rmcp/Cargo.toml b/qiming-run_code_rmcp/Cargo.toml
new file mode 100644
index 00000000..dd059115
--- /dev/null
+++ b/qiming-run_code_rmcp/Cargo.toml
@@ -0,0 +1,56 @@
+[package]
+name = "run_code_rmcp"
+description = "云函数服务,执行JS/TS/Python语言代码,脚本必须有约定的函数名称(handler/main),会调用约定的函数名称结果和日志返回."
+version = "0.0.35"
+edition = "2024"
+license = "Apache-2.0"
+repository = "https://github.com/nuwax-ai/run_code_rmcp"
+homepage = "https://nuwax.com/"
+readme = "README.md"
+default-run = "run_code_rmcp"
+
+[features]
+default = []
+mcp = ["dep:rmcp"]
+
+[dependencies]
+tokio = { version = "1.47", features = [
+ "fs",
+ "macros",
+ "net",
+ "rt",
+ "rt-multi-thread",
+ "signal",
+ "io-util",
+ "process",
+ "time",
+] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+log = "0.4"
+env_logger = "0.11"
+anyhow = "1.0"
+rmcp = { version = "0.12", features = [
+ "server",
+ "client",
+ "transport-io",
+ "transport-async-rw",
+ "macros",
+], optional = true }
+tempfile = "3.21"
+regex = "1.11"
+clap = { version = "4.5", features = ["derive"] }
+thiserror = "2.0"
+pest = { version = "2.8", features = ["pretty-print"] }
+pest_derive = "2.8"
+blake3 = "1.8"
+pin-project = "1.1"
+libc = "0.2"
+once_cell = "1.21"
+schemars = "1.0"
+async-trait = "0.1"
+
+[[bin]]
+name = "script_runner"
+path = "src/script_runner.rs"
+required-features = ["mcp"]
diff --git a/qiming-run_code_rmcp/Dockerfile b/qiming-run_code_rmcp/Dockerfile
new file mode 100644
index 00000000..eaf2e471
--- /dev/null
+++ b/qiming-run_code_rmcp/Dockerfile
@@ -0,0 +1,80 @@
+FROM rust:1.85
+
+# 设置环境变量
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=Asia/Shanghai
+ENV PATH="/root/.deno/bin:/root/.cargo/bin:${PATH}"
+
+# 安装基础依赖
+RUN apt-get update && apt-get install -y \
+ python3.11 \
+ python3.11-venv \
+ python3.11-distutils \
+ python3.11-dev \
+ python3-pip \
+ curl \
+ vim \
+ net-tools \
+ gettext-base \
+ telnet \
+ wget \
+ && rm -rf /var/lib/apt/lists/* \
+ && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
+ && echo $TZ > /etc/timezone
+
+# 安装 Node.js
+RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
+ && apt-get install -y nodejs \
+ && npm install -g npm@latest
+
+# 安装 go
+# 设置 Go 环境变量
+ENV GOPATH=/root/go
+ENV PATH=$PATH:/usr/local/go/bin:/root/go/bin
+
+# 创建 Go 工作目录
+RUN mkdir -p $GOPATH/bin $GOPATH/src $GOPATH/pkg
+# 安装 Go 1.24(根据架构自动选择下载链接)
+ARG TARGETARCH
+RUN echo "Target Architecture: $TARGETARCH" \
+ && if [ "$TARGETARCH" = "amd64" ]; then \
+ GO_URL="https://go.dev/dl/go1.24.3.linux-amd64.tar.gz"; \
+ elif [ "$TARGETARCH" = "arm64" ]; then \
+ GO_URL="https://go.dev/dl/go1.24.3.linux-arm64.tar.gz"; \
+ else \
+ echo "Unsupported architecture: $TARGETARCH"; \
+ exit 1; \
+ fi \
+ && echo "Downloading Go from: $GO_URL" \
+ && curl -fsSL "$GO_URL" -o go.tar.gz \
+ && tar -C /usr/local -xzf go.tar.gz \
+ && rm go.tar.gz \
+ && ln -s /usr/local/go/bin/go /usr/local/bin/go \
+ && ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt
+
+
+RUN go version
+
+# 测试mcp用 go-mcp-mysql@latest
+RUN go install -v github.com/Zhwt/go-mcp-mysql@latest
+
+
+# 安装 Deno
+RUN curl -fsSL https://deno.land/install.sh | sh
+
+
+# 安装 uv
+RUN curl -LsSf https://astral.sh/uv/install.sh | sh
+
+# 添加uv到PATH
+ENV PATH="/root/.local/bin:${PATH}"
+
+# 创建虚拟环境
+RUN uv venv
+
+# 设置工作目录
+WORKDIR /app
+
+# 暴露端口,实际端口一般是8080
+EXPOSE 8080
+
diff --git a/qiming-run_code_rmcp/LICENSE b/qiming-run_code_rmcp/LICENSE
new file mode 100644
index 00000000..79d46eaa
--- /dev/null
+++ b/qiming-run_code_rmcp/LICENSE
@@ -0,0 +1,202 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction,
+and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by
+the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all
+other entities that control, are controlled by, or are under common
+control with that entity. For the purposes of this definition,
+"control" means (i) the power, direct or indirect, to cause the
+direction or management of such entity, whether by contract or
+otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity
+exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications,
+including but not limited to software source code, documentation
+source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical
+transformation or translation of a Source form, including but not
+limited to compiled object code, generated documentation, and
+conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or
+Object form, made available under the License, as indicated by a
+copyright notice that is included in or attached to the work
+(an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object
+form, that is based on (or derived from) the Work and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship. For the purposes
+of this License, Derivative Works shall not include works that remain
+separable from, or merely link (or bind by name) to the interfaces of,
+the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including
+the original version of the Work and any modifications or additions
+to that Work or Derivative Works thereof, that is intentionally
+submitted to Licensor for inclusion in the Work by the copyright owner
+or by an individual or Legal Entity authorized to submit on behalf of
+the copyright owner. For the purposes of this definition, "submitted"
+means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems,
+and issue tracking systems that are managed by, or on behalf of, the
+Licensor for the purpose of discussing and improving the Work, but
+excluding communication that is conspicuously marked or otherwise
+designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity
+on behalf of whom a Contribution has been received by Licensor and
+subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the
+Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+(except as stated in this section) patent license to make, have made,
+use, offer to sell, sell, import, and otherwise transfer the Work,
+where such license applies only to those patent claims licensable
+by such Contributor that are necessarily infringed by their
+Contribution(s) alone or by combination of their Contribution(s)
+with the Work to which such Contribution(s) was submitted. If You
+institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work
+or a Contribution incorporated within the Work constitutes direct
+or contributory patent infringement, then any patent licenses
+granted to You under this License for that Work shall terminate
+as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+Work or Derivative Works thereof in any medium, with or without
+modifications, and in Source or Object form, provided that You
+meet the following conditions:
+
+(a) You must give any other recipients of the Work or
+Derivative Works a copy of this License; and
+
+(b) You must cause any modified files to carry prominent notices
+stating that You changed the files; and
+
+(c) You must retain, in the Source form of any Derivative Works
+that You distribute, all copyright, patent, trademark, and
+attribution notices from the Source form of the Work,
+excluding those notices that do not pertain to any part of
+the Derivative Works; and
+
+(d) If the Work includes a "NOTICE" text file as part of its
+distribution, then any Derivative Works that You distribute must
+include a readable copy of the attribution notices contained
+within such NOTICE file, excluding those notices that do not
+pertain to any part of the Derivative Works, in at least one
+of the following places: within a NOTICE text file distributed
+as part of the Derivative Works; within the Source form or
+documentation, if provided along with the Derivative Works; or,
+within a display generated by the Derivative Works, if and
+wherever such third-party notices normally appear. The contents
+of the NOTICE file are for informational purposes only and
+do not modify the License. You may add Your own attribution
+notices within Derivative Works that You distribute, alongside
+or as an addendum to the NOTICE text from the Work, provided
+that such additional attribution notices cannot be construed
+as modifying the License.
+
+You may add Your own copyright statement to Your modifications and
+may provide additional or different license terms and conditions
+for use, reproduction, or distribution of Your modifications, or
+for any such Derivative Works as a whole, provided Your use,
+reproduction, and distribution of the Work otherwise complies with
+the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+any Contribution intentionally submitted for inclusion in the Work
+by You to the Licensor shall be under the terms and conditions of
+this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify
+the terms of any separate license agreement you may have executed
+with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+names, trademarks, service marks, or product names of the Licensor,
+except as required for reasonable and customary use in describing the
+origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+agreed to in writing, Licensor provides the Work (and each
+Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied, including, without limitation, any warranties or conditions
+of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+PARTICULAR PURPOSE. You are solely responsible for determining the
+appropriateness of using or redistributing the Work and assume any
+risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+whether in tort (including negligence), contract, or otherwise,
+unless required by applicable law (such as deliberate and grossly
+negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special,
+incidental, or consequential damages of any character arising as a
+result of this License or out of the use or inability to use the
+Work (including but not limited to damages for loss of goodwill,
+work stoppage, computer failure or malfunction, or any and all
+other commercial damages or losses), even if such Contributor
+has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+the Work or Derivative Works thereof, You may choose to offer,
+and charge a fee for, acceptance of support, warranty, indemnity,
+or other liability obligations and/or rights consistent with this
+License. However, in accepting such obligations, You may act only
+on Your own behalf and on Your sole responsibility, not on behalf
+of any other Contributor, and only if You agree to indemnify,
+defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason
+of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+To apply the Apache License to your work, attach the following
+boilerplate notice, with the fields enclosed by brackets "[]"
+replaced with your own identifying information. (Don't include
+the brackets!) The text should be enclosed in the appropriate
+comment syntax for the file format. We also recommend that a
+file or class name and description of purpose be included on the
+same "printed page" as the copyright notice for easier
+identification within third-party archives.
+
+Copyright 2024-2025 Nuwax AI and contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
diff --git a/qiming-run_code_rmcp/LICENSE-APACHE b/qiming-run_code_rmcp/LICENSE-APACHE
new file mode 100644
index 00000000..79d46eaa
--- /dev/null
+++ b/qiming-run_code_rmcp/LICENSE-APACHE
@@ -0,0 +1,202 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction,
+and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by
+the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all
+other entities that control, are controlled by, or are under common
+control with that entity. For the purposes of this definition,
+"control" means (i) the power, direct or indirect, to cause the
+direction or management of such entity, whether by contract or
+otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity
+exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications,
+including but not limited to software source code, documentation
+source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical
+transformation or translation of a Source form, including but not
+limited to compiled object code, generated documentation, and
+conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or
+Object form, made available under the License, as indicated by a
+copyright notice that is included in or attached to the work
+(an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object
+form, that is based on (or derived from) the Work and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship. For the purposes
+of this License, Derivative Works shall not include works that remain
+separable from, or merely link (or bind by name) to the interfaces of,
+the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including
+the original version of the Work and any modifications or additions
+to that Work or Derivative Works thereof, that is intentionally
+submitted to Licensor for inclusion in the Work by the copyright owner
+or by an individual or Legal Entity authorized to submit on behalf of
+the copyright owner. For the purposes of this definition, "submitted"
+means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems,
+and issue tracking systems that are managed by, or on behalf of, the
+Licensor for the purpose of discussing and improving the Work, but
+excluding communication that is conspicuously marked or otherwise
+designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity
+on behalf of whom a Contribution has been received by Licensor and
+subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the
+Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+(except as stated in this section) patent license to make, have made,
+use, offer to sell, sell, import, and otherwise transfer the Work,
+where such license applies only to those patent claims licensable
+by such Contributor that are necessarily infringed by their
+Contribution(s) alone or by combination of their Contribution(s)
+with the Work to which such Contribution(s) was submitted. If You
+institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work
+or a Contribution incorporated within the Work constitutes direct
+or contributory patent infringement, then any patent licenses
+granted to You under this License for that Work shall terminate
+as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+Work or Derivative Works thereof in any medium, with or without
+modifications, and in Source or Object form, provided that You
+meet the following conditions:
+
+(a) You must give any other recipients of the Work or
+Derivative Works a copy of this License; and
+
+(b) You must cause any modified files to carry prominent notices
+stating that You changed the files; and
+
+(c) You must retain, in the Source form of any Derivative Works
+that You distribute, all copyright, patent, trademark, and
+attribution notices from the Source form of the Work,
+excluding those notices that do not pertain to any part of
+the Derivative Works; and
+
+(d) If the Work includes a "NOTICE" text file as part of its
+distribution, then any Derivative Works that You distribute must
+include a readable copy of the attribution notices contained
+within such NOTICE file, excluding those notices that do not
+pertain to any part of the Derivative Works, in at least one
+of the following places: within a NOTICE text file distributed
+as part of the Derivative Works; within the Source form or
+documentation, if provided along with the Derivative Works; or,
+within a display generated by the Derivative Works, if and
+wherever such third-party notices normally appear. The contents
+of the NOTICE file are for informational purposes only and
+do not modify the License. You may add Your own attribution
+notices within Derivative Works that You distribute, alongside
+or as an addendum to the NOTICE text from the Work, provided
+that such additional attribution notices cannot be construed
+as modifying the License.
+
+You may add Your own copyright statement to Your modifications and
+may provide additional or different license terms and conditions
+for use, reproduction, or distribution of Your modifications, or
+for any such Derivative Works as a whole, provided Your use,
+reproduction, and distribution of the Work otherwise complies with
+the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+any Contribution intentionally submitted for inclusion in the Work
+by You to the Licensor shall be under the terms and conditions of
+this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify
+the terms of any separate license agreement you may have executed
+with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+names, trademarks, service marks, or product names of the Licensor,
+except as required for reasonable and customary use in describing the
+origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+agreed to in writing, Licensor provides the Work (and each
+Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied, including, without limitation, any warranties or conditions
+of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+PARTICULAR PURPOSE. You are solely responsible for determining the
+appropriateness of using or redistributing the Work and assume any
+risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+whether in tort (including negligence), contract, or otherwise,
+unless required by applicable law (such as deliberate and grossly
+negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special,
+incidental, or consequential damages of any character arising as a
+result of this License or out of the use or inability to use the
+Work (including but not limited to damages for loss of goodwill,
+work stoppage, computer failure or malfunction, or any and all
+other commercial damages or losses), even if such Contributor
+has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+the Work or Derivative Works thereof, You may choose to offer,
+and charge a fee for, acceptance of support, warranty, indemnity,
+or other liability obligations and/or rights consistent with this
+License. However, in accepting such obligations, You may act only
+on Your own behalf and on Your sole responsibility, not on behalf
+of any other Contributor, and only if You agree to indemnify,
+defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason
+of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+To apply the Apache License to your work, attach the following
+boilerplate notice, with the fields enclosed by brackets "[]"
+replaced with your own identifying information. (Don't include
+the brackets!) The text should be enclosed in the appropriate
+comment syntax for the file format. We also recommend that a
+file or class name and description of purpose be included on the
+same "printed page" as the copyright notice for easier
+identification within third-party archives.
+
+Copyright 2024-2025 Nuwax AI and contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
diff --git a/qiming-run_code_rmcp/NOTICE b/qiming-run_code_rmcp/NOTICE
new file mode 100644
index 00000000..ee1c0605
--- /dev/null
+++ b/qiming-run_code_rmcp/NOTICE
@@ -0,0 +1,16 @@
+Copyright 2024-2025 Nuwax AI
+
+This product includes software developed by Nuwax AI and contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
diff --git a/qiming-run_code_rmcp/README.md b/qiming-run_code_rmcp/README.md
new file mode 100644
index 00000000..4e60c1ac
--- /dev/null
+++ b/qiming-run_code_rmcp/README.md
@@ -0,0 +1,335 @@
+# MCP Code Execution Tool
+
+A Rust-based tool that provides both a command-line interface and an MCP (Model Context Protocol) server for executing JavaScript, TypeScript, and Python code in isolated environments.
+
+**Repository:**
+
+English | [中文](./README_zh.md)
+
+## Features
+
+- Execute JavaScript, TypeScript, and Python code in isolated environments
+- **JavaScript/TypeScript**: Powered by Deno runtime
+- **Python**: Powered by `uv` with automatic dependency management
+- Automatic code caching using blake3 hashing for faster repeated executions
+- Support for ESM and CommonJS modules in JavaScript
+- Automatic Python dependency parsing and installation
+- Separate log capture and execution result handling
+- Available as both CLI tool (`run_code_rmcp`) and MCP server (`script_runner`)
+
+## Installation
+
+### Install CLI Tool
+
+```bash
+# Clone the repository
+git clone https://github.com/nuwax-ai/run_code_rmcp.git
+cd run_code_rmcp
+
+# Install the CLI tool (run_code_rmcp)
+cargo install --path .
+
+# Or install both CLI tool and MCP server
+cargo install --path . --features mcp
+cargo install --path . --bin script_runner --features mcp
+```
+
+### System Requirements
+
+- **Rust**: 1.85 or higher (for building)
+- **Deno**: Required for JavaScript/TypeScript execution
+- **uv**: Required for Python execution and dependency management
+
+Install Deno:
+```bash
+curl -fsSL https://deno.land/install.sh | sh
+```
+
+Install uv:
+```bash
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+## Usage
+
+### CLI Tool: run_code_rmcp
+
+The `run_code_rmcp` command allows you to execute code directly from the command line.
+
+#### Basic Syntax
+
+```bash
+run_code_rmcp [OPTIONS]
+```
+
+**Options:**
+- `--show-logs`: Display execution logs
+- `--clear-cache`: Clear cache before execution
+- `--use-mcp`: Use MCP SDK integration (requires mcp feature)
+
+**Commands:**
+- `js`: Execute JavaScript code
+- `ts`: Execute TypeScript code
+- `python`: Execute Python code
+- `clear-cache`: Clear cached files
+
+#### Execute JavaScript/TypeScript
+
+```bash
+# Execute a JavaScript file
+run_code_rmcp --show-logs js -f fixtures/test_js.js
+
+# Execute with parameters
+run_code_rmcp --show-logs js -f fixtures/test_js_params.js -p '{"name":"User"}'
+
+# Execute TypeScript
+run_code_rmcp --show-logs ts -f fixtures/test_ts.ts
+
+# Execute inline code
+run_code_rmcp js -c "function handler() { return 'Hello!'; }"
+```
+
+#### Execute Python
+
+```bash
+# Execute a Python file
+run_code_rmcp --show-logs python -f fixtures/test_python.py
+
+# Execute with parameters
+run_code_rmcp --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
+
+# Execute inline code
+run_code_rmcp python -c "def handler(): return 'Hello from Python!'"
+```
+
+#### Cache Management
+
+```bash
+# Clear all cache
+run_code_rmcp clear-cache --language all
+
+# Clear specific language cache
+run_code_rmcp clear-cache --language python
+run_code_rmcp clear-cache --language js
+run_code_rmcp clear-cache --language ts
+
+# Clear cache before execution
+run_code_rmcp --clear-cache js -f fixtures/test_js.js
+```
+
+### Using as a Rust Library
+
+```toml
+[dependencies]
+run_code_rmcp = { git = "https://github.com/nuwax-ai/run_code_rmcp.git" }
+```
+
+```rust
+use run_code_rmcp::{CodeExecutor, LanguageScript};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ // Execute JavaScript
+ let js_result = CodeExecutor::execute_with_params_compat(
+ "function handler() { return {success: true}; }",
+ LanguageScript::Js,
+ None
+ ).await?;
+
+ // Execute Python
+ let py_result = CodeExecutor::execute_with_params_compat(
+ "def handler(): return {'success': True}",
+ LanguageScript::Python,
+ None
+ ).await?;
+
+ Ok(())
+}
+```
+
+### MCP Server: script_runner
+
+The `script_runner` is an MCP server that provides tools for AI assistants.
+
+#### Starting the Server
+
+```bash
+# Start MCP server
+script_runner
+
+# Start with verbose logging
+script_runner --verbose
+```
+
+The server communicates via stdio and waits for MCP protocol JSON requests.
+
+#### Available Tools
+
+The MCP server provides the following tools:
+
+1. **run_javascript** - Execute JavaScript code
+2. **run_typescript** - Execute TypeScript code
+3. **run_python** - Execute Python code
+
+Each tool accepts:
+- `code` (string): The code to execute
+- `params` (object, optional): Parameters to pass to the handler function
+
+#### Integration with AI Assistants
+
+Add to your MCP client configuration (e.g., Claude Desktop):
+
+```json
+{
+ "name": "script-runner",
+ "command": "script_runner",
+ "transport": "stdio"
+}
+```
+
+## Code Structure
+
+### Handler Functions
+
+Your scripts should define a handler function that returns the result.
+
+#### JavaScript/TypeScript
+
+Supports both `main` and `handler` functions (main takes priority):
+
+```javascript
+// This function takes priority
+function main(input) {
+ console.log("Processing with main:", input);
+ return { result: "Hello from main!" };
+}
+
+// This function is used if main is not defined
+function handler(input) {
+ console.log("Processing with handler:", input);
+ return { result: "Hello from handler!" };
+}
+```
+
+ESM modules are also supported:
+
+```javascript
+import { serve } from "https://deno.land/std/http/server.ts";
+
+function handler(input) {
+ return { message: "ESM works!" };
+}
+```
+
+#### Python
+
+Supports both `handler` and `main` functions (handler takes priority):
+
+```python
+import pandas as pd
+
+# This function takes priority
+def handler(args):
+ print(f"Processing: {args}")
+ data = pd.DataFrame({"a": [1, 2, 3]})
+ return {"result": data.to_dict()}
+
+# This function is used if handler is not defined
+def main(args):
+ return {"result": "Using main instead"}
+```
+
+### Parameter Passing
+
+Parameters are passed to your handler function:
+
+```bash
+run_code_rmcp js -f script.js -p '{"name": "User", "count": 42}'
+```
+
+In your code:
+```javascript
+function handler(input) {
+ // input = { name: "User", count: 42 }
+ return `Hello ${input.name}, count: ${input.count}`;
+}
+```
+
+### Python Dependencies
+
+Python dependencies are automatically detected and installed:
+
+```python
+import requests
+import pandas as pd
+
+def handler(args):
+ # Dependencies are automatically installed via uv
+ response = requests.get("https://api.example.com")
+ data = pd.DataFrame(response.json())
+ return data.to_dict()
+```
+
+### Log Capture
+
+All console output is captured separately from the return value:
+
+```javascript
+console.log("This goes to logs");
+console.log("So does this");
+
+function handler() {
+ return "This is the result";
+}
+```
+
+Result:
+```json
+{
+ "logs": ["This goes to logs", "So does this"],
+ "result": "This is the result",
+ "error": null
+}
+```
+
+## Development
+
+```bash
+# Build
+cargo build
+
+# Run with cargo
+cargo run -- --show-logs js -f fixtures/test_js.js
+
+# Run tests
+cargo test
+
+# Build with MCP feature
+cargo build --features mcp
+
+# Run MCP server with cargo
+cargo run --bin script_runner --features mcp
+```
+
+## Environment Pre-warming
+
+The project includes a warm-up function to cache common dependencies:
+
+```rust
+use run_code_rmcp::warm_up_all_envs;
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ // Pre-install common Python and JS/TS dependencies
+ warm_up_all_envs(None, None, None, None).await?;
+ Ok(())
+}
+```
+
+## TODO
+
+- [ ] TTS (Text-to-Speech) functionality - Currently has known issues
+
+## License
+
+This project is licensed under the Apache License 2.0. See `LICENSE` and `NOTICE` files in the root directory for details.
diff --git a/qiming-run_code_rmcp/README_zh.md b/qiming-run_code_rmcp/README_zh.md
new file mode 100644
index 00000000..9bed86bd
--- /dev/null
+++ b/qiming-run_code_rmcp/README_zh.md
@@ -0,0 +1,335 @@
+# MCP 代码执行工具
+
+一个基于 Rust 的工具,提供命令行界面和 MCP(Model Context Protocol)服务器,用于在隔离环境中执行 JavaScript、TypeScript 和 Python 代码。
+
+**项目仓库:**
+
+[English](./README.md) | 中文
+
+## 功能特点
+
+- 在隔离环境中执行 JavaScript、TypeScript 和 Python 代码
+- **JavaScript/TypeScript**:由 Deno 运行时驱动
+- **Python**:由 `uv` 驱动,支持自动依赖管理
+- 使用 blake3 哈希自动缓存代码,加快重复执行速度
+- 支持 JavaScript 的 ESM 和 CommonJS 模块
+- 自动解析和安装 Python 依赖
+- 独立的日志捕获和执行结果处理
+- 提供命令行工具(`run_code_rmcp`)和 MCP 服务器(`script_runner`)两种使用方式
+
+## 安装方法
+
+### 安装命令行工具
+
+```bash
+# 克隆仓库
+git clone https://github.com/nuwax-ai/run_code_rmcp.git
+cd run_code_rmcp
+
+# 安装命令行工具 (run_code_rmcp)
+cargo install --path .
+
+# 或同时安装命令行工具和 MCP 服务器
+cargo install --path . --features mcp
+cargo install --path . --bin script_runner --features mcp
+```
+
+### 系统要求
+
+- **Rust**:1.85 或更高版本(用于构建)
+- **Deno**:JavaScript/TypeScript 执行必需
+- **uv**:Python 执行和依赖管理必需
+
+安装 Deno:
+```bash
+curl -fsSL https://deno.land/install.sh | sh
+```
+
+安装 uv:
+```bash
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+## 使用方法
+
+### 命令行工具:run_code_rmcp
+
+`run_code_rmcp` 命令允许直接从命令行执行代码。
+
+#### 基本语法
+
+```bash
+run_code_rmcp [选项] <命令>
+```
+
+**选项:**
+- `--show-logs`:显示执行日志
+- `--clear-cache`:执行前清除缓存
+- `--use-mcp`:使用 MCP SDK 集成(需要 mcp feature)
+
+**命令:**
+- `js`:执行 JavaScript 代码
+- `ts`:执行 TypeScript 代码
+- `python`:执行 Python 代码
+- `clear-cache`:清除缓存文件
+
+#### 执行 JavaScript/TypeScript
+
+```bash
+# 执行 JavaScript 文件
+run_code_rmcp --show-logs js -f fixtures/test_js.js
+
+# 带参数执行
+run_code_rmcp --show-logs js -f fixtures/test_js_params.js -p '{"name":"User"}'
+
+# 执行 TypeScript
+run_code_rmcp --show-logs ts -f fixtures/test_ts.ts
+
+# 执行内联代码
+run_code_rmcp js -c "function handler() { return 'Hello!'; }"
+```
+
+#### 执行 Python
+
+```bash
+# 执行 Python 文件
+run_code_rmcp --show-logs python -f fixtures/test_python.py
+
+# 带参数执行
+run_code_rmcp --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
+
+# 执行内联代码
+run_code_rmcp python -c "def handler(): return 'Hello from Python!'"
+```
+
+#### 缓存管理
+
+```bash
+# 清除所有缓存
+run_code_rmcp clear-cache --language all
+
+# 清除特定语言的缓存
+run_code_rmcp clear-cache --language python
+run_code_rmcp clear-cache --language js
+run_code_rmcp clear-cache --language ts
+
+# 执行前清除缓存
+run_code_rmcp --clear-cache js -f fixtures/test_js.js
+```
+
+### 作为 Rust 库使用
+
+```toml
+[dependencies]
+run_code_rmcp = { git = "https://github.com/nuwax-ai/run_code_rmcp.git" }
+```
+
+```rust
+use run_code_rmcp::{CodeExecutor, LanguageScript};
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ // 执行 JavaScript
+ let js_result = CodeExecutor::execute_with_params_compat(
+ "function handler() { return {success: true}; }",
+ LanguageScript::Js,
+ None
+ ).await?;
+
+ // 执行 Python
+ let py_result = CodeExecutor::execute_with_params_compat(
+ "def handler(): return {'success': True}",
+ LanguageScript::Python,
+ None
+ ).await?;
+
+ Ok(())
+}
+```
+
+### MCP 服务器:script_runner
+
+`script_runner` 是一个 MCP 服务器,为 AI 助手提供代码执行工具。
+
+#### 启动服务器
+
+```bash
+# 启动 MCP 服务器
+script_runner
+
+# 启用详细日志
+script_runner --verbose
+```
+
+服务器通过 stdio 通信,等待 MCP 协议的 JSON 请求。
+
+#### 可用工具
+
+MCP 服务器提供以下工具:
+
+1. **run_javascript** - 执行 JavaScript 代码
+2. **run_typescript** - 执行 TypeScript 代码
+3. **run_python** - 执行 Python 代码
+
+每个工具接受:
+- `code`(字符串):要执行的代码
+- `params`(对象,可选):传递给 handler 函数的参数
+
+#### 与 AI 助手集成
+
+添加到你的 MCP 客户端配置(如 Claude Desktop):
+
+```json
+{
+ "name": "script-runner",
+ "command": "script_runner",
+ "transport": "stdio"
+}
+```
+
+## 代码结构
+
+### Handler 函数
+
+脚本应定义一个返回结果的 handler 函数。
+
+#### JavaScript/TypeScript
+
+支持 `main` 和 `handler` 函数(main 优先):
+
+```javascript
+// 这个函数优先级更高
+function main(input) {
+ console.log("使用 main 处理:", input);
+ return { result: "Hello from main!" };
+}
+
+// 如果没有定义 main,则使用这个函数
+function handler(input) {
+ console.log("使用 handler 处理:", input);
+ return { result: "Hello from handler!" };
+}
+```
+
+也支持 ESM 模块:
+
+```javascript
+import { serve } from "https://deno.land/std/http/server.ts";
+
+function handler(input) {
+ return { message: "ESM works!" };
+}
+```
+
+#### Python
+
+支持 `handler` 和 `main` 函数(handler 优先):
+
+```python
+import pandas as pd
+
+# 这个函数优先级更高
+def handler(args):
+ print(f"处理中: {args}")
+ data = pd.DataFrame({"a": [1, 2, 3]})
+ return {"result": data.to_dict()}
+
+# 如果没有定义 handler,则使用这个函数
+def main(args):
+ return {"result": "使用 main 代替"}
+```
+
+### 参数传递
+
+参数会传递给 handler 函数:
+
+```bash
+run_code_rmcp js -f script.js -p '{"name": "User", "count": 42}'
+```
+
+在代码中:
+```javascript
+function handler(input) {
+ // input = { name: "User", count: 42 }
+ return `Hello ${input.name}, count: ${input.count}`;
+}
+```
+
+### Python 依赖
+
+Python 依赖会被自动检测并安装:
+
+```python
+import requests
+import pandas as pd
+
+def handler(args):
+ # 依赖会通过 uv 自动安装
+ response = requests.get("https://api.example.com")
+ data = pd.DataFrame(response.json())
+ return data.to_dict()
+```
+
+### 日志捕获
+
+所有控制台输出都会与返回值分开捕获:
+
+```javascript
+console.log("这是日志");
+console.log("这也是日志");
+
+function handler() {
+ return "这是结果";
+}
+```
+
+结果:
+```json
+{
+ "logs": ["这是日志", "这也是日志"],
+ "result": "这是结果",
+ "error": null
+}
+```
+
+## 开发
+
+```bash
+# 构建
+cargo build
+
+# 使用 cargo 运行
+cargo run -- --show-logs js -f fixtures/test_js.js
+
+# 运行测试
+cargo test
+
+# 使用 mcp feature 构建
+cargo build --features mcp
+
+# 使用 cargo 运行 MCP 服务器
+cargo run --bin script_runner --features mcp
+```
+
+## 环境预热
+
+项目包含预热函数来缓存常用依赖:
+
+```rust
+use run_code_rmcp::warm_up_all_envs;
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ // 预安装常用的 Python 和 JS/TS 依赖
+ warm_up_all_envs(None, None, None, None).await?;
+ Ok(())
+}
+```
+
+## 待办事项
+
+- [ ] TTS(文本转语音)功能 - 目前存在已知问题
+
+## 许可证
+
+本项目采用 Apache License 2.0 发布。详见根目录的 `LICENSE` 与 `NOTICE` 文件。
diff --git a/qiming-run_code_rmcp/examples/cursor_mcp_config.json b/qiming-run_code_rmcp/examples/cursor_mcp_config.json
new file mode 100644
index 00000000..64b47458
--- /dev/null
+++ b/qiming-run_code_rmcp/examples/cursor_mcp_config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Script Runner",
+ "description": "执行JavaScript、TypeScript和Python代码",
+ "path": "/path/to/script_runner",
+ "transport": "stdio"
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/examples/mcp_client.js b/qiming-run_code_rmcp/examples/mcp_client.js
new file mode 100644
index 00000000..936d4bb2
--- /dev/null
+++ b/qiming-run_code_rmcp/examples/mcp_client.js
@@ -0,0 +1,151 @@
+// MCP客户端示例,用于与script_runner交互
+// 使用方法:node mcp_client.js
+
+const { spawn } = require('node:child_process');
+const readline = require('node:readline');
+
+// 启动script_runner进程
+const scriptRunner = spawn('script_runner', ['--verbose']);
+
+// 创建读取标准输入的接口
+const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout
+});
+
+// 处理script_runner的输出
+scriptRunner.stdout.on('data', (data) => {
+ console.log(`接收到MCP服务器响应: ${data}`);
+ try {
+ const response = JSON.parse(data);
+ console.log('解析后的响应:', JSON.stringify(response, null, 2));
+ } catch (e) {
+ console.log('无法解析为JSON,显示原始输出');
+ }
+});
+
+// 处理script_runner的错误
+scriptRunner.stderr.on('data', (data) => {
+ console.error(`MCP服务器错误: ${data}`);
+});
+
+// 处理script_runner进程结束
+scriptRunner.on('close', (code) => {
+ console.log(`MCP服务器进程退出,退出码: ${code}`);
+ rl.close();
+});
+
+// 初始化MCP连接
+const initializeRequest = {
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'initialize',
+ params: {
+ client_info: {
+ name: 'mcp_client_example'
+ }
+ }
+};
+
+// 发送初始化请求
+console.log('发送初始化请求...');
+scriptRunner.stdin.write(JSON.stringify(initializeRequest) + '\n');
+
+// 等待初始化响应后,发送工具列表请求
+setTimeout(() => {
+ const listToolsRequest = {
+ jsonrpc: '2.0',
+ id: 2,
+ method: 'listTools'
+ };
+ console.log('发送工具列表请求...');
+ scriptRunner.stdin.write(JSON.stringify(listToolsRequest) + '\n');
+}, 1000);
+
+// 等待工具列表响应后,执行JavaScript代码
+setTimeout(() => {
+ const callToolRequest = {
+ jsonrpc: '2.0',
+ id: 3,
+ method: 'callTool',
+ params: {
+ name: 'run_javascript',
+ arguments: {
+ code: 'console.log("Hello from JavaScript!"); return { message: "Hello, World!", value: 42 };'
+ }
+ }
+ };
+ console.log('发送JavaScript代码执行请求...');
+ scriptRunner.stdin.write(JSON.stringify(callToolRequest) + '\n');
+}, 2000);
+
+// 等待JavaScript执行响应后,执行TypeScript代码
+setTimeout(() => {
+ const callToolRequest = {
+ jsonrpc: '2.0',
+ id: 4,
+ method: 'callTool',
+ params: {
+ name: 'run_typescript',
+ arguments: {
+ code: 'const greeting: string = "Hello from TypeScript!"; console.log(greeting); return { message: greeting, timestamp: new Date().toISOString() };'
+ }
+ }
+ };
+ console.log('发送TypeScript代码执行请求...');
+ scriptRunner.stdin.write(JSON.stringify(callToolRequest) + '\n');
+}, 3000);
+
+// 等待TypeScript执行响应后,执行Python代码
+setTimeout(() => {
+ const callToolRequest = {
+ jsonrpc: '2.0',
+ id: 5,
+ method: 'callTool',
+ params: {
+ name: 'run_python',
+ arguments: {
+ code: 'import math\nprint("Hello from Python!")\nresult = math.sqrt(16)\nprint(f"Square root of 16 is {result}")\n{"message": "Hello from Python", "result": result}'
+ }
+ }
+ };
+ console.log('发送Python代码执行请求...');
+ scriptRunner.stdin.write(JSON.stringify(callToolRequest) + '\n');
+}, 4000);
+
+// 处理用户输入,允许用户发送自定义请求
+rl.on('line', (input) => {
+ if (input.toLowerCase() === 'exit') {
+ console.log('退出程序...');
+ scriptRunner.kill();
+ rl.close();
+ return;
+ }
+
+ try {
+ // 尝试解析用户输入为JSON
+ const request = JSON.parse(input);
+ console.log('发送自定义请求:', JSON.stringify(request, null, 2));
+ scriptRunner.stdin.write(JSON.stringify(request) + '\n');
+ } catch (e) {
+ console.error('输入不是有效的JSON:', e.message);
+ }
+});
+
+// 显示帮助信息
+console.log('\n=== MCP客户端示例 ===');
+console.log('已自动发送初始化、工具列表和代码执行请求');
+console.log('您可以输入自定义JSON请求,或输入"exit"退出程序');
+console.log('示例请求:');
+console.log(JSON.stringify({
+ jsonrpc: '2.0',
+ id: 100,
+ method: 'callTool',
+ params: {
+ name: 'run_javascript',
+ arguments: {
+ code: 'return "Hello, " + new Date().toISOString();'
+ }
+ }
+}, null, 2));
+console.log('====================\n');
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/examples/mcp_client_example.js b/qiming-run_code_rmcp/examples/mcp_client_example.js
new file mode 100644
index 00000000..621a7f5a
--- /dev/null
+++ b/qiming-run_code_rmcp/examples/mcp_client_example.js
@@ -0,0 +1,78 @@
+// 这是一个使用JavaScript调用MCP服务的示例
+// 你可以使用任何支持MCP协议的客户端来调用我们的服务
+// 以下是一个简单的示例,展示如何调用我们的服务
+
+// 假设我们已经连接到了MCP服务
+
+// 执行JavaScript代码示例
+async function testRunJavaScript() {
+ const code = `
+ // 一些处理代码
+ console.log("Processing JavaScript...");
+
+ function handler(input) {
+ // 输入包含通过参数传递的数据
+ console.log("Received input:", input);
+
+ // 返回最终结果
+ return "Hello from JavaScript! Input: " + JSON.stringify(input);
+ }
+ `;
+
+ const params = { name: "JavaScript User", data: [1, 2, 3] };
+
+ // 在实际的MCP客户端中,你会使用类似以下的代码调用服务
+ // const result = await client.run_javascript({ code, params });
+ // console.log(result);
+}
+
+// 执行TypeScript代码示例
+async function testRunTypeScript() {
+ const code = `
+ // 一些处理代码
+ console.log("Processing TypeScript...");
+
+ function handler(input: any): string {
+ // 输入包含通过参数传递的数据
+ console.log("Received input:", input);
+
+ // 返回最终结果
+ return \`Hello from TypeScript! Input: \${JSON.stringify(input)}\`;
+ }
+ `;
+
+ const params = { name: "TypeScript User", count: 42 };
+
+ // 在实际的MCP客户端中,你会使用类似以下的代码调用服务
+ // const result = await client.run_typescript({ code, params });
+ // console.log(result);
+}
+
+// 执行Python代码示例
+async function testRunPython() {
+ const code = `
+# 一些处理代码
+print("Processing Python...")
+
+def handler(args):
+ # args 包含通过参数传递的数据
+ print(f"Received args: {args}")
+
+ # 返回最终结果
+ return f"Hello from Python! Args: {args}"
+ `;
+
+ const params = { name: "Python User", values: [10, 20, 30] };
+
+ // 在实际的MCP客户端中,你会使用类似以下的代码调用服务
+ // const result = await client.run_python({ code, params });
+ // console.log(result);
+}
+
+// 在实际的MCP客户端中,你可以按顺序调用这些函数
+// await testRunJavaScript();
+// await testRunTypeScript();
+// await testRunPython();
+
+// 注意:这个文件只是一个示例,不能直接运行
+// 你需要使用支持MCP协议的客户端(如Cursor)来调用我们的服务
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/examples/test_mcp.sh b/qiming-run_code_rmcp/examples/test_mcp.sh
new file mode 100644
index 00000000..4b12c562
--- /dev/null
+++ b/qiming-run_code_rmcp/examples/test_mcp.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# 测试script_runner的简单bash脚本
+
+# 初始化请求
+echo "发送初始化请求..."
+echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"client_info":{"name":"test_client"}}}' | script_runner
+
+# 等待一下
+sleep 1
+
+# 列出工具请求
+echo -e "\n发送列出工具请求..."
+echo '{"jsonrpc":"2.0","id":2,"method":"listTools"}' | script_runner
+
+# 等待一下
+sleep 1
+
+# 执行JavaScript代码
+echo -e "\n发送JavaScript代码执行请求..."
+echo '{"jsonrpc":"2.0","id":3,"method":"callTool","params":{"name":"run_javascript","arguments":{"code":"console.log(\"Hello from JavaScript!\"); return { message: \"Hello, World!\", value: 42 };"}}}' | script_runner
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/cow_say_hello.js b/qiming-run_code_rmcp/fixtures/cow_say_hello.js
new file mode 100644
index 00000000..408eff61
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/cow_say_hello.js
@@ -0,0 +1,24 @@
+import * as o from 'https://deno.land/x/cowsay/mod.ts'
+
+// 使用类型注解时应确保使用的是支持该特性的环境或编译器(如 TypeScript)
+function add(input) {
+ console.log("test Add", input.a, "+", input.b);
+
+ let m = o.say({
+ text: 'hello every one'
+ })
+ console.log(m)
+
+ return input.a + input.b;
+}
+
+
+export default async function handler(input) {
+
+ let a = add(input)
+ console.log("计算结果=" + a)
+
+ return {
+ message: a,
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/import_axios_example.js b/qiming-run_code_rmcp/fixtures/import_axios_example.js
new file mode 100644
index 00000000..9edc8330
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/import_axios_example.js
@@ -0,0 +1,30 @@
+import axios from 'npm:axios';
+
+// 使用axios发起HTTP请求的函数
+async function fetchData(url) {
+ console.log(`正在请求: ${url}`);
+ try {
+ const response = await axios.get(url);
+ console.log(`请求成功,状态码: ${response.status}`);
+ return response.data;
+ } catch (error) {
+ console.error(`请求失败: ${error.message}`);
+ return { error: error.message };
+ }
+}
+
+export default async function handler(input) {
+ // 默认URL或从输入参数获取
+ const url = input.url || 'https://jsonplaceholder.typicode.com/todos/1';
+
+ console.log(`开始处理请求,URL: ${url}`);
+
+ // 发起请求并获取数据
+ const data = await fetchData(url);
+
+ return {
+ message: '请求完成',
+ data: data,
+ timestamp: new Date().toISOString()
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/import_deno_std_example.js b/qiming-run_code_rmcp/fixtures/import_deno_std_example.js
new file mode 100644
index 00000000..9ad808c1
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/import_deno_std_example.js
@@ -0,0 +1,79 @@
+import { join, basename } from "https://deno.land/std/path/mod.ts";
+import { readLines } from "https://deno.land/std/io/mod.ts";
+import { format } from "https://deno.land/std/datetime/mod.ts";
+
+// 使用Deno标准库处理路径和文件
+async function processPath(path) {
+ console.log(`处理路径: ${path}`);
+
+ // 使用path模块处理路径
+ const fileName = basename(path);
+ console.log(`文件名: ${fileName}`);
+
+ const fullPath = join("/tmp", fileName);
+ console.log(`完整路径: ${fullPath}`);
+
+ // 获取当前时间并格式化
+ const now = new Date();
+ const formattedDate = format(now, "yyyy-MM-dd HH:mm:ss");
+ console.log(`当前时间: ${formattedDate}`);
+
+ return {
+ originalPath: path,
+ fileName: fileName,
+ fullPath: fullPath,
+ timestamp: formattedDate
+ };
+}
+
+// 模拟读取文件内容
+async function simulateFileReading() {
+ const text = `这是第一行
+这是第二行
+这是第三行
+这是最后一行`;
+
+ // 使用TextEncoder和StringReader代替Deno.Buffer
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+ const data = encoder.encode(text);
+
+ // 创建一个可读流
+ const stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue(data);
+ controller.close();
+ }
+ });
+
+ // 将流转换为Reader
+ const reader = stream.getReader();
+
+ console.log("开始读取文件内容:");
+
+ // 手动处理文本行
+ const lines = text.split('\n');
+ for (let i = 0; i < lines.length; i++) {
+ console.log(`第${i+1}行: ${lines[i]}`);
+ }
+
+ return lines;
+}
+
+export default async function handler(input) {
+ console.log("开始处理");
+
+ // 处理路径
+ const path = input.path || "example.txt";
+ const pathInfo = await processPath(path);
+
+ // 模拟读取文件
+ const fileLines = await simulateFileReading();
+
+ return {
+ message: '处理完成',
+ pathInfo: pathInfo,
+ fileContent: fileLines,
+ linesCount: fileLines.length
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/import_esm_module_example.js b/qiming-run_code_rmcp/fixtures/import_esm_module_example.js
new file mode 100644
index 00000000..ac634a1a
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/import_esm_module_example.js
@@ -0,0 +1,59 @@
+// 使用ES模块导入方式
+import { createHash } from "node:crypto";
+import { Buffer } from "node:buffer";
+
+// 使用Node.js内置模块处理数据
+function hashData(data, algorithm = 'sha256') {
+ console.log(`使用 ${algorithm} 算法处理数据`);
+
+ // 将数据转换为Buffer
+ const buffer = typeof data === 'string'
+ ? Buffer.from(data)
+ : Buffer.from(JSON.stringify(data));
+
+ // 创建哈希
+ const hash = createHash(algorithm);
+ hash.update(buffer);
+
+ // 获取哈希结果
+ const digest = hash.digest('hex');
+ console.log(`哈希结果: ${digest}`);
+
+ return digest;
+}
+
+// 加密数据
+function encryptData(data, salt = 'default-salt') {
+ console.log(`加密数据,使用盐值: ${salt}`);
+
+ // 组合数据和盐值
+ const combined = `${data}:${salt}:${Date.now()}`;
+
+ // 生成多种哈希
+ const sha256Hash = hashData(combined, 'sha256');
+ const md5Hash = hashData(combined, 'md5');
+
+ return {
+ original: data,
+ salt,
+ sha256: sha256Hash,
+ md5: md5Hash,
+ timestamp: new Date().toISOString()
+ };
+}
+
+export default async function handler(input) {
+ console.log("开始处理加密任务");
+
+ // 获取输入数据
+ const data = input.data || "这是需要加密的默认数据";
+ const salt = input.salt || "custom-salt-" + Math.floor(Math.random() * 1000);
+
+ // 加密数据
+ const encryptedResult = encryptData(data, salt);
+
+ return {
+ message: '加密处理完成',
+ result: encryptedResult
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/import_jsr_example.js b/qiming-run_code_rmcp/fixtures/import_jsr_example.js
new file mode 100644
index 00000000..10e8db25
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/import_jsr_example.js
@@ -0,0 +1,79 @@
+import { assertEquals } from "jsr:@std/assert";
+import { delay } from "jsr:@std/async";
+import { bold, red, green } from "jsr:@std/fmt/colors";
+
+// 使用JSR标准库中的工具函数
+async function runTests(input) {
+ console.log(bold("开始运行测试..."));
+
+ // 使用delay函数模拟异步操作
+ console.log("等待1秒...");
+ await delay(1000);
+
+ // 测试结果数组
+ const results = [];
+
+ // 测试1: 加法运算
+ try {
+ const a = input.a || 5;
+ const b = input.b || 3;
+ const expected = input.expected || 8;
+
+ console.log(`测试加法: ${a} + ${b} = ${expected}`);
+ assertEquals(a + b, expected);
+
+ console.log(green("✓ 加法测试通过"));
+ results.push({ name: "加法测试", success: true });
+ } catch (error) {
+ console.log(red(`✗ 加法测试失败: ${error.message}`));
+ results.push({ name: "加法测试", success: false, error: error.message });
+ }
+
+ // 测试2: 字符串操作
+ try {
+ const str1 = input.str1 || "hello";
+ const str2 = input.str2 || "world";
+ const expectedStr = input.expectedStr || "hello world";
+
+ console.log(`测试字符串连接: "${str1}" + " " + "${str2}" = "${expectedStr}"`);
+ assertEquals(`${str1} ${str2}`, expectedStr);
+
+ console.log(green("✓ 字符串测试通过"));
+ results.push({ name: "字符串测试", success: true });
+ } catch (error) {
+ console.log(red(`✗ 字符串测试失败: ${error.message}`));
+ results.push({ name: "字符串测试", success: false, error: error.message });
+ }
+
+ // 等待再次展示结果
+ console.log("处理结果中...");
+ await delay(500);
+
+ return results;
+}
+
+export default async function handler(input) {
+ console.log(bold("JSR依赖示例"));
+
+ // 运行测试
+ const testResults = await runTests(input);
+
+ // 统计结果
+ const passed = testResults.filter(r => r.success).length;
+ const failed = testResults.filter(r => !r.success).length;
+
+ // 使用彩色输出显示结果
+ console.log(bold("\n测试结果摘要:"));
+ console.log(green(`通过: ${passed}`));
+ console.log(failed > 0 ? red(`失败: ${failed}`) : green(`失败: ${failed}`));
+
+ return {
+ message: '测试完成',
+ results: testResults,
+ summary: {
+ total: testResults.length,
+ passed,
+ failed
+ }
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/import_local_module_example.js b/qiming-run_code_rmcp/fixtures/import_local_module_example.js
new file mode 100644
index 00000000..a29b548e
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/import_local_module_example.js
@@ -0,0 +1,74 @@
+// 首先创建一个本地模块,通过相对路径引入
+// 注意:这个示例假设有一个名为utils.js的本地模块
+
+// 模拟本地utils.js模块的内容
+const utils = {
+ formatNumber(num) {
+ return num.toLocaleString('zh-CN');
+ },
+
+ calculateTax(amount, rate = 0.1) {
+ return amount * rate;
+ },
+
+ generateId() {
+ return `id-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
+ },
+
+ formatDate(date = new Date()) {
+ return date.toLocaleDateString('zh-CN');
+ }
+};
+
+// 使用本地模块的函数
+function processOrder(order) {
+ console.log(`处理订单: ${order.id || utils.generateId()}`);
+
+ // 计算订单总额
+ const total = order.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
+ console.log(`订单总额: ${utils.formatNumber(total)}`);
+
+ // 计算税费
+ const taxRate = order.taxRate || 0.13;
+ const tax = utils.calculateTax(total, taxRate);
+ console.log(`税费(${taxRate * 100}%): ${utils.formatNumber(tax)}`);
+
+ // 计算最终金额
+ const finalAmount = total + tax;
+ console.log(`最终金额: ${utils.formatNumber(finalAmount)}`);
+
+ // 生成订单日期
+ const orderDate = order.date ? new Date(order.date) : new Date();
+ console.log(`订单日期: ${utils.formatDate(orderDate)}`);
+
+ return {
+ id: order.id || utils.generateId(),
+ items: order.items,
+ subtotal: total,
+ tax: tax,
+ total: finalAmount,
+ date: utils.formatDate(orderDate)
+ };
+}
+
+export default async function handler(input) {
+ console.log("开始处理订单");
+
+ // 默认订单或从输入获取
+ const order = input.order || {
+ items: [
+ { name: "产品A", price: 100, quantity: 2 },
+ { name: "产品B", price: 50, quantity: 1 },
+ { name: "产品C", price: 200, quantity: 3 }
+ ],
+ taxRate: 0.13
+ };
+
+ // 处理订单
+ const processedOrder = processOrder(order);
+
+ return {
+ message: '订单处理完成',
+ order: processedOrder
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/import_lodash_example.js b/qiming-run_code_rmcp/fixtures/import_lodash_example.js
new file mode 100644
index 00000000..5400af9b
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/import_lodash_example.js
@@ -0,0 +1,43 @@
+import _ from 'npm:lodash';
+
+// 使用lodash处理数据的函数
+function processData(input) {
+ console.log("输入数据:", input);
+
+ // 使用lodash的chunk方法将数组分块
+ if (Array.isArray(input.data)) {
+ const chunks = _.chunk(input.data, input.chunkSize || 2);
+ console.log("数据分块结果:", chunks);
+
+ // 使用lodash的map方法处理每个块
+ const processedChunks = _.map(chunks, (chunk) => {
+ return {
+ items: chunk,
+ sum: _.sum(chunk),
+ average: _.mean(chunk)
+ };
+ });
+
+ return processedChunks;
+ }
+
+ return { error: "输入数据不是数组" };
+}
+
+export default async function handler(input) {
+ console.log("开始处理数据");
+
+ // 从输入获取数据,或使用默认数据
+ const data = input.data || [1, 2, 3, 4, 5, 6, 7, 8, 9];
+ const chunkSize = input.chunkSize || 3;
+
+ // 处理数据
+ const result = processData({ data, chunkSize });
+
+ return {
+ message: '数据处理完成',
+ originalData: data,
+ processedData: result,
+ timestamp: new Date().toISOString()
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/rfunction_js_test3.js b/qiming-run_code_rmcp/fixtures/rfunction_js_test3.js
new file mode 100644
index 00000000..6c10280c
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/rfunction_js_test3.js
@@ -0,0 +1,13 @@
+//引入js组件,必须是http(s)形式引入,可以去网站: https://deno.land/x 搜索自己所需组件
+//下面是一个引入示例
+//import * as o from https://deno.land/x/cowsay/mod.ts
+//网络请求,可以直接使用fetch ,具体自行查阅 fetch 文档
+//打印日志使用console,比如:console.log
+
+// 入口函数不可修改,否则无法执行,args 为配置的入参
+export default async function main(args) {
+ // 构建输出对象,出参中的key需与配置的出参保持一致
+ return {
+ key: 'value',
+ };
+}
diff --git a/qiming-run_code_rmcp/fixtures/rfunction_python.py b/qiming-run_code_rmcp/fixtures/rfunction_python.py
new file mode 100644
index 00000000..9eb7f648
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/rfunction_python.py
@@ -0,0 +1,26 @@
+import requests
+from bs4 import BeautifulSoup
+
+# 发送请求到百度
+url = 'https://www.baidu.com'
+response = requests.get(url)
+
+# 检查请求是否成功
+if response.status_code == 200:
+ # 解析网页内容
+ soup = BeautifulSoup(response.text, 'html.parser')
+
+ # 提取你想要的信息,例如标题
+ title = soup.title.string
+else:
+ print(f"请求失败,状态码:")
+
+
+# 入口函数不可修改,否则无法执行,args 为配置的入参
+def main(args: dict) -> dict:
+ params = args.get("params")
+ # 构建输出对象,出参中的key需与配置的出参保持一致
+ ret = {
+ "key": "value"
+ }
+ return ret
diff --git a/qiming-run_code_rmcp/fixtures/rfunction_test1.js b/qiming-run_code_rmcp/fixtures/rfunction_test1.js
new file mode 100644
index 00000000..08bb4dee
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/rfunction_test1.js
@@ -0,0 +1,20 @@
+// 使用类型注解时应确保使用的是支持该特性的环境或编译器(如 TypeScript)
+function add(input) {
+ console.log("test Add", input.a, "+", input.b);
+
+ // 简单的字符串输出,不使用外部模块
+ console.log("Hello from JavaScript!");
+
+ return input.a + input.b;
+}
+
+function handler(input) {
+ console.log("输入参数:", JSON.stringify(input));
+
+ let a = add(input);
+ console.log("计算结果=" + a);
+
+ return {
+ message: a,
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/rfunction_test2.py b/qiming-run_code_rmcp/fixtures/rfunction_test2.py
new file mode 100644
index 00000000..2e2571c5
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/rfunction_test2.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+import pandas as pd
+
+import logging
+import json
+
+# 创建一个简单的数据结构,不使用pandas
+data = {
+ 'Name': ['Alice', 'Bob', 'Charlie'],
+ 'Age': [25, 30, 35]
+}
+df = pd.DataFrame(data)
+# 记录数据信息
+logging.info(f"Created data structure:\n{json.dumps(data, indent=2)}")
+
+def main(args: dict) -> dict:
+ logging.info(f"input args: {args}")
+
+ params = args.get("params")
+
+ # 处理params为None的情况
+ if params is None:
+ logging.warning("params is None, using default values")
+ params = {"input": "default_value"}
+ elif not isinstance(params, dict):
+ logging.warning(f"params is not a dictionary: {type(params)}, using default values")
+ params = {"input": str(params)}
+ elif "input" not in params:
+ logging.warning("input key not found in params, using default value")
+ params["input"] = "default_value"
+
+ # 构建输出对象
+ ret = {
+ "key0": params['input'], # 使用input参数值
+ "key1": ["hello", "world"], # 输出一个数组
+ "key2": { # 输出一个Object
+ "key21": "hi"
+ },
+ }
+ return ret
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_js.js b/qiming-run_code_rmcp/fixtures/test_js.js
new file mode 100644
index 00000000..58e0bb01
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_js.js
@@ -0,0 +1,25 @@
+// Sample JavaScript file with handler function
+
+// Log some debug information
+console.log("Initializing JavaScript test...");
+
+// Define a simple add function
+function add(a, b) {
+ console.log(`Adding ${a} + ${b}`);
+ return a + b;
+}
+
+// Some processing
+const numbers = [1, 2, 3, 4, 5];
+console.log("Processing numbers:", numbers);
+
+// Handler function that will be called to get the result
+function handler() {
+ console.log("Handler function called");
+
+ // Calculate sum
+ const sum = numbers.reduce((acc, num) => add(acc, num), 0);
+ console.log("Final calculation completed");
+
+ return `The sum of [${numbers.join(", ")}] is ${sum}`;
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_js_large_params.js b/qiming-run_code_rmcp/fixtures/test_js_large_params.js
new file mode 100644
index 00000000..16076c8b
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_js_large_params.js
@@ -0,0 +1,75 @@
+// Test JavaScript script with large text parameters to verify "Argument list too long" fix
+
+// Generate a large text parameter (>2MB) to test the temporary file parameter passing solution
+function generateLargeText(sizeInMB) {
+ const chunk = "This is a test chunk of text designed to generate large parameters. ".repeat(100);
+ const iterations = Math.ceil((sizeInMB * 1024 * 1024) / chunk.length);
+
+ let result = "";
+ for (let i = 0; i < iterations; i++) {
+ result += chunk + `Iteration ${i}\n`;
+ }
+
+ return result;
+}
+
+// Handler function that processes large text parameters
+function handler(args) {
+ console.log("Handler function called with large parameters");
+
+ // Test that we received the large text parameter
+ if (!args || !args.largeText) {
+ console.log("ERROR: No largeText parameter found");
+ return { success: false, error: "Missing largeText parameter" };
+ }
+
+ const largeText = args.largeText;
+ console.log("Received large text parameter, length:", largeText.length);
+ console.log("Large text parameter size (MB):", (largeText.length / 1024 / 1024).toFixed(2));
+
+ // Verify the text contains expected content
+ if (largeText.includes("This is a test chunk of text designed to generate large parameters")) {
+ console.log("SUCCESS: Large text parameter contains expected content");
+
+ // Count occurrences of "Iteration" to verify data integrity
+ const iterationCount = (largeText.match(/Iteration \d+/g) || []).length;
+ console.log("Found", iterationCount, "iterations in the text");
+
+ // Return some statistics about the processed data
+ return {
+ success: true,
+ message: "Successfully processed large text parameter",
+ textSize: largeText.length,
+ sizeMB: (largeText.length / 1024 / 1024).toFixed(2),
+ iterationCount: iterationCount,
+ sampleText: largeText.substring(0, 100) + "..."
+ };
+ } else {
+ console.log("ERROR: Large text parameter does not contain expected content");
+ return { success: false, error: "Invalid content in largeText parameter" };
+ }
+}
+
+// This part won't be executed when the code is run through the MCP runner
+if (typeof require !== 'undefined' && require.main === module) {
+ console.log("Running large parameter test directly...");
+
+ // Generate test data (3MB to ensure we exceed the 2MB threshold)
+ const largeText = generateLargeText(3);
+ console.log("Generated test data, size:", (largeText.length / 1024 / 1024).toFixed(2), "MB");
+
+ // Create test arguments
+ const testArgs = {
+ largeText: largeText,
+ additionalParam: "test value",
+ numberParam: 42
+ };
+
+ console.log("Calling handler with large parameters...");
+ const result = handler(testArgs);
+ console.log("Result:", JSON.stringify(result, null, 2));
+}
+
+if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = { handler, generateLargeText };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_js_params.js b/qiming-run_code_rmcp/fixtures/test_js_params.js
new file mode 100644
index 00000000..65301562
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_js_params.js
@@ -0,0 +1,39 @@
+// JavaScript测试文件,演示参数传递
+
+// 打印一些调试信息
+console.log("JavaScript脚本开始执行...");
+
+// 定义一个加法函数
+function add(a, b) {
+ console.log(`正在计算: ${a} + ${b}`);
+ return a + b;
+}
+
+// 处理一些数据
+const numbers = [1, 2, 3, 4, 5];
+console.log(`处理数字列表: ${numbers}`);
+
+/**
+ * 处理函数,接收参数并返回结果
+ * 注意:这个函数必须存在,并且会被框架调用来获取结果
+ */
+function handler(input) {
+ console.log(`接收到的参数: ${JSON.stringify(input)}`);
+
+ // 从参数中获取值,提供默认值
+ const a = input.a || 0;
+ const b = input.b || 0;
+ const name = input.name || "用户";
+
+ // 计算结果
+ const result = add(a, b);
+ console.log(`计算完成: ${a} + ${b} = ${result}`);
+
+ // 返回结果
+ return {
+ sum: result,
+ numbers: numbers,
+ greeting: `你好,${name}!`,
+ message: `成功计算 ${a} + ${b} 的结果`
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_python.py b/qiming-run_code_rmcp/fixtures/test_python.py
new file mode 100644
index 00000000..c5685101
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_python.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# Sample Python file with handler function
+
+import time
+time.time()
+# Debug print to confirm script execution
+print("DEBUGGING: Script is being executed!")
+
+# Log some debug information
+print("Initializing Python test...")
+
+# Define a simple function
+def multiply(a, b):
+ print(f"Multiplying {a} * {b}")
+ return a * b
+
+# Some processing
+numbers = [1, 2, 3, 4, 5]
+print(f"Processing numbers: {numbers}")
+
+# Handler function that will be called to get the result
+def handler(args):
+ print("Handler function called")
+ print(f"Received args: {args}")
+
+ # Calculate product (using the predefined numbers)
+ product = 1
+ for num in numbers:
+ product = multiply(product, num)
+
+ print("Final calculation completed")
+
+ return f"The product of {numbers} is {product}"
+
+# This part won't be executed when the code is run through the MCP runner
+if __name__ == "__main__":
+ print("Running directly, calling handler...")
+ # Create sample args for direct execution
+ sample_args = {"test": "direct_execution", "data": [1, 2, 3]}
+ result = handler(sample_args)
+ print(f"Result: {result}")
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_python_large_params.py b/qiming-run_code_rmcp/fixtures/test_python_large_params.py
new file mode 100644
index 00000000..6dcb0285
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_python_large_params.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+# Test Python script with large text parameters to verify "Argument list too long" fix
+
+import sys
+import json
+import time
+
+# Generate a large text parameter (>2MB) to test the temporary file parameter passing solution
+def generate_large_text(size_in_mb):
+ chunk = "This is a test chunk of text designed to generate large parameters for Python testing. " * 100
+ iterations = (size_in_mb * 1024 * 1024) // len(chunk)
+
+ result = ""
+ for i in range(iterations):
+ result += chunk + f"Iteration {i}\n"
+
+ return result
+
+# Handler function that processes large text parameters
+def handler(args):
+ print("Handler function called with large parameters")
+
+ # Test that we received the large text parameter
+ if not args or 'largeText' not in args:
+ print("ERROR: No largeText parameter found")
+ return {"success": False, "error": "Missing largeText parameter"}
+
+ large_text = args['largeText']
+ print(f"Received large text parameter, length: {len(large_text)}")
+ print(f"Large text parameter size (MB): {len(large_text) / 1024 / 1024:.2f}")
+
+ # Verify the text contains expected content
+ expected_content = "This is a test chunk of text designed to generate large parameters for Python testing"
+ if expected_content in large_text:
+ print("SUCCESS: Large text parameter contains expected content")
+
+ # Count occurrences of "Iteration" to verify data integrity
+ iteration_count = large_text.count("Iteration")
+ print(f"Found {iteration_count} iterations in the text")
+
+ return {
+ "success": True,
+ "message": "Successfully processed large text parameter",
+ "textSize": len(large_text),
+ "sizeMB": round(len(large_text) / 1024 / 1024, 2),
+ "iterationCount": iteration_count,
+ "sampleText": large_text[:100] + "..."
+ }
+ else:
+ print("ERROR: Large text parameter does not contain expected content")
+ return {"success": False, "error": "Invalid content in largeText parameter"}
+
+# This part won't be executed when the code is run through the MCP runner
+if __name__ == "__main__":
+ print("Running large parameter test directly...")
+
+ # Generate test data (3MB to ensure we exceed the 2MB threshold)
+ large_text = generate_large_text(3)
+ print(f"Generated test data, size: {len(large_text) / 1024 / 1024:.2f} MB")
+
+ # Create test arguments
+ test_args = {
+ "largeText": large_text,
+ "additionalParam": "test value",
+ "numberParam": 42
+ }
+
+ print("Calling handler with large parameters...")
+ result = handler(test_args)
+ print("Result:", json.dumps(result, indent=2))
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_python_logging.py b/qiming-run_code_rmcp/fixtures/test_python_logging.py
new file mode 100644
index 00000000..771f4f42
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_python_logging.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# 测试logging模块的日志捕获功能
+
+import logging
+import json
+
+# 配置日志
+logging.basicConfig(level=logging.DEBUG)
+
+# 使用不同级别的日志
+def log_messages():
+ logging.debug("这是一条DEBUG级别的日志")
+ logging.info("这是一条INFO级别的日志")
+ logging.warning("这是一条WARNING级别的日志")
+ logging.error("这是一条ERROR级别的日志")
+ logging.critical("这是一条CRITICAL级别的日志")
+
+ # 使用格式化
+ data = {"name": "测试", "value": 42}
+ logging.info(f"格式化的JSON数据: {json.dumps(data, ensure_ascii=False)}")
+
+ return "日志测试完成"
+
+def handler(args):
+ # 记录输入参数
+ logging.info(f"收到参数: {args}")
+
+ # 生成一些日志
+ result = log_messages()
+
+ # 返回结果
+ return {
+ "message": result,
+ "log_count": 6, # 总共记录了6条日志
+ "args": args
+ }
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_python_params.py b/qiming-run_code_rmcp/fixtures/test_python_params.py
new file mode 100644
index 00000000..4307fb6a
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_python_params.py
@@ -0,0 +1,33 @@
+import json
+import os
+
+# 打印一些调试信息
+print("Python脚本开始执行...")
+
+# 定义一个简单的加法函数
+def add(a, b):
+ print("正在计算: " + str(a) + " + " + str(b))
+ return a + b
+
+# 处理一些数据
+numbers = [1, 2, 3, 4, 5]
+print("处理数字列表: " + str(numbers))
+
+# 入口函数,接收参数
+def main(args):
+ print("接收到的参数: " + str(args))
+
+ # 从参数中获取值
+ a = args.get("a", 0)
+ b = args.get("b", 0)
+
+ # 计算结果
+ result = add(a, b)
+ print("计算完成: " + str(a) + " + " + str(b) + " = " + str(result))
+
+ # 返回结果
+ return {
+ "sum": result,
+ "numbers": numbers,
+ "message": "成功计算 " + str(a) + " + " + str(b) + " 的结果"
+ }
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_python_simple.py b/qiming-run_code_rmcp/fixtures/test_python_simple.py
new file mode 100644
index 00000000..624a752f
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_python_simple.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+# 简单的Python测试脚本,用于测试参数传递机制
+
+def main(args: dict) -> dict:
+ print(f"接收到的参数: {args}")
+
+ # 尝试通过两种方式获取参数
+ direct_params = args.get("input", "direct_default")
+ nested_params = None
+
+ params = args.get("params")
+ if params:
+ if isinstance(params, dict):
+ nested_params = params.get("input", "nested_default")
+ else:
+ nested_params = str(params)
+
+ # 返回结果,展示两种方式获取的参数
+ return {
+ "direct_access": direct_params,
+ "nested_access": nested_params,
+ "args_structure": args
+ }
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_python_types.py b/qiming-run_code_rmcp/fixtures/test_python_types.py
new file mode 100644
index 00000000..3615b830
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_python_types.py
@@ -0,0 +1,46 @@
+import json
+import os
+
+# 打印一些调试信息
+print("Python类型测试脚本开始执行...")
+
+# 入口函数,接收参数并返回不同类型的值
+def main(args):
+ print("接收到的参数:", args)
+
+ # 获取要测试的类型
+ test_type = args.get("type", "string")
+
+ # 根据参数返回不同类型的值
+ if test_type == "string":
+ print("返回字符串类型")
+ return "这是一个字符串"
+
+ elif test_type == "number":
+ print("返回数字类型")
+ return 12345
+
+ elif test_type == "boolean":
+ print("返回布尔类型")
+ return True
+
+ elif test_type == "null":
+ print("返回None类型")
+ return None
+
+ elif test_type == "list":
+ print("返回列表类型")
+ return [1, 2, 3, "四", "五", True]
+
+ elif test_type == "dict":
+ print("返回字典类型")
+ return {
+ "name": "测试用户",
+ "age": 30,
+ "is_active": True,
+ "tags": ["python", "rust", "json"]
+ }
+
+ else:
+ print("未知类型,返回默认字符串")
+ return "未知类型"
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_ts.ts b/qiming-run_code_rmcp/fixtures/test_ts.ts
new file mode 100644
index 00000000..848a7d7a
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_ts.ts
@@ -0,0 +1,41 @@
+// Sample TypeScript file with handler function
+
+// Log some debug information
+console.log("Initializing TypeScript test...");
+
+// Define a simple add function with type annotations
+function add(a: number, b: number): number {
+ console.log(`Adding ${a} + ${b}`);
+ return a + b;
+}
+
+// Some processing with type annotations
+const numbers: number[] = [1, 2, 3, 4, 5];
+console.log("Processing numbers:", numbers);
+
+// Interface for a person object
+interface Person {
+ name: string;
+ age: number;
+}
+
+// Create a person object
+const person: Person = {
+ name: "TypeScript User",
+ age: 30
+};
+console.log("Person object:", person);
+
+/**
+ * Handler function that will be called to get the result
+ * 注意:这个函数必须存在,并且会被框架调用来获取结果
+ */
+function handler(): string {
+ console.log("Handler function called");
+
+ // Calculate sum
+ const sum = numbers.reduce((acc, num) => add(acc, num), 0);
+ console.log("Final calculation completed");
+
+ return `Hello ${person.name}! The sum of [${numbers.join(", ")}] is ${sum}`;
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/fixtures/test_ts_params.ts b/qiming-run_code_rmcp/fixtures/test_ts_params.ts
new file mode 100644
index 00000000..11c2eb23
--- /dev/null
+++ b/qiming-run_code_rmcp/fixtures/test_ts_params.ts
@@ -0,0 +1,46 @@
+// TypeScript测试文件,演示参数传递
+
+// 定义参数接口
+interface InputParams {
+ a: number;
+ b: number;
+ name?: string;
+}
+
+// 打印一些调试信息
+console.log("TypeScript脚本开始执行...");
+
+// 定义一个带类型的加法函数
+function add(a: number, b: number): number {
+ console.log(`正在计算: ${a} + ${b}`);
+ return a + b;
+}
+
+// 处理一些数据
+const numbers: number[] = [1, 2, 3, 4, 5];
+console.log(`处理数字列表: ${numbers}`);
+
+/**
+ * 处理函数,接收参数并返回结果
+ * 注意:这个函数必须存在,并且会被框架调用来获取结果
+ */
+function handler(input: InputParams): object {
+ console.log(`接收到的参数: ${JSON.stringify(input)}`);
+
+ // 从参数中获取值,提供默认值
+ const a = input.a || 0;
+ const b = input.b || 0;
+ const name = input.name || "用户";
+
+ // 计算结果
+ const result = add(a, b);
+ console.log(`计算完成: ${a} + ${b} = ${result}`);
+
+ // 返回结果
+ return {
+ sum: result,
+ numbers: numbers,
+ greeting: `你好,${name}!`,
+ message: `成功计算 ${a} + ${b} 的结果`
+ };
+}
\ No newline at end of file
diff --git a/qiming-run_code_rmcp/src/app_error.rs b/qiming-run_code_rmcp/src/app_error.rs
new file mode 100644
index 00000000..826bd1d0
--- /dev/null
+++ b/qiming-run_code_rmcp/src/app_error.rs
@@ -0,0 +1,10 @@
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum AppError {
+ #[error("Failed to create temporary file: {0}")]
+ IoError(#[from] std::io::Error),
+
+ #[error("Failed to parse JSON: {0}")]
+ JsonError(#[from] serde_json::Error),
+}
diff --git a/qiming-run_code_rmcp/src/cache/code_file_cache.rs b/qiming-run_code_rmcp/src/cache/code_file_cache.rs
new file mode 100644
index 00000000..f3c5beb5
--- /dev/null
+++ b/qiming-run_code_rmcp/src/cache/code_file_cache.rs
@@ -0,0 +1,131 @@
+use anyhow::{Context, Result};
+use log::info;
+use std::path::{Path, PathBuf};
+use tokio::fs::{self, File, create_dir_all};
+use tokio::io::AsyncWriteExt;
+
+use crate::model::LanguageScript;
+///针对用的代码,进行检测和缓存
+pub struct CodeFileCache;
+
+impl CodeFileCache {
+ /// 检查代码文件缓存
+ pub fn obtain_code_hash(code: &str) -> String {
+ // 使用BLAKE3计算哈希,更快速且安全
+ let hash = blake3::hash(code.as_bytes());
+ let hash_str = hash.to_hex().to_string();
+ info!("计算代码hash值: {}", &hash_str);
+ hash_str
+ }
+
+ /// 根据代码的hash检查是否存在缓存
+ pub async fn check_code_file_cache_exisht(hash: &str, language: &LanguageScript) -> bool {
+ fs::try_exists(Self::get_cache_file_path(hash, language))
+ .await
+ .unwrap_or(false)
+ }
+
+ /// 获取代码文件缓存
+ pub async fn get_code_file_cache(
+ hash: &str,
+ language: &LanguageScript,
+ ) -> Result<(File, PathBuf)> {
+ let file_path = Self::get_cache_file_path(hash, language);
+ let file = File::open(&file_path)
+ .await
+ .with_context(|| format!("无法打开缓存文件: {}", file_path.display()))?;
+ Ok((file, file_path))
+ }
+
+ /// 保存代码文件缓存
+ pub async fn save_code_file_cache(
+ hash: &str,
+ code: &str,
+ language: &LanguageScript,
+ ) -> Result<(File, PathBuf)> {
+ // 确保缓存目录存在
+ let cache_dir = Self::get_cache_dir();
+ //检查目录是否存在
+ if !fs::try_exists(&cache_dir).await.unwrap_or(false) {
+ create_dir_all(&cache_dir)
+ .await
+ .with_context(|| format!("无法创建缓存目录: {}", cache_dir.display()))?;
+ }
+
+ // 构建文件路径并保存代码
+ let file_path = Self::get_cache_file_path(hash, language);
+ let mut file = File::create(&file_path)
+ .await
+ .with_context(|| format!("无法创建缓存文件: {}", file_path.display()))?;
+
+ file.write_all(code.as_bytes())
+ .await
+ .with_context(|| "写入代码到缓存文件失败")?;
+
+ // 设置文件权限
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ let mut perms = fs::metadata(&file_path).await?.permissions();
+ perms.set_mode(0o755); // rwxr-xr-x
+ fs::set_permissions(&file_path, perms).await?;
+ }
+
+ let file_path = Self::get_cache_file_path(hash, language);
+ Ok((file, file_path))
+ }
+
+ /// 清除指定语言的缓存文件
+ pub async fn clear_cache_by_language(language: &LanguageScript) -> Result<()> {
+ let cache_dir = Self::get_cache_dir();
+ if !fs::try_exists(&cache_dir).await.unwrap_or(false) {
+ return Ok(());
+ }
+
+ let suffix = language.get_file_suffix();
+ let mut entries = fs::read_dir(cache_dir).await?;
+
+ while let Some(entry) = entries.next_entry().await? {
+ let path = entry.path();
+ if let Some(file_name) = path.file_name() {
+ if let Some(name_str) = file_name.to_str() {
+ if name_str.ends_with(suffix) {
+ fs::remove_file(&path)
+ .await
+ .with_context(|| format!("无法删除缓存文件: {}", path.display()))?;
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// 清除所有缓存文件
+ pub async fn clear_all_cache() -> Result<()> {
+ let cache_dir = Self::get_cache_dir();
+ if fs::try_exists(&cache_dir).await.unwrap_or(false) {
+ fs::remove_dir_all(&cache_dir)
+ .await
+ .with_context(|| format!("无法删除缓存目录: {}", cache_dir.display()))?;
+ }
+ Ok(())
+ }
+
+ /// 获取缓存目录路径
+ fn get_cache_dir() -> PathBuf {
+ // 在容器环境中使用固定路径
+ Path::new("/tmp/code_cache").to_path_buf()
+ }
+
+ /// 获取缓存文件路径
+ fn get_cache_file_path(hash: &str, language: &LanguageScript) -> PathBuf {
+ let mut path = Self::get_cache_dir();
+
+ // 根据语言类型添加对应的文件扩展名
+ let file_name = format!("{}{}", hash, language.get_file_suffix());
+
+ path.push(file_name);
+ path
+ }
+}
diff --git a/qiming-run_code_rmcp/src/cache/mod.rs b/qiming-run_code_rmcp/src/cache/mod.rs
new file mode 100644
index 00000000..bf41884d
--- /dev/null
+++ b/qiming-run_code_rmcp/src/cache/mod.rs
@@ -0,0 +1,3 @@
+mod code_file_cache;
+
+pub use code_file_cache::CodeFileCache;
diff --git a/qiming-run_code_rmcp/src/deno_runner/common_runner.rs b/qiming-run_code_rmcp/src/deno_runner/common_runner.rs
new file mode 100644
index 00000000..fd78228b
--- /dev/null
+++ b/qiming-run_code_rmcp/src/deno_runner/common_runner.rs
@@ -0,0 +1,117 @@
+use crate::cache::CodeFileCache;
+use crate::model::{CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript};
+use anyhow::Result;
+use log::{debug, error, info};
+use serde_json::Value;
+use std::io::Write;
+use std::path::PathBuf;
+use tempfile::NamedTempFile;
+use tokio::fs;
+use tokio::io::AsyncWriteExt;
+use tokio::process::Command;
+
+/// 通用的 Deno 脚本执行逻辑,供 JS/TS Runner 复用
+pub async fn run_deno_script_with_params(
+ code: &str,
+ params: Option,
+ timeout_seconds: Option,
+ lang: LanguageScript,
+ prepare_code_fn: F,
+) -> Result
+where
+ F: Fn(&str, bool) -> String,
+{
+ debug!("开始执行{lang:?}脚本...,执行参数: {params:?}");
+
+ let hash = CodeFileCache::obtain_code_hash(code);
+ let cache_exist = CodeFileCache::check_code_file_cache_exisht(&hash, &lang).await;
+
+ let run_code_script_file_tuple = if cache_exist {
+ let cache_code = CodeFileCache::get_code_file_cache(&hash, &lang).await;
+ debug!("从缓存中读取代码:hash值 {:?}", &hash);
+ cache_code?
+ } else {
+ let wrapped_code = prepare_code_fn(code, true);
+ CodeFileCache::save_code_file_cache(&hash, &wrapped_code, &lang).await?;
+ let code_script_file_tuple = CodeFileCache::get_code_file_cache(&hash, &lang).await?;
+ debug!("创建脚本缓存:hash值 {:?}", &hash);
+ code_script_file_tuple
+ };
+
+ let temp_path = run_code_script_file_tuple.1;
+
+ let mut execute_command = Command::new("deno");
+ execute_command
+ .arg("run")
+ .arg("--allow-net")
+ .arg("--allow-env")
+ .arg("--allow-read")
+ .arg("--no-check")
+ .arg("--v8-flags=--max-heap-size=512")
+ .arg(&temp_path)
+ .kill_on_drop(true);
+
+ // 处理参数:统一使用临时文件传递
+ let temp_input_path = if let Some(params) = params {
+ let params_json = serde_json::to_string(¶ms)?;
+
+ // 创建临时文件写入参数
+ let temp_dir = tempfile::TempDir::new()?;
+ let temp_file_path = temp_dir.path().join("input_params.json");
+
+ // 写入参数到临时文件
+ std::fs::write(&temp_file_path, params_json.as_bytes())?;
+
+ // 保持TempDir存在(这样文件就不会被删除)
+ let temp_dir_path = temp_dir.path().to_path_buf();
+ std::mem::forget(temp_dir);
+
+ // 设置环境变量指向临时文件
+ execute_command.env("INPUT_JSON_FILE", &temp_file_path);
+ debug!("使用临时文件传递参数,文件路径: {:?}", temp_file_path);
+
+ Some(temp_file_path)
+ } else {
+ // 没有参数时设置空对象
+ execute_command.env("INPUT_JSON", "{}");
+ None
+ };
+
+ debug!("Deno命令[{:?}]: {:?}", lang, &execute_command);
+
+ let executor = match timeout_seconds {
+ Some(timeout) => CommandExecutor::with_timeout(execute_command.output(), timeout),
+ None => CommandExecutor::default(execute_command.output()),
+ };
+ info!("执行命令: {:?}", &execute_command);
+
+ let executor_result = executor.await;
+ let output = match executor_result {
+ Ok(cmd_result) => match cmd_result {
+ Ok(output) => output,
+ Err(e) => {
+ error!("Deno命令执行失败 [{lang:?}]: {e:?}");
+ return Err(e.into());
+ }
+ },
+ Err(e) => {
+ error!("Deno任务执行异常 [{lang:?}]: {e:?}");
+ return Err(e.into());
+ }
+ };
+ debug!("标准输出:\n{}", String::from_utf8_lossy(&output.stdout));
+ debug!("错误输出:\n{}", String::from_utf8_lossy(&output.stderr));
+
+ // 执行完成后删除临时文件和目录
+ if let Some(temp_file_path) = temp_input_path {
+ // 删除文件
+ let _ = fs::remove_file(&temp_file_path).await;
+ // 尝试删除父目录(如果为空)
+ if let Some(parent) = temp_file_path.parent() {
+ let _ = fs::remove_dir(parent).await;
+ }
+ debug!("已删除临时文件: {:?}", temp_file_path);
+ }
+
+ CodeExecutor::parse_execution_output(&output.stdout, &output.stderr).await
+}
diff --git a/qiming-run_code_rmcp/src/deno_runner/js_runner.rs b/qiming-run_code_rmcp/src/deno_runner/js_runner.rs
new file mode 100644
index 00000000..9630076b
--- /dev/null
+++ b/qiming-run_code_rmcp/src/deno_runner/js_runner.rs
@@ -0,0 +1,61 @@
+// deno 运行js脚本
+use crate::deno_runner::common_runner::run_deno_script_with_params;
+use crate::model::{CodeScriptExecutionResult, LanguageScript, RunCode};
+use anyhow::Result;
+
+#[derive(Default)]
+pub struct JsRunner;
+
+
+impl RunCode for JsRunner {
+ async fn run_with_params(
+ &self,
+ code: &str,
+ params: Option,
+ timeout_seconds: Option,
+ ) -> Result {
+ run_deno_script_with_params(
+ code,
+ params,
+ timeout_seconds,
+ LanguageScript::Js,
+ |c, show_logs| self.prepare_js_code(c, show_logs),
+ )
+ .await
+ }
+}
+
+impl JsRunner {
+ /// 准备JavaScript代码,添加日志捕获和handler函数执行逻辑
+ fn prepare_js_code(&self, code: &str, show_logs: bool) -> String {
+ // 检查代码中是否包含ES模块特征
+ let is_esm = {
+ // 检查正式的 import/export 语句
+ let has_import_export = code.contains("import ")
+ || code.contains("export ")
+ || code.contains("import{")
+ || code.contains("export{");
+
+ // 检查动态 import
+ let has_dynamic_import = code.contains("import(");
+
+ // 检查是否有 require - CommonJS 的标志
+ let has_require = code.contains("require(");
+
+ // 如果有 import/export 特征,或者动态 import,但没有 require,则判定为 ESM
+ (has_import_export || has_dynamic_import) && !has_require
+ };
+
+ // 根据代码特征选择合适的模板
+ let template = if is_esm {
+ include_str!("../templates/js_template_es.js")
+ } else {
+ include_str!("../templates/js_template_normal.js")
+ };
+
+ // 替换模板中的占位符
+ template
+ .replace("{{USER_CODE}}", code)
+ .replace("{{SHOW_LOGS}}", &show_logs.to_string())
+ }
+}
diff --git a/qiming-run_code_rmcp/src/deno_runner/mod.rs b/qiming-run_code_rmcp/src/deno_runner/mod.rs
new file mode 100644
index 00000000..b3652922
--- /dev/null
+++ b/qiming-run_code_rmcp/src/deno_runner/mod.rs
@@ -0,0 +1,6 @@
+mod common_runner;
+mod js_runner;
+mod ts_runner;
+
+pub use js_runner::JsRunner;
+pub use ts_runner::TsRunner;
diff --git a/qiming-run_code_rmcp/src/deno_runner/ts_runner.rs b/qiming-run_code_rmcp/src/deno_runner/ts_runner.rs
new file mode 100644
index 00000000..d0252bf5
--- /dev/null
+++ b/qiming-run_code_rmcp/src/deno_runner/ts_runner.rs
@@ -0,0 +1,37 @@
+// deno 运行ts脚本
+use crate::model::{CodeScriptExecutionResult, LanguageScript, RunCode};
+use anyhow::Result;
+use crate::deno_runner::common_runner::run_deno_script_with_params;
+
+#[derive(Default)]
+pub struct TsRunner;
+
+impl RunCode for TsRunner {
+ async fn run_with_params(
+ &self,
+ code: &str,
+ params: Option,
+ timeout_seconds: Option,
+ ) -> Result {
+ run_deno_script_with_params(
+ code,
+ params,
+ timeout_seconds,
+ LanguageScript::Ts,
+ |c, show_logs| self.prepare_ts_code(c, show_logs),
+ ).await
+ }
+}
+
+
+impl TsRunner {
+
+ /// 准备TypeScript代码,添加日志捕获和handler函数执行逻辑
+ fn prepare_ts_code(&self, code: &str, show_logs: bool) -> String {
+ let template = include_str!("../templates/ts_template.ts");
+
+ template
+ .replace("{{USER_CODE}}", code)
+ .replace("{{SHOW_LOGS}}", &show_logs.to_string())
+ }
+}
diff --git a/qiming-run_code_rmcp/src/lib.rs b/qiming-run_code_rmcp/src/lib.rs
new file mode 100644
index 00000000..eb579329
--- /dev/null
+++ b/qiming-run_code_rmcp/src/lib.rs
@@ -0,0 +1,19 @@
+mod app_error;
+mod cache;
+mod deno_runner;
+#[cfg(feature = "mcp")]
+mod mcp;
+mod model;
+mod python_runner;
+#[cfg(test)]
+mod tests;
+mod warm_up;
+
+pub use cache::*;
+pub use deno_runner::*;
+#[cfg(feature = "mcp")]
+pub use mcp::CodeRunRequest;
+pub use model::RunCodeHttpResult;
+pub use model::{CodeExecutor, CodeScriptExecutionResult, LanguageScript};
+pub use python_runner::*;
+pub use warm_up::warm_up_all_envs;
diff --git a/qiming-run_code_rmcp/src/main.rs b/qiming-run_code_rmcp/src/main.rs
new file mode 100644
index 00000000..a068dfd4
--- /dev/null
+++ b/qiming-run_code_rmcp/src/main.rs
@@ -0,0 +1,194 @@
+use anyhow::{Context, Result};
+use clap::{Args, Parser, Subcommand};
+use log::{error, info};
+use serde_json::Value;
+use std::{fs, path::PathBuf};
+
+mod app_error;
+mod cache;
+mod deno_runner;
+#[cfg(feature = "mcp")]
+mod mcp;
+mod model;
+mod python_runner;
+
+use crate::cache::CodeFileCache;
+use crate::model::{CodeExecutor, CodeScriptExecutionResult, LanguageScript};
+
+#[derive(Parser)]
+#[command(name = "run_code_rmcp")]
+#[command(about = "Execute JavaScript and Python code using MCP SDK", long_about = None)]
+struct Cli {
+ #[command(subcommand)]
+ command: Commands,
+
+ /// Show execution logs
+ #[arg(short, long)]
+ show_logs: bool,
+
+ /// Use MCP SDK integration
+ #[cfg(feature = "mcp")]
+ #[arg(short, long)]
+ use_mcp: bool,
+
+ /// Clear cache before execution
+ #[arg(short = 'c', long)]
+ clear_cache: bool,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+ /// Execute JavaScript code
+ Js(CodeArgs),
+
+ /// Execute TypeScript code
+ Ts(CodeArgs),
+
+ /// Execute Python code
+ Python(CodeArgs),
+
+ /// Clear cache files
+ ClearCache {
+ /// Language to clear cache for (js, ts, python, or all)
+ #[arg(short, long)]
+ language: String,
+ },
+}
+
+#[derive(Args)]
+struct CodeArgs {
+ /// Path to the code file
+ #[arg(short, long)]
+ file: Option,
+
+ /// Code content as string
+ #[arg(short, long)]
+ code: Option,
+
+ /// Parameters to pass to the script (JSON format)
+ #[arg(short, long)]
+ params: Option,
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ // 解析命令行参数
+ let cli = Cli::parse();
+
+ // 初始化日志
+ let mut builder = env_logger::Builder::from_default_env();
+ if cli.show_logs {
+ // 如果--show-logs参数为true,则至少显示info级别的日志
+ builder.filter_level(log::LevelFilter::Info);
+ }
+ builder.init();
+
+ if let Commands::ClearCache { language } = &cli.command {
+ match language.to_lowercase().as_str() {
+ "js" => {
+ CodeFileCache::clear_cache_by_language(&LanguageScript::Js).await?;
+ info!("已清除 JavaScript 缓存");
+ }
+ "ts" => {
+ CodeFileCache::clear_cache_by_language(&LanguageScript::Ts).await?;
+ info!("已清除 TypeScript 缓存");
+ }
+ "python" => {
+ CodeFileCache::clear_cache_by_language(&LanguageScript::Python).await?;
+ info!("已清除 Python 缓存");
+ }
+ "all" => {
+ CodeFileCache::clear_all_cache().await?;
+ info!("已清除所有缓存");
+ }
+ _ => {
+ info!("无效的语言类型,可选项: js, ts, python, all");
+ return Ok(());
+ }
+ }
+ return Ok(());
+ }
+
+ // 解析传递给脚本的参数
+ let params = match &cli.command {
+ Commands::Js(args) => parse_params(&args.params)?,
+ Commands::Ts(args) => parse_params(&args.params)?,
+ Commands::Python(args) => parse_params(&args.params)?,
+ _ => None,
+ };
+
+ // 从参数获取代码
+ let (code, language) = match &cli.command {
+ Commands::Js(args) => (get_code(args)?, LanguageScript::Js),
+ Commands::Ts(args) => (get_code(args)?, LanguageScript::Ts),
+ Commands::Python(args) => (get_code(args)?, LanguageScript::Python),
+ _ => unreachable!(),
+ };
+
+ // 如果指定了清除缓存选项,则清除对应语言的缓存
+ if cli.clear_cache {
+ CodeFileCache::clear_cache_by_language(&language).await?;
+ info!("已清除 {language:?} 缓存");
+ }
+
+ // 执行代码
+ #[cfg(feature = "mcp")]
+ let result = if cli.use_mcp {
+ // 使用MCP SDK集成
+ CodeExecutor::execute_with_params_compat(&code, language, params).await?
+ } else {
+ // 直接执行
+ CodeExecutor::execute_with_params_compat(&code, language, params).await?
+ };
+
+ #[cfg(not(feature = "mcp"))]
+ let result = CodeExecutor::execute_with_params_compat(&code, language, params).await?;
+
+ // 打印结果
+ print_result(result);
+
+ Ok(())
+}
+
+fn get_code(args: &CodeArgs) -> Result {
+ if let Some(file) = &args.file {
+ fs::read_to_string(file).context("Failed to read code file")
+ } else if let Some(code) = &args.code {
+ Ok(code.clone())
+ } else {
+ anyhow::bail!("Either file or code must be provided")
+ }
+}
+
+fn parse_params(params_str: &Option) -> Result
+
+
+ {navItems.map(item => (
+
+ {item.icon}
+ {item.label}
+
+ ))}
+
+
+
+
+
+
+ );
+};
+
+const App: React.FC = () => {
+ const { isLoading, error } = useAppStore();
+
+ if (error) {
+ return (
+
+
Application Error
+
{error}
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ }>
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/components/DemoElement.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/components/DemoElement.tsx
new file mode 100644
index 00000000..64a1e04d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/src/components/DemoElement.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { cn } from '../utils/cn';
+
+export interface DemoElementProps {
+ element: {
+ id: string;
+ tag: string;
+ content: string;
+ className: string;
+ };
+ isDesignMode: boolean;
+ onSelect: (element: HTMLElement) => void;
+}
+
+export const DemoElement: React.FC = ({ element, isDesignMode, onSelect }) => {
+ const Tag = element.tag as any;
+
+ return (
+ {
+ if (isDesignMode) {
+ e.preventDefault();
+ onSelect(e.currentTarget as HTMLElement);
+ }
+ }}
+ data-element={element.id}
+ >
+ {element.content}
+
+ );
+};
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/index.css b/qiming-vite-plugin-design-mode/examples/advanced/src/index.css
new file mode 100644
index 00000000..ecb18830
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/src/index.css
@@ -0,0 +1,957 @@
+@import "tailwindcss";
+
+/* Advanced AppDev Design Mode Example Styles */
+
+/* CSS Variables for theming */
+:root {
+ /* Light theme colors */
+ --bg-primary: #ffffff;
+ --bg-secondary: #f8fafc;
+ --bg-tertiary: #f1f5f9;
+ --text-primary: #1e293b;
+ --text-secondary: #64748b;
+ --text-muted: #94a3b8;
+ --border-color: #e2e8f0;
+ --border-hover: #cbd5e1;
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
+ --accent-primary: #3b82f6;
+ --accent-secondary: #8b5cf6;
+ --success: #10b981;
+ --warning: #f59e0b;
+ --danger: #ef4444;
+
+ /* Spacing */
+ --spacing-xs: 0.25rem;
+ --spacing-sm: 0.5rem;
+ --spacing-md: 1rem;
+ --spacing-lg: 1.5rem;
+ --spacing-xl: 2rem;
+ --spacing-2xl: 3rem;
+
+ /* Typography */
+ --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ --font-mono: 'Fira Code', 'Monaco', 'Cascadia Code', monospace;
+
+ /* Border radius */
+ --radius-sm: 0.375rem;
+ --radius-md: 0.5rem;
+ --radius-lg: 0.75rem;
+ --radius-xl: 1rem;
+}
+
+/* Dark theme */
+.dark {
+ --bg-primary: #0f172a;
+ --bg-secondary: #1e293b;
+ --bg-tertiary: #334155;
+ --text-primary: #f8fafc;
+ --text-secondary: #cbd5e1;
+ --text-muted: #94a3b8;
+ --border-color: #334155;
+ --border-hover: #475569;
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4);
+}
+
+/* Reset and base styles */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ font-family: var(--font-family);
+ line-height: 1.6;
+ color: var(--text-primary);
+ background: var(--bg-primary);
+ transition: background-color 0.3s ease, color 0.3s ease;
+}
+
+/* App layout */
+.app {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Navigation */
+.nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--spacing-md) var(--spacing-xl);
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-color);
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ backdrop-filter: blur(10px);
+}
+
+.nav-brand {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.nav-brand h1 {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: var(--text-primary);
+}
+
+.nav-brand .version {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ background: var(--bg-tertiary);
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--radius-sm);
+}
+
+.nav-links {
+ display: flex;
+ gap: var(--spacing-lg);
+}
+
+.nav-link {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm) var(--spacing-md);
+ color: var(--text-secondary);
+ text-decoration: none;
+ border-radius: var(--radius-md);
+ transition: all 0.2s ease;
+}
+
+.nav-link:hover {
+ color: var(--text-primary);
+ background: var(--bg-tertiary);
+}
+
+.nav-link.active {
+ color: var(--accent-primary);
+ background: rgba(59, 130, 246, 0.1);
+}
+
+.nav-link .icon {
+ font-size: 1.1rem;
+}
+
+.theme-toggle {
+ background: none;
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ padding: var(--spacing-sm);
+ cursor: pointer;
+ font-size: 1.2rem;
+ transition: all 0.2s ease;
+}
+
+.theme-toggle:hover {
+ background: var(--bg-tertiary);
+ border-color: var(--border-hover);
+}
+
+/* Main content */
+.main-content {
+ flex: 1;
+ padding: var(--spacing-xl);
+ max-width: 1200px;
+ margin: 0 auto;
+ width: 100%;
+}
+
+/* Dashboard specific styles */
+.dashboard {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-2xl);
+}
+
+/* Welcome section */
+.welcome-section {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--spacing-xl);
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
+ border-radius: var(--radius-xl);
+ color: white;
+}
+
+.welcome-content h1 {
+ font-size: 2.5rem;
+ font-weight: 800;
+ margin-bottom: var(--spacing-sm);
+}
+
+.welcome-content p {
+ font-size: 1.2rem;
+ opacity: 0.9;
+}
+
+.status-indicators {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-md);
+}
+
+.status-item {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ background: rgba(255, 255, 255, 0.1);
+ padding: var(--spacing-sm) var(--spacing-md);
+ border-radius: var(--radius-md);
+ backdrop-filter: blur(10px);
+}
+
+.status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--success);
+ animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+/* Sections */
+.section-title {
+ font-size: 1.875rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-lg);
+}
+
+/* Metrics section */
+.metrics-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: var(--spacing-lg);
+}
+
+.metric-card {
+ background: var(--bg-secondary);
+ padding: var(--spacing-xl);
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--border-color);
+ transition: all 0.2s ease;
+}
+
+.metric-card:hover {
+ box-shadow: var(--shadow-md);
+ transform: translateY(-2px);
+}
+
+.metric-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: var(--spacing-md);
+}
+
+.metric-header h3 {
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.metric-icon {
+ font-size: 1.5rem;
+}
+
+.metric-value {
+ font-size: 2.25rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-sm);
+}
+
+.metric-change {
+ font-size: 0.875rem;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-xs);
+}
+
+.metric-change.up {
+ color: var(--success);
+}
+
+.metric-change.down {
+ color: var(--danger);
+}
+
+.metric-change.neutral {
+ color: var(--text-muted);
+}
+
+/* Feature demonstrations */
+.features-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--spacing-xl);
+}
+
+.feature-demo {
+ background: var(--bg-secondary);
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--border-color);
+ overflow: hidden;
+}
+
+.feature-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--spacing-lg);
+ background: var(--bg-tertiary);
+ border-bottom: 1px solid var(--border-color);
+}
+
+.feature-header h3 {
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.feature-tabs {
+ display: flex;
+ background: var(--bg-primary);
+ border-radius: var(--radius-md);
+ padding: 0.25rem;
+}
+
+.tab {
+ padding: var(--spacing-sm) var(--spacing-md);
+ border: none;
+ background: none;
+ color: var(--text-secondary);
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.tab.active {
+ background: var(--accent-primary);
+ color: white;
+}
+
+.feature-description {
+ padding: var(--spacing-lg);
+ color: var(--text-secondary);
+ border-bottom: 1px solid var(--border-color);
+}
+
+.feature-content {
+ min-height: 200px;
+}
+
+.demo-container,
+.code-container {
+ padding: var(--spacing-lg);
+}
+
+.code-container {
+ background: var(--bg-tertiary);
+ overflow-x: auto;
+ font-family: var(--font-mono);
+ font-size: 0.875rem;
+ line-height: 1.6;
+}
+
+/* Component registry */
+.components-section {
+ margin-top: var(--spacing-2xl);
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: var(--spacing-lg);
+}
+
+.search-container {
+ position: relative;
+}
+
+.search-input {
+ padding: var(--spacing-sm) var(--spacing-md);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ width: 300px;
+ transition: border-color 0.2s ease;
+}
+
+.search-input:focus {
+ outline: none;
+ border-color: var(--accent-primary);
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+}
+
+.components-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: var(--spacing-lg);
+}
+
+.component-card {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg);
+ padding: var(--spacing-lg);
+ transition: all 0.2s ease;
+}
+
+.component-card:hover {
+ box-shadow: var(--shadow-md);
+ border-color: var(--border-hover);
+}
+
+.component-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: var(--spacing-md);
+}
+
+.component-header h4 {
+ font-size: 1.125rem;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.component-type {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ background: var(--bg-tertiary);
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--radius-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.props-preview {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--spacing-xs);
+}
+
+.prop-tag {
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ background: var(--bg-tertiary);
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--radius-sm);
+}
+
+/* Demo specific styles */
+.source-map-demo {
+ text-align: center;
+ padding: var(--spacing-lg);
+}
+
+.demo-element {
+ display: inline-block;
+ padding: var(--spacing-md) var(--spacing-lg);
+ background: var(--accent-primary);
+ color: white;
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.demo-element:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-lg);
+}
+
+.component-tree {
+ padding: var(--spacing-lg);
+ font-family: var(--font-mono);
+ font-size: 0.875rem;
+}
+
+.tree-node {
+ margin: var(--spacing-sm) 0;
+}
+
+.tree-node.root>.node-label {
+ font-weight: 600;
+ color: var(--accent-primary);
+}
+
+.children {
+ margin-left: var(--spacing-lg);
+ border-left: 2px solid var(--border-color);
+ padding-left: var(--spacing-md);
+}
+
+.node-label {
+ color: var(--text-primary);
+}
+
+.live-edit-demo {
+ padding: var(--spacing-lg);
+}
+
+.editable-element {
+ background: var(--bg-tertiary);
+ padding: var(--spacing-lg);
+ border-radius: var(--radius-md);
+ text-align: center;
+ margin-bottom: var(--spacing-lg);
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.editable-element:hover {
+ background: var(--accent-primary);
+ color: white;
+}
+
+.style-controls {
+ display: flex;
+ gap: var(--spacing-sm);
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+.style-btn {
+ padding: var(--spacing-sm) var(--spacing-md);
+ border: none;
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ font-weight: 500;
+ transition: all 0.2s ease;
+}
+
+.style-btn.primary {
+ background: var(--accent-primary);
+ color: white;
+}
+
+.style-btn.secondary {
+ background: var(--accent-secondary);
+ color: white;
+}
+
+.style-btn.danger {
+ background: var(--danger);
+ color: white;
+}
+
+.style-btn:hover {
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-sm);
+}
+
+/* Footer */
+.app-footer {
+ background: var(--bg-secondary);
+ border-top: 1px solid var(--border-color);
+ padding: var(--spacing-xl);
+ text-align: center;
+ color: var(--text-secondary);
+ margin-top: auto;
+}
+
+/* App error state */
+.app-error {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 50vh;
+ padding: var(--spacing-xl);
+ text-align: center;
+}
+
+.app-error h1 {
+ color: var(--danger);
+ margin-bottom: var(--spacing-md);
+}
+
+.app-error button {
+ margin-top: var(--spacing-lg);
+ padding: var(--spacing-md) var(--spacing-lg);
+ background: var(--accent-primary);
+ color: white;
+ border: none;
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ font-weight: 600;
+ transition: background 0.2s ease;
+}
+
+.app-error button:hover {
+ background: var(--accent-secondary);
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+ .nav {
+ flex-direction: column;
+ gap: var(--spacing-md);
+ }
+
+ .nav-links {
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .main-content {
+ padding: var(--spacing-md);
+ }
+
+ .welcome-section {
+ flex-direction: column;
+ text-align: center;
+ }
+
+ .welcome-content h1 {
+ font-size: 2rem;
+ }
+
+ .section-header {
+ flex-direction: column;
+ gap: var(--spacing-md);
+ align-items: stretch;
+ }
+
+ .search-input {
+ width: 100%;
+ }
+
+ .metrics-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .components-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* Design mode specific styles */
+[data-design-mode-enabled="true"] [data-source-info] {
+ position: relative;
+}
+
+[data-design-mode-enabled="true"] [data-source-info]:hover {
+ outline: 2px dashed var(--accent-primary) !important;
+ outline-offset: 2px !important;
+ cursor: pointer !important;
+}
+
+/* Print styles */
+@media print {
+
+ .nav,
+ .app-footer {
+ display: none;
+ }
+
+ .main-content {
+ padding: 0;
+ }
+}
+
+/* Interactive Demo Styles */
+.interactive-demo {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-xl);
+}
+
+.demo-header {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-lg);
+ background: var(--bg-secondary);
+ padding: var(--spacing-xl);
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--border-color);
+}
+
+.demo-controls {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.design-mode-toggle {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-md) var(--spacing-lg);
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-md);
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.design-mode-toggle.active {
+ background: var(--accent-primary);
+ color: white;
+ border-color: var(--accent-primary);
+}
+
+.element-editor {
+ background: var(--bg-primary);
+ padding: var(--spacing-lg);
+ border-radius: var(--radius-md);
+ border: 1px solid var(--border-color);
+ animation: slideDown 0.3s ease;
+}
+
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.editor-controls {
+ display: flex;
+ gap: var(--spacing-xl);
+ margin-top: var(--spacing-md);
+ flex-wrap: wrap;
+}
+
+.control-group {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.demo-workspace {
+ display: grid;
+ grid-template-columns: 1fr 300px;
+ gap: var(--spacing-xl);
+ min-height: 500px;
+}
+
+.preview-area {
+ background: var(--bg-secondary);
+ border: 2px dashed var(--border-color);
+ border-radius: var(--radius-lg);
+ padding: var(--spacing-xl);
+ position: relative;
+ transition: all 0.3s ease;
+}
+
+.preview-area.design-mode-active {
+ border-color: var(--accent-primary);
+ background: rgba(59, 130, 246, 0.05);
+}
+
+.preview-content {
+ background: var(--bg-primary);
+ padding: var(--spacing-2xl);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-sm);
+ min-height: 100%;
+}
+
+.modifications-panel {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.panel-header {
+ padding: var(--spacing-md) var(--spacing-lg);
+ background: var(--bg-tertiary);
+ border-bottom: 1px solid var(--border-color);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.reset-btn {
+ font-size: 0.875rem;
+ color: var(--danger);
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: var(--spacing-xs) var(--spacing-sm);
+ border-radius: var(--radius-sm);
+}
+
+.reset-btn:hover:not(:disabled) {
+ background: rgba(239, 68, 68, 0.1);
+}
+
+.reset-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.modifications-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--spacing-md);
+}
+
+.modification-item {
+ background: var(--bg-primary);
+ padding: var(--spacing-md);
+ border-radius: var(--radius-md);
+ border: 1px solid var(--border-color);
+ margin-bottom: var(--spacing-sm);
+ font-size: 0.875rem;
+}
+
+.mod-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: var(--spacing-xs);
+ font-weight: 600;
+}
+
+.property {
+ color: var(--text-secondary);
+ font-family: var(--font-mono);
+}
+
+.mod-values {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: var(--text-secondary);
+}
+
+.value-change {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-xs);
+ font-family: var(--font-mono);
+}
+
+.arrow {
+ color: var(--text-muted);
+}
+
+.new-value {
+ color: var(--accent-primary);
+}
+
+.mod-time {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+}
+
+.demo-footer {
+ background: var(--bg-secondary);
+ padding: var(--spacing-lg);
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--border-color);
+}
+
+.instructions ol {
+ margin-left: var(--spacing-xl);
+ margin-top: var(--spacing-md);
+ color: var(--text-secondary);
+}
+
+.instructions li {
+ margin-bottom: var(--spacing-xs);
+}
+
+/* Demo Elements */
+.demo-title {
+ font-size: 2.5rem;
+ color: var(--text-primary);
+ margin-bottom: var(--spacing-md);
+}
+
+.demo-description {
+ font-size: 1.125rem;
+ color: var(--text-secondary);
+ margin-bottom: var(--spacing-xl);
+ max-width: 600px;
+}
+
+.demo-button {
+ padding: var(--spacing-md) var(--spacing-xl);
+ border-radius: var(--radius-md);
+ border: none;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.demo-button.primary {
+ background: var(--accent-primary);
+ color: white;
+}
+
+.demo-button:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-md);
+}
+
+.demo-card {
+ background: white;
+ padding: var(--spacing-xl);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-md);
+ margin-top: var(--spacing-xl);
+ border: 1px solid var(--border-color);
+}
+
+/* Selected state for design mode */
+.selected {
+ outline: 2px solid var(--accent-primary) !important;
+ outline-offset: 2px;
+ position: relative;
+}
+
+.selected::after {
+ content: 'Selected';
+ position: absolute;
+ top: -24px;
+ left: 0;
+ background: var(--accent-primary);
+ color: white;
+ font-size: 0.75rem;
+ padding: 2px 6px;
+ border-radius: 4px;
+ pointer-events: none;
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/main.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/main.tsx
new file mode 100644
index 00000000..8ec9ad88
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/src/main.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import App from './App';
+import './index.css';
+import './index.css';
+
+// 开发模式下的额外日志
+if (import.meta.env.DEV) {
+ console.log('🚀 Advanced AppDev Design Mode Example');
+ console.log('🔧 Plugin Configuration:', {
+ enabled: true,
+ prefix: 'design-mode',
+ verbose: true,
+ typescript: true,
+ react: true,
+ routing: true,
+ stateManagement: true
+ });
+}
+
+// 错误边界组件
+class ErrorBoundary extends React.Component<
+ { children: React.ReactNode },
+ { hasError: boolean }
+> {
+ constructor(props: { children: React.ReactNode }) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(): { hasError: boolean } {
+ return { hasError: true };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.error('Error caught by boundary:', error, errorInfo);
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
Something went wrong.
+
Please refresh the page to try again.
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+
+
+
+
+);
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ComponentShowcase.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ComponentShowcase.tsx
new file mode 100644
index 00000000..d7755cca
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ComponentShowcase.tsx
@@ -0,0 +1,197 @@
+import React, { useState } from 'react';
+
+const ComponentShowcase: React.FC = () => {
+ const [activeCategory, setActiveCategory] = useState('buttons');
+ const [selectedComponent, setSelectedComponent] = useState(null);
+
+ const categories = [
+ { id: 'buttons', label: 'Buttons', icon: '🔘' },
+ { id: 'forms', label: 'Forms', icon: '📝' },
+ { id: 'cards', label: 'Cards', icon: '🃏' },
+ { id: 'navigation', label: 'Navigation', icon: '🧭' }
+ ];
+
+ const components = {
+ buttons: [
+ {
+ id: 'primary-btn',
+ name: 'Primary Button',
+ description: 'Main action button with primary styling',
+ component: (
+
+ ),
+ code: ``
+ },
+ {
+ id: 'secondary-btn',
+ name: 'Secondary Button',
+ description: 'Secondary action button',
+ component: (
+
+ ),
+ code: ``
+ },
+ {
+ id: 'danger-btn',
+ name: 'Danger Button',
+ description: 'Destructive action button',
+ component: (
+
+ ),
+ code: ``
+ }
+ ],
+ forms: [
+ {
+ id: 'input-field',
+ name: 'Input Field',
+ description: 'Standard text input with label',
+ component: (
+
+
+
+
+ ),
+ code: `
+
+
+
`
+ },
+ {
+ id: 'checkbox-group',
+ name: 'Checkbox Group',
+ description: 'Multiple choice checkboxes',
+ component: (
+
+ ),
+ code: ``
+ }
+ ],
+ cards: [
+ {
+ id: 'info-card',
+ name: 'Info Card',
+ description: 'Card with title, content and actions',
+ component: (
+
+
+
Card Title
+
+
+
This is a sample card component with some descriptive content.
+
+
+
+
+
+ ),
+ code: `
+
+
Card Title
+
+
+
This is a sample card component with some descriptive content.
+
+
+
+
+
`
+ }
+ ],
+ navigation: [
+ {
+ id: 'breadcrumb',
+ name: 'Breadcrumb',
+ description: 'Navigation breadcrumb trail',
+ component: (
+
+ ),
+ code: ``
+ }
+ ]
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {components[activeCategory as keyof typeof components]?.map(component => (
+
setSelectedComponent(
+ selectedComponent === component.id ? null : component.id
+ )}
+ >
+
+ {component.component}
+
+
+
{component.name}
+
{component.description}
+
+ {selectedComponent === component.id && (
+
+ )}
+
+ ))}
+
+
+
+
+ );
+};
+
+export default ComponentShowcase;
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ConfigurationGuide.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ConfigurationGuide.tsx
new file mode 100644
index 00000000..2b8fed0d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/ConfigurationGuide.tsx
@@ -0,0 +1,292 @@
+import React, { useState } from 'react';
+
+const ConfigurationGuide: React.FC = () => {
+ const [activeSection, setActiveSection] = useState('basic');
+
+ const sections = [
+ { id: 'basic', label: 'Basic Setup', icon: '⚙️' },
+ { id: 'options', label: 'Configuration Options', icon: '🔧' },
+ { id: 'advanced', label: 'Advanced Usage', icon: '🚀' },
+ { id: 'troubleshooting', label: 'Troubleshooting', icon: '🛠️' }
+ ];
+
+ const configExamples = {
+ basic: {
+ title: 'Basic Setup',
+ description: 'Get started with the plugin in minutes',
+ code: `// vite.config.ts
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import appdevDesignMode from 'vite-plugin-appdev-design-mode';
+
+export default defineConfig({
+ plugins: [
+ react(),
+ appdevDesignMode() // Basic configuration
+ ]
+});`,
+ explanation: 'This is the minimal configuration needed to get started. The plugin will use all default options.'
+ },
+ options: {
+ title: 'Configuration Options',
+ description: 'Customize the plugin behavior with various options',
+ code: `// vite.config.ts
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import appdevDesignMode from 'vite-plugin-appdev-design-mode';
+
+export default defineConfig({
+ plugins: [
+ react(),
+ appdevDesignMode({
+ // Enable/disable the plugin
+ enabled: true,
+
+ // Only enable in development
+ enableInProduction: false,
+
+ // Custom attribute prefix
+ attributePrefix: 'design-mode',
+
+ // Enable verbose logging
+ verbose: true,
+
+ // File patterns to exclude
+ exclude: [
+ 'node_modules',
+ '.git',
+ 'dist',
+ '**/*.test.{ts,tsx,js,jsx}'
+ ],
+
+ // File patterns to include
+ include: [
+ 'src/**/*.{ts,tsx,js,jsx}',
+ 'components/**/*.{ts,tsx,js,jsx}'
+ ]
+ })
+ ]
+});`,
+ explanation: 'This configuration shows all available options and their default values.'
+ },
+ advanced: {
+ title: 'Advanced Usage',
+ description: 'Advanced patterns and integration techniques',
+ code: `// vite.config.ts
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import appdevDesignMode from 'vite-plugin-appdev-design-mode';
+
+export default defineConfig({
+ plugins: [
+ react(),
+ // Multiple configurations for different environments
+ appdevDesignMode({
+ enabled: true,
+ attributePrefix: 'dev-mode',
+ verbose: process.env.NODE_ENV === 'development',
+
+ // Conditional inclusion based on environment
+ include: process.env.NODE_ENV === 'development'
+ ? ['src/**/*.{ts,tsx,js,jsx}']
+ : [], // Disable in production
+
+ // Custom exclude patterns
+ exclude: [
+ 'node_modules',
+ '.git',
+ 'dist',
+ 'build',
+ '**/*.test.{ts,tsx,js,jsx}',
+ '**/__tests__/**',
+ 'stories/**',
+ '**/*.stories.{ts,tsx}'
+ ]
+ })
+ ],
+
+ // Server configuration for API endpoints
+ server: {
+ port: 5173,
+ host: true,
+ middlewareMode: false
+ }
+});
+
+// Environment-specific configuration
+const isDevelopment = process.env.NODE_ENV === 'development';
+
+export default defineConfig({
+ plugins: isDevelopment ? [
+ react(),
+ appdevDesignMode({
+ verbose: true,
+ attributePrefix: 'dev'
+ })
+ ] : [react()]
+});`,
+ explanation: 'This example shows how to configure the plugin differently based on environment and use cases.'
+ },
+ troubleshooting: {
+ title: 'Troubleshooting',
+ description: 'Common issues and their solutions',
+ code: `# Common Issues and Solutions
+
+## Plugin not injecting source information
+
+### Problem: Elements don't have data attributes
+### Solution: Check file inclusion patterns
+\`\`\`js
+appdevDesignMode({
+ include: ['src/**/*.{ts,tsx,js,jsx}'] // Make sure this matches your project structure
+})
+\`\`\`
+
+## Performance issues
+
+### Problem: Plugin slowing down development server
+### Solution: Exclude unnecessary files
+\`\`\`js
+appdevDesignMode({
+ exclude: [
+ 'node_modules',
+ '**/*.test.{ts,tsx,js,jsx}',
+ '**/__tests__/**',
+ 'stories/**'
+ ]
+})
+\`\`\`
+
+## TypeScript errors
+
+### Problem: Cannot find module '@babel/core'
+### Solution: Install peer dependencies
+\`\`\`bash
+npm install --save-dev @babel/core @babel/traverse @babel/types
+\`\`\`
+
+## Custom configuration not working
+
+### Problem: Plugin using default configuration
+### Solution: Check import path and plugin options
+\`\`\`js
+// Correct import
+import appdevDesignMode from 'vite-plugin-appdev-design-mode';
+
+// Make sure options are passed correctly
+appdevDesignMode({
+ enabled: true, // This should not be undefined
+ attributePrefix: 'custom'
+})
+\`\`\`
+
+## API endpoints not accessible
+
+### Problem: Cannot access plugin API endpoints
+### Solution: Check server configuration and CORS
+\`\`\`js
+export default defineConfig({
+ server: {
+ cors: true, // Enable CORS for API access
+ headers: {
+ 'Access-Control-Allow-Origin': '*'
+ }
+ }
+})
+\`\`\``,
+ explanation: 'This section covers common problems developers encounter and their solutions.'
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {configExamples[activeSection as keyof typeof configExamples].title}
+
+
+ {configExamples[activeSection as keyof typeof configExamples].description}
+
+
+
+
+
+
+
Configuration
+
+
+
+ {configExamples[activeSection as keyof typeof configExamples].code}
+
+
+
+
+
Explanation
+
{configExamples[activeSection as keyof typeof configExamples].explanation}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ConfigurationGuide;
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/pages/Dashboard.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/Dashboard.tsx
new file mode 100644
index 00000000..14070055
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/Dashboard.tsx
@@ -0,0 +1,242 @@
+import React, { useState } from 'react';
+import { useAppStore } from '../store/appStore';
+
+interface MetricCardProps {
+ title: string;
+ value: string | number;
+ change?: string;
+ trend?: 'up' | 'down' | 'neutral';
+ icon: string;
+}
+
+const MetricCard: React.FC = ({ title, value, change, trend, icon }) => (
+
+
+
{title}
+ {icon}
+
+
{value}
+ {change && (
+
+ {trend === 'up' && '↗️'}
+ {trend === 'down' && '↘️'}
+ {trend === 'neutral' && '➡️'}
+ {change}
+
+ )}
+
+);
+
+interface FeatureDemoProps {
+ title: string;
+ description: string;
+ code: string;
+ demo: React.ReactNode;
+}
+
+const FeatureDemo: React.FC = ({ title, description, code, demo }) => {
+ const [showCode, setShowCode] = useState(false);
+ const [activeTab, setActiveTab] = useState<'demo' | 'code'>('demo');
+
+ return (
+
+
+
{title}
+
+
+
+
+
+
+
{description}
+
+
+ {activeTab === 'demo' ? (
+
+ {demo}
+
+ ) : (
+
+ {code}
+
+ )}
+
+
+ );
+};
+
+const Dashboard: React.FC = () => {
+ const { theme, user, components } = useAppStore();
+ const [searchTerm, setSearchTerm] = useState('');
+
+ const metrics = [
+ { title: 'Total Components', value: components.length, change: '+2', trend: 'up' as const, icon: '🧩' },
+ { title: 'Active Elements', value: 24, change: '+5', trend: 'up' as const, icon: '⚡' },
+ { title: 'Design Mode Sessions', value: 156, change: '+12', trend: 'up' as const, icon: '🎨' },
+ { title: 'Code Quality Score', value: '94%', change: '+2%', trend: 'up' as const, icon: '📊' }
+ ];
+
+ const componentList = components.filter(comp =>
+ comp.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ comp.type.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ return (
+
+ {/* Welcome Section */}
+
+
+
+ Welcome back, {user.name}!
+
+
+ Here's what's happening with your design mode plugin today.
+
+
+
+
+
+ Plugin Active
+
+
+ {theme === 'light' ? '☀️' : '🌙'}
+ {theme.charAt(0).toUpperCase() + theme.slice(1)} Mode
+
+
+
+
+ {/* Metrics Grid */}
+
+ System Metrics
+
+ {metrics.map((metric, index) => (
+
+ ))}
+
+
+
+ {/* Feature Demonstrations */}
+
+ Plugin Features
+
+
+ Content here
+
`}
+ demo={
+
+
+ Hover over me to see source mapping info
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+ {
+ await fetch('/__modify_element_source', {
+ method: 'POST',
+ body: JSON.stringify({ elementId, newStyles })
+ });
+};`}
+ demo={
+
+
+ Click to edit styles
+
+
+
+
+
+
+
+ }
+ />
+
+
+
+ {/* Component Registry */}
+
+
+
Component Registry
+
+ setSearchTerm(e.target.value)}
+ className="search-input"
+ data-element="input"
+ />
+
+
+
+
+ {componentList.map((component) => (
+
+
+
{component.name}
+ {component.type}
+
+
+
ID: {component.id}
+
+ {Object.entries(component.props).slice(0, 2).map(([key, value]) => (
+
+ {key}: {String(value)}
+
+ ))}
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default Dashboard;
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/pages/InteractiveDemo.tsx b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/InteractiveDemo.tsx
new file mode 100644
index 00000000..bfbe257e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/src/pages/InteractiveDemo.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import { DemoElement } from '../components/DemoElement';
+
+const InteractiveDemo: React.FC = () => {
+ // No local state or context needed - Design Mode is handled globally by the plugin
+
+ const demoElements = [
+ {
+ id: 'hero-title',
+ tag: 'h1',
+ content: 'Interactive Design Mode Demo',
+ className: 'text-4xl font-bold text-slate-900 mb-4'
+ },
+ {
+ id: 'hero-description',
+ tag: 'p',
+ content: 'Click on elements in design mode to modify their properties in real-time',
+ className: 'text-lg text-slate-600 mb-8 max-w-2xl'
+ },
+ {
+ id: 'demo-button',
+ tag: 'button',
+ content: 'Click Me',
+ className: 'px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 transition-colors shadow-sm'
+ },
+ {
+ id: 'demo-card',
+ tag: 'div',
+ content: 'Sample Card Content',
+ className: 'mt-8 p-6 bg-white rounded-xl shadow-md border border-slate-200'
+ }
+ ];
+
+ return (
+
+
+
+
+
+
+ {demoElements.map(element => (
+ {}} // Handled globally
+ />
+ ))}
+
+
+
+
+
+
+ );
+};
+
+export default InteractiveDemo;
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/store/appStore.ts b/qiming-vite-plugin-design-mode/examples/advanced/src/store/appStore.ts
new file mode 100644
index 00000000..e9690af7
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/src/store/appStore.ts
@@ -0,0 +1,173 @@
+import { create } from 'zustand';
+import { devtools, persist } from 'zustand/middleware';
+
+interface AppState {
+ // UI状态
+ theme: 'light' | 'dark';
+ isLoading: boolean;
+ error: string | null;
+
+ // 应用状态
+ currentPage: string;
+ user: {
+ name: string;
+ email: string;
+ preferences: {
+ showTooltips: boolean;
+ animations: boolean;
+ compactMode: boolean;
+ };
+ };
+
+ // 组件状态
+ components: Array<{
+ id: string;
+ name: string;
+ type: string;
+ props: Record;
+ children?: string[];
+ parent?: string;
+ }>;
+
+ // 设计模式状态
+ designMode: {
+ enabled: boolean;
+ selectedElement: string | null;
+ showGrid: boolean;
+ showInfo: boolean;
+ };
+
+ // Actions
+ setTheme: (theme: 'light' | 'dark') => void;
+ toggleTheme: () => void;
+ setLoading: (loading: boolean) => void;
+ setError: (error: string | null) => void;
+ setCurrentPage: (page: string) => void;
+ updateUserPreferences: (preferences: Partial) => void;
+
+ // 组件操作
+ addComponent: (component: Omit) => void;
+ updateComponent: (id: string, updates: Partial) => void;
+ removeComponent: (id: string) => void;
+
+ // 设计模式操作
+ setDesignModeEnabled: (enabled: boolean) => void;
+ setSelectedElement: (element: string | null) => void;
+ toggleGrid: () => void;
+ toggleInfo: () => void;
+
+ // 重置状态
+ reset: () => void;
+}
+
+const initialState = {
+ theme: 'light' as const,
+ isLoading: false,
+ error: null,
+ currentPage: 'dashboard',
+ user: {
+ name: 'Demo User',
+ email: 'demo@example.com',
+ preferences: {
+ showTooltips: true,
+ animations: true,
+ compactMode: false
+ }
+ },
+ components: [
+ {
+ id: 'header-1',
+ name: 'AppHeader',
+ type: 'header',
+ props: { title: 'Advanced Demo', showNav: true }
+ },
+ {
+ id: 'card-1',
+ name: 'InfoCard',
+ type: 'card',
+ props: { title: 'Welcome', content: 'This is an advanced example' }
+ }
+ ],
+ designMode: {
+ enabled: false,
+ selectedElement: null,
+ showGrid: false,
+ showInfo: true
+ }
+};
+
+export const useAppStore = create()(
+ devtools(
+ persist(
+ (set, get) => ({
+ ...initialState,
+
+ // UI Actions
+ setTheme: (theme) => set({ theme }, false, 'setTheme'),
+ toggleTheme: () => set((state) => ({
+ theme: state.theme === 'light' ? 'dark' : 'light'
+ }), false, 'toggleTheme'),
+
+ setLoading: (loading) => set({ isLoading: loading }, false, 'setLoading'),
+ setError: (error) => set({ error }, false, 'setError'),
+ setCurrentPage: (page) => set({ currentPage: page }, false, 'setCurrentPage'),
+
+ updateUserPreferences: (preferences) => set((state) => ({
+ user: {
+ ...state.user,
+ preferences: { ...state.user.preferences, ...preferences }
+ }
+ }), false, 'updateUserPreferences'),
+
+ // Component Actions
+ addComponent: (component) => set((state) => ({
+ components: [
+ ...state.components,
+ { ...component, id: `${component.name}-${Date.now()}` }
+ ]
+ }), false, 'addComponent'),
+
+ updateComponent: (id, updates) => set((state) => ({
+ components: state.components.map(comp =>
+ comp.id === id ? { ...comp, ...updates } : comp
+ )
+ }), false, 'updateComponent'),
+
+ removeComponent: (id) => set((state) => ({
+ components: state.components.filter(comp => comp.id !== id)
+ }), false, 'removeComponent'),
+
+ // Design Mode Actions
+ setDesignModeEnabled: (enabled) => set((state) => ({
+ designMode: { ...state.designMode, enabled }
+ }), false, 'setDesignModeEnabled'),
+
+ setSelectedElement: (element) => set((state) => ({
+ designMode: { ...state.designMode, selectedElement: element }
+ }), false, 'setSelectedElement'),
+
+ toggleGrid: () => set((state) => ({
+ designMode: { ...state.designMode, showGrid: !state.designMode.showGrid }
+ }), false, 'toggleGrid'),
+
+ toggleInfo: () => set((state) => ({
+ designMode: { ...state.designMode, showInfo: !state.designMode.showInfo }
+ }), false, 'toggleInfo'),
+
+ // Reset
+ reset: () => set(initialState, false, 'reset')
+ }),
+ {
+ name: 'advanced-design-mode-storage',
+ partialize: (state) => ({
+ theme: state.theme,
+ user: state.user,
+ designMode: state.designMode
+ })
+ }
+ ),
+ {
+ name: 'app-store'
+ }
+ )
+);
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/src/utils/cn.ts b/qiming-vite-plugin-design-mode/examples/advanced/src/utils/cn.ts
new file mode 100644
index 00000000..365058ce
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/src/utils/cn.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/tailwind.config.js b/qiming-vite-plugin-design-mode/examples/advanced/tailwind.config.js
new file mode 100644
index 00000000..d37737fc
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/tailwind.config.js
@@ -0,0 +1,12 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
+
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.json b/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.json
new file mode 100644
index 00000000..ca631e91
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"],
+ "@components/*": ["src/components/*"],
+ "@pages/*": ["src/pages/*"],
+ "@utils/*": ["src/utils/*"],
+ "@types/*": ["src/types/*"],
+ "@hooks/*": ["src/hooks/*"]
+ }
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.node.json b/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.node.json
new file mode 100644
index 00000000..42872c59
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/qiming-vite-plugin-design-mode/examples/advanced/vite.config.ts b/qiming-vite-plugin-design-mode/examples/advanced/vite.config.ts
new file mode 100644
index 00000000..e4c08817
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/advanced/vite.config.ts
@@ -0,0 +1,77 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import appdevDesignMode from '../../packages/plugin/src/index';
+
+export default defineConfig({
+ plugins: [
+ react(),
+ // 高级配置示例
+ appdevDesignMode({
+ enabled: true,
+ enableInProduction: false, // 只在开发环境启用
+ attributePrefix: 'design-mode', // 自定义属性前缀
+ verbose: true, // 详细日志输出
+ exclude: [
+ 'node_modules',
+ '.git',
+ 'dist',
+ 'build',
+ '**/*.test.{ts,tsx,js,jsx}',
+ '**/*.spec.{ts,tsx,js,jsx}',
+ '**/__tests__/**',
+ '**/tests/**',
+ ],
+ include: [
+ 'src/**/*.{ts,tsx,js,jsx}',
+ 'components/**/*.{ts,tsx,js,jsx}',
+ 'pages/**/*.{ts,tsx,js,jsx}',
+ '!**/node_modules/**',
+ ],
+ }),
+ ],
+
+ // 高级Vite配置
+ server: {
+ port: 5174, // 使用不同端口避免冲突
+ host: true,
+ open: true,
+ cors: true,
+ fs: {
+ allow: ['..', '../../src'], // Allow serving the plugin source
+ },
+ },
+
+ build: {
+ outDir: 'dist',
+ sourcemap: true,
+ minify: 'esbuild',
+ rollupOptions: {
+ output: {
+ manualChunks: {
+ vendor: ['react', 'react-dom'],
+ router: ['react-router-dom'],
+ state: ['zustand'],
+ },
+ },
+ },
+ },
+
+ resolve: {
+ alias: {
+ '@': '/src',
+ '@components': '/src/components',
+ '@pages': '/src/pages',
+ '@utils': '/src/utils',
+ },
+ },
+
+ css: {
+ modules: {
+ localsConvention: 'camelCaseOnly',
+ },
+ },
+
+ optimizeDeps: {
+ include: ['react', 'react-dom', 'react-router-dom', 'zustand'],
+ },
+});
diff --git a/qiming-vite-plugin-design-mode/examples/basic/index.html b/qiming-vite-plugin-design-mode/examples/basic/index.html
new file mode 100644
index 00000000..1f1c1cbd
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/basic/index.html
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+ Basic AppDev Design Mode Example
+
+
+
+
+
Basic Design Mode Example
+
+ This is a simple HTML example showing how the design mode plugin works.
+
+
+
+
Interactive Elements
+
+ Click the buttons below to test the design mode functionality.
+
+
+
+
+
+
+
+
Content Section
+
+ This section contains various HTML elements for testing source mapping.
+
+
+ - Source mapping works with vanilla HTML
+ - Plugin adds data attributes automatically
+ - No React or framework required
+
+
+
+
+
+
+
diff --git a/qiming-vite-plugin-design-mode/examples/basic/main.js b/qiming-vite-plugin-design-mode/examples/basic/main.js
new file mode 100644
index 00000000..f2c56db4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/basic/main.js
@@ -0,0 +1,171 @@
+// Basic JavaScript example for AppDev Design Mode plugin
+console.log('AppDev Design Mode Plugin - Basic Example Loaded');
+
+// Sample data for the application
+const sampleData = {
+ features: [
+ 'No framework required',
+ 'Works with vanilla HTML',
+ 'Automatic source mapping',
+ 'Lightweight and fast',
+ ],
+ actions: {
+ primary: 'Primary action triggered',
+ secondary: 'Secondary action triggered',
+ danger: 'Danger action triggered',
+ },
+};
+
+// Interactive functionality
+function initializeApp() {
+ console.log('Initializing basic app...');
+
+ // Add event listeners to buttons
+ const buttons = document.querySelectorAll('[data-appdev-action]');
+ buttons.forEach(button => {
+ button.addEventListener('click', handleButtonClick);
+ });
+
+ // Add click handlers to list items
+ const listItems = document.querySelectorAll('[data-appdev-item]');
+ listItems.forEach((item, index) => {
+ item.addEventListener('click', () => handleListItemClick(item, index));
+ });
+
+ // Simulate dynamic content addition
+ addDynamicContent();
+}
+
+function handleButtonClick(event) {
+ const action = event.target.getAttribute('data-appdev-action');
+ const message = sampleData.actions[action] || 'Unknown action';
+
+ console.log(`Button clicked: ${action}`);
+
+ // Visual feedback
+ event.target.style.background = '#48bb78';
+ setTimeout(() => {
+ event.target.style.background = '#4299e1';
+ }, 200);
+
+ showNotification(message);
+}
+
+function handleListItemClick(item, index) {
+ console.log(`List item clicked: ${item.textContent}`);
+
+ // Visual feedback
+ item.style.background = '#e6fffa';
+ setTimeout(() => {
+ item.style.background = '';
+ }, 300);
+
+ showNotification(`Clicked: ${item.textContent}`);
+}
+
+function addDynamicContent() {
+ const contentSection = document.querySelector(
+ '[data-appdev-component="content-section"]'
+ );
+ if (!contentSection) return;
+
+ const dynamicDiv = document.createElement('div');
+ dynamicDiv.setAttribute('data-appdev-component', 'dynamic-content');
+ dynamicDiv.setAttribute('data-dynamic', 'true');
+ dynamicDiv.innerHTML = `
+ Dynamically Added Content
+
+ This content was added dynamically after page load.
+
+
+ `;
+
+ contentSection.appendChild(dynamicDiv);
+
+ // Add event listener to new button
+ const newButton = dynamicDiv.querySelector('[data-appdev-action="dynamic"]');
+ newButton.addEventListener('click', event => {
+ console.log('Dynamic button clicked');
+ showNotification('Dynamic action triggered!');
+ event.target.style.background = '#ed8936';
+ setTimeout(() => {
+ event.target.style.background = '#4299e1';
+ }, 200);
+ });
+}
+
+function showNotification(message) {
+ // Remove existing notification
+ const existing = document.querySelector('.notification');
+ if (existing) {
+ existing.remove();
+ }
+
+ // Create new notification
+ const notification = document.createElement('div');
+ notification.className = 'notification';
+ notification.textContent = message;
+ notification.style.cssText = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ background: #48bb78;
+ color: white;
+ padding: 1rem 1.5rem;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ z-index: 1000;
+ animation: slideIn 0.3s ease-out;
+ `;
+
+ document.body.appendChild(notification);
+
+ // Auto remove after 3 seconds
+ setTimeout(() => {
+ notification.style.animation = 'slideOut 0.3s ease-in';
+ setTimeout(() => {
+ if (notification.parentNode) {
+ notification.parentNode.removeChild(notification);
+ }
+ }, 300);
+ }, 3000);
+}
+
+// Add CSS animations
+const style = document.createElement('style');
+style.textContent = `
+ @keyframes slideIn {
+ from {
+ transform: translateX(100%);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ }
+
+ @keyframes slideOut {
+ from {
+ transform: translateX(0);
+ opacity: 1;
+ }
+ to {
+ transform: translateX(100%);
+ opacity: 0;
+ }
+ }
+`;
+document.head.appendChild(style);
+
+// Initialize when DOM is loaded
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initializeApp);
+} else {
+ initializeApp();
+}
+
+// Log plugin status
+console.log('AppDev Design Mode Plugin: Basic example ready for testing');
diff --git a/qiming-vite-plugin-design-mode/examples/basic/package.json b/qiming-vite-plugin-design-mode/examples/basic/package.json
new file mode 100644
index 00000000..483e44ca
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/basic/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "vite-plugin-appdev-design-mode-basic-example",
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "vite": "^5.0.0"
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/basic/vite.config.ts b/qiming-vite-plugin-design-mode/examples/basic/vite.config.ts
new file mode 100644
index 00000000..6a43c00b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/basic/vite.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import appdevDesignMode from '../../packages/plugin/src/index';
+
+export default defineConfig({
+ plugins: [
+ react(),
+ appdevDesignMode({
+ verbose: true,
+ attributePrefix: 'data-appdev'
+ })
+ ]
+});
diff --git a/qiming-vite-plugin-design-mode/examples/demo/.gitignore b/qiming-vite-plugin-design-mode/examples/demo/.gitignore
new file mode 100644
index 00000000..a547bf36
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/qiming-vite-plugin-design-mode/examples/demo/README.md b/qiming-vite-plugin-design-mode/examples/demo/README.md
new file mode 100644
index 00000000..10304903
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/README.md
@@ -0,0 +1,61 @@
+# Design Mode Demo
+
+这是一个完整的示例项目,展示了 `@xagi/vite-plugin-design-mode` 的所有功能。
+
+## 功能演示
+
+### 1. 样式编辑
+- 点击任意元素查看编辑面板
+- 实时修改 Tailwind CSS 类
+- 自动保存到源文件
+
+### 2. 内容编辑
+- 双击文本元素进入编辑模式
+- 修改后自动更新源代码
+- 支持纯文本内容
+
+### 3. Iframe 支持
+- 在 iframe 中自动隐藏 UI
+- 通过 postMessage 通信
+- 支持外部设计面板控制
+
+## 快速开始
+
+```bash
+# 安装依赖
+npm install
+
+# 启动开发服务器
+npm run dev
+```
+
+访问 `http://localhost:5175` 查看演示。
+
+## 使用说明
+
+1. **启用设计模式**:点击右下角的 "Design Mode" 开关
+2. **选择元素**:点击页面上的任意元素
+3. **修改样式**:使用右侧面板修改背景、文字颜色、内边距等
+4. **编辑内容**:双击文本元素直接编辑
+5. **查看源码**:所有修改会自动保存到对应的 `.tsx` 文件
+
+## 页面说明
+
+- **Home**: 主页,展示核心功能卡片和可编辑示例
+- **Features**: 详细功能展示,包含各种可编辑元素
+- **Iframe Demo**: 演示 iframe 集成和通信机制
+
+## 技术栈
+
+- React 18
+- TypeScript
+- Vite 6
+- Tailwind CSS 4
+- Radix UI
+- @xagi/vite-plugin-design-mode
+
+## 注意事项
+
+- 仅支持静态字符串 className 的修改
+- 内容编辑仅支持纯文本节点
+- 所有修改会直接写入源文件,建议使用版本控制
diff --git a/qiming-vite-plugin-design-mode/examples/demo/README_CUSTOMIZABLE.md b/qiming-vite-plugin-design-mode/examples/demo/README_CUSTOMIZABLE.md
new file mode 100644
index 00000000..510aed6a
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/README_CUSTOMIZABLE.md
@@ -0,0 +1,580 @@
+# 可自定义属性面板配置指南
+
+## 概述
+
+本文档详细介绍了如何使用和配置可自定义的属性面板系统,该系统支持用户自定义配置,提供完整的双向同步设计模式架构。
+
+## 功能特性
+
+### 🎨 样式自定义
+- **颜色方案**: 自定义颜色库,支持导入导出
+- **样式预设**: 保存和管理常用样式组合
+- **主题切换**: 浅色/深色/跟随系统主题
+- **响应式布局**: 自适应不同屏幕尺寸
+
+### 📝 内容自定义
+- **富文本编辑**: 支持格式化文本编辑
+- **历史记录**: 自动保存编辑历史,支持回溯
+- **模板系统**: 自定义内容模板和占位符
+- **快捷操作**: 支持快捷键和批量操作
+
+### ⚙️ 高级配置
+- **面板布局**: 水平和垂直布局切换
+- **快捷键自定义**: 用户可自定义键盘快捷键
+- **自动保存**: 配置自动保存间隔和策略
+- **数据管理**: 支持配置的导入导出和重置
+
+## 安装和配置
+
+### 1. 基础安装
+
+```bash
+# 安装依赖
+npm install antd @ant-design/icons
+
+# 或使用 yarn
+yarn add antd @ant-design/icons
+```
+
+### 2. 配置文件
+
+创建 `vite.config.ts`:
+
+```typescript
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import appdevDesignMode from '@xagi/vite-plugin-design-mode';
+
+export default defineConfig({
+ plugins: [
+ react(),
+ appdevDesignMode({
+ enabled: true,
+ iframeMode: {
+ enabled: true,
+ hideUI: true,
+ enableSelection: true,
+ enableDirectEdit: true,
+ },
+ batchUpdate: {
+ enabled: true,
+ debounceMs: 300,
+ },
+ bridge: {
+ timeout: 10000,
+ retryAttempts: 3,
+ heartbeatInterval: 30000,
+ debug: true,
+ }
+ })
+ ]
+});
+```
+
+### 3. 基础组件使用
+
+```tsx
+import React, { useState } from 'react';
+import { PropertyPanel } from '@xagi/design-mode';
+import type { PropertyPanelConfig } from '@xagi/design-mode';
+
+function MyApp() {
+ const [selectedElement, setSelectedElement] = useState(null);
+ const [currentStyle, setCurrentStyle] = useState('');
+ const [currentContent, setCurrentContent] = useState('');
+
+ const handleStyleUpdate = (data) => {
+ console.log('Style updated:', data);
+ };
+
+ const handleContentUpdate = (data) => {
+ console.log('Content updated:', data);
+ };
+
+ return (
+
+
{
+ console.log('Config changed:', config);
+ }}
+ />
+
+ );
+}
+```
+
+## 高级配置
+
+### 自定义配置接口
+
+```typescript
+export interface PropertyPanelConfig {
+ // 主题配置
+ theme: 'light' | 'dark' | 'auto';
+
+ // 自动保存设置
+ autoSave: boolean;
+ autoSaveInterval: number;
+
+ // 显示选项
+ showElementInfo: boolean;
+ showTooltips: boolean;
+
+ // 交互设置
+ enableKeyboardShortcuts: boolean;
+ defaultPanel: 'style' | 'content' | 'settings';
+ panelLayout: 'horizontal' | 'vertical';
+
+ // 自定义颜色库
+ colorPalette: string[];
+
+ // 自定义预设
+ customPresets: CustomPreset[];
+
+ // 快捷键配置
+ shortcuts: ShortcutConfig;
+}
+```
+
+### 快捷键配置
+
+```typescript
+const shortcuts = {
+ save: 'Ctrl+S', // 保存当前设置
+ undo: 'Ctrl+Z', // 撤销操作
+ redo: 'Ctrl+Y', // 重做操作
+ togglePanel: 'F2', // 切换面板
+ clearAll: 'Ctrl+Delete', // 清除所有
+ custom1: 'Ctrl+1', // 自定义快捷键1
+ custom2: 'Ctrl+2', // 自定义快捷键2
+ custom3: 'Ctrl+3' // 自定义快捷键3
+};
+```
+
+### 自定义预设管理
+
+```typescript
+interface CustomPreset {
+ id: string;
+ name: string; // 预设名称
+ type: 'style' | 'content'; // 预设类型
+ value: string; // 预设值
+ description: string; // 预设描述
+ tags: string[]; // 标签数组
+ favorite: boolean; // 是否收藏
+ createdAt: number; // 创建时间
+}
+
+// 创建自定义预设
+const myPreset: CustomPreset = {
+ id: 'preset_001',
+ name: '主要按钮样式',
+ type: 'style',
+ value: 'bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600',
+ description: '常用的主要按钮样式',
+ tags: ['按钮', '主要', '蓝色'],
+ favorite: true,
+ createdAt: Date.now()
+};
+```
+
+## API 接口
+
+### 组件属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+|--------|------|------|--------|
+| `selectedElement` | `ElementInfo \| null` | 当前选中的元素信息 | `null` |
+| `currentStyle` | `string` | 当前样式类名 | `''` |
+| `currentContent` | `string` | 当前内容 | `''` |
+| `onStyleUpdate` | `(data) => void` | 样式更新回调 | 必填 |
+| `onContentUpdate` | `(data) => void` | 内容更新回调 | 必填 |
+| `onStyleChange` | `(style) => void` | 样式变化回调 | 必填 |
+| `onContentChange` | `(content) => void` | 内容变化回调 | 必填 |
+| `config` | `Partial` | 面板配置 | `undefined` |
+| `onConfigChange` | `(config) => void` | 配置变化回调 | `undefined` |
+
+### 回调函数参数
+
+#### onStyleUpdate
+```typescript
+{
+ sourceInfo: {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+ };
+ newClass: string;
+}
+```
+
+#### onContentUpdate
+```typescript
+{
+ sourceInfo: {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+ };
+ newContent: string;
+}
+```
+
+## 事件处理
+
+### 键盘事件
+
+组件支持以下键盘事件:
+
+- **Ctrl+S**: 保存当前设置
+- **Ctrl+Z**: 撤销操作
+- **Ctrl+Y**: 重做操作
+- **F2**: 切换面板
+- **Ctrl+Delete**: 清除所有
+
+### 鼠标事件
+
+- **单击**: 选择元素
+- **双击**: 进入编辑模式
+- **右键**: 显示上下文菜单
+- **悬停**: 显示工具提示
+
+## 样式定制
+
+### 主题定制
+
+```typescript
+import { ConfigProvider } from 'antd';
+
+// 自定义主题
+const customTheme = {
+ algorithm: theme.darkAlgorithm,
+ token: {
+ colorPrimary: '#your-primary-color',
+ borderRadius: 8,
+ // 更多主题配置...
+ }
+};
+
+
+
+
+```
+
+### 自定义样式类
+
+```css
+/* 自定义面板样式 */
+.custom-property-panel {
+ --panel-bg: #f8f9fa;
+ --panel-border: #e9ecef;
+ --panel-text: #212529;
+}
+
+.custom-property-panel .ant-card {
+ background: var(--panel-bg);
+ border-color: var(--panel-border);
+}
+
+.custom-property-panel .ant-tabs-tab {
+ color: var(--panel-text);
+}
+```
+
+## 数据存储
+
+### LocalStorage 键值
+
+- `appdev_property_panel_config`: 面板配置
+- `appdev_property_panel_presets`: 自定义预设
+
+### 数据格式
+
+#### 配置数据格式
+```json
+{
+ "theme": "light",
+ "autoSave": true,
+ "autoSaveInterval": 3000,
+ "showElementInfo": true,
+ "showTooltips": true,
+ "enableKeyboardShortcuts": true,
+ "defaultPanel": "style",
+ "panelLayout": "horizontal",
+ "colorPalette": ["#000000", "#ffffff", "#3b82f6"],
+ "customPresets": [],
+ "shortcuts": {
+ "save": "Ctrl+S",
+ "undo": "Ctrl+Z",
+ "redo": "Ctrl+Y",
+ "togglePanel": "F2",
+ "clearAll": "Ctrl+Delete"
+ }
+}
+```
+
+#### 预设数据格式
+```json
+[
+ {
+ "id": "preset_001",
+ "name": "主要按钮样式",
+ "type": "style",
+ "value": "bg-blue-500 text-white px-4 py-2 rounded-lg",
+ "description": "常用的主要按钮样式",
+ "tags": ["按钮", "主要", "蓝色"],
+ "favorite": true,
+ "createdAt": 1640995200000
+ }
+]
+```
+
+## 错误处理
+
+### 常见错误
+
+#### 1. 导入配置失败
+```typescript
+try {
+ const config = JSON.parse(configString);
+ // 验证配置格式
+ if (!config.theme || !config.shortcuts) {
+ throw new Error('配置格式不完整');
+ }
+} catch (error) {
+ console.error('导入配置失败:', error);
+ message.error('配置格式错误,请检查文件格式');
+}
+```
+
+#### 2. 预设保存失败
+```typescript
+const savePreset = (preset) => {
+ try {
+ // 验证预设格式
+ if (!preset.name || !preset.value) {
+ throw new Error('预设名称和值不能为空');
+ }
+
+ // 保存预设
+ presetManager.savePreset(preset);
+ message.success('预设保存成功');
+ } catch (error) {
+ console.error('保存预设失败:', error);
+ message.error('保存失败: ' + error.message);
+ }
+};
+```
+
+### 错误边界
+
+```typescript
+import { ErrorBoundary } from 'react-error-boundary';
+
+function ErrorFallback({ error }) {
+ return (
+
+
配置面板出现错误
+
{error.message}
+
+
+ );
+}
+
+// 使用错误边界包装组件
+
+
+
+```
+
+## 性能优化
+
+### 1. 组件优化
+
+```typescript
+// 使用 React.memo 优化重渲染
+const PropertyPanel = React.memo(({
+ selectedElement,
+ currentStyle,
+ currentContent,
+ onStyleUpdate,
+ onContentUpdate,
+ onStyleChange,
+ onContentChange,
+ config,
+ onConfigChange
+}) => {
+ // 组件实现...
+});
+
+// 使用 useMemo 缓存计算结果
+const processedPresets = useMemo(() => {
+ return presets.filter(preset => preset.favorite);
+}, [presets]);
+
+// 使用 useCallback 缓存回调函数
+const handleStyleUpdate = useCallback((data) => {
+ onStyleUpdate(data);
+}, [onStyleUpdate]);
+```
+
+### 2. 内存管理
+
+```typescript
+// 清理副作用
+useEffect(() => {
+ const handleKeydown = (event) => {
+ // 处理键盘事件
+ };
+
+ document.addEventListener('keydown', handleKeydown);
+
+ return () => {
+ document.removeEventListener('keydown', handleKeydown);
+ };
+}, []);
+
+// 清理定时器
+useEffect(() => {
+ const timer = setInterval(() => {
+ // 自动保存逻辑
+ }, config.autoSaveInterval);
+
+ return () => {
+ clearInterval(timer);
+ };
+}, [config.autoSaveInterval]);
+```
+
+## 最佳实践
+
+### 1. 配置管理
+- 合理设置自动保存间隔,避免过于频繁的保存操作
+- 为不同用户角色设置不同的默认配置
+- 定期清理无用的自定义预设
+
+### 2. 性能优化
+- 大量预设数据时分页加载
+- 使用虚拟滚动优化长列表显示
+- 合理使用防抖和节流
+
+### 3. 用户体验
+- 提供清晰的错误提示和恢复机制
+- 支持快捷键组合,满足熟练用户需求
+- 保持界面的响应性和直观性
+
+### 4. 数据安全
+- 定期备份用户配置
+- 验证导入数据的格式和安全性
+- 提供配置恢复功能
+
+## 故障排除
+
+### 常见问题
+
+#### Q: 面板不显示或加载失败
+**A**: 检查以下项目:
+1. 确认所有依赖包已正确安装
+2. 检查网络连接是否正常
+3. 查看浏览器控制台是否有错误信息
+4. 验证配置格式是否正确
+
+#### Q: 自定义预设无法保存
+**A**: 可能原因:
+1. LocalStorage 存储已满,尝试清理无用数据
+2. 预设数据格式不正确,检查数据结构
+3. 浏览器隐私设置阻止了本地存储
+
+#### Q: 快捷键不生效
+**A**: 检查项目:
+1. 确认键盘事件监听器正确注册
+2. 检查是否有其他组件占用了相同的快捷键
+3. 验证快捷键格式是否符合系统要求
+
+#### Q: 配置导入导出失败
+**A**: 可能的问题:
+1. JSON 格式错误,使用在线工具验证
+2. 文件权限问题,检查文件读写权限
+3. 数据大小超限,压缩或分批导入
+
+### 调试技巧
+
+#### 1. 启用调试模式
+```typescript
+const config = {
+ debug: true,
+ // 其他配置...
+};
+```
+
+#### 2. 监控配置变化
+```typescript
+useEffect(() => {
+ console.log('Config changed:', config);
+}, [config]);
+```
+
+#### 3. 检查存储状态
+```typescript
+const checkStorage = () => {
+ const config = localStorage.getItem('appdev_property_panel_config');
+ const presets = localStorage.getItem('appdev_property_panel_presets');
+ console.log('Stored config:', config);
+ console.log('Stored presets:', presets);
+};
+```
+
+## 更新日志
+
+### v2.0.0 (当前版本)
+- ✨ 新增完整的可自定义属性面板
+- ✨ 支持主题切换和个性化配置
+- ✨ 新增预设管理系统
+- ✨ 支持配置导入导出
+- ✨ 增强键盘快捷键支持
+- ✨ 改进错误处理和恢复机制
+
+### v1.0.0
+- 🎉 初始版本发布
+- ✨ 基础的样式和内容编辑功能
+- ✨ 简单的面板配置选项
+
+## 技术支持
+
+如果您在使用过程中遇到问题,请通过以下方式获取帮助:
+
+1. **查看文档**: 详细阅读本文档和使用指南
+2. **检查示例**: 参考 examples/demo 目录中的示例代码
+3. **调试模式**: 启用 debug 模式查看详细日志
+4. **社区支持**: 在 GitHub 上提交 Issue
+5. **联系支持**: 发送邮件到 support@example.com
+
+## 贡献指南
+
+我们欢迎社区贡献!请查看 CONTRIBUTING.md 了解如何参与项目开发。
+
+### 贡献方式
+- 🐛 报告 Bug
+- 💡 提出新功能建议
+- 📝 完善文档
+- 💻 提交代码改进
+- 🧪 编写测试用例
+
+感谢您对项目的关注和支持!🎉
diff --git a/qiming-vite-plugin-design-mode/examples/demo/eslint.config.js b/qiming-vite-plugin-design-mode/examples/demo/eslint.config.js
new file mode 100644
index 00000000..5e6b472f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/eslint.config.js
@@ -0,0 +1,23 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/qiming-vite-plugin-design-mode/examples/demo/index.html b/qiming-vite-plugin-design-mode/examples/demo/index.html
new file mode 100644
index 00000000..3bcfba0d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+ 设计模式演示
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/demo/package-lock.json b/qiming-vite-plugin-design-mode/examples/demo/package-lock.json
new file mode 100644
index 00000000..01729e1e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/package-lock.json
@@ -0,0 +1,2097 @@
+{
+ "name": "design-mode-demo",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "design-mode-demo",
+ "version": "0.0.0",
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.2",
+ "@radix-ui/react-switch": "^1.1.1",
+ "clsx": "^2.1.1",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "tailwind-merge": "^2.5.5"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4.0.0",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "tailwindcss": "^4.0.0",
+ "typescript": "~5.6.2",
+ "vite": "^7.2.6"
+ }
+ },
+ "../..": {
+ "name": "@xagi/vite-plugin-design-mode",
+ "version": "1.0.25",
+ "extraneous": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/standalone": "^7.23.0",
+ "@babel/traverse": "^7.24.0",
+ "clsx": "^2.1.1",
+ "tailwind-merge": "^3.4.0"
+ },
+ "bin": {
+ "vite-plugin-design-mode": "dist/cli/index.js"
+ },
+ "devDependencies": {
+ "@types/babel__core": "^7.20.0",
+ "@types/babel__standalone": "^7.1.9",
+ "@types/babel__traverse": "^7.20.0",
+ "@types/node": "^20.0.0",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "typescript": "^5.0.0",
+ "vite": "^5.4.21",
+ "vitest": "^1.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0",
+ "@babel/types": "^7.0.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0",
+ "vite": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": false
+ },
+ "react-dom": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@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"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.15",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.2.6",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.3",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.17",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.17",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-x64": "4.1.17",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.17",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.17",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.17",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.17",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.1.17",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.1.17",
+ "@tailwindcss/oxide": "4.1.17",
+ "postcss": "^8.4.41",
+ "tailwindcss": "4.1.17"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.27",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "dev": true,
+ "license": "MIT",
+ "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.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.32",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.0",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.8.25",
+ "caniuse-lite": "^1.0.30001754",
+ "electron-to-chromium": "^1.5.249",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.1.4"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001757",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.263",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.53.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
+ "@rollup/rollup-android-arm64": "4.53.3",
+ "@rollup/rollup-darwin-arm64": "4.53.3",
+ "@rollup/rollup-darwin-x64": "4.53.3",
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
+ "@rollup/rollup-freebsd-x64": "4.53.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "2.6.0",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.17",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "license": "0BSD"
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.4",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.2.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz",
+ "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/demo/package.json b/qiming-vite-plugin-design-mode/examples/demo/package.json
new file mode 100644
index 00000000..cc1a2178
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "design-mode-demo",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.2",
+ "@radix-ui/react-switch": "^1.1.1",
+ "clsx": "^2.1.1",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "tailwind-merge": "^2.5.5"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4.0.0",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "tailwindcss": "^4.0.0",
+ "typescript": "~5.6.2",
+ "vite": "^7.2.6"
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/demo/postcss.config.js b/qiming-vite-plugin-design-mode/examples/demo/postcss.config.js
new file mode 100644
index 00000000..a34a3d56
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/postcss.config.js
@@ -0,0 +1,5 @@
+export default {
+ plugins: {
+ '@tailwindcss/postcss': {},
+ },
+};
diff --git a/qiming-vite-plugin-design-mode/examples/demo/public/vite.svg b/qiming-vite-plugin-design-mode/examples/demo/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/App.css b/qiming-vite-plugin-design-mode/examples/demo/src/App.css
new file mode 100644
index 00000000..b9d355df
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/App.css
@@ -0,0 +1,42 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/App.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/App.tsx
new file mode 100644
index 00000000..01c888d4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/App.tsx
@@ -0,0 +1,64 @@
+import React, { useState } from 'react';
+import HomePage from './pages/HomePage';
+import FeaturesPage from './pages/FeaturesPage';
+import IframeDemoPage from './pages/IframeDemoPage';
+
+function App() {
+ const [currentPage, setCurrentPage] = useState<'home' | 'features' | 'iframe'>('home');
+
+ return (
+
+ {/* 导航栏 */}
+
+
+ {/* Page Content */}
+
+ {currentPage === 'home' && }
+ {currentPage === 'features' && }
+ {currentPage === 'iframe' && }
+
+
+ );
+}
+
+export default App;
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/assets/react.svg b/qiming-vite-plugin-design-mode/examples/demo/src/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/ContentPanel.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/ContentPanel.tsx
new file mode 100644
index 00000000..a71ea57f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/ContentPanel.tsx
@@ -0,0 +1,612 @@
+import React from '../../react';
+import { useState, useEffect } from 'react';
+import { Card, Button, Input, Select, Space, Row, Col, Divider, Tooltip, Badge } from 'antd';
+import {
+ FontSizeOutlined,
+ BoldOutlined,
+ ItalicOutlined,
+ UnderlineOutlined,
+ AlignLeftOutlined,
+ AlignCenterOutlined,
+ AlignRightOutlined,
+ EditOutlined,
+ HistoryOutlined,
+ ClearOutlined,
+ SaveOutlined,
+ UndoOutlined,
+ EyeOutlined
+} from '@ant-design/icons';
+import type { ElementInfo, SourceInfo } from '../../../../packages/client-shared/src/messages';
+
+const { TextArea } = Input;
+const { Option } = Select;
+
+export interface ContentUpdateData {
+ sourceInfo: SourceInfo;
+ newContent: string;
+}
+
+export interface ContentPanelProps {
+ selectedElement: ElementInfo | null;
+ onUpdateContent: (data: ContentUpdateData) => void;
+ currentContent: string;
+ onContentChange: (newContent: string) => void;
+}
+
+/**
+ * 内容编辑工具配置
+ */
+const contentTools = {
+ // 文本格式化
+ formatting: [
+ { label: '加粗', value: '**', icon: , description: '添加加粗格式' },
+ { label: '斜体', value: '*', icon: , description: '添加斜体格式' },
+ { label: '下划线', value: '__', icon: , description: '添加下划线' }
+ ],
+
+ // 对齐方式
+ alignment: [
+ { label: '左对齐', value: 'text-left', icon: },
+ { label: '居中对齐', value: 'text-center', icon: },
+ { label: '右对齐', value: 'text-right', icon: }
+ ],
+
+ // 文本样式预设
+ textStyles: [
+ { label: '标题 1', value: 'text-4xl font-bold', preview: '大标题' },
+ { label: '标题 2', value: 'text-3xl font-semibold', preview: '中标题' },
+ { label: '标题 3', value: 'text-2xl font-medium', preview: '小标题' },
+ { label: '正文', value: 'text-base', preview: '普通文本' },
+ { label: '小字', value: 'text-sm text-gray-600', preview: '小字体' },
+ { label: '说明文字', value: 'text-xs text-gray-500', preview: '说明文字' }
+ ],
+
+ // 常用的占位符文本
+ placeholders: [
+ '请输入内容...',
+ '点击编辑文本',
+ '请输入标题',
+ '请输入描述',
+ '请输入按钮文本',
+ '请输入链接文本'
+ ]
+};
+
+/**
+ * 内容历史记录
+ */
+interface ContentHistory {
+ id: string;
+ content: string;
+ timestamp: number;
+ description: string;
+}
+
+/**
+ * 内容编辑面板组件
+ */
+export const ContentPanel: React.FC = ({
+ selectedElement,
+ onUpdateContent,
+ currentContent,
+ onContentChange
+}) => {
+ const [activeTab, setActiveTab] = useState('edit');
+ const [editingContent, setEditingContent] = useState(currentContent);
+ const [originalContent, setOriginalContent] = useState(currentContent);
+ const [contentHistory, setContentHistory] = useState([]);
+ const [isPreviewMode, setIsPreviewMode] = useState(false);
+ const [autoSave, setAutoSave] = useState(true);
+ const [wordCount, setWordCount] = useState(0);
+ const [characterCount, setCharacterCount] = useState(0);
+
+ useEffect(() => {
+ setEditingContent(currentContent);
+ setOriginalContent(currentContent);
+ updateCounts(currentContent);
+ }, [currentContent]);
+
+ /**
+ * 更新字符和单词计数
+ */
+ const updateCounts = (content: string) => {
+ setCharacterCount(content.length);
+ const words = content.trim().split(/\s+/).filter(word => word.length > 0);
+ setWordCount(words.length);
+ };
+
+ /**
+ * 处理内容变化
+ */
+ const handleContentChange = (newContent: string) => {
+ setEditingContent(newContent);
+ onContentChange(newContent);
+ updateCounts(newContent);
+
+ // 自动保存历史记录
+ if (autoSave && newContent !== originalContent) {
+ addToHistory(newContent, '自动保存');
+ }
+ };
+
+ /**
+ * 添加到历史记录
+ */
+ const addToHistory = (content: string, description: string) => {
+ const historyItem: ContentHistory = {
+ id: `history_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ content,
+ timestamp: Date.now(),
+ description
+ };
+
+ setContentHistory(prev => [historyItem, ...prev.slice(0, 9)]); // 保留最新10条记录
+ };
+
+ /**
+ * 恢复到历史记录
+ */
+ const restoreFromHistory = (historyItem: ContentHistory) => {
+ setEditingContent(historyItem.content);
+ onContentChange(historyItem.content);
+ updateCounts(historyItem.content);
+ addToHistory(historyItem.content, '恢复历史记录');
+ };
+
+ /**
+ * 清除内容
+ */
+ const clearContent = () => {
+ setEditingContent('');
+ onContentChange('');
+ updateCounts('');
+ addToHistory('', '清除内容');
+ };
+
+ /**
+ * 恢复原始内容
+ */
+ const restoreOriginal = () => {
+ setEditingContent(originalContent);
+ onContentChange(originalContent);
+ updateCounts(originalContent);
+ };
+
+ /**
+ * 应用内容更新
+ */
+ const applyChanges = () => {
+ if (!selectedElement) return;
+
+ onUpdateContent({
+ sourceInfo: selectedElement.sourceInfo,
+ newContent: editingContent
+ });
+
+ setOriginalContent(editingContent);
+ addToHistory(editingContent, '应用更改');
+ };
+
+ /**
+ * 插入格式化文本
+ */
+ const insertFormatting = (format: string) => {
+ const textarea = document.querySelector('textarea[data-content-editor]') as HTMLTextAreaElement;
+ if (!textarea) return;
+
+ const start = textarea.selectionStart;
+ const end = textarea.selectionEnd;
+ const selectedText = editingContent.substring(start, end);
+
+ let newContent = editingContent;
+ let newSelectedText = selectedText;
+
+ // 根据格式化类型插入
+ switch (format) {
+ case '**':
+ newSelectedText = `**${selectedText}**`;
+ break;
+ case '*':
+ newSelectedText = `*${selectedText}*`;
+ break;
+ case '__':
+ newSelectedText = `__${selectedText}__`;
+ break;
+ }
+
+ newContent =
+ editingContent.substring(0, start) +
+ newSelectedText +
+ editingContent.substring(end);
+
+ handleContentChange(newContent);
+
+ // 重新设置光标位置
+ setTimeout(() => {
+ textarea.focus();
+ textarea.setSelectionRange(
+ start + (format === '**' || format === '__' ? 2 : 1),
+ start + (format === '**' || format === '__' ? 2 : 1) + selectedText.length
+ );
+ }, 0);
+ };
+
+ /**
+ * 插入占位符
+ */
+ const insertPlaceholder = (placeholder: string) => {
+ const newContent = editingContent + placeholder;
+ handleContentChange(newContent);
+ };
+
+ /**
+ * 应用文本样式
+ */
+ const applyTextStyle = (style: string) => {
+ // 这里可以应用内联样式或类名
+ console.log('Applying text style:', style);
+ };
+
+ /**
+ * 获取文本统计信息
+ */
+ const getTextStats = () => {
+ const lines = editingContent.split('\n');
+ const paragraphs = editingContent.split('\n\n').filter(p => p.trim().length > 0);
+
+ return {
+ lines: lines.length,
+ paragraphs: paragraphs.length,
+ words: wordCount,
+ characters: characterCount,
+ charactersNoSpaces: editingContent.replace(/\s/g, '').length
+ };
+ };
+
+ if (!selectedElement) {
+ return (
+
+
+
+ );
+ }
+
+ // Check if element is editable (has static text)
+ if (selectedElement.isStaticText === false) {
+ return (
+
+
+
+
+
该元素不可编辑
+
只有纯静态文本可以编辑
+
(不包含变量或表达式)
+
+
+
+ );
+ }
+
+ const stats = getTextStats();
+
+ return (
+
+
+ 内容编辑
+
+
+ }
+ className="h-full"
+ extra={
+
+
+ : }
+ onClick={() => setIsPreviewMode(!isPreviewMode)}
+ type={isPreviewMode ? 'primary' : 'default'}
+ >
+ {isPreviewMode ? '编辑' : '预览'}
+
+
+ }>
+ 清除
+
+ }
+ >
+ 保存更改
+
+
+ }
+ >
+
+ {/* 元素信息 */}
+
+
当前元素
+
+
标签: <{selectedElement.tagName}>
+
当前位置: {selectedElement.sourceInfo.fileName.split('/').pop()}:{selectedElement.sourceInfo.lineNumber}
+
原内容: {originalContent.substring(0, 50)}{originalContent.length > 50 ? '...' : ''}
+
+
+
+ {/* 标签页导航 */}
+
+
+ {[
+ { key: 'edit', label: '编辑内容' },
+ { key: 'format', label: '格式化' },
+ { key: 'style', label: '文本样式' },
+ { key: 'history', label: '历史记录' },
+ { key: 'stats', label: '统计信息' }
+ ].map(tab => (
+
+ ))}
+
+
+
+ {/* 编辑内容 */}
+ {activeTab === 'edit' && (
+
+ {isPreviewMode ? (
+ // 预览模式
+
+
') }} />
+
+ ) : (
+ // 编辑模式
+
+ )}
+
+ )}
+
+ {/* 格式化工具 */}
+ {activeTab === 'format' && (
+
+ {/* 快速格式化 */}
+
+
快速格式化
+
+ {contentTools.formatting.map((format, index) => (
+
+ ))}
+
+
+
+ {/* 占位符 */}
+
+
常用占位符
+
+ {contentTools.placeholders.map((placeholder, index) => (
+
+ ))}
+
+
+
+ {/* 对齐方式 */}
+
+
对齐方式
+
+ {contentTools.alignment.map((align, index) => (
+
+ ))}
+
+
+
+ )}
+
+ {/* 文本样式 */}
+ {activeTab === 'style' && (
+
+
文本样式预设
+
+ {contentTools.textStyles.map((style, index) => (
+
+ ))}
+
+
+ )}
+
+ {/* 历史记录 */}
+ {activeTab === 'history' && (
+
+
+
编辑历史
+ }
+ onClick={() => setContentHistory([])}
+ >
+ 清除历史
+
+
+
+ {contentHistory.length === 0 ? (
+
+ ) : (
+
+ {contentHistory.map((item, index) => (
+
restoreFromHistory(item)}
+ >
+
+
+ {new Date(item.timestamp).toLocaleTimeString()}
+
+ {item.description}
+
+
+ {item.content.substring(0, 60)}{item.content.length > 60 ? '...' : ''}
+
+
+ ))}
+
+ )}
+
+ )}
+
+ {/* 统计信息 */}
+ {activeTab === 'stats' && (
+
+
文本统计
+
+
+
+
{stats.characters}
+
字符数(含空格)
+
+
+
{stats.charactersNoSpaces}
+
字符数(不含空格)
+
+
+
+
{stats.paragraphs}
+
段落数
+
+
+
+ {/* 阅读时间估算 */}
+
+
+ 预估阅读时间: 约 {Math.ceil(stats.words / 200)} 分钟
+
+
+
+ )}
+
+ {/* 自动保存设置 */}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ContentPanel;
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/PropertyPanel.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/PropertyPanel.tsx
new file mode 100644
index 00000000..da0797ba
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/PropertyPanel.tsx
@@ -0,0 +1,856 @@
+import React from '../../react';
+import { useState, useEffect, useCallback } from 'react';
+import {
+ Card,
+ Tabs,
+ Button,
+ Input,
+ Select,
+ Space,
+ Row,
+ Col,
+ Divider,
+ Switch,
+ Slider,
+ InputNumber,
+ Upload,
+ Modal,
+ Form,
+ List,
+ Tag,
+ ColorPicker,
+ Tooltip,
+ Popconfirm,
+ message,
+ ConfigProvider,
+ theme
+} from 'antd';
+import {
+ SettingOutlined,
+ SaveOutlined,
+ UploadOutlined,
+ DownloadOutlined,
+ PlusOutlined,
+ DeleteOutlined,
+ EditOutlined,
+ ReloadOutlined,
+ BulbOutlined,
+ EyeOutlined,
+ ToolOutlined,
+ FontSizeOutlined,
+ BgColorsOutlined,
+ BorderOutlined,
+ LayoutOutlined,
+ ThunderboltOutlined,
+ UserOutlined,
+ HistoryOutlined,
+ StarOutlined
+} from '@ant-design/icons';
+import type { ElementInfo, SourceInfo } from '../../../../packages/client-shared/src/messages';
+import StylePanel from './StylePanel';
+import ContentPanel from './ContentPanel';
+
+const { TabPane } = Tabs;
+const { Option } = Select;
+const { TextArea } = Input;
+
+export interface PropertyPanelConfig {
+ theme: 'light' | 'dark' | 'auto';
+ autoSave: boolean;
+ autoSaveInterval: number;
+ showElementInfo: boolean;
+ showTooltips: boolean;
+ enableKeyboardShortcuts: boolean;
+ defaultPanel: 'style' | 'content' | 'settings';
+ panelLayout: 'horizontal' | 'vertical';
+ colorPalette: string[];
+ customPresets: CustomPreset[];
+ shortcuts: ShortcutConfig;
+}
+
+export interface CustomPreset {
+ id: string;
+ name: string;
+ type: 'style' | 'content';
+ value: string;
+ description: string;
+ tags: string[];
+ favorite: boolean;
+ createdAt: number;
+}
+
+export interface ShortcutConfig {
+ save: string;
+ undo: string;
+ redo: string;
+ togglePanel: string;
+ clearAll: string;
+ custom1?: string;
+ custom2?: string;
+ custom3?: string;
+}
+
+export interface PropertyPanelProps {
+ selectedElement: ElementInfo | null;
+ currentStyle: string;
+ currentContent: string;
+ onStyleUpdate: (data: { sourceInfo: SourceInfo; newClass: string }) => void;
+ onContentUpdate: (data: { sourceInfo: SourceInfo; newContent: string }) => void;
+ onStyleChange: (newStyle: string) => void;
+ onContentChange: (newContent: string) => void;
+ config?: Partial
;
+ onConfigChange?: (config: PropertyPanelConfig) => void;
+}
+
+/**
+ * 默认配置
+ */
+const defaultConfig: PropertyPanelConfig = {
+ theme: 'light',
+ autoSave: true,
+ autoSaveInterval: 3000,
+ showElementInfo: true,
+ showTooltips: true,
+ enableKeyboardShortcuts: true,
+ defaultPanel: 'style',
+ panelLayout: 'horizontal',
+ colorPalette: [
+ '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff', '#ffff00',
+ '#ff00ff', '#00ffff', '#ffa500', '#800080', '#008000', '#000080'
+ ],
+ customPresets: [],
+ shortcuts: {
+ save: 'Ctrl+S',
+ undo: 'Ctrl+Z',
+ redo: 'Ctrl+Y',
+ togglePanel: 'F2',
+ clearAll: 'Ctrl+Delete'
+ }
+};
+
+/**
+ * 预设管理器
+ */
+class PresetManager {
+ private storageKey = 'appdev_property_panel_presets';
+
+ getPresets(): CustomPreset[] {
+ try {
+ const stored = localStorage.getItem(this.storageKey);
+ return stored ? JSON.parse(stored) : [];
+ } catch {
+ return [];
+ }
+ }
+
+ savePreset(preset: CustomPreset): void {
+ const presets = this.getPresets();
+ const existingIndex = presets.findIndex(p => p.id === preset.id);
+
+ if (existingIndex >= 0) {
+ presets[existingIndex] = { ...preset, createdAt: Date.now() };
+ } else {
+ presets.push({ ...preset, id: `preset_${Date.now()}`, createdAt: Date.now() });
+ }
+
+ localStorage.setItem(this.storageKey, JSON.stringify(presets));
+ }
+
+ deletePreset(presetId: string): void {
+ const presets = this.getPresets().filter(p => p.id !== presetId);
+ localStorage.setItem(this.storageKey, JSON.stringify(presets));
+ }
+
+ exportPresets(): string {
+ return JSON.stringify(this.getPresets(), null, 2);
+ }
+
+ importPresets(jsonData: string): void {
+ try {
+ const presets: CustomPreset[] = JSON.parse(jsonData);
+ if (Array.isArray(presets)) {
+ localStorage.setItem(this.storageKey, JSON.stringify(presets));
+ }
+ } catch (error) {
+ throw new Error('无效的预设数据格式');
+ }
+ }
+}
+
+/**
+ * 配置管理器
+ */
+class ConfigManager {
+ private storageKey = 'appdev_property_panel_config';
+
+ getConfig(): PropertyPanelConfig {
+ try {
+ const stored = localStorage.getItem(this.storageKey);
+ return stored ? { ...defaultConfig, ...JSON.parse(stored) } : defaultConfig;
+ } catch {
+ return defaultConfig;
+ }
+ }
+
+ saveConfig(config: PropertyPanelConfig): void {
+ localStorage.setItem(this.storageKey, JSON.stringify(config));
+ }
+
+ resetConfig(): PropertyPanelConfig {
+ localStorage.removeItem(this.storageKey);
+ return defaultConfig;
+ }
+}
+
+/**
+ * 快捷键管理器
+ */
+class ShortcutManager {
+ private listeners: Map void> = new Map();
+
+ register(shortcut: string, callback: () => void): void {
+ this.listeners.set(shortcut, callback);
+ }
+
+ handleKeydown(event: KeyboardEvent): void {
+ const shortcuts = Array.from(this.listeners.keys());
+
+ for (const shortcut of shortcuts) {
+ if (this.matchShortcut(event, shortcut)) {
+ event.preventDefault();
+ this.listeners.get(shortcut)?.();
+ break;
+ }
+ }
+ }
+
+ private matchShortcut(event: KeyboardEvent, shortcut: string): boolean {
+ const keys = shortcut.toLowerCase().split('+');
+ const eventKey = event.key.toLowerCase();
+ const ctrl = event.ctrlKey || event.metaKey;
+ const shift = event.shiftKey;
+ const alt = event.altKey;
+
+ // 检查修饰键
+ if (keys.includes('ctrl') && !ctrl) return false;
+ if (keys.includes('shift') && !shift) return false;
+ if (keys.includes('alt') && !alt) return false;
+
+ // 检查主键
+ const mainKey = keys[keys.length - 1];
+ if (mainKey !== eventKey) return false;
+
+ return true;
+ }
+}
+
+const presetManager = new PresetManager();
+const configManager = new ConfigManager();
+const shortcutManager = new ShortcutManager();
+
+/**
+ * 可自定义的属性面板组件
+ */
+export const PropertyPanel: React.FC = ({
+ selectedElement,
+ currentStyle,
+ currentContent,
+ onStyleUpdate,
+ onContentUpdate,
+ onStyleChange,
+ onContentChange,
+ config: userConfig,
+ onConfigChange
+}) => {
+ const [config, setConfig] = useState(() => ({
+ ...defaultConfig,
+ ...userConfig
+ }));
+
+ const [activeTab, setActiveTab] = useState(config.defaultPanel);
+ const [presets, setPresets] = useState(() => presetManager.getPresets());
+ const [isPresetModalVisible, setIsPresetModalVisible] = useState(false);
+ const [isConfigModalVisible, setIsConfigModalVisible] = useState(false);
+ const [newPreset, setNewPreset] = useState>({});
+ const [messageApi, contextHolder] = message.useMessage();
+
+ /**
+ * 保存配置
+ */
+ const saveConfig = useCallback((newConfig: PropertyPanelConfig) => {
+ const finalConfig = { ...config, ...newConfig };
+ setConfig(finalConfig);
+ configManager.saveConfig(finalConfig);
+ onConfigChange?.(finalConfig);
+ messageApi.success('配置已保存');
+ }, [config, onConfigChange, messageApi]);
+
+ /**
+ * 保存预设
+ */
+ const savePreset = useCallback((preset: CustomPreset) => {
+ presetManager.savePreset(preset);
+ const updatedPresets = presetManager.getPresets();
+ setPresets(updatedPresets);
+ messageApi.success(`预设 "${preset.name}" 已保存`);
+ }, [messageApi]);
+
+ /**
+ * 删除预设
+ */
+ const deletePreset = useCallback((presetId: string) => {
+ presetManager.deletePreset(presetId);
+ const updatedPresets = presetManager.getPresets();
+ setPresets(updatedPresets);
+ messageApi.success('预设已删除');
+ }, [messageApi]);
+
+ /**
+ * 导出配置
+ */
+ const exportConfig = useCallback(() => {
+ const exportData = {
+ config,
+ presets: presetManager.exportPresets()
+ };
+
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `property-panel-config-${Date.now()}.json`;
+ a.click();
+ URL.revokeObjectURL(url);
+
+ messageApi.success('配置已导出');
+ }, [config, messageApi]);
+
+ /**
+ * 导入配置
+ */
+ const importConfig = useCallback((file: File) => {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ try {
+ const importData = JSON.parse(e.target?.result as string);
+
+ if (importData.config) {
+ saveConfig(importData.config);
+ }
+
+ if (importData.presets) {
+ presetManager.importPresets(importData.presets);
+ setPresets(presetManager.getPresets());
+ }
+
+ messageApi.success('配置已导入');
+ } catch (error) {
+ messageApi.error('导入失败:无效的配置文件');
+ }
+ };
+ reader.readAsText(file);
+ }, [saveConfig, messageApi]);
+
+ /**
+ * 处理快捷键
+ */
+ useEffect(() => {
+ if (!config.enableKeyboardShortcuts) return;
+
+ const handleKeydown = (event: KeyboardEvent) => {
+ shortcutManager.handleKeydown(event);
+ };
+
+ // 注册快捷键
+ shortcutManager.register(config.shortcuts.save, () => {
+ // 这里可以添加保存逻辑
+ console.log('快捷键:保存');
+ });
+
+ shortcutManager.register(config.shortcuts.togglePanel, () => {
+ setActiveTab(prev => prev === 'style' ? 'content' : prev === 'content' ? 'settings' : 'style');
+ });
+
+ document.addEventListener('keydown', handleKeydown);
+ return () => document.removeEventListener('keydown', handleKeydown);
+ }, [config.enableKeyboardShortcuts, config.shortcuts]);
+
+ /**
+ * 创建预设
+ */
+ const handleCreatePreset = () => {
+ if (!newPreset.name || !newPreset.value) {
+ messageApi.error('请填写预设名称和值');
+ return;
+ }
+
+ const preset: CustomPreset = {
+ id: `preset_${Date.now()}`,
+ name: newPreset.name!,
+ type: newPreset.type!,
+ value: newPreset.value!,
+ description: newPreset.description || '',
+ tags: newPreset.tags || [],
+ favorite: false,
+ createdAt: Date.now()
+ };
+
+ savePreset(preset);
+ setIsPresetModalVisible(false);
+ setNewPreset({});
+ };
+
+ /**
+ * 获取主题配置
+ */
+ const getThemeConfig = () => {
+ const theme = config.theme === 'auto'
+ ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
+ : config.theme;
+
+ return {
+ algorithm: theme === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
+ token: {
+ colorPrimary: '#3b82f6',
+ borderRadius: 6,
+ }
+ };
+ };
+
+ /**
+ * 获取快速操作组件
+ */
+ const QuickActions = () => (
+
+
+
+
+ } block />
+
+
+
+
+ } block />
+
+
+
+
+ } onClick={exportConfig} block />
+
+
+
+
+ {
+ importConfig(file);
+ return false;
+ }}
+ >
+ } block />
+
+
+
+
+
+ );
+
+ /**
+ * 获取元素信息组件
+ */
+ const ElementInfo = () => (
+ selectedElement && config.showElementInfo ? (
+
+
+
标签: <{selectedElement.tagName}>
+
类名: {selectedElement.className || '无'}
+
文件: {selectedElement.sourceInfo.fileName.split('/').pop()}
+
位置: {selectedElement.sourceInfo.lineNumber}:{selectedElement.sourceInfo.columnNumber}
+
内容: {selectedElement.textContent.substring(0, 30)}{selectedElement.textContent.length > 30 ? '...' : ''}
+
+
+ ) : null
+ );
+
+ /**
+ * 获取自定义预设组件
+ */
+ const CustomPresets = () => (
+
+ 自定义预设
+ }
+ onClick={() => setIsPresetModalVisible(true)}
+ >
+ 新建
+
+
+ }
+ >
+ (
+
+ }
+ onClick={() => {
+ if (preset.type === 'style') {
+ onStyleChange(preset.value);
+ } else {
+ onContentChange(preset.value);
+ }
+ }}
+ />
+ ,
+ deletePreset(preset.id)}
+ >
+ } danger />
+
+ ]}
+ >
+ {preset.name}}
+ description={
+
+
{preset.value}
+
+ {preset.tags.map(tag => (
+ {tag}
+ ))}
+
+
+ }
+ />
+
+ )}
+ />
+
+ );
+
+ return (
+
+ {contextHolder}
+
+ {/* 顶部工具栏 */}
+
+
+
+
+ 属性面板
+ {selectedElement && (
+ {selectedElement.tagName}
+ )}
+
+
+
+ }
+ onClick={() => setIsConfigModalVisible(true)}
+ />
+
+
+ : }
+ onClick={() => saveConfig({
+ theme: config.theme === 'light' ? 'dark' : config.theme === 'dark' ? 'auto' : 'light'
+ })}
+ />
+
+
+
+
+
+ {/* 主体内容 */}
+
+
+ );
+};
+
+export default PropertyPanel;
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/StylePanel.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/StylePanel.tsx
new file mode 100644
index 00000000..77d5d531
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/external-panel/StylePanel.tsx
@@ -0,0 +1,567 @@
+import React from '../../react';
+import { useState, useEffect } from 'react';
+import { Card, Button, Input, Select, Slider, ColorPicker, InputNumber, Divider, Tag, Space, Row, Col } from 'antd';
+import { BoldOutlined, ItalicOutlined, UnderlineOutlined, FontSizeOutlined, BgColorsOutlined, BorderOutlined } from '@ant-design/icons';
+import type { ElementInfo, SourceInfo } from '../../../../packages/client-shared/src/messages';
+
+const { TextArea } = Input;
+const { Option } = Select;
+
+export interface StyleUpdateData {
+ sourceInfo: SourceInfo;
+ newClass: string;
+}
+
+export interface StylePanelProps {
+ selectedElement: ElementInfo | null;
+ onUpdateStyle: (data: StyleUpdateData) => void;
+ currentClass: string;
+ onClassChange: (newClass: string) => void;
+}
+
+/**
+ * 样式预设配置
+ */
+const stylePresets = {
+ colors: {
+ backgrounds: [
+ { label: '白色', value: 'bg-white', color: '#ffffff' },
+ { label: '浅灰色', value: 'bg-gray-100', color: '#f3f4f6' },
+ { label: '浅蓝色', value: 'bg-blue-100', color: '#dbeafe' },
+ { label: '浅绿色', value: 'bg-green-100', color: '#dcfce7' },
+ { label: '浅红色', value: 'bg-red-100', color: '#fee2e2' },
+ { label: '浅黄色', value: 'bg-yellow-100', color: '#fef3c7' },
+ { label: '浅紫色', value: 'bg-purple-100', color: '#f3e8ff' },
+ { label: '深蓝色', value: 'bg-blue-600', color: '#2563eb' },
+ ],
+ text: [
+ { label: '黑色', value: 'text-black', color: '#000000' },
+ { label: '深灰色', value: 'text-gray-900', color: '#111827' },
+ { label: '中灰色', value: 'text-gray-600', color: '#4b5563' },
+ { label: '蓝色', value: 'text-blue-600', color: '#2563eb' },
+ { label: '绿色', value: 'text-green-600', color: '#16a34a' },
+ { label: '红色', value: 'text-red-600', color: '#dc2626' },
+ { label: '黄色', value: 'text-yellow-600', color: '#ca8a04' },
+ { label: '紫色', value: 'text-purple-600', color: '#9333ea' },
+ ]
+ },
+
+ spacing: {
+ padding: [
+ { label: '无内边距', value: 'p-0', preview: 'padding: 0px' },
+ { label: '小内边距', value: 'p-2', preview: 'padding: 8px' },
+ { label: '中等内边距', value: 'p-4', preview: 'padding: 16px' },
+ { label: '大内边距', value: 'p-6', preview: 'padding: 24px' },
+ { label: '超大内边距', value: 'p-8', preview: 'padding: 32px' },
+ ],
+ margin: [
+ { label: '无外边距', value: 'm-0', preview: 'margin: 0px' },
+ { label: '小外边距', value: 'm-2', preview: 'margin: 8px' },
+ { label: '中等外边距', value: 'm-4', preview: 'margin: 16px' },
+ { label: '大外边距', value: 'm-6', preview: 'margin: 24px' },
+ ]
+ },
+
+ border: {
+ radius: [
+ { label: '无圆角', value: 'rounded-none', preview: 'border-radius: 0px' },
+ { label: '小圆角', value: 'rounded-sm', preview: 'border-radius: 2px' },
+ { label: '中等圆角', value: 'rounded-md', preview: 'border-radius: 6px' },
+ { label: '大圆角', value: 'rounded-lg', preview: 'border-radius: 8px' },
+ { label: '超大圆角', value: 'rounded-xl', preview: 'border-radius: 12px' },
+ { label: '完全圆角', value: 'rounded-full', preview: 'border-radius: 9999px' },
+ ],
+ width: [
+ { label: '无边框', value: 'border-0', preview: 'border-width: 0px' },
+ { label: '细边框', value: 'border', preview: 'border-width: 1px' },
+ { label: '中等边框', value: 'border-2', preview: 'border-width: 2px' },
+ { label: '粗边框', value: 'border-4', preview: 'border-width: 4px' },
+ ]
+ },
+
+ typography: {
+ fontSize: [
+ { label: '很小', value: 'text-xs', preview: 'font-size: 12px' },
+ { label: '小', value: 'text-sm', preview: 'font-size: 14px' },
+ { label: '正常', value: 'text-base', preview: 'font-size: 16px' },
+ { label: '大', value: 'text-lg', preview: 'font-size: 18px' },
+ { label: '很大', value: 'text-xl', preview: 'font-size: 20px' },
+ { label: '巨大', value: 'text-2xl', preview: 'font-size: 24px' },
+ ],
+ fontWeight: [
+ { label: '细体', value: 'font-thin', preview: 'font-weight: 100' },
+ { label: '特细', value: 'font-extralight', preview: 'font-weight: 200' },
+ { label: '细体', value: 'font-light', preview: 'font-weight: 300' },
+ { label: '正常', value: 'font-normal', preview: 'font-weight: 400' },
+ { label: '中粗', value: 'font-medium', preview: 'font-weight: 500' },
+ { label: '半粗', value: 'font-semibold', preview: 'font-weight: 600' },
+ { label: '粗体', value: 'font-bold', preview: 'font-weight: 700' },
+ { label: '超粗', value: 'font-black', preview: 'font-weight: 900' },
+ ]
+ },
+
+ layout: {
+ display: [
+ { label: '块级', value: 'block', preview: 'display: block' },
+ { label: '内联', value: 'inline', preview: 'display: inline' },
+ { label: '内联块', value: 'inline-block', preview: 'display: inline-block' },
+ { label: '弹性', value: 'flex', preview: 'display: flex' },
+ { label: '网格', value: 'grid', preview: 'display: grid' },
+ { label: '隐藏', value: 'hidden', preview: 'display: none' },
+ ],
+ position: [
+ { label: '静态', value: 'static', preview: 'position: static' },
+ { label: '相对', value: 'relative', preview: 'position: relative' },
+ { label: '绝对', value: 'absolute', preview: 'position: absolute' },
+ { label: '固定', value: 'fixed', preview: 'position: fixed' },
+ { label: '粘性', value: 'sticky', preview: 'position: sticky' },
+ ]
+ }
+};
+
+/**
+ * 样式编辑面板组件
+ */
+export const StylePanel: React.FC
= ({
+ selectedElement,
+ onUpdateStyle,
+ currentClass,
+ onClassChange
+}) => {
+ const [activeTab, setActiveTab] = useState('presets');
+ const [customStyles, setCustomStyles] = useState(currentClass);
+ const [colorPickerVisible, setColorPickerVisible] = useState(false);
+ const [customColor, setCustomColor] = useState('#3b82f6');
+
+ useEffect(() => {
+ setCustomStyles(currentClass);
+ }, [currentClass]);
+
+ /**
+ * 添加样式类
+ */
+ const addStyleClass = (className: string) => {
+ const newStyles = mergeClasses(customStyles, className);
+ setCustomStyles(newStyles);
+ onClassChange(newStyles);
+ };
+
+ /**
+ * 移除样式类
+ */
+ const removeStyleClass = (className: string) => {
+ const newStyles = removeClass(customStyles, className);
+ setCustomStyles(newStyles);
+ onClassChange(newStyles);
+ };
+
+ /**
+ * 更新样式
+ */
+ const updateStyles = () => {
+ if (!selectedElement) return;
+
+ onUpdateStyle({
+ sourceInfo: selectedElement.sourceInfo,
+ newClass: customStyles
+ });
+ };
+
+ /**
+ * 清除所有样式
+ */
+ const clearAllStyles = () => {
+ setCustomStyles('');
+ onClassChange('');
+ };
+
+ /**
+ * 合并样式类
+ */
+ const mergeClasses = (existing: string, newClass: string): string => {
+ const classes = existing.split(' ').filter(Boolean);
+ const newClasses = newClass.split(' ').filter(Boolean);
+
+ // 移除冲突的同类样式
+ const filteredClasses = classes.filter(cls => {
+ return !newClasses.some(newCls =>
+ (cls.startsWith('bg-') && newCls.startsWith('bg-')) ||
+ (cls.startsWith('text-') && newCls.startsWith('text-')) ||
+ (cls.startsWith('p-') && newCls.startsWith('p-')) ||
+ (cls.startsWith('m-') && newCls.startsWith('m-')) ||
+ (cls.startsWith('rounded') && newCls.startsWith('rounded')) ||
+ (cls.startsWith('border-') && newCls.startsWith('border-')) ||
+ (cls.startsWith('text-') && newCls.startsWith('text-')) ||
+ (cls.startsWith('font-') && newCls.startsWith('font-')) ||
+ (cls.startsWith('flex') && newCls.startsWith('flex')) ||
+ (cls.startsWith('grid') && newCls.startsWith('grid')) ||
+ cls === newCls
+ );
+ });
+
+ return [...filteredClasses, ...newClasses].join(' ').trim();
+ };
+
+ /**
+ * 移除样式类
+ */
+ const removeClass = (existing: string, classToRemove: string): string => {
+ return existing.split(' ')
+ .filter(cls => cls !== classToRemove && !cls.startsWith(classToRemove.split('-')[0]))
+ .join(' ')
+ .trim();
+ };
+
+ /**
+ * 获取当前应用的样式类
+ */
+ const getAppliedStyles = (): string[] => {
+ return customStyles.split(' ').filter(Boolean);
+ };
+
+ if (!selectedElement) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+ 样式编辑
+
+ }
+ className="h-full"
+ extra={
+
+
+
+
+ }
+ >
+
+ {/* 元素信息 */}
+
+
当前元素
+
+
标签: <{selectedElement.tagName}>
+
位置: {selectedElement.sourceInfo.fileName.split('/').pop()}:{selectedElement.sourceInfo.lineNumber}
+
+
+
+ {/* 快速预设标签页 */}
+
+
+ {[
+ { key: 'presets', label: '预设样式' },
+ { key: 'colors', label: '颜色' },
+ { key: 'typography', label: '字体' },
+ { key: 'layout', label: '布局' },
+ { key: 'custom', label: '自定义' }
+ ].map(tab => (
+
+ ))}
+
+
+
+ {/* 预设样式 */}
+ {activeTab === 'presets' && (
+
+ {/* 背景颜色 */}
+
+
背景颜色
+
+ {stylePresets.colors.backgrounds.map((color, index) => (
+
+ ))}
+
+
+
+ {/* 圆角 */}
+
+
圆角
+
+ {stylePresets.border.radius.map((radius, index) => (
+
+ ))}
+
+
+
+ {/* 边距 */}
+
+
边距
+
+ {stylePresets.spacing.padding.map((padding, index) => (
+
+ ))}
+
+
+
+ )}
+
+ {/* 颜色面板 */}
+ {activeTab === 'colors' && (
+
+ {/* 背景颜色 */}
+
+
背景颜色
+
+ {stylePresets.colors.backgrounds.map((color, index) => (
+
+ ))}
+
+
+
+ {/* 文字颜色 */}
+
+
文字颜色
+
+ {stylePresets.colors.text.map((color, index) => (
+
+ ))}
+
+
+
+ {/* 自定义颜色 */}
+
+
+ )}
+
+ {/* 字体面板 */}
+ {activeTab === 'typography' && (
+
+ {/* 字体大小 */}
+
+
字体大小
+
+ {stylePresets.typography.fontSize.map((fontSize, index) => (
+
+ ))}
+
+
+
+ {/* 字体粗细 */}
+
+
字体粗细
+
+ {stylePresets.typography.fontWeight.map((fontWeight, index) => (
+
+ ))}
+
+
+
+ )}
+
+ {/* 布局面板 */}
+ {activeTab === 'layout' && (
+
+ {/* 显示类型 */}
+
+
显示类型
+
+ {stylePresets.layout.display.map((display, index) => (
+
+ ))}
+
+
+
+ {/* 定位 */}
+
+
定位
+
+ {stylePresets.layout.position.map((position, index) => (
+
+ ))}
+
+
+
+ )}
+
+ {/* 自定义样式 */}
+ {activeTab === 'custom' && (
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {/* 已应用的样式标签 */}
+ {getAppliedStyles().length > 0 && (
+
+
已应用样式
+
+ {getAppliedStyles().map((styleClass, index) => (
+ removeStyleClass(styleClass)}
+ className="mb-1"
+ >
+ {styleClass}
+
+ ))}
+
+
+ )}
+
+
+ );
+};
+
+export default StylePanel;
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/index.css b/qiming-vite-plugin-design-mode/examples/demo/src/index.css
new file mode 100644
index 00000000..a461c505
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/index.css
@@ -0,0 +1 @@
+@import "tailwindcss";
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/main.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/main.tsx
new file mode 100644
index 00000000..2239905c
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import './index.css';
+import App from './App.tsx';
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/pages/CustomizableDemoPage.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/pages/CustomizableDemoPage.tsx
new file mode 100644
index 00000000..7318b977
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/pages/CustomizableDemoPage.tsx
@@ -0,0 +1,499 @@
+import React from '../react';
+import { useState, useEffect } from 'react';
+import { Card, Row, Col, Button, Space, Divider, message, Badge } from 'antd';
+import { ThunderboltOutlined, CodeOutlined, BgColorsOutlined, EditOutlined, EyeOutlined, ReloadOutlined, WarningOutlined, CheckCircleOutlined } from '@ant-design/icons';
+import type { ElementInfo, SourceInfo } from '../../../../packages/client-shared/src/messages';
+import PropertyPanel from '../external-panel/PropertyPanel';
+import type { PropertyPanelConfig } from '../external-panel/PropertyPanel';
+import { bridge } from '../../../../packages/client-react/src/bridge';
+
+export default function CustomizableDemoPage() {
+ const [selectedElement, setSelectedElement] = useState(null);
+ const [currentStyle, setCurrentStyle] = useState('');
+ const [currentContent, setCurrentContent] = useState('');
+ const [demoConfig, setDemoConfig] = useState();
+ const [bridgeStatus, setBridgeStatus] = useState<'checking' | 'connected' | 'disconnected' | 'error'>('checking');
+ const [bridgeInfo, setBridgeInfo] = useState(null);
+
+ // Bridge状态检查
+ useEffect(() => {
+ const checkBridgeStatus = async () => {
+ try {
+ // 检查环境信息
+ const envInfo = bridge.getEnvironmentInfo();
+ setBridgeInfo(envInfo);
+
+ console.log('[Demo] Bridge environment info:', envInfo);
+
+ if (!envInfo.isIframe) {
+ setBridgeStatus('disconnected');
+ console.log('[Demo] Running in main window, bridge connection not needed');
+ return;
+ }
+
+ // 执行健康检查
+ const health = await bridge.healthCheck();
+ console.log('[Demo] Bridge health check:', health);
+
+ if (health.status === 'healthy') {
+ setBridgeStatus('connected');
+ } else if (health.status === 'degraded') {
+ setBridgeStatus('error');
+ } else {
+ setBridgeStatus('disconnected');
+ }
+ } catch (error) {
+ console.error('[Demo] Bridge health check failed:', error);
+ setBridgeStatus('error');
+ }
+ };
+
+ // 立即检查一次
+ checkBridgeStatus();
+
+ // 定期检查
+ const statusTimer = setInterval(checkBridgeStatus, 10000); // 10秒检查一次
+
+ return () => clearInterval(statusTimer);
+ }, []);
+
+ /**
+ * 获取Bridge状态显示组件
+ */
+ const BridgeStatusPanel = () => {
+ const getStatusIcon = () => {
+ switch (bridgeStatus) {
+ case 'connected':
+ return ;
+ case 'disconnected':
+ return ;
+ case 'error':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getStatusText = () => {
+ switch (bridgeStatus) {
+ case 'connected':
+ return 'Bridge已连接';
+ case 'disconnected':
+ return 'Bridge未连接(主窗口模式)';
+ case 'error':
+ return 'Bridge连接错误';
+ case 'checking':
+ return '检查Bridge状态...';
+ default:
+ return '未知状态';
+ }
+ };
+
+ const getStatusColor = () => {
+ switch (bridgeStatus) {
+ case 'connected':
+ return 'success';
+ case 'disconnected':
+ return 'warning';
+ case 'error':
+ return 'error';
+ default:
+ return 'processing';
+ }
+ };
+
+ return (
+
+
+
+ {getStatusIcon()}
+ {getStatusText()}
+
+
+
+
+
+
+
+ {bridgeInfo && (
+
+
环境: {bridgeInfo.isIframe ? 'Iframe' : '主窗口'}
+
位置: {bridgeInfo.location.substring(0, 50)}...
+
来源: {bridgeInfo.origin}
+
+ )}
+
+ );
+ };
+
+ // 模拟iframe内容
+ const [iframeSrc] = useState(() => {
+ // 生成一个包含源码映射的演示HTML
+ return `data:text/html;charset=utf-8,${encodeURIComponent(`
+
+
+
+ 设计模式演示
+
+
+
+
+
+
设计模式演示
+
体验强大的可视化开发工具,让设计触手可及
+
+
+
+
+
实时预览
+
即时查看修改效果,所见即所得的开发体验
+
+
+
智能提示
+
AI驱动的智能建议,提升开发效率
+
+
+
一键部署
+
无缝集成CI/CD流程,快速交付产品
+
+
+
+
+
准备好开始了吗?
+
立即体验下一代设计开发工具
+
+
+
+
+
// 简单的使用示例
+
import { DesignMode } from '@xagi/design-mode';
+
+
const config = {
+
enableSelection: true,
+
autoSave: true
+
};
+
+
DesignMode.init(config);
+
+
+
+
+
+
+ `)}`);
+
+ /**
+ * 处理iframe中的元素选择
+ */
+ useEffect(() => {
+ const handleMessage = (event: MessageEvent) => {
+ const { type, payload } = event.data;
+
+ switch (type) {
+ case 'ELEMENT_SELECTED':
+ setSelectedElement(payload.elementInfo);
+ setCurrentStyle(payload.elementInfo.className);
+ setCurrentContent(payload.elementInfo.textContent);
+ console.log('[Parent] Element selected:', payload.elementInfo);
+ break;
+
+ case 'ELEMENT_DESELECTED':
+ setSelectedElement(null);
+ console.log('[Parent] Element deselected');
+ break;
+
+ case 'STYLE_UPDATED':
+ console.log('[Parent] Style updated:', payload);
+ // 更新当前样式
+ if (selectedElement) {
+ setCurrentStyle(payload.newClass);
+ }
+ break;
+
+ case 'CONTENT_UPDATED':
+ console.log('[Parent] Content updated:', payload);
+ // 更新当前内容
+ if (selectedElement) {
+ setCurrentContent(payload.newValue);
+ }
+ break;
+ }
+ };
+
+ window.addEventListener('message', handleMessage);
+ return () => window.removeEventListener('message', handleMessage);
+ }, [selectedElement]);
+
+ /**
+ * 处理样式更新
+ */
+ const handleStyleUpdate = (data: { sourceInfo: SourceInfo; newClass: string }) => {
+ // 这里可以发送消息到iframe或者直接调用API
+ console.log('Style update requested:', data);
+
+ // 模拟更新成功
+ message.success('样式更新成功');
+ };
+
+ /**
+ * 处理内容更新
+ */
+ const handleContentUpdate = (data: { sourceInfo: SourceInfo; newContent: string }) => {
+ // 这里可以发送消息到iframe或者直接调用API
+ console.log('Content update requested:', data);
+
+ // 模拟更新成功
+ message.success('内容更新成功');
+ };
+
+ /**
+ * 配置更改处理
+ */
+ const handleConfigChange = (config: PropertyPanelConfig) => {
+ setDemoConfig(config);
+ console.log('Panel config changed:', config);
+ };
+
+ /**
+ * 重置演示
+ */
+ const resetDemo = () => {
+ setSelectedElement(null);
+ setCurrentStyle('');
+ setCurrentContent('');
+
+ // 发送重置消息到iframe
+ const iframe = document.querySelector('iframe') as HTMLIFrameElement;
+ if (iframe && iframe.contentWindow) {
+ iframe.contentWindow.postMessage({ type: 'RESET_DEMO' }, '*');
+ }
+
+ message.info('演示已重置');
+ };
+
+ return (
+
+ {/* 顶部标题栏 */}
+
+
+
+
+
+
+
+
可自定义属性面板演示
+
体验完整的双向同步设计模式架构
+
+
+
+
+ } onClick={() => window.open('https://github.com/your-repo', '_blank')}>
+ 查看源码
+
+ } onClick={resetDemo}>
+ 重置演示
+
+
+
+
+
+
+ {/* 左侧:可自定义属性面板 */}
+
+ {/* Bridge状态面板 */}
+
+
+
+
+ {/* 主体面板内容 */}
+
+
+
+ {/* 右侧:iframe预览 */}
+
+
+ {/* 预览工具栏 */}
+
+
+
+
+
实时预览
+ {selectedElement && (
+ <>
+
+
+ 已选择: <{selectedElement.tagName}>
+
+ >
+ )}
+
+
+
+
+
+
+ {/* iframe容器 */}
+
+
+
+
+
+
+
+ {/* 底部功能说明 */}
+
+
+
+
+
+ 样式自定义
+
+
+ - • 自定义颜色方案和预设
+ - • 保存和加载样式配置
+ - • 主题切换(浅色/深色/跟随系统)
+ - • 快捷键自定义
+
+
+
+
+
+
+ 内容自定义
+
+
+ - • 富文本编辑和格式化
+ - • 内容历史记录管理
+ - • 模板和占位符自定义
+ - • 自动保存配置
+
+
+
+
+
+
+ 高级功能
+
+
+ - • 批量操作和防抖处理
+ - • 配置导入导出
+ - • 错误处理和回滚机制
+ - • 实时双向同步
+
+
+
+
+
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/pages/FeaturesPage.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/pages/FeaturesPage.tsx
new file mode 100644
index 00000000..e2f2d9f9
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/pages/FeaturesPage.tsx
@@ -0,0 +1,107 @@
+import React from 'react';
+
+export default function FeaturesPage() {
+ return (
+
+
全部功能
+
+ {/* 功能 1: 样式编辑 */}
+
+ 1. 样式编辑
+
+ 选择任意元素,使用可视化编辑器修改其 Tailwind CSS 类。
+
+
+
+
+
+ {/* 功能 2: 内容编辑 */}
+
+ 2. 内容编辑
+
+ 双击任意文本即可直接编辑。修改会保存到源文件中。
+
+
+
+
+
+ {/* 功能 3: 源码映射 */}
+
+ 3. 源码映射
+
+ 每个元素都有指向其源文件位置的元数据。
+
+
+
+
data-source-file="/src/pages/FeaturesPage.tsx"
+
data-source-line="42"
+
data-source-column="8"
+
+
+
+ {/* 功能 4: 实时更新 */}
+
+ 4. 实时更新
+
+ 所有修改都会立即反映在界面上并保存到磁盘。
+
+
+
+
+
+ {/* 交互演示 */}
+
+ 交互演示
+
+ 启用设计模式,尝试修改这些元素:
+
+
+
+
+
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/pages/HomePage.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/pages/HomePage.tsx
new file mode 100644
index 00000000..d52e0b3c
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/pages/HomePage.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+
+export default function HomePage() {
+ const title = "欢迎使用设计模式";
+ const content = "体验实时可视化编辑,自动更新源代码。";
+ return (
+
+ {/* 主标题区域 */}
+
+
+ 你好,{title}
+
+
+ {content}
+ 点击右下角的设计模式开关即可开始!
+ {'--变量测试--'}
+
+
+
+ {/* 功能卡片 */}
+
+
+
+
样式编辑
+
+ 点击任意元素,实时修改其 Tailwind CSS 类。
+
+
+
+
+
+
内容编辑12
+
+ 双击文本元素直接编辑内容,自动更新源文件。
+
+
+
+
+
+
源码更新
+
+ 所有修改都会自动保存到源代码文件中。
+
+
+
+
+ 3111
+
+
+
可编辑卡片
+
+ 这是一个可编辑的段落。在设计模式下试着点击它!
+
+
+
+
+ 源码更新 1
+
+ 你可以修改背景、文字颜色、内边距等等。
+
+
+
+
+
第三个卡片
+
+ 双击这段文字可以直接编辑!3434341212121212
+
+
+
+
+ {/* 列表修改测试区域 */}
+
+
列表修改测试
+
+ {[
+ { text: "写一篇关于人工智能的文章", icon: (props: any) =>
},
+ { text: "解释量子计算", icon: (props: any) =>
},
+ { text: "设计一个Logo", icon: (props: any) =>
},
+ ].slice(0, 6).map((prompt, index) => {
+ const IconComponent = prompt.icon;
+ return (
+
+
+
+
+
+ "{prompt.text}"
+
+
这量个相同的文本
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/pages/IframeDemoPage.tsx b/qiming-vite-plugin-design-mode/examples/demo/src/pages/IframeDemoPage.tsx
new file mode 100644
index 00000000..89c0579b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/pages/IframeDemoPage.tsx
@@ -0,0 +1,515 @@
+import React from '../react';
+import { useState, useEffect } from 'react';
+import type { ElementInfo } from '@/types/messages';
+
+
+// 确保React在所有情况下都可用
+if (typeof React === 'undefined') {
+ throw new Error(
+ 'React is not imported. Please ensure React is imported correctly.'
+ );
+}
+
+export default function IframeDemoPage() {
+ const [iframeDesignMode, setIframeDesignMode] = useState(false);
+ const [selectedElement, setSelectedElement] = useState(
+ null
+ );
+ const [editingContent, setEditingContent] = useState('');
+ const [editingClass, setEditingClass] = useState('');
+ const [pendingChanges, setPendingChanges] = useState<
+ Array<{
+ type: 'style' | 'content';
+ sourceInfo: any;
+ newValue: string;
+ originalValue?: string;
+ }>
+ >([]);
+
+ const iframeRef = React.useRef(null);
+
+ // Listen for messages from iframe
+ useEffect(() => {
+ const handleMessage = (event: MessageEvent) => {
+ // Debug log for message source
+ if (event.data.type === 'ELEMENT_SELECTED') {
+ console.log('[Parent] Received ELEMENT_SELECTED', {
+ source: event.source,
+ iframeWindow: iframeRef.current?.contentWindow,
+ isMatch: iframeRef.current && event.source === iframeRef.current.contentWindow,
+ data: event.data
+ });
+ }
+ if (event.data.type === 'ADD_TO_CHAT') {
+ return console.log('[Parent] Add to chat:', event.data.payload);
+ }
+
+ if (event.data.type === 'COPY_ELEMENT') {
+ return console.log('[Parent] Copy element:', event.data.payload);
+ }
+
+ // Only accept messages from the iframe
+ if (iframeRef.current && event.source !== iframeRef.current.contentWindow) {
+ if (event.data.type === 'ELEMENT_SELECTED') {
+ console.warn('[Parent] Ignoring message from non-iframe source');
+ }
+ return;
+ }
+
+ const { type, payload } = event.data;
+
+ switch (type) {
+ case 'DESIGN_MODE_CHANGED':
+ setIframeDesignMode(event.data.enabled);
+ break;
+
+ case 'ELEMENT_SELECTED':
+ console.log('[Parent] Processing ELEMENT_SELECTED', payload);
+
+ // 验证 sourceInfo 是否有效
+ if (
+ !payload.elementInfo?.sourceInfo ||
+ !payload.elementInfo.sourceInfo.fileName ||
+ payload.elementInfo.sourceInfo.lineNumber === 0
+ ) {
+ console.warn(
+ '[Parent] Invalid sourceInfo received:',
+ payload.elementInfo?.sourceInfo
+ );
+ console.warn('[Parent] This may cause update operations to fail');
+ }
+
+ setSelectedElement(payload.elementInfo);
+ setEditingContent(payload.elementInfo.textContent);
+ setEditingClass(payload.elementInfo.className);
+ break;
+
+ case 'ELEMENT_DESELECTED':
+ setSelectedElement(null);
+ console.log('[Parent] Element deselected');
+ break;
+
+ case 'CONTENT_UPDATED':
+ console.log('[Parent] Content updated:', payload);
+ break;
+
+ case 'STYLE_UPDATED':
+ console.log('[Parent] Style updated:', payload);
+ break;
+
+ }
+ };
+
+ window.addEventListener('message', handleMessage);
+ return () => window.removeEventListener('message', handleMessage);
+ }, []);
+
+ // Debounce hook
+ const useDebounce = (value: T, delay: number): T => {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+ return () => clearTimeout(handler);
+ }, [value, delay]);
+ return debouncedValue;
+ };
+
+ const debouncedContent = useDebounce(editingContent, 300);
+ const debouncedClass = useDebounce(editingClass, 300);
+
+ // Upsert pending change
+ const upsertPendingChange = (
+ type: 'style' | 'content',
+ newValue: string,
+ originalValue?: string
+ ) => {
+ if (!selectedElement) return;
+ setPendingChanges(prev => {
+ const existingIndex = prev.findIndex(
+ item =>
+ item.type === type &&
+ item.sourceInfo.fileName === selectedElement.sourceInfo.fileName &&
+ item.sourceInfo.lineNumber === selectedElement.sourceInfo.lineNumber
+ );
+
+ const newChange = {
+ type,
+ sourceInfo: selectedElement.sourceInfo,
+ newValue,
+ originalValue: originalValue || (type === 'style' ? selectedElement.className : selectedElement.textContent),
+ };
+
+ if (existingIndex >= 0) {
+ const newList = [...prev];
+ newList[existingIndex] = newChange;
+ return newList;
+ } else {
+ return [...prev, newChange];
+ }
+ });
+ };
+
+ const lastSelectedElementRef = React.useRef(selectedElement);
+
+ // Real-time content update
+ useEffect(() => {
+ if (!selectedElement) return;
+
+ // If selection changed, skip this update cycle
+ if (selectedElement !== lastSelectedElementRef.current) {
+ console.log('[Parent] Skipping content update due to selection change');
+ return;
+ }
+
+ console.log('[Parent] Content effect triggered', {
+ debouncedContent,
+ currentContent: selectedElement.textContent,
+ shouldUpdate: debouncedContent !== selectedElement.textContent
+ });
+
+ if (debouncedContent === selectedElement.textContent) return;
+
+ console.log('[Parent] Sending UPDATE_CONTENT', debouncedContent);
+ upsertPendingChange('content', debouncedContent);
+
+ const iframe = iframeRef.current;
+ if (iframe && iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ {
+ type: 'UPDATE_CONTENT',
+ payload: {
+ sourceInfo: selectedElement.sourceInfo,
+ newContent: debouncedContent,
+ persist: false,
+ },
+ timestamp: Date.now(),
+ },
+ '*'
+ );
+ }
+ }, [debouncedContent, selectedElement]); // Removed upsertPendingChange to fix infinite loop
+
+ // Real-time style update
+ useEffect(() => {
+ if (!selectedElement) return;
+
+ // If selection changed, skip this update cycle
+ if (selectedElement !== lastSelectedElementRef.current) {
+ console.log('[Parent] Skipping style update due to selection change');
+ return;
+ }
+
+ if (debouncedClass === selectedElement.className) return;
+
+ upsertPendingChange('style', debouncedClass);
+
+ const iframe = iframeRef.current;
+ if (iframe && iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ {
+ type: 'UPDATE_STYLE',
+ payload: {
+ sourceInfo: selectedElement.sourceInfo,
+ newClass: debouncedClass,
+ persist: false,
+ },
+ timestamp: Date.now(),
+ },
+ '*'
+ );
+ }
+ }, [debouncedClass, selectedElement]); // Removed upsertPendingChange to fix infinite loop
+
+ // Update lastSelectedElementRef AFTER other effects
+ useEffect(() => {
+ lastSelectedElementRef.current = selectedElement;
+ }, [selectedElement]);
+
+ // Style Manager Logic
+ const toggleStyle = (newStyle: string, categoryRegex: RegExp) => {
+ let currentClasses = editingClass.split(' ').filter(c => c.trim());
+
+ // Remove existing class in the same category
+ currentClasses = currentClasses.filter(c => !categoryRegex.test(c));
+
+ // Add new style if it's not empty (allows clearing style)
+ if (newStyle) {
+ currentClasses.push(newStyle);
+ }
+
+ setEditingClass(currentClasses.join(' '));
+ };
+
+ const hasStyle = (style: string) => {
+ return editingClass.split(' ').includes(style);
+ };
+
+ // UI Controls
+ const renderColorPicker = (prefix: string, colors: string[], label: string) => (
+
+
+
+ {colors.map(color => {
+ const styleClass = `${prefix}-${color}`;
+ const isActive = hasStyle(styleClass);
+ return (
+
+
+ );
+
+ const renderSelect = (prefix: string, options: { label: string; value: string }[], label: string) => (
+
+
+
+ {options.map(opt => {
+ const styleClass = opt.value ? `${prefix}-${opt.value}` : '';
+ const isActive = styleClass ? hasStyle(styleClass) : !options.some(o => o.value && hasStyle(`${prefix}-${o.value}`));
+ return (
+ toggleStyle(styleClass, new RegExp(`^${prefix}(-[a-z0-9]+)?$`))}
+ className={`px-3 py-1 text-sm rounded border transition-all ${isActive
+ ? 'bg-blue-600 text-white border-blue-600'
+ : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
+ }`}
+ >
+ {opt.label}
+
+ )
+ })}
+
+
+ );
+
+ // Toggle design mode in iframe
+ const toggleIframeDesignMode = () => {
+ const iframe = iframeRef.current;
+ console.log('[Parent] Toggling iframe design mode...', iframe);
+ if (iframe && iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ {
+ type: 'TOGGLE_DESIGN_MODE',
+ enabled: !iframeDesignMode,
+ timestamp: Date.now(),
+ },
+ '*'
+ );
+ }
+ };
+
+ // 保存所有更改
+ const saveChanges = async () => {
+ if (pendingChanges.length === 0) return;
+
+ console.log('[Parent] Saving changes...', pendingChanges);
+
+ try {
+ const response = await fetch('/__appdev_design_mode/batch-update', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ updates: pendingChanges.map(change => ({
+ filePath: change.sourceInfo.fileName,
+ line: change.sourceInfo.lineNumber,
+ column: change.sourceInfo.columnNumber,
+ newValue: change.newValue,
+ type: change.type,
+ originalValue: change.originalValue,
+ })),
+ }),
+ });
+
+ if (response.ok) {
+ const result = await response.json();
+ console.log('[Parent] Batch update success:', result);
+ alert(`成功保存 ${result.summary.successful} 个更改!`);
+ setPendingChanges([]); // 清空待保存列表
+ } else {
+ const error = await response.json();
+ console.error('[Parent] Batch update failed:', error);
+ alert('保存失败,请查看控制台错误信息。');
+ }
+ } catch (error) {
+ console.error('[Parent] Error saving changes:', error);
+ alert('保存出错,请检查网络连接。');
+ }
+ };
+
+ return (
+
+ {/* Top Control Bar */}
+
+
+
+ Iframe 设计模式演示
+
+
+ {pendingChanges.length > 0 && (
+
+ 保存更改 ({pendingChanges.length})
+
+
+ )}
+
+ {iframeDesignMode ? '✓ 设计模式已启用' : '启用设计模式'}
+
+
+
+
+
+
+ {/* Left: External Property Panel */}
+
+
+
+ 外部属性面板
+
+
+ {selectedElement ? (
+
+ {/* Element Info */}
+
+
+ 选中元素
+
+
+
+ 标签:{' '}
+ {selectedElement.tagName}
+
+
+ 文件:{' '}
+ {selectedElement.sourceInfo.fileName.split('/').pop()}
+
+
+
+
+ {/* Content Editor - 仅当 isStaticText 为 true 时显示 */}
+ {selectedElement.isStaticText && (
+
+
+
+ )}
+
+
+
+ {/* Smart Style Controls */}
+
+
样式设置
+
+ {renderColorPicker('bg', ['white', 'gray-100', 'red-100', 'blue-100', 'green-100', 'yellow-100', 'purple-100'], '背景颜色')}
+ {renderColorPicker('text', ['gray-900', 'gray-500', 'red-600', 'blue-600', 'green-600', 'purple-600', 'white'], '文字颜色')}
+
+ {renderSelect('p', [
+ { label: '无', value: '0' },
+ { label: '小', value: '2' },
+ { label: '中', value: '4' },
+ { label: '大', value: '8' },
+ { label: '特大', value: '12' }
+ ], '内边距 (Padding)')}
+
+ {renderSelect('rounded', [
+ { label: '直角', value: 'none' },
+ { label: '小圆角', value: 'sm' },
+ { label: '圆角', value: 'md' },
+ { label: '大圆角', value: 'lg' },
+ { label: '全圆角', value: 'full' }
+ ], '圆角 (Border Radius)')}
+
+ {renderSelect('text', [
+ { label: '小', value: 'sm' },
+ { label: '默认', value: 'base' },
+ { label: '中', value: 'lg' },
+ { label: '大', value: 'xl' },
+ { label: '特大', value: '2xl' }
+ ], '文字大小 (Font Size)')}
+
+
+ {/* Raw Style Editor */}
+
+
+
+
+ ) : (
+
+
+ {iframeDesignMode
+ ? '点击 iframe 内的元素开始编辑'
+ : '请先启用设计模式'}
+
+
+ )}
+
+
+
+ {/* Right: Iframe Preview */}
+
+
+
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/demo/src/react.ts b/qiming-vite-plugin-design-mode/examples/demo/src/react.ts
new file mode 100644
index 00000000..bd7518d3
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/src/react.ts
@@ -0,0 +1,10 @@
+// React重新导出,确保在所有情况下都能正确导入
+export { useState, useEffect, useCallback, useMemo, useRef, useReducer } from 'react';
+export { createContext, useContext } from 'react';
+export { createElement, Fragment } from 'react';
+export type { ReactNode, ReactElement, ComponentType } from 'react';
+
+// React主对象重新导出(兼容旧版本)
+import * as ReactModule from 'react';
+export default ReactModule;
+export const React = ReactModule;
diff --git a/qiming-vite-plugin-design-mode/examples/demo/tsconfig.app.json b/qiming-vite-plugin-design-mode/examples/demo/tsconfig.app.json
new file mode 100644
index 00000000..84c6f5e5
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/tsconfig.app.json
@@ -0,0 +1,40 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": [
+ "ES2022",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "module": "ESNext",
+ "types": [
+ "vite/client"
+ ],
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": [
+ "../../packages/plugin/src/*"
+ ]
+ }
+ },
+ "include": [
+ "src"
+ ]
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/demo/tsconfig.json b/qiming-vite-plugin-design-mode/examples/demo/tsconfig.json
new file mode 100644
index 00000000..1ffef600
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/qiming-vite-plugin-design-mode/examples/demo/tsconfig.node.json b/qiming-vite-plugin-design-mode/examples/demo/tsconfig.node.json
new file mode 100644
index 00000000..8a67f62f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/qiming-vite-plugin-design-mode/examples/demo/vite.config.ts b/qiming-vite-plugin-design-mode/examples/demo/vite.config.ts
new file mode 100644
index 00000000..cb62ae0e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/demo/vite.config.ts
@@ -0,0 +1,44 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import path from 'path';
+import appdevDesignMode from '@xagi/vite-plugin-design-mode';
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [
+
+ //
+ {
+ name: 'dev-inject',
+ enforce: 'post', // 确保在 HTML 注入阶段最后执行
+ transformIndexHtml(html) {
+ if (!html.includes('data-id="dev-inject-monitor"')) {
+ return html.replace("", `
+
+ \n`);
+ }
+ return html;
+ }
+ },
+ //
+ react(),
+ appdevDesignMode()
+ ],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src')
+ }
+ }
+});
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/.gitignore b/qiming-vite-plugin-design-mode/examples/full-featured/.gitignore
new file mode 100644
index 00000000..11f9e171
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/.gitignore
@@ -0,0 +1,146 @@
+# Dependencies
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Build outputs
+dist/
+build/
+*.local
+
+# Environment variables
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Logs
+logs/
+*.log
+
+# Runtime data
+pids/
+*.pid
+*.seed
+*.pid.lock
+
+# Coverage directory used by tools like istanbul
+coverage/
+*.lcov
+
+# nyc test coverage
+.nyc_output/
+
+# Dependency directories
+jspm_packages/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache/
+.parcel-cache/
+
+# next.js build output
+.next/
+
+# nuxt.js build output
+.nuxt/
+
+# vuepress build output
+.vuepress/dist/
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache/
+.parcel-cache/
+
+# Storybook build outputs
+.out
+.storybook-out
+
+# Temporary folders
+tmp/
+temp/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# Design system files
+*.tokens.json
+design-tokens/
+tokens/
+
+# Generated files
+generated/
+auto-generated/
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/README.md b/qiming-vite-plugin-design-mode/examples/full-featured/README.md
new file mode 100644
index 00000000..c4660170
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/README.md
@@ -0,0 +1,449 @@
+# AppDev Design Mode - Full Featured Showcase
+
+这是一个完整展示 `@vite-plugin-appdev-design-mode` 插件最新功能的示例项目。该项目构建了一个现代化的设计系统管理平台,展示了插件在可视化编辑、实时预览、源码调试等方面的强大功能。
+
+## 🚀 功能特性
+
+### 核心功能
+
+1. **组件可视化编辑器**
+ - 拖拽式组件编辑
+ - 实时属性编辑面板
+ - 多设备预览支持
+ - 组件变体管理
+
+2. **设计系统管理**
+ - 完整的令牌管理系统
+ - 颜色调色板管理
+ - 字体和排版令牌
+ - 间距和阴影令牌
+ - 动画和时间令牌
+
+3. **实时预览系统**
+ - 多设备响应式预览
+ - 主题切换支持
+ - 实时组件更新
+ - 全屏预览模式
+
+4. **源码调试增强**
+ - 自动源码映射
+ - 组件层次可视化
+ - 调试信息展示
+ - 设计模式指示器
+
+### 技术亮点
+
+- **React 19** 最新特性支持
+- **Vite 6** 高性能构建
+- **TailwindCSS 4** 现代化样式方案
+- **Radix UI** 无障碍组件库
+- **TypeScript** 完整类型支持
+- **Framer Motion** 流畅动画效果
+- **Zustand** 轻量级状态管理
+
+## 🛠️ 技术栈
+
+### 前端框架
+- **React 19.2.0** - 最新的 React 版本,支持并发特性
+- **TypeScript 5.6** - 严格的类型检查
+- **Vite 6.0** - 极速的开发服务器和构建工具
+
+### UI 组件
+- **Radix UI** - 无障碍的底层组件
+- **TailwindCSS 4.1** - 实用优先的 CSS 框架
+- **Framer Motion 10.18** - 高性能动画库
+
+### 开发工具
+- **AntV X6** - 图形可视化(可选)
+- **React Hook Form** - 表单管理
+- **Zustand** - 状态管理
+- **React Query** - 服务器状态管理
+
+### 构建配置
+- **PostCSS** - CSS 后处理器
+- **Autoprefixer** - 自动添加浏览器前缀
+- **ESLint** - 代码质量检查
+- **Prettier** - 代码格式化
+
+## 📦 安装和运行
+
+### 环境要求
+
+- Node.js 18.0.0 或更高版本
+- pnpm 8.0.0 或更高版本
+- 现代浏览器支持
+
+### 安装依赖
+
+```bash
+# 克隆项目
+git clone
+cd vite-plugin-appdev-design-mode/examples/full-featured
+
+# 安装依赖
+pnpm install
+```
+
+### 开发模式
+
+```bash
+# 启动开发服务器
+pnpm dev
+
+# 访问 http://localhost:5175
+```
+
+### 构建生产版本
+
+```bash
+# 构建项目
+pnpm build
+
+# 预览生产版本
+pnpm preview
+```
+
+### 代码检查
+
+```bash
+# 类型检查
+pnpm type-check
+
+# 代码规范检查
+pnpm lint
+```
+
+## 📁 项目结构
+
+```
+examples/full-featured/
+├── src/
+│ ├── components/ # 可复用组件
+│ │ ├── ui/ # 基础 UI 组件
+│ │ ├── layout/ # 布局组件
+│ │ ├── editor/ # 编辑器组件
+│ │ ├── design-system/ # 设计系统组件
+│ │ └── showcase/ # 展示组件
+│ ├── pages/ # 页面组件
+│ │ ├── Dashboard.tsx # 仪表盘页面
+│ │ ├── DesignSystem.tsx # 设计系统页面
+│ │ ├── ComponentEditor.tsx # 组件编辑器页面
+│ │ └── LivePreview.tsx # 实时预览页面
+│ ├── hooks/ # 自定义 Hooks
+│ ├── store/ # 状态管理
+│ ├── utils/ # 工具函数
+│ ├── types/ # TypeScript 类型定义
+│ ├── App.tsx # 主应用组件
+│ ├── main.tsx # 应用入口
+│ └── index.css # 全局样式
+├── public/ # 静态资源
+├── package.json # 项目配置
+├── vite.config.ts # Vite 配置
+├── tailwind.config.js # TailwindCSS 配置
+├── tsconfig.json # TypeScript 配置
+└── README.md # 项目文档
+```
+
+## 🎨 设计系统令牌
+
+### 颜色令牌
+
+项目定义了完整的颜色系统:
+
+```css
+/* 主色调 */
+--color-primary-50: #eff6ff;
+--color-primary-500: #3b82f6;
+--color-primary-900: #1e3a8a;
+
+/* 次要色调 */
+--color-secondary-50: #faf5ff;
+--color-secondary-500: #a855f7;
+
+/* 功能色彩 */
+--color-success-500: #22c55e;
+--color-warning-500: #eab308;
+--color-error-500: #ef4444;
+```
+
+### 字体令牌
+
+```css
+/* 字体家族 */
+--font-family-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
+--font-family-mono: 'JetBrains Mono', ui-monospace, monospace;
+
+/* 字体大小 */
+--font-size-base: 1rem;
+--font-size-lg: 1.125rem;
+--font-size-xl: 1.25rem;
+```
+
+### 间距令牌
+
+```css
+/* 基础间距 */
+--spacing-1: 0.25rem; /* 4px */
+--spacing-4: 1rem; /* 16px */
+--spacing-8: 2rem; /* 32px */
+```
+
+## 🔧 插件配置
+
+### 基础配置
+
+```typescript
+// vite.config.ts
+appdevDesignMode({
+ enabled: true,
+ enableInProduction: false,
+ attributePrefix: 'design-mode',
+ verbose: true,
+ exclude: [
+ 'node_modules',
+ 'dist',
+ '**/*.test.{ts,tsx,js,jsx}'
+ ],
+ include: [
+ 'src/**/*.{ts,tsx,js,jsx}',
+ 'components/**/*.{ts,tsx,js,jsx}',
+ 'pages/**/*.{ts,tsx,js,jsx}'
+ ]
+})
+```
+
+### 高级配置选项
+
+- **enabled**: 启用/禁用设计模式
+- **enableInProduction**: 生产环境是否启用
+- **attributePrefix**: 自定义属性前缀
+- **verbose**: 详细日志输出
+- **exclude/include**: 文件过滤规则
+
+## 🎯 功能演示
+
+### 1. 组件可视化编辑
+
+访问 `/editor` 页面体验:
+
+- 拖拽式组件编辑
+- 实时属性面板编辑
+- 多设备预览切换
+- 组件代码生成
+
+### 2. 设计系统管理
+
+访问 `/design-system` 页面体验:
+
+- 完整的令牌管理系统
+- 颜色调色板可视化
+- 字体和排版管理
+- 间距和阴影令牌
+
+### 3. 实时预览
+
+访问 `/preview` 页面体验:
+
+- 多设备响应式预览
+- 主题切换(Light/Dark/System)
+- 实时组件更新
+- 全屏预览模式
+
+### 4. 源码调试
+
+在开发模式下:
+
+- 自动源码映射
+- 组件层次可视化
+- 设计模式指示器
+- 调试信息展示
+
+## 🚀 性能优化
+
+### 代码分割
+
+项目使用 React.lazy() 实现路由级别的代码分割:
+
+```typescript
+const Dashboard = React.lazy(() => import('./pages/Dashboard'));
+const ComponentEditor = React.lazy(() => import('./pages/ComponentEditor'));
+```
+
+### 资源优化
+
+- 图片懒加载
+- CSS 压缩和优化
+- JavaScript 代码分割
+- 浏览器缓存策略
+
+### 响应式设计
+
+项目完全支持响应式设计:
+
+- 移动端优先设计
+- 断点适配
+- 触摸友好交互
+- 性能优化
+
+## 🎨 主题系统
+
+### 暗色模式支持
+
+项目支持完整的暗色模式:
+
+```css
+/* CSS 变量定义 */
+:root {
+ --color-bg-primary: #ffffff;
+ --color-text-primary: #171717;
+}
+
+.dark {
+ --color-bg-primary: #0a0a0a;
+ --color-text-primary: #fafafa;
+}
+```
+
+### 自定义主题
+
+用户可以通过设计系统页面自定义主题:
+
+- 颜色调色板
+- 字体设置
+- 间距调整
+- 动画效果
+
+## ♿ 无障碍支持
+
+项目注重无障碍访问:
+
+- 语义化 HTML 结构
+- ARIA 属性支持
+- 键盘导航
+- 屏幕阅读器兼容
+- 高对比度模式
+
+## 🔍 调试功能
+
+### 开发模式特性
+
+- 组件调试面板
+- 状态检查工具
+- 性能监控
+- 错误边界
+
+### 生产模式优化
+
+- 错误处理
+- 性能监控
+- 用户体验优化
+- 日志记录
+
+## 📱 响应式设计
+
+### 断点系统
+
+```css
+/* TailwindCSS 断点 */
+sm: 640px /* 小型设备 */
+md: 768px /* 中型设备 */
+lg: 1024px /* 大型设备 */
+xl: 1280px /* 超大设备 */
+2xl: 1536px /* 2K 显示器 */
+```
+
+### 移动端优化
+
+- 触摸手势支持
+- 移动端菜单
+- 响应式图片
+- 性能优化
+
+## 🧪 测试策略
+
+### 单元测试
+
+```bash
+# 运行单元测试
+pnpm test
+
+# 生成覆盖率报告
+pnpm test:coverage
+```
+
+### 集成测试
+
+- 组件测试
+- 页面测试
+- 用户流程测试
+
+### E2E 测试
+
+- 浏览器自动化测试
+- 用户交互测试
+- 跨浏览器兼容性
+
+## 📚 最佳实践
+
+### 代码规范
+
+- ESLint 配置
+- Prettier 格式化
+- TypeScript 严格模式
+- 组件命名约定
+
+### 性能最佳实践
+
+- React.memo 优化
+- useMemo 和 useCallback
+- 虚拟滚动
+- 懒加载策略
+
+### 安全最佳实践
+
+- XSS 防护
+- CSRF 保护
+- 内容安全策略
+- 依赖安全管理
+
+## 🤝 贡献指南
+
+### 开发流程
+
+1. Fork 项目
+2. 创建功能分支
+3. 提交代码更改
+4. 创建 Pull Request
+
+### 代码规范
+
+- 使用 TypeScript
+- 遵循 ESLint 规则
+- 添加必要的注释
+- 编写测试用例
+
+## 📄 许可证
+
+本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。
+
+## 🙏 致谢
+
+感谢以下开源项目的支持:
+
+- [React](https://react.dev/)
+- [Vite](https://vitejs.dev/)
+- [TailwindCSS](https://tailwindcss.com/)
+- [Radix UI](https://www.radix-ui.com/)
+- [Framer Motion](https://www.framer.com/motion/)
+
+## 📞 联系我们
+
+如有问题或建议,请通过以下方式联系:
+
+- 创建 [Issue](../../issues)
+- 发送邮件至 [team@xagi.dev](mailto:team@xagi.dev)
+- 访问我们的 [官网](https://vite-plugin-appdev-design-mode.dev/)
+
+---
+
+**© 2024 XAGI Team. All rights reserved.**
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/index.html b/qiming-vite-plugin-design-mode/examples/full-featured/index.html
new file mode 100644
index 00000000..dfae050d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/index.html
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AppDev Design Mode - Full Featured Showcase
+
+
+
+
+
+
+
+
+
AppDev Design Mode
+
Full Featured Showcase
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/package.json b/qiming-vite-plugin-design-mode/examples/full-featured/package.json
new file mode 100644
index 00000000..8bf189b9
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "vite-plugin-appdev-design-mode-full-featured-example",
+ "version": "1.0.0",
+ "description": "Complete showcase of vite-plugin-appdev-design-mode latest features",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "type-check": "tsc --noEmit",
+ "lint": "eslint src --ext .ts,.tsx",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@radix-ui/react-accordion": "^1.1.2",
+ "@radix-ui/react-alert-dialog": "^1.0.5",
+ "@radix-ui/react-avatar": "^1.0.4",
+ "@radix-ui/react-checkbox": "^1.0.4",
+ "@radix-ui/react-collapsible": "^1.0.3",
+ "@radix-ui/react-context-menu": "^2.1.5",
+ "@radix-ui/react-dialog": "^1.0.5",
+ "@radix-ui/react-dropdown-menu": "^2.0.6",
+ "@radix-ui/react-hover-card": "^1.0.7",
+ "@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-menubar": "^1.0.4",
+ "@radix-ui/react-navigation-menu": "^1.1.4",
+ "@radix-ui/react-popover": "^1.0.7",
+ "@radix-ui/react-progress": "^1.0.3",
+ "@radix-ui/react-radio-group": "^1.1.3",
+ "@radix-ui/react-scroll-area": "^1.0.5",
+ "@radix-ui/react-select": "^2.0.0",
+ "@radix-ui/react-separator": "^1.0.3",
+ "@radix-ui/react-slider": "^1.1.2",
+ "@radix-ui/react-switch": "^1.0.3",
+ "@radix-ui/react-tabs": "^1.0.4",
+ "@radix-ui/react-toast": "^1.1.5",
+ "@radix-ui/react-toggle": "^1.0.3",
+ "@radix-ui/react-toggle-group": "^1.0.4",
+ "@radix-ui/react-toolbar": "^1.0.4",
+ "@radix-ui/react-tooltip": "^1.0.7",
+ "@tanstack/react-query": "^5.17.0",
+ "clsx": "^2.1.0",
+ "framer-motion": "^10.18.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "react-hook-form": "^7.49.2",
+ "react-router-dom": "^6.21.0",
+ "recharts": "^2.10.1",
+ "tailwind-merge": "^2.2.0",
+ "zustand": "^4.4.7"
+ },
+ "devDependencies": {
+ "@tailwindcss/forms": "^0.5.7",
+ "@tailwindcss/typography": "^0.5.10",
+ "@types/babel__core": "^7.20.5",
+ "@types/node": "^20.11.0",
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.16",
+ "postcss": "^8.4.33",
+ "tailwindcss": "^4.1.0",
+ "typescript": "^5.6.3",
+ "vite": "^6.0.0"
+ },
+ "keywords": [
+ "vite",
+ "react",
+ "design-mode",
+ "component-editor",
+ "visual-editor",
+ "radix-ui",
+ "tailwindcss"
+ ],
+ "author": "XAGI Team",
+ "license": "MIT"
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/pnpm-lock.yaml b/qiming-vite-plugin-design-mode/examples/full-featured/pnpm-lock.yaml
new file mode 100644
index 00000000..7e399dc7
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/pnpm-lock.yaml
@@ -0,0 +1,3265 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@radix-ui/react-accordion':
+ specifier: ^1.1.2
+ version: 1.2.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-alert-dialog':
+ specifier: ^1.0.5
+ version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-avatar':
+ specifier: ^1.0.4
+ version: 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-checkbox':
+ specifier: ^1.0.4
+ version: 1.3.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-collapsible':
+ specifier: ^1.0.3
+ version: 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-context-menu':
+ specifier: ^2.1.5
+ version: 2.2.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-dialog':
+ specifier: ^1.0.5
+ version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-dropdown-menu':
+ specifier: ^2.0.6
+ version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-hover-card':
+ specifier: ^1.0.7
+ version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-label':
+ specifier: ^2.0.2
+ version: 2.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-menubar':
+ specifier: ^1.0.4
+ version: 1.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-navigation-menu':
+ specifier: ^1.1.4
+ version: 1.2.14(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-popover':
+ specifier: ^1.0.7
+ version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-progress':
+ specifier: ^1.0.3
+ version: 1.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-radio-group':
+ specifier: ^1.1.3
+ version: 1.3.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-scroll-area':
+ specifier: ^1.0.5
+ version: 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-select':
+ specifier: ^2.0.0
+ version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-separator':
+ specifier: ^1.0.3
+ version: 1.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-slider':
+ specifier: ^1.1.2
+ version: 1.3.6(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-switch':
+ specifier: ^1.0.3
+ version: 1.2.6(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-tabs':
+ specifier: ^1.0.4
+ version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-toast':
+ specifier: ^1.1.5
+ version: 1.2.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-toggle':
+ specifier: ^1.0.3
+ version: 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-toggle-group':
+ specifier: ^1.0.4
+ version: 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-toolbar':
+ specifier: ^1.0.4
+ version: 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-tooltip':
+ specifier: ^1.0.7
+ version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@tanstack/react-query':
+ specifier: ^5.17.0
+ version: 5.90.10(react@19.2.0)
+ clsx:
+ specifier: ^2.1.0
+ version: 2.1.1
+ framer-motion:
+ specifier: ^10.18.0
+ version: 10.18.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react:
+ specifier: ^19.2.0
+ version: 19.2.0
+ react-dom:
+ specifier: ^19.2.0
+ version: 19.2.0(react@19.2.0)
+ react-hook-form:
+ specifier: ^7.49.2
+ version: 7.66.1(react@19.2.0)
+ react-router-dom:
+ specifier: ^6.21.0
+ version: 6.30.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ recharts:
+ specifier: ^2.10.1
+ version: 2.15.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ tailwind-merge:
+ specifier: ^2.2.0
+ version: 2.6.0
+ zustand:
+ specifier: ^4.4.7
+ version: 4.5.7(@types/react@18.3.27)(react@19.2.0)
+ devDependencies:
+ '@tailwindcss/forms':
+ specifier: ^0.5.7
+ version: 0.5.10(tailwindcss@4.1.17)
+ '@tailwindcss/typography':
+ specifier: ^0.5.10
+ version: 0.5.19(tailwindcss@4.1.17)
+ '@types/babel__core':
+ specifier: ^7.20.5
+ version: 7.20.5
+ '@types/node':
+ specifier: ^20.11.0
+ version: 20.19.25
+ '@types/react':
+ specifier: ^18.2.48
+ version: 18.3.27
+ '@types/react-dom':
+ specifier: ^18.2.18
+ version: 18.3.7(@types/react@18.3.27)
+ '@vitejs/plugin-react':
+ specifier: ^4.2.1
+ version: 4.7.0(vite@6.4.1(@types/node@20.19.25))
+ autoprefixer:
+ specifier: ^10.4.16
+ version: 10.4.22(postcss@8.5.6)
+ postcss:
+ specifier: ^8.4.33
+ version: 8.5.6
+ tailwindcss:
+ specifier: ^4.1.0
+ version: 4.1.17
+ typescript:
+ specifier: ^5.6.3
+ version: 5.9.3
+ vite:
+ specifier: ^6.0.0
+ version: 6.4.1(@types/node@20.19.25)
+
+packages:
+
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.5':
+ resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.5':
+ resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.5':
+ resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.27.2':
+ resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.27.1':
+ resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.3':
+ resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.4':
+ resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.27.2':
+ resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.5':
+ resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+ engines: {node: '>=6.9.0'}
+
+ '@emotion/is-prop-valid@0.8.8':
+ resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
+
+ '@emotion/memoize@0.7.4':
+ resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
+
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@floating-ui/core@1.7.3':
+ resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
+ '@floating-ui/dom@1.7.4':
+ resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
+ '@floating-ui/react-dom@2.1.6':
+ resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.10':
+ resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@radix-ui/number@1.1.1':
+ resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
+
+ '@radix-ui/primitive@1.1.3':
+ resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
+
+ '@radix-ui/react-accordion@1.2.12':
+ resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-alert-dialog@1.1.15':
+ resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-arrow@1.1.7':
+ resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-avatar@1.1.11':
+ resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-checkbox@1.3.3':
+ resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collapsible@1.1.12':
+ resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collection@1.1.7':
+ resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-compose-refs@1.1.2':
+ resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-context-menu@2.2.16':
+ resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-context@1.1.2':
+ resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-context@1.1.3':
+ resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dialog@1.1.15':
+ resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-direction@1.1.1':
+ resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dismissable-layer@1.1.11':
+ resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-dropdown-menu@2.1.16':
+ resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-focus-guards@1.1.3':
+ resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-focus-scope@1.1.7':
+ resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-hover-card@1.1.15':
+ resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-id@1.1.1':
+ resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-label@2.1.8':
+ resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-menu@2.1.16':
+ resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-menubar@1.1.16':
+ resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-navigation-menu@1.2.14':
+ resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popover@1.1.15':
+ resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popper@1.2.8':
+ resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-portal@1.1.9':
+ resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-presence@1.1.5':
+ resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.1.3':
+ resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.1.4':
+ resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-progress@1.1.8':
+ resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-radio-group@1.3.8':
+ resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-roving-focus@1.1.11':
+ resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-scroll-area@1.2.10':
+ resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-select@2.2.6':
+ resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-separator@1.1.7':
+ resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-separator@1.1.8':
+ resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slider@1.3.6':
+ resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slot@1.2.3':
+ resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-slot@1.2.4':
+ resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-switch@1.2.6':
+ resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-tabs@1.1.13':
+ resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toast@1.2.15':
+ resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toggle-group@1.1.11':
+ resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toggle@1.1.10':
+ resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toolbar@1.1.11':
+ resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-tooltip@1.2.8':
+ resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-use-callback-ref@1.1.1':
+ resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-controllable-state@1.2.2':
+ resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-effect-event@0.0.2':
+ resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-escape-keydown@1.1.1':
+ resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-is-hydrated@0.1.0':
+ resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.1':
+ resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-previous@1.1.1':
+ resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-rect@1.1.1':
+ resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-size@1.1.1':
+ resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-visually-hidden@1.2.3':
+ resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/rect@1.1.1':
+ resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
+
+ '@remix-run/router@1.23.1':
+ resolution: {integrity: sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==}
+ engines: {node: '>=14.0.0'}
+
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
+ '@rollup/rollup-android-arm-eabi@4.53.3':
+ resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.53.3':
+ resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.53.3':
+ resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.53.3':
+ resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.53.3':
+ resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.53.3':
+ resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.3':
+ resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.53.3':
+ resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.53.3':
+ resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.53.3':
+ resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.53.3':
+ resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.53.3':
+ resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.53.3':
+ resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.53.3':
+ resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.53.3':
+ resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.53.3':
+ resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.53.3':
+ resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openharmony-arm64@4.53.3':
+ resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.53.3':
+ resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.53.3':
+ resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.53.3':
+ resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.53.3':
+ resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/forms@0.5.10':
+ resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
+
+ '@tailwindcss/typography@0.5.19':
+ resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
+
+ '@tanstack/query-core@5.90.10':
+ resolution: {integrity: sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ==}
+
+ '@tanstack/react-query@5.90.10':
+ resolution: {integrity: sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==}
+ peerDependencies:
+ react: ^18 || ^19
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/d3-array@3.2.2':
+ resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+
+ '@types/d3-color@3.1.3':
+ resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+ '@types/d3-ease@3.0.2':
+ resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+ '@types/d3-interpolate@3.0.4':
+ resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+ '@types/d3-path@3.1.1':
+ resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+
+ '@types/d3-scale@4.0.9':
+ resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
+ '@types/d3-shape@3.1.7':
+ resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
+
+ '@types/d3-time@3.0.4':
+ resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
+ '@types/d3-timer@3.0.2':
+ resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/node@20.19.25':
+ resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==}
+
+ '@types/prop-types@15.7.15':
+ resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+ '@types/react-dom@18.3.7':
+ resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+
+ '@types/react@18.3.27':
+ resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==}
+
+ '@vitejs/plugin-react@4.7.0':
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+ engines: {node: '>=10'}
+
+ autoprefixer@10.4.22:
+ resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
+ baseline-browser-mapping@2.8.31:
+ resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==}
+ hasBin: true
+
+ browserslist@4.28.0:
+ resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ caniuse-lite@1.0.30001757:
+ resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ d3-array@3.2.4:
+ resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+ engines: {node: '>=12'}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-format@3.1.0:
+ resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-path@3.1.0:
+ resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+ engines: {node: '>=12'}
+
+ d3-scale@4.0.2:
+ resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+ engines: {node: '>=12'}
+
+ d3-shape@3.2.0:
+ resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+ engines: {node: '>=12'}
+
+ d3-time-format@4.1.0:
+ resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+ engines: {node: '>=12'}
+
+ d3-time@3.1.0:
+ resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decimal.js-light@2.5.1:
+ resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
+
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
+ dom-helpers@5.2.1:
+ resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+
+ electron-to-chromium@1.5.260:
+ resolution: {integrity: sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==}
+
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ eventemitter3@4.0.7:
+ resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+
+ fast-equals@5.3.3:
+ resolution: {integrity: sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==}
+ engines: {node: '>=6.0.0'}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fraction.js@5.3.4:
+ resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
+
+ framer-motion@10.18.0:
+ resolution: {integrity: sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==}
+ peerDependencies:
+ react: ^18.0.0
+ react-dom: ^18.0.0
+ peerDependenciesMeta:
+ react:
+ optional: true
+ react-dom:
+ optional: true
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
+ internmap@2.0.3:
+ resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+ engines: {node: '>=12'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ mini-svg-data-uri@1.4.4:
+ resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
+ hasBin: true
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ normalize-range@0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ postcss-selector-parser@6.0.10:
+ resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+ engines: {node: '>=4'}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ react-dom@19.2.0:
+ resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
+ peerDependencies:
+ react: ^19.2.0
+
+ react-hook-form@7.66.1:
+ resolution: {integrity: sha512-2KnjpgG2Rhbi+CIiIBQQ9Df6sMGH5ExNyFl4Hw9qO7pIqMBR8Bvu9RQyjl3JM4vehzCh9soiNUM/xYMswb2EiA==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-is@18.3.1:
+ resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
+ react-remove-scroll-bar@2.3.8:
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.7.1:
+ resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-router-dom@6.30.2:
+ resolution: {integrity: sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+ react-dom: '>=16.8'
+
+ react-router@6.30.2:
+ resolution: {integrity: sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+
+ react-smooth@4.0.4:
+ resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ react-style-singleton@2.2.3:
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-transition-group@4.4.5:
+ resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
+ peerDependencies:
+ react: '>=16.6.0'
+ react-dom: '>=16.6.0'
+
+ react@19.2.0:
+ resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
+ engines: {node: '>=0.10.0'}
+
+ recharts-scale@0.4.5:
+ resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
+
+ recharts@2.15.4:
+ resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ rollup@4.53.3:
+ resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ tailwind-merge@2.6.0:
+ resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
+
+ tailwindcss@4.1.17:
+ resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
+
+ tiny-invariant@1.3.3:
+ resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ update-browserslist-db@1.1.4:
+ resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ use-callback-ref@1.3.3:
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sidecar@1.1.3:
+ resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ victory-vendor@36.9.2:
+ resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
+
+ vite@6.4.1:
+ resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ zustand@4.5.7:
+ resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+
+snapshots:
+
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.5': {}
+
+ '@babel/core@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5)
+ '@babel/helpers': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.28.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.27.2':
+ dependencies:
+ '@babel/compat-data': 7.28.5
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.0
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.4':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+
+ '@babel/parser@7.28.5':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/runtime@7.28.4': {}
+
+ '@babel/template@7.27.2':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+
+ '@babel/traverse@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.28.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@emotion/is-prop-valid@0.8.8':
+ dependencies:
+ '@emotion/memoize': 0.7.4
+ optional: true
+
+ '@emotion/memoize@0.7.4':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
+ '@floating-ui/core@1.7.3':
+ dependencies:
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/dom@1.7.4':
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/react-dom@2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+
+ '@floating-ui/utils@0.2.10': {}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@radix-ui/number@1.1.1': {}
+
+ '@radix-ui/primitive@1.1.3': {}
+
+ '@radix-ui/react-accordion@1.2.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-avatar@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-checkbox@1.3.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-context-menu@2.2.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-context@1.1.2(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-context@1.1.3(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ aria-hidden: 1.2.6
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-direction@1.1.1(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-hover-card@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-id@1.1.1(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-label@2.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ aria-hidden: 1.2.6
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-menubar@1.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ aria-hidden: 1.2.6
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/rect': 1.1.1
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-slot': 1.2.4(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-progress@1.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-radio-group@1.3.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ aria-hidden: 1.2.6
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-separator@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-separator@1.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-slider@1.3.6(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-slot@1.2.3(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-slot@1.2.4(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-switch@1.2.6(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-toast@1.2.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-toggle': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-toggle@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-toolbar@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-separator': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.27)(react@19.2.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ use-sync-external-store: 1.6.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ '@radix-ui/rect': 1.1.1
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-use-size@1.1.1(@types/react@18.3.27)(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.0)
+ react: 19.2.0
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ '@types/react-dom': 18.3.7(@types/react@18.3.27)
+
+ '@radix-ui/rect@1.1.1': {}
+
+ '@remix-run/router@1.23.1': {}
+
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
+ '@rollup/rollup-android-arm-eabi@4.53.3':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.53.3':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.53.3':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.53.3':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.53.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.53.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.53.3':
+ optional: true
+
+ '@tailwindcss/forms@0.5.10(tailwindcss@4.1.17)':
+ dependencies:
+ mini-svg-data-uri: 1.4.4
+ tailwindcss: 4.1.17
+
+ '@tailwindcss/typography@0.5.19(tailwindcss@4.1.17)':
+ dependencies:
+ postcss-selector-parser: 6.0.10
+ tailwindcss: 4.1.17
+
+ '@tanstack/query-core@5.90.10': {}
+
+ '@tanstack/react-query@5.90.10(react@19.2.0)':
+ dependencies:
+ '@tanstack/query-core': 5.90.10
+ react: 19.2.0
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@types/d3-array@3.2.2': {}
+
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-ease@3.0.2': {}
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-path@3.1.1': {}
+
+ '@types/d3-scale@4.0.9':
+ dependencies:
+ '@types/d3-time': 3.0.4
+
+ '@types/d3-shape@3.1.7':
+ dependencies:
+ '@types/d3-path': 3.1.1
+
+ '@types/d3-time@3.0.4': {}
+
+ '@types/d3-timer@3.0.2': {}
+
+ '@types/estree@1.0.8': {}
+
+ '@types/node@20.19.25':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/prop-types@15.7.15': {}
+
+ '@types/react-dom@18.3.7(@types/react@18.3.27)':
+ dependencies:
+ '@types/react': 18.3.27
+
+ '@types/react@18.3.27':
+ dependencies:
+ '@types/prop-types': 15.7.15
+ csstype: 3.2.3
+
+ '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@20.19.25))':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 6.4.1(@types/node@20.19.25)
+ transitivePeerDependencies:
+ - supports-color
+
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
+ autoprefixer@10.4.22(postcss@8.5.6):
+ dependencies:
+ browserslist: 4.28.0
+ caniuse-lite: 1.0.30001757
+ fraction.js: 5.3.4
+ normalize-range: 0.1.2
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+
+ baseline-browser-mapping@2.8.31: {}
+
+ browserslist@4.28.0:
+ dependencies:
+ baseline-browser-mapping: 2.8.31
+ caniuse-lite: 1.0.30001757
+ electron-to-chromium: 1.5.260
+ node-releases: 2.0.27
+ update-browserslist-db: 1.1.4(browserslist@4.28.0)
+
+ caniuse-lite@1.0.30001757: {}
+
+ clsx@2.1.1: {}
+
+ convert-source-map@2.0.0: {}
+
+ cssesc@3.0.0: {}
+
+ csstype@3.2.3: {}
+
+ d3-array@3.2.4:
+ dependencies:
+ internmap: 2.0.3
+
+ d3-color@3.1.0: {}
+
+ d3-ease@3.0.1: {}
+
+ d3-format@3.1.0: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-path@3.1.0: {}
+
+ d3-scale@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+ d3-format: 3.1.0
+ d3-interpolate: 3.0.1
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ d3-shape@3.2.0:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-time-format@4.1.0:
+ dependencies:
+ d3-time: 3.1.0
+
+ d3-time@3.1.0:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-timer@3.0.1: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decimal.js-light@2.5.1: {}
+
+ detect-node-es@1.1.0: {}
+
+ dom-helpers@5.2.1:
+ dependencies:
+ '@babel/runtime': 7.28.4
+ csstype: 3.2.3
+
+ electron-to-chromium@1.5.260: {}
+
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
+ escalade@3.2.0: {}
+
+ eventemitter3@4.0.7: {}
+
+ fast-equals@5.3.3: {}
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ fraction.js@5.3.4: {}
+
+ framer-motion@10.18.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ tslib: 2.8.1
+ optionalDependencies:
+ '@emotion/is-prop-valid': 0.8.8
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+
+ fsevents@2.3.3:
+ optional: true
+
+ gensync@1.0.0-beta.2: {}
+
+ get-nonce@1.0.1: {}
+
+ internmap@2.0.3: {}
+
+ js-tokens@4.0.0: {}
+
+ jsesc@3.1.0: {}
+
+ json5@2.2.3: {}
+
+ lodash@4.17.21: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ mini-svg-data-uri@1.4.4: {}
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ node-releases@2.0.27: {}
+
+ normalize-range@0.1.2: {}
+
+ object-assign@4.1.1: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.3: {}
+
+ postcss-selector-parser@6.0.10:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-value-parser@4.2.0: {}
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ react-dom@19.2.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ scheduler: 0.27.0
+
+ react-hook-form@7.66.1(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+
+ react-is@16.13.1: {}
+
+ react-is@18.3.1: {}
+
+ react-refresh@0.17.0: {}
+
+ react-remove-scroll-bar@2.3.8(@types/react@18.3.27)(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ react-style-singleton: 2.2.3(@types/react@18.3.27)(react@19.2.0)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ react-remove-scroll@2.7.1(@types/react@18.3.27)(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ react-remove-scroll-bar: 2.3.8(@types/react@18.3.27)(react@19.2.0)
+ react-style-singleton: 2.2.3(@types/react@18.3.27)(react@19.2.0)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@18.3.27)(react@19.2.0)
+ use-sidecar: 1.1.3(@types/react@18.3.27)(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ react-router-dom@6.30.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ '@remix-run/router': 1.23.1
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ react-router: 6.30.2(react@19.2.0)
+
+ react-router@6.30.2(react@19.2.0):
+ dependencies:
+ '@remix-run/router': 1.23.1
+ react: 19.2.0
+
+ react-smooth@4.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ fast-equals: 5.3.3
+ prop-types: 15.8.1
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+
+ react-style-singleton@2.2.3(@types/react@18.3.27)(react@19.2.0):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 19.2.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ react-transition-group@4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ '@babel/runtime': 7.28.4
+ dom-helpers: 5.2.1
+ loose-envify: 1.4.0
+ prop-types: 15.8.1
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+
+ react@19.2.0: {}
+
+ recharts-scale@0.4.5:
+ dependencies:
+ decimal.js-light: 2.5.1
+
+ recharts@2.15.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ clsx: 2.1.1
+ eventemitter3: 4.0.7
+ lodash: 4.17.21
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ react-is: 18.3.1
+ react-smooth: 4.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ recharts-scale: 0.4.5
+ tiny-invariant: 1.3.3
+ victory-vendor: 36.9.2
+
+ rollup@4.53.3:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.53.3
+ '@rollup/rollup-android-arm64': 4.53.3
+ '@rollup/rollup-darwin-arm64': 4.53.3
+ '@rollup/rollup-darwin-x64': 4.53.3
+ '@rollup/rollup-freebsd-arm64': 4.53.3
+ '@rollup/rollup-freebsd-x64': 4.53.3
+ '@rollup/rollup-linux-arm-gnueabihf': 4.53.3
+ '@rollup/rollup-linux-arm-musleabihf': 4.53.3
+ '@rollup/rollup-linux-arm64-gnu': 4.53.3
+ '@rollup/rollup-linux-arm64-musl': 4.53.3
+ '@rollup/rollup-linux-loong64-gnu': 4.53.3
+ '@rollup/rollup-linux-ppc64-gnu': 4.53.3
+ '@rollup/rollup-linux-riscv64-gnu': 4.53.3
+ '@rollup/rollup-linux-riscv64-musl': 4.53.3
+ '@rollup/rollup-linux-s390x-gnu': 4.53.3
+ '@rollup/rollup-linux-x64-gnu': 4.53.3
+ '@rollup/rollup-linux-x64-musl': 4.53.3
+ '@rollup/rollup-openharmony-arm64': 4.53.3
+ '@rollup/rollup-win32-arm64-msvc': 4.53.3
+ '@rollup/rollup-win32-ia32-msvc': 4.53.3
+ '@rollup/rollup-win32-x64-gnu': 4.53.3
+ '@rollup/rollup-win32-x64-msvc': 4.53.3
+ fsevents: 2.3.3
+
+ scheduler@0.27.0: {}
+
+ semver@6.3.1: {}
+
+ source-map-js@1.2.1: {}
+
+ tailwind-merge@2.6.0: {}
+
+ tailwindcss@4.1.17: {}
+
+ tiny-invariant@1.3.3: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tslib@2.8.1: {}
+
+ typescript@5.9.3: {}
+
+ undici-types@6.21.0: {}
+
+ update-browserslist-db@1.1.4(browserslist@4.28.0):
+ dependencies:
+ browserslist: 4.28.0
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ use-callback-ref@1.3.3(@types/react@18.3.27)(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ use-sidecar@1.1.3(@types/react@18.3.27)(react@19.2.0):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 19.2.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.27
+
+ use-sync-external-store@1.6.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+
+ util-deprecate@1.0.2: {}
+
+ victory-vendor@36.9.2:
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/d3-ease': 3.0.2
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-scale': 4.0.9
+ '@types/d3-shape': 3.1.7
+ '@types/d3-time': 3.0.4
+ '@types/d3-timer': 3.0.2
+ d3-array: 3.2.4
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-scale: 4.0.2
+ d3-shape: 3.2.0
+ d3-time: 3.1.0
+ d3-timer: 3.0.1
+
+ vite@6.4.1(@types/node@20.19.25):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.53.3
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 20.19.25
+ fsevents: 2.3.3
+
+ yallist@3.1.1: {}
+
+ zustand@4.5.7(@types/react@18.3.27)(react@19.2.0):
+ dependencies:
+ use-sync-external-store: 1.6.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 18.3.27
+ react: 19.2.0
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/postcss.config.js b/qiming-vite-plugin-design-mode/examples/full-featured/postcss.config.js
new file mode 100644
index 00000000..d902995e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/postcss.config.js
@@ -0,0 +1,5 @@
+export default {
+ plugins: {
+ autoprefixer: {},
+ },
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/App.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/App.tsx
new file mode 100644
index 00000000..c5aab26b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/App.tsx
@@ -0,0 +1,318 @@
+/**
+ * @fileoverview 主应用组件
+ * @description 负责应用的路由配置、布局和设计模式集成
+ */
+
+import React, { Suspense } from 'react';
+import { Routes, Route, Navigate } from 'react-router-dom';
+import { motion, AnimatePresence } from 'framer-motion';
+import { useDesignModeStore } from './main';
+
+// 页面组件(懒加载)
+const Dashboard = React.lazy(() => import('./pages/Dashboard'));
+const ComponentEditor = React.lazy(() => import('./pages/ComponentEditor'));
+const DesignSystem = React.lazy(() => import('./pages/DesignSystem'));
+const LivePreview = React.lazy(() => import('./pages/LivePreview'));
+
+// 布局组件
+import AppLayout from './components/layout/AppLayout';
+import LoadingSpinner from './components/ui/LoadingSpinner';
+
+// 页面过渡动画配置
+const pageVariants = {
+ initial: {
+ opacity: 0,
+ y: 20,
+ },
+ in: {
+ opacity: 1,
+ y: 0,
+ },
+ out: {
+ opacity: 0,
+ y: -20,
+ },
+};
+
+const pageTransition = {
+ type: 'tween',
+ ease: 'anticipate',
+ duration: 0.3,
+};
+
+/**
+ * 页面容器组件 - 提供统一的页面布局和动画
+ */
+const PageContainer: React.FC<{
+ children: React.ReactNode;
+ title: string;
+ description?: string;
+ className?: string;
+}> = ({ children, title, description, className = '' }) => (
+
+
+
+ {title}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {children}
+
+
+);
+
+/**
+ * 页面路由配置组件
+ */
+const AppRoutes: React.FC = () => {
+ return (
+
+ }>
+
+ {/* 默认重定向到仪表盘 */}
+
+ }
+ />
+
+ {/* 仪表盘页面 */}
+
+
+
+ }
+ />
+
+ {/* 组件编辑器页面 */}
+
+
+
+ }
+ />
+
+ {/* 设计系统页面 */}
+
+
+
+ }
+ />
+
+ {/* 实时预览页面 */}
+
+
+
+ }
+ />
+
+ {/* 404 页面 */}
+
+
+
+ }
+ />
+
+
+
+ );
+};
+
+/**
+ * 设计模式集成组件
+ */
+const DesignModeIntegration: React.FC = () => {
+ const { isDesignModeEnabled, selectedComponent, hoveredComponent } = useDesignModeStore();
+
+ // 为组件添加设计模式属性
+ React.useEffect(() => {
+ if (!isDesignModeEnabled) return;
+
+ const addDesignModeAttributes = () => {
+ const components = document.querySelectorAll('[data-component]');
+ components.forEach((element, index) => {
+ const componentName = element.getAttribute('data-component');
+ const elementName = element.getAttribute('data-element');
+ const action = element.getAttribute('data-action');
+
+ // 添加设计模式属性
+ element.setAttribute('data-design-mode', 'true');
+ element.setAttribute('data-design-mode-component', componentName || 'Unknown');
+ if (elementName) {
+ element.setAttribute('data-design-mode-element', elementName);
+ }
+ if (action) {
+ element.setAttribute('data-design-mode-action', action);
+ }
+ });
+ };
+
+ // 监听 DOM 变化
+ const observer = new MutationObserver(addDesignModeAttributes);
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ attributes: true,
+ });
+
+ // 初始调用
+ addDesignModeAttributes();
+
+ return () => {
+ observer.disconnect();
+ };
+ }, [isDesignModeEnabled]);
+
+ // 添加全局样式
+ React.useEffect(() => {
+ if (isDesignModeEnabled) {
+ const style = document.createElement('style');
+ style.id = 'design-mode-styles';
+ style.textContent = `
+ [data-design-mode="true"] {
+ cursor: crosshair;
+ }
+
+ [data-design-mode="true"]:hover {
+ outline: 2px solid #3b82f6 !important;
+ outline-offset: 2px !important;
+ background-color: rgba(59, 130, 246, 0.05) !important;
+ }
+
+ [data-design-mode-active="true"] {
+ outline: 3px solid #ef4444 !important;
+ outline-offset: 2px !important;
+ background-color: rgba(239, 68, 68, 0.1) !important;
+ }
+
+ [data-design-mode-hover="true"] {
+ outline: 2px solid #f59e0b !important;
+ outline-offset: 2px !important;
+ background-color: rgba(245, 158, 11, 0.1) !important;
+ }
+ `;
+ document.head.appendChild(style);
+
+ return () => {
+ document.getElementById('design-mode-styles')?.remove();
+ };
+ }
+ }, [isDesignModeEnabled]);
+
+ return null;
+};
+
+/**
+ * 主应用组件
+ */
+const App: React.FC = () => {
+ const { isDesignModeEnabled } = useDesignModeStore();
+
+ return (
+
+ {/* 设计模式集成 */}
+
+
+ {/* 主要内容 */}
+
+
+
+
+ {/* 开发模式信息 */}
+ {import.meta.env.DEV && (
+
+
Design Mode: {isDesignModeEnabled ? 'Enabled' : 'Disabled'}
+
React: {React.version}
+
Vite: {import.meta.env.MODE}
+
+ )}
+
+ );
+};
+
+export default App;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/components/design-system/ColorPalette.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/design-system/ColorPalette.tsx
new file mode 100644
index 00000000..d2bd05e8
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/design-system/ColorPalette.tsx
@@ -0,0 +1,484 @@
+/**
+ * @fileoverview 颜色面板组件
+ * @description 展示和管理设计系统中的颜色令牌
+ */
+
+import React, { useState, useMemo } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Search, Filter, Grid, List, Plus, MoreVertical } from 'lucide-react';
+import { Card, CardBody, CardHeader } from '../ui/Card';
+import { Button, IconButton } from '../ui/Button';
+import { ColorToken } from '../../types/design-system';
+import { useColors, useDesignSystemStore } from '../../store/design-system-store';
+import { clsx } from 'clsx';
+
+// 颜色面板属性接口
+interface ColorPaletteProps {
+ title?: string;
+ variant?: 'grid' | 'list' | 'swatch';
+ showControls?: boolean;
+ interactive?: boolean;
+ className?: string;
+}
+
+// 颜色色板属性接口
+interface ColorSwatchProps {
+ token: ColorToken;
+ variant?: 'default' | 'compact' | 'large';
+ showLabel?: boolean;
+ showValue?: boolean;
+ interactive?: boolean;
+ onClick?: () => void;
+ className?: string;
+}
+
+/**
+ * 颜色色板组件
+ */
+const ColorSwatch: React.FC = ({
+ token,
+ variant = 'default',
+ showLabel = true,
+ showValue = true,
+ interactive = true,
+ onClick,
+ className
+}) => {
+ const [copied, setCopied] = useState(false);
+ const { setSelectedToken } = useDesignSystemStore();
+
+ // 复制颜色值
+ const copyColor = async (e: React.MouseEvent) => {
+ e.stopPropagation();
+ try {
+ await navigator.clipboard.writeText(token.value);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (error) {
+ console.error('Failed to copy color:', error);
+ }
+ };
+
+ // 处理点击
+ const handleClick = () => {
+ if (interactive) {
+ setSelectedToken(token);
+ onClick?.();
+ }
+ };
+
+ // 尺寸配置
+ const sizeConfig = {
+ default: { width: 'w-full', height: 'h-20', padding: 'p-4' },
+ compact: { width: 'w-full', height: 'h-12', padding: 'p-2' },
+ large: { width: 'w-full', height: 'h-32', padding: 'p-6' },
+ };
+
+ const config = sizeConfig[variant];
+
+ // 显示文本颜色(基于背景亮度)
+ const getContrastColor = (hexColor: string) => {
+ // 简单的亮度计算
+ const r = parseInt(hexColor.slice(1, 3), 16);
+ const g = parseInt(hexColor.slice(3, 5), 16);
+ const b = parseInt(hexColor.slice(5, 7), 16);
+ const brightness = (r * 299 + g * 587 + b * 114) / 1000;
+ return brightness > 128 ? '#000000' : '#ffffff';
+ };
+
+ return (
+
+ {/* 颜色背景 */}
+
+
+ {/* 覆盖层 */}
+
+
+ {/* 色板内容 */}
+
+ {/* 顶部标签 */}
+ {showLabel && (
+
+
+
+ {token.name}
+
+ {token.deprecated && (
+
+ Deprecated
+
+ )}
+
+
+ {interactive && (
+
+ {copied ? (
+
+ ✓
+
+ ) : (
+
+ #
+
+ )}
+
+ )}
+
+ )}
+
+ {/* 底部信息 */}
+ {showValue && (
+
+
+ {token.value}
+
+
+ {token.scale && token.palette && (
+
+ {token.palette} • {token.scale}
+
+ )}
+
+ )}
+
+
+ {/* 选择指示器 */}
+
+
+
+
+ );
+};
+
+/**
+ * 颜色面板主组件
+ */
+const ColorPalette: React.FC = ({
+ title = 'Color Palette',
+ variant = 'grid',
+ showControls = true,
+ interactive = true,
+ className
+}) => {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [selectedPalette, setSelectedPalette] = useState(null);
+ const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
+ const [showFilters, setShowFilters] = useState(false);
+
+ const colors = useColors();
+ const { setSelectedToken, selectedToken } = useDesignSystemStore();
+
+ // 过滤和搜索颜色
+ const filteredColors = useMemo(() => {
+ return colors.filter(color => {
+ const matchesSearch = searchQuery === '' ||
+ color.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ color.value.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ color.description?.toLowerCase().includes(searchQuery.toLowerCase());
+
+ const matchesPalette = selectedPalette === null ||
+ color.palette === selectedPalette;
+
+ return matchesSearch && matchesPalette;
+ });
+ }, [colors, searchQuery, selectedPalette]);
+
+ // 按调色板分组
+ const colorsByPalette = useMemo(() => {
+ const grouped: Record = {};
+ filteredColors.forEach(color => {
+ const palette = color.palette || 'other';
+ if (!grouped[palette]) {
+ grouped[palette] = [];
+ }
+ grouped[palette].push(color);
+ });
+ return grouped;
+ }, [filteredColors]);
+
+ // 获取所有调色板
+ const palettes = useMemo(() => {
+ const paletteSet = new Set(colors.map(color => color.palette || 'other'));
+ return Array.from(paletteSet);
+ }, [colors]);
+
+ return (
+
+ {/* 控制面板 */}
+ {showControls && (
+
+
+
+ {/* 标题 */}
+
+ {title}
+
+
+ {/* 控制按钮 */}
+
+ {/* 搜索框 */}
+
+
+ setSearchQuery(e.target.value)}
+ className="pl-10 pr-4 py-2 border border-neutral-300 dark:border-neutral-600 rounded-md bg-white dark:bg-neutral-800 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
+ data-component="Input"
+ data-element="search-input"
+ data-placeholder="search-colors"
+ />
+
+
+ {/* 过滤器按钮 */}
+
setShowFilters(!showFilters)}
+ data-component="IconButton"
+ data-element="filter-button"
+ data-action="toggle-filters"
+ >
+
+
+
+ {/* 视图模式切换 */}
+
+ setViewMode('grid')}
+ className={clsx(
+ 'px-3 py-2 text-sm transition-colors',
+ viewMode === 'grid'
+ ? 'bg-primary-500 text-white'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ )}
+ data-component="ToggleButton"
+ data-element="grid-view-button"
+ data-action="set-view-grid"
+ >
+
+
+ setViewMode('list')}
+ className={clsx(
+ 'px-3 py-2 text-sm transition-colors',
+ viewMode === 'list'
+ ? 'bg-primary-500 text-white'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ )}
+ data-component="ToggleButton"
+ data-element="list-view-button"
+ data-action="set-view-list"
+ >
+
+
+
+
+
+
+ {/* 过滤器面板 */}
+
+ {showFilters && (
+
+
+ setSelectedPalette(null)}
+ className={clsx(
+ 'px-3 py-1.5 text-sm rounded-full border transition-colors',
+ selectedPalette === null
+ ? 'bg-primary-500 text-white border-primary-500'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 border-neutral-300 dark:border-neutral-600 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ )}
+ data-component="FilterChip"
+ data-element="all-palettes-chip"
+ data-action="select-all-palettes"
+ >
+ All Palettes
+
+
+ {palettes.map(palette => (
+ setSelectedPalette(palette)}
+ className={clsx(
+ 'px-3 py-1.5 text-sm rounded-full border transition-colors capitalize',
+ selectedPalette === palette
+ ? 'bg-primary-500 text-white border-primary-500'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 border-neutral-300 dark:border-neutral-600 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ )}
+ data-component="FilterChip"
+ data-element={`${palette}-palette-chip`}
+ data-action={`select-${palette}-palette`}
+ >
+ {palette}
+
+ ))}
+
+
+ )}
+
+
+
+ )}
+
+ {/* 颜色网格/列表 */}
+
+ {Object.entries(colorsByPalette).map(([palette, paletteColors]) => (
+
+
+ {palette} Palette ({paletteColors.length})
+
+
+
+
+ {paletteColors.map((color, index) => (
+
+
+
+ ))}
+
+
+
+ ))}
+
+
+ {/* 空状态 */}
+ {filteredColors.length === 0 && (
+
+
+
+
+
+ No colors found
+
+
+ Try adjusting your search or filter criteria.
+
+
{
+ setSearchQuery('');
+ setSelectedPalette(null);
+ }}
+ data-component="Button"
+ data-element="clear-filters-button"
+ data-action="clear-all-filters"
+ >
+ Clear Filters
+
+
+ )}
+
+ );
+};
+
+export { ColorPalette, ColorSwatch };
+export type { ColorPaletteProps, ColorSwatchProps };
+
+export default ColorPalette;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/components/design-system/TokenCard.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/design-system/TokenCard.tsx
new file mode 100644
index 00000000..cd113c20
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/design-system/TokenCard.tsx
@@ -0,0 +1,395 @@
+/**
+ * @fileoverview 令牌卡片组件
+ * @description 展示设计系统令牌的可视化组件
+ */
+
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import { Copy, Check, Edit3, Trash2, Tag } from 'lucide-react';
+import { Card, CardBody, CardHeader } from '../ui/Card';
+import { Button, IconButton } from '../ui/Button';
+import { DesignToken, ColorToken, TypographyToken, SpacingToken, ShadowToken, BorderRadiusToken, AnimationToken } from '../../types/design-system';
+import { useDesignSystemStore } from '../../store/design-system-store';
+import { clsx } from 'clsx';
+
+// 令牌卡片属性接口
+interface TokenCardProps {
+ token: DesignToken;
+ variant?: 'default' | 'compact' | 'detailed';
+ interactive?: boolean;
+ showActions?: boolean;
+ className?: string;
+}
+
+/**
+ * 令牌卡片主组件
+ */
+const TokenCard: React.FC = ({
+ token,
+ variant = 'default',
+ interactive = true,
+ showActions = true,
+ className
+}) => {
+ const [copied, setCopied] = useState(false);
+ const [isEditing, setIsEditing] = useState(false);
+ const { setSelectedToken } = useDesignSystemStore();
+
+ // 复制令牌值
+ const copyToken = async () => {
+ try {
+ await navigator.clipboard.writeText(String(token.value));
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (error) {
+ console.error('Failed to copy token:', error);
+ }
+ };
+
+ // 选择令牌
+ const handleSelect = () => {
+ if (interactive) {
+ setSelectedToken(token);
+ }
+ };
+
+ // 渲染令牌预览
+ const renderTokenPreview = () => {
+ switch (token.category) {
+ case 'color':
+ return ;
+ case 'typography':
+ return ;
+ case 'spacing':
+ return ;
+ case 'shadow':
+ return ;
+ case 'border-radius':
+ return ;
+ case 'animation':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ // 渲染令牌信息
+ const renderTokenInfo = () => {
+ const categoryIcons = {
+ color: '🎨',
+ typography: '📝',
+ spacing: '📏',
+ shadow: '🌑',
+ 'border-radius': '🔘',
+ 'z-index': '📊',
+ animation: '⚡',
+ };
+
+ return (
+
+
+
+ {categoryIcons[token.category]}
+
+
+ {token.name}
+
+ {token.deprecated && (
+
+ Deprecated
+
+ )}
+
+
+ {token.description && (
+
+ {token.description}
+
+ )}
+
+ {token.tags && token.tags.length > 0 && (
+
+ {token.tags.slice(0, 3).map((tag, index) => (
+
+
+ {tag}
+
+ ))}
+ {token.tags.length > 3 && (
+
+ +{token.tags.length - 3} more
+
+ )}
+
+ )}
+
+ );
+ };
+
+ return (
+
+
+
+ {/* 令牌预览 */}
+
+ {renderTokenPreview()}
+
+
+ {/* 令牌信息 */}
+ {renderTokenInfo()}
+
+ {/* 令牌值和操作 */}
+
+
+
+ {typeof token.value === 'object' ? JSON.stringify(token.value) : token.value}
+
+
+
+ {showActions && (
+
+ {
+ e.stopPropagation();
+ copyToken();
+ }}
+ aria-label="Copy token value"
+ data-component="IconButton"
+ data-element="copy-button"
+ data-action="copy-value"
+ >
+ {copied ? (
+
+ ) : (
+
+ )}
+
+
+ {interactive && (
+ {
+ e.stopPropagation();
+ setIsEditing(!isEditing);
+ }}
+ aria-label="Edit token"
+ data-component="IconButton"
+ data-element="edit-button"
+ data-action="edit-token"
+ >
+
+
+ )}
+
+ )}
+
+
+ {/* 编辑模式 */}
+ {isEditing && interactive && (
+
+
+
+
+
+ setIsEditing(false)}
+ >
+ Cancel
+
+
+ Save
+
+
+
+
+ )}
+
+
+
+ );
+};
+
+// 颜色令牌预览组件
+const ColorTokenPreview: React.FC<{ token: ColorToken }> = ({ token }) => {
+ return (
+
+
+
+
+ {token.value}
+
+ {token.scale && (
+
+ {token.palette} {token.scale}
+
+ )}
+
+
+ );
+};
+
+// 字体令牌预览组件
+const TypographyTokenPreview: React.FC<{ token: TypographyToken }> = ({ token }) => {
+ const { fontSize, fontWeight, lineHeight } = token.value;
+
+ return (
+
+
+ The quick brown fox
+
+
+ {fontSize &&
Size: {fontSize}
}
+ {fontWeight &&
Weight: {fontWeight}
}
+ {lineHeight &&
Line Height: {lineHeight}
}
+
+
+ );
+};
+
+// 间距令牌预览组件
+const SpacingTokenPreview: React.FC<{ token: SpacingToken }> = ({ token }) => {
+ return (
+
+ );
+};
+
+// 阴影令牌预览组件
+const ShadowTokenPreview: React.FC<{ token: ShadowToken }> = ({ token }) => {
+ const { offsetX = '0', offsetY = '0', blurRadius = '0', spreadRadius = '0', color = 'transparent' } = token.value;
+
+ return (
+
+
+
+
X: {offsetX}
+
Y: {offsetY}
+
Blur: {blurRadius}
+
Spread: {spreadRadius}
+
+
+ );
+};
+
+// 圆角令牌预览组件
+const BorderRadiusTokenPreview: React.FC<{ token: BorderRadiusToken }> = ({ token }) => {
+ return (
+
+ );
+};
+
+// 动画令牌预览组件
+const AnimationTokenPreview: React.FC<{ token: AnimationToken }> = ({ token }) => {
+ const { duration = '300ms', timingFunction = 'ease' } = token.value;
+
+ return (
+
+
+
+
+
Duration: {duration}
+
Easing: {timingFunction}
+
+
+
+ );
+};
+
+// 默认令牌预览组件
+const DefaultTokenPreview: React.FC<{ token: DesignToken }> = ({ token }) => {
+ return (
+
+
+ {typeof token.value === 'object' ? JSON.stringify(token.value) : String(token.value)}
+
+
+ );
+};
+
+export { TokenCard, ColorTokenPreview, TypographyTokenPreview, SpacingTokenPreview, ShadowTokenPreview, BorderRadiusTokenPreview, AnimationTokenPreview };
+export type { TokenCardProps };
+
+export default TokenCard;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/components/layout/AppLayout.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/layout/AppLayout.tsx
new file mode 100644
index 00000000..5dc74e0e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/layout/AppLayout.tsx
@@ -0,0 +1,283 @@
+/**
+ * @fileoverview 应用布局组件
+ * @description 提供侧边栏导航、顶部栏和主内容区域的布局结构
+ */
+
+import React, { useState } from 'react';
+import { Link, useLocation } from 'react-router-dom';
+import { motion, AnimatePresence } from 'framer-motion';
+import { useDesignModeStore } from '../../main';
+
+// 导航项目类型定义
+interface NavItem {
+ path: string;
+ label: string;
+ icon: string;
+ description: string;
+ badge?: string | number;
+}
+
+/**
+ * 应用布局主组件
+ */
+const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [sidebarOpen, setSidebarOpen] = useState(false);
+ const location = useLocation();
+ const { isDesignModeEnabled, toggleDesignMode } = useDesignModeStore();
+
+ // 导航菜单配置
+ const navItems: NavItem[] = [
+ {
+ path: '/dashboard',
+ label: 'Dashboard',
+ icon: '🏠',
+ description: 'Overview and system status',
+ badge: 'Main'
+ },
+ {
+ path: '/editor',
+ label: 'Component Editor',
+ icon: '🎨',
+ description: 'Visual component builder',
+ badge: 'Pro'
+ },
+ {
+ path: '/design-system',
+ label: 'Design System',
+ icon: '🎯',
+ description: 'Design tokens and guidelines',
+ badge: 'Core'
+ },
+ {
+ path: '/preview',
+ label: 'Live Preview',
+ icon: '⚡',
+ description: 'Real-time component preview'
+ }
+ ];
+
+ // 导航项目组件
+ const NavItemComponent: React.FC<{ item: NavItem; isActive: boolean }> = ({ item, isActive }) => (
+ setSidebarOpen(false)}
+ >
+
+ {item.icon}
+
+
+
+ {item.label}
+
+
+ {item.description}
+
+
+ {item.badge && (
+
+ {item.badge}
+
+ )}
+
+ {/* 设计模式属性 */}
+
+
+ );
+
+ return (
+
+ {/* 移动端遮罩层 */}
+
+ {sidebarOpen && (
+ setSidebarOpen(false)}
+ />
+ )}
+
+
+ {/* 侧边栏 */}
+
+ {/* 侧边栏头部 */}
+
+
+
+ AD
+
+
+
+ Design Mode
+
+
+ Full Featured Showcase
+
+
+
+
+ {/* 移动端关闭按钮 */}
+
setSidebarOpen(false)}
+ className="lg:hidden p-1 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800"
+ data-component="IconButton"
+ data-element="close-button"
+ data-action="close-sidebar"
+ >
+
+
+
+
+ {/* 导航菜单 */}
+
+
+ {/* 侧边栏底部 */}
+
+
+
+ {isDesignModeEnabled ? '🎯' : '👁️'}
+ {isDesignModeEnabled ? 'Design Mode ON' : 'Design Mode OFF'}
+
+
+
+ Version 1.0.0
+
+
+
+
+
+ {/* 主内容区域 */}
+
+ {/* 顶部导航栏 */}
+
+
+ {/* 主内容 */}
+
+ {children}
+
+
+
+ );
+};
+
+export default AppLayout;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/Button.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/Button.tsx
new file mode 100644
index 00000000..ab0b47ef
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/Button.tsx
@@ -0,0 +1,274 @@
+/**
+ * @fileoverview 按钮组件
+ * @description 提供各种样式和状态的按钮组件
+ */
+
+import React, { forwardRef } from 'react';
+import { motion } from 'framer-motion';
+import { clsx } from 'clsx';
+
+// 按钮变体类型
+type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'link' | 'danger';
+
+// 按钮尺寸类型
+type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
+
+// 按钮属性接口
+interface ButtonProps extends React.ButtonHTMLAttributes {
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+ loading?: boolean;
+ loadingText?: string;
+ icon?: React.ReactNode;
+ iconPosition?: 'left' | 'right';
+ fullWidth?: boolean;
+ children?: React.ReactNode;
+ className?: string;
+ variantClass?: string;
+ sizeClass?: string;
+}
+
+/**
+ * 按钮组件实现
+ */
+const Button = forwardRef(({
+ variant = 'primary',
+ size = 'md',
+ loading = false,
+ loadingText,
+ icon,
+ iconPosition = 'left',
+ fullWidth = false,
+ children,
+ className,
+ disabled,
+ variantClass,
+ sizeClass,
+ onClick,
+ ...props
+}, ref) => {
+ // 按钮变体样式配置
+ const variantStyles: Record = {
+ primary: `
+ bg-primary-600 border-primary-600 text-white
+ hover:bg-primary-700 hover:border-primary-700
+ focus:ring-primary-500 focus:ring-offset-2
+ active:bg-primary-800 active:border-primary-800
+ disabled:bg-primary-300 disabled:border-primary-300
+ `,
+ secondary: `
+ bg-neutral-200 border-neutral-300 text-neutral-900
+ hover:bg-neutral-300 hover:border-neutral-400
+ focus:ring-neutral-500 focus:ring-offset-2
+ active:bg-neutral-400 active:border-neutral-500
+ disabled:bg-neutral-100 disabled:border-neutral-200
+ dark:bg-neutral-700 dark:border-neutral-600 dark:text-neutral-100
+ dark:hover:bg-neutral-600 dark:hover:border-neutral-500
+ dark:active:bg-neutral-500 dark:active:border-neutral-400
+ dark:disabled:bg-neutral-800 dark:disabled:border-neutral-700
+ `,
+ outline: `
+ bg-transparent border-primary-600 text-primary-600
+ hover:bg-primary-600 hover:text-white
+ focus:ring-primary-500 focus:ring-offset-2
+ active:bg-primary-700 active:border-primary-700
+ disabled:text-primary-300 disabled:border-primary-300
+ disabled:hover:bg-transparent disabled:hover:text-primary-300
+ `,
+ ghost: `
+ bg-transparent border-transparent text-neutral-700
+ hover:bg-neutral-100 hover:text-neutral-900
+ focus:ring-neutral-500 focus:ring-offset-2
+ active:bg-neutral-200 active:text-neutral-900
+ disabled:text-neutral-400
+ dark:text-neutral-300 dark:hover:bg-neutral-800 dark:hover:text-neutral-100
+ dark:active:bg-neutral-700 dark:active:text-neutral-100
+ `,
+ link: `
+ bg-transparent border-transparent text-primary-600
+ hover:text-primary-700 hover:underline
+ focus:ring-primary-500 focus:ring-offset-2
+ active:text-primary-800
+ disabled:text-primary-300 disabled:no-underline
+ `,
+ danger: `
+ bg-error-600 border-error-600 text-white
+ hover:bg-error-700 hover:border-error-700
+ focus:ring-error-500 focus:ring-offset-2
+ active:bg-error-800 active:border-error-800
+ disabled:bg-error-300 disabled:border-error-300
+ `,
+ };
+
+ // 按钮尺寸样式配置
+ const sizeStyles: Record = {
+ sm: 'px-3 py-1.5 text-sm',
+ md: 'px-4 py-2 text-sm',
+ lg: 'px-6 py-3 text-base',
+ xl: 'px-8 py-4 text-lg',
+ };
+
+ // 计算按钮内容
+ const buttonContent = (
+
+ {icon && iconPosition === 'left' && !loading && (
+
+ {icon}
+
+ )}
+
+ {loading && (
+
+
+
+ )}
+
+
+ {loading && loadingText ? loadingText : children}
+
+
+ {icon && iconPosition === 'right' && !loading && (
+
+ {icon}
+
+ )}
+
+ );
+
+ // 应用自定义样式类
+ const combinedVariantClass = variantClass || variantStyles[variant];
+ const combinedSizeClass = sizeClass || sizeStyles[size];
+
+ return (
+
+ {buttonContent}
+
+ );
+});
+
+// 显示名称
+Button.displayName = 'Button';
+
+/**
+ * 图标按钮组件
+ */
+interface IconButtonProps extends Omit {
+ size?: Exclude;
+ 'aria-label': string; // 无障碍属性必需
+}
+
+const IconButton = forwardRef(({
+ size = 'md',
+ className,
+ ...props
+}, ref) => {
+ const iconSizeStyles: Record = {
+ sm: 'w-8 h-8 p-1.5',
+ md: 'w-10 h-10 p-2',
+ lg: 'w-12 h-12 p-2.5',
+ xl: 'w-14 h-14 p-3',
+ };
+
+ return (
+
+ );
+});
+
+IconButton.displayName = 'IconButton';
+
+/**
+ * 浮动操作按钮
+ */
+interface FloatingActionButtonProps extends Omit {
+ variant?: ButtonVariant;
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
+}
+
+const FloatingActionButton = forwardRef(({
+ position = 'bottom-right',
+ className,
+ ...props
+}, ref) => {
+ const positionStyles = {
+ 'bottom-right': 'fixed bottom-6 right-6 z-50',
+ 'bottom-left': 'fixed bottom-6 left-6 z-50',
+ 'top-right': 'fixed top-6 right-6 z-50',
+ 'top-left': 'fixed top-6 left-6 z-50',
+ };
+
+ return (
+
+
+
+ );
+});
+
+FloatingActionButton.displayName = 'FloatingActionButton';
+
+export { Button, IconButton, FloatingActionButton };
+export type { ButtonProps, IconButtonProps, FloatingActionButtonProps, ButtonVariant, ButtonSize };
+
+export default Button;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/Card.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/Card.tsx
new file mode 100644
index 00000000..54d7c7a1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/Card.tsx
@@ -0,0 +1,421 @@
+/**
+ * @fileoverview 卡片组件
+ * @description 提供各种样式和布局的卡片容器组件
+ */
+
+import React, { forwardRef } from 'react';
+import { motion } from 'framer-motion';
+import { clsx } from 'clsx';
+
+// 卡片变体类型
+type CardVariant = 'default' | 'elevated' | 'outlined' | 'ghost' | 'filled';
+
+// 卡片尺寸类型
+type CardSize = 'sm' | 'md' | 'lg' | 'xl';
+
+// 卡片属性接口
+interface CardProps extends React.HTMLAttributes {
+ variant?: CardVariant;
+ size?: CardSize;
+ hover?: boolean;
+ interactive?: boolean;
+ loading?: boolean;
+ children?: React.ReactNode;
+ className?: string;
+ variantClass?: string;
+ sizeClass?: string;
+}
+
+// 卡片标题属性接口
+interface CardHeaderProps extends React.HTMLAttributes {
+ title?: string;
+ subtitle?: string;
+ action?: React.ReactNode;
+ children?: React.ReactNode;
+ className?: string;
+}
+
+// 卡片内容属性接口
+interface CardBodyProps extends React.HTMLAttributes {
+ children?: React.ReactNode;
+ className?: string;
+}
+
+// 卡片底部属性接口
+interface CardFooterProps extends React.HTMLAttributes {
+ children?: React.ReactNode;
+ className?: string;
+}
+
+// 卡片图片属性接口
+interface CardImageProps extends React.ImgHTMLAttributes {
+ src: string;
+ alt: string;
+ overlay?: boolean;
+ height?: string | number;
+ className?: string;
+}
+
+/**
+ * 卡片组件实现
+ */
+const Card = forwardRef(({
+ variant = 'default',
+ size = 'md',
+ hover = false,
+ interactive = false,
+ loading = false,
+ children,
+ className,
+ variantClass,
+ sizeClass,
+ onClick,
+ ...props
+}, ref) => {
+ // 卡片变体样式配置
+ const variantStyles: Record = {
+ default: `
+ bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800
+ `,
+ elevated: `
+ bg-white dark:bg-neutral-900 border-0 shadow-lg dark:shadow-neutral-900/20
+ hover:shadow-xl transition-shadow duration-300
+ `,
+ outlined: `
+ bg-transparent border-2 border-primary-200 dark:border-primary-800
+ hover:border-primary-300 dark:hover:border-primary-700
+ `,
+ ghost: `
+ bg-transparent border-0
+ hover:bg-neutral-50 dark:hover:bg-neutral-800/50
+ `,
+ filled: `
+ bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700
+ `,
+ };
+
+ // 卡片尺寸样式配置
+ const sizeStyles: Record = {
+ sm: 'p-3 space-y-2',
+ md: 'p-4 space-y-3',
+ lg: 'p-6 space-y-4',
+ xl: 'p-8 space-y-6',
+ };
+
+ // 悬停效果样式
+ const hoverStyles = hover || interactive ? `
+ transition-all duration-200 ease-in-out cursor-pointer
+ hover:shadow-md hover:-translate-y-1
+ hover:shadow-neutral-500/10 dark:hover:shadow-neutral-900/20
+ ` : '';
+
+ // 交互样式
+ const interactiveStyles = interactive ? `
+ active:scale-95 active:shadow-sm
+ ` : '';
+
+ return (
+
+ {loading ? (
+
+ ) : (
+ children
+ )}
+
+ );
+});
+
+Card.displayName = 'Card';
+
+/**
+ * 卡片头部组件
+ */
+const CardHeader = forwardRef(({
+ title,
+ subtitle,
+ action,
+ children,
+ className,
+ ...props
+}, ref) => {
+ return (
+
+
+ {title && (
+
+ {title}
+
+ )}
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+ {children}
+
+ {action && (
+
+ {action}
+
+ )}
+
+ );
+});
+
+CardHeader.displayName = 'CardHeader';
+
+/**
+ * 卡片内容组件
+ */
+const CardBody = forwardRef(({
+ children,
+ className,
+ ...props
+}, ref) => {
+ return (
+
+ {children}
+
+ );
+});
+
+CardBody.displayName = 'CardBody';
+
+/**
+ * 卡片底部组件
+ */
+const CardFooter = forwardRef(({
+ children,
+ className,
+ ...props
+}, ref) => {
+ return (
+
+ {children}
+
+ );
+});
+
+CardFooter.displayName = 'CardFooter';
+
+/**
+ * 卡片图片组件
+ */
+const CardImage = forwardRef(({
+ src,
+ alt,
+ overlay = false,
+ height = 200,
+ className,
+ ...props
+}, ref) => {
+ return (
+
+

+ {overlay && (
+
+ )}
+
+ );
+});
+
+CardImage.displayName = 'CardImage';
+
+/**
+ * 卡片骨架屏组件
+ */
+const CardSkeleton: React.FC<{ variant: CardVariant; size: CardSize }> = ({ variant, size }) => {
+ const skeletonSizes = {
+ sm: { padding: 'p-3', gap: 'space-y-2' },
+ md: { padding: 'p-4', gap: 'space-y-3' },
+ lg: { padding: 'p-6', gap: 'space-y-4' },
+ xl: { padding: 'p-8', gap: 'space-y-6' },
+ };
+
+ const { padding, gap } = skeletonSizes[size];
+
+ return (
+
+ {/* 头部骨架 */}
+
+
+ {/* 内容骨架 */}
+
+
+ {/* 底部骨架 */}
+
+
+ );
+};
+
+/**
+ * 卡片组组件
+ */
+interface CardGroupProps extends React.HTMLAttributes {
+ columns?: 1 | 2 | 3 | 4;
+ gap?: 'sm' | 'md' | 'lg' | 'xl';
+ children?: React.ReactNode;
+}
+
+const CardGroup: React.FC = ({
+ columns = 3,
+ gap = 'md',
+ children,
+ className,
+ ...props
+}) => {
+ const columnClasses = {
+ 1: 'grid-cols-1',
+ 2: 'grid-cols-1 md:grid-cols-2',
+ 3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
+ 4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
+ };
+
+ const gapClasses = {
+ sm: 'gap-3',
+ md: 'gap-4',
+ lg: 'gap-6',
+ xl: 'gap-8',
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export {
+ Card,
+ CardHeader,
+ CardBody,
+ CardFooter,
+ CardImage,
+ CardGroup,
+ CardSkeleton
+};
+
+export type {
+ CardProps,
+ CardHeaderProps,
+ CardBodyProps,
+ CardFooterProps,
+ CardImageProps,
+ CardGroupProps,
+ CardVariant,
+ CardSize
+};
+
+export default Card;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/LoadingSpinner.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/LoadingSpinner.tsx
new file mode 100644
index 00000000..2af1b8bd
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/components/ui/LoadingSpinner.tsx
@@ -0,0 +1,297 @@
+/**
+ * @fileoverview 加载动画组件
+ * @description 提供各种加载状态的可复用组件
+ */
+
+import React from 'react';
+import { motion } from 'framer-motion';
+
+/**
+ * 旋转加载器组件
+ */
+const LoadingSpinner: React.FC<{
+ size?: 'sm' | 'md' | 'lg' | 'xl';
+ color?: 'primary' | 'secondary' | 'neutral' | 'white';
+ className?: string;
+}> = ({
+ size = 'md',
+ color = 'primary',
+ className = ''
+}) => {
+ const sizeClasses = {
+ sm: 'w-4 h-4',
+ md: 'w-6 h-6',
+ lg: 'w-8 h-8',
+ xl: 'w-12 h-12'
+ };
+
+ const colorClasses = {
+ primary: 'border-primary-600 border-t-transparent',
+ secondary: 'border-secondary-600 border-t-transparent',
+ neutral: 'border-neutral-400 border-t-transparent',
+ white: 'border-white border-t-transparent'
+ };
+
+ return (
+
+ );
+};
+
+/**
+ * 点状加载器组件
+ */
+const LoadingDots: React.FC<{
+ size?: 'sm' | 'md' | 'lg';
+ color?: 'primary' | 'secondary' | 'neutral' | 'white';
+ className?: string;
+}> = ({
+ size = 'md',
+ color = 'primary',
+ className = ''
+}) => {
+ const sizeClasses = {
+ sm: 'w-1 h-1',
+ md: 'w-2 h-2',
+ lg: 'w-3 h-3'
+ };
+
+ const colorClasses = {
+ primary: 'bg-primary-600',
+ secondary: 'bg-secondary-600',
+ neutral: 'bg-neutral-400',
+ white: 'bg-white'
+ };
+
+ return (
+
+ {[0, 1, 2].map((index) => (
+
+ ))}
+
+ );
+};
+
+/**
+ * 脉冲加载器组件
+ */
+const LoadingPulse: React.FC<{
+ size?: 'sm' | 'md' | 'lg' | 'xl';
+ color?: 'primary' | 'secondary' | 'neutral' | 'white';
+ className?: string;
+}> = ({
+ size = 'md',
+ color = 'primary',
+ className = ''
+}) => {
+ const sizeClasses = {
+ sm: 'w-4 h-4',
+ md: 'w-6 h-6',
+ lg: 'w-8 h-8',
+ xl: 'w-12 h-12'
+ };
+
+ const colorClasses = {
+ primary: 'bg-primary-600',
+ secondary: 'bg-secondary-600',
+ neutral: 'bg-neutral-400',
+ white: 'bg-white'
+ };
+
+ return (
+
+ );
+};
+
+/**
+ * 页面级加载组件
+ */
+const PageLoading: React.FC<{
+ title?: string;
+ description?: string;
+ variant?: 'spinner' | 'dots' | 'pulse';
+ className?: string;
+}> = ({
+ title = 'Loading...',
+ description = 'Please wait while we prepare your content.',
+ variant = 'spinner',
+ className = ''
+}) => {
+ const renderLoader = () => {
+ switch (variant) {
+ case 'dots':
+ return ;
+ case 'pulse':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ return (
+
+
+ {renderLoader()}
+
+
+
+ {title}
+
+
+
+ {description}
+
+
+ );
+};
+
+/**
+ * 按钮内加载组件
+ */
+const ButtonLoading: React.FC<{
+ size?: 'sm' | 'md' | 'lg';
+ color?: 'primary' | 'secondary' | 'neutral' | 'white';
+ text?: string;
+}> = ({
+ size = 'sm',
+ color = 'white',
+ text = 'Loading...'
+}) => {
+ return (
+
+
+ {text}
+
+ );
+};
+
+/**
+ * 骨架屏加载组件
+ */
+const SkeletonLoader: React.FC<{
+ lines?: number;
+ className?: string;
+}> = ({
+ lines = 3,
+ className = ''
+}) => {
+ return (
+
+ {Array.from({ length: lines }).map((_, index) => (
+
+ ))}
+
+ );
+};
+
+export {
+ LoadingSpinner,
+ LoadingDots,
+ LoadingPulse,
+ PageLoading,
+ ButtonLoading,
+ SkeletonLoader
+};
+
+export default LoadingSpinner;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/index.css b/qiming-vite-plugin-design-mode/examples/full-featured/src/index.css
new file mode 100644
index 00000000..d344840c
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/index.css
@@ -0,0 +1,388 @@
+/**
+ * @fileoverview 全局样式文件
+ * @description 包含 TailwindCSS 导入、基础样式重置和应用级样式
+ */
+
+/* TailwindCSS 基础导入 */
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* 自定义 CSS 变量 */
+:root {
+ /* 颜色变量 */
+ --color-primary-50: #eff6ff;
+ --color-primary-500: #3b82f6;
+ --color-primary-600: #2563eb;
+ --color-primary-700: #1d4ed8;
+
+ --color-secondary-50: #faf5ff;
+ --color-secondary-500: #a855f7;
+ --color-secondary-600: #9333ea;
+
+ /* 字体变量 */
+ --font-family-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
+ --font-family-mono: 'JetBrains Mono', ui-monospace, monospace;
+
+ /* 阴影变量 */
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+
+ /* 圆角变量 */
+ --radius-sm: 0.25rem;
+ --radius-md: 0.375rem;
+ --radius-lg: 0.5rem;
+ --radius-xl: 0.75rem;
+}
+
+/* 暗色主题变量 */
+.dark {
+ --color-bg-primary: #0a0a0a;
+ --color-bg-secondary: #171717;
+ --color-text-primary: #fafafa;
+ --color-text-secondary: #a3a3a3;
+}
+
+/* 基础样式重置 */
+* {
+ box-sizing: border-box;
+}
+
+html {
+ font-family: var(--font-family-sans);
+ line-height: 1.5;
+ -webkit-text-size-adjust: 100%;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ min-height: 100vh;
+ background-color: #fafafa;
+ color: #171717;
+ overflow-x: hidden;
+}
+
+/* 应用容器样式 */
+.app {
+ min-height: 100vh;
+ position: relative;
+ transition: all 0.3s ease;
+}
+
+.app.design-mode-enabled {
+ cursor: crosshair;
+}
+
+/* 页面容器 */
+.page-container {
+ @apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
+ padding-top: 2rem;
+ padding-bottom: 2rem;
+}
+
+.page-header {
+ @apply mb-8;
+ border-bottom: 1px solid #e5e5e5;
+ padding-bottom: 1.5rem;
+}
+
+.page-content {
+ @apply w-full;
+}
+
+/* 布局组件样式 */
+.layout-container {
+ @apply min-h-screen flex flex-col;
+}
+
+.layout-sidebar {
+ @apply w-64 bg-white dark:bg-neutral-900 border-r border-neutral-200 dark:border-neutral-800;
+ transition: width 0.3s ease;
+}
+
+.layout-main {
+ @apply flex-1 flex flex-col overflow-hidden;
+}
+
+.layout-header {
+ @apply h-16 bg-white dark:bg-neutral-900 border-b border-neutral-200 dark:border-neutral-800 flex items-center px-6;
+}
+
+.layout-content {
+ @apply flex-1 overflow-auto bg-neutral-50 dark:bg-neutral-800;
+}
+
+/* 组件高亮样式 */
+.component-highlight {
+ @apply transition-all duration-200 ease-in-out;
+ position: relative;
+}
+
+.component-highlight:hover {
+ @apply bg-primary-50 dark:bg-primary-900/20;
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-md);
+}
+
+/* 设计模式样式 */
+[data-design-mode="true"] {
+ transition: all 0.2s ease;
+}
+
+[data-design-mode="true"]:hover {
+ @apply bg-primary-50 border-primary-200;
+ outline: 2px solid theme('colors.primary.500');
+ outline-offset: 2px;
+}
+
+[data-design-mode-active="true"] {
+ @apply bg-error-50 border-error-200;
+ outline: 3px solid theme('colors.error.500');
+ outline-offset: 2px;
+ animation: pulse-error 1s ease-in-out;
+}
+
+[data-design-mode-hover="true"] {
+ @apply bg-warning-50 border-warning-200;
+ outline: 2px solid theme('colors.warning.500');
+ outline-offset: 2px;
+}
+
+@keyframes pulse-error {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.7; }
+}
+
+/* 表单组件样式 */
+.form-group {
+ @apply space-y-2;
+}
+
+.form-label {
+ @apply block text-sm font-medium text-neutral-700 dark:text-neutral-300;
+}
+
+.form-input {
+ @apply w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-md shadow-sm;
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500;
+ @apply dark:bg-neutral-700 dark:text-neutral-100;
+}
+
+.form-input:invalid {
+ @apply border-error-300 focus:ring-error-500 focus:border-error-500;
+}
+
+/* 按钮组件样式 */
+.btn {
+ @apply inline-flex items-center justify-center px-4 py-2 border font-medium rounded-md;
+ @apply transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
+ @apply disabled:opacity-50 disabled:cursor-not-allowed;
+}
+
+.btn-primary {
+ @apply btn bg-primary-600 border-primary-600 text-white;
+ @apply hover:bg-primary-700 hover:border-primary-700 focus:ring-primary-500;
+}
+
+.btn-secondary {
+ @apply btn bg-neutral-200 border-neutral-300 text-neutral-900;
+ @apply hover:bg-neutral-300 hover:border-neutral-400 focus:ring-neutral-500;
+ @apply dark:bg-neutral-700 dark:border-neutral-600 dark:text-neutral-100;
+ @apply dark:hover:bg-neutral-600 dark:hover:border-neutral-500;
+}
+
+.btn-outline {
+ @apply btn border-primary-600 text-primary-600 bg-transparent;
+ @apply hover:bg-primary-600 hover:text-white focus:ring-primary-500;
+}
+
+.btn-sm {
+ @apply px-3 py-1.5 text-sm;
+}
+
+.btn-lg {
+ @apply px-6 py-3 text-lg;
+}
+
+/* 卡片组件样式 */
+.card {
+ @apply bg-white dark:bg-neutral-900 rounded-lg shadow border border-neutral-200 dark:border-neutral-800;
+ @apply transition-shadow duration-200;
+}
+
+.card:hover {
+ box-shadow: var(--shadow-lg);
+}
+
+.card-header {
+ @apply px-6 py-4 border-b border-neutral-200 dark:border-neutral-800;
+}
+
+.card-body {
+ @apply px-6 py-4;
+}
+
+.card-footer {
+ @apply px-6 py-4 border-t border-neutral-200 dark:border-neutral-800;
+}
+
+/* 模态框样式 */
+.modal-overlay {
+ @apply fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50;
+ backdrop-filter: blur(4px);
+}
+
+.modal-content {
+ @apply bg-white dark:bg-neutral-900 rounded-lg shadow-xl max-w-md w-full mx-4;
+ @apply animate-in fade-in duration-200;
+}
+
+/* 工具提示样式 */
+.tooltip {
+ @apply absolute z-50 px-2 py-1 text-xs text-white bg-neutral-900 rounded shadow-lg;
+ @apply pointer-events-none opacity-0 transition-opacity duration-200;
+}
+
+.tooltip.show {
+ @apply opacity-100;
+}
+
+/* 加载状态样式 */
+.loading-spinner {
+ @apply inline-block w-4 h-4 border-2 border-current border-t-transparent rounded-full;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.loading-dots {
+ @apply flex space-x-1;
+}
+
+.loading-dots > div {
+ @apply w-2 h-2 bg-current rounded-full;
+ animation: loading-dots 1.4s ease-in-out infinite both;
+}
+
+.loading-dots > div:nth-child(1) { animation-delay: -0.32s; }
+.loading-dots > div:nth-child(2) { animation-delay: -0.16s; }
+
+@keyframes loading-dots {
+ 0%, 80%, 100% {
+ transform: scale(0);
+ }
+ 40% {
+ transform: scale(1);
+ }
+}
+
+/* 响应式设计增强 */
+@media (max-width: 768px) {
+ .layout-sidebar {
+ @apply fixed inset-y-0 left-0 z-40 transform -translate-x-full;
+ transition: transform 0.3s ease;
+ }
+
+ .layout-sidebar.open {
+ @apply translate-x-0;
+ }
+
+ .page-container {
+ @apply px-4;
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+ }
+}
+
+/* 无障碍支持 */
+.sr-only {
+ @apply absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0;
+ clip: rect(0, 0, 0, 0);
+}
+
+/* 高对比度模式支持 */
+@media (prefers-contrast: high) {
+ .card {
+ @apply border-2;
+ }
+
+ .btn {
+ @apply border-2;
+ }
+}
+
+/* 减少动画支持 */
+@media (prefers-reduced-motion: reduce) {
+ * {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+}
+
+/* 打印样式 */
+@media print {
+ .layout-sidebar,
+ .layout-header {
+ @apply hidden;
+ }
+
+ .layout-content {
+ @apply ml-0;
+ }
+
+ .page-container {
+ @apply max-w-none px-0;
+ }
+}
+
+/* 自定义滚动条 */
+.custom-scrollbar {
+ scrollbar-width: thin;
+ scrollbar-color: theme('colors.neutral.400') theme('colors.neutral.100');
+}
+
+.custom-scrollbar::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-track {
+ background: theme('colors.neutral.100');
+ border-radius: 3px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb {
+ background: theme('colors.neutral.400');
+ border-radius: 3px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb:hover {
+ background: theme('colors.neutral.500');
+}
+
+/* 暗色主题滚动条 */
+.dark .custom-scrollbar {
+ scrollbar-color: theme('colors.neutral.600') theme('colors.neutral.800');
+}
+
+.dark .custom-scrollbar::-webkit-scrollbar-track {
+ background: theme('colors.neutral.800');
+}
+
+.dark .custom-scrollbar::-webkit-scrollbar-thumb {
+ background: theme('colors.neutral.600');
+}
+
+.dark .custom-scrollbar::-webkit-scrollbar-thumb:hover {
+ background: theme('colors.neutral.500');
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/main.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/main.tsx
new file mode 100644
index 00000000..cad182f4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/main.tsx
@@ -0,0 +1,215 @@
+/**
+ * @fileoverview 应用程序入口文件
+ * @description 负责初始化 React 应用、路由、状态管理和设计模式桥接
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { create } from 'zustand';
+import { subscribeWithSelector } from 'zustand/middleware';
+import { devtools } from 'zustand/middleware';
+import { AnimatePresence } from 'framer-motion';
+
+// 导入应用样式
+import './index.css';
+
+// 导入主要组件
+import App from './App';
+
+// 创建设计模式状态存储
+const useDesignModeStore = create<{
+ isDesignModeEnabled: boolean;
+ selectedComponent: any;
+ hoveredComponent: any;
+ componentTree: any[];
+ toggleDesignMode: () => void;
+ setSelectedComponent: (component: any) => void;
+ setHoveredComponent: (component: any) => void;
+ setComponentTree: (tree: any[]) => void;
+ clearSelection: () => void;
+}>()(
+ devtools(
+ subscribeWithSelector((set, get) => ({
+ // 设计模式状态
+ isDesignModeEnabled: true,
+ selectedComponent: null,
+ hoveredComponent: null,
+ componentTree: [],
+
+ // 操作方法
+ toggleDesignMode: () => set((state) => ({
+ isDesignModeEnabled: !state.isDesignModeEnabled
+ })),
+
+ setSelectedComponent: (component) => set({ selectedComponent: component }),
+ setHoveredComponent: (component) => set({ hoveredComponent: component }),
+ setComponentTree: (tree) => set({ componentTree: tree }),
+
+ // 清除选择
+ clearSelection: () => set({
+ selectedComponent: null,
+ hoveredComponent: null
+ }),
+ })),
+ {
+ name: 'design-mode-store',
+ }
+ )
+);
+
+// 创建 React Query 客户端配置
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 1000 * 60 * 5, // 5分钟
+ gcTime: 1000 * 60 * 30, // 30分钟
+ retry: (failureCount, error) => {
+ if (error instanceof Error && error.message.includes('404')) {
+ return false;
+ }
+ return failureCount < 3;
+ },
+ refetchOnWindowFocus: false,
+ },
+ mutations: {
+ retry: 1,
+ },
+ },
+});
+
+// 错误处理组件
+const ErrorBoundary: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [hasError, setHasError] = React.useState(false);
+ const [error, setError] = React.useState(null);
+
+ React.useEffect(() => {
+ const handleError = (error: ErrorEvent) => {
+ console.error('Global error caught:', error);
+ setHasError(true);
+ setError(error.error || new Error(error.message));
+ };
+
+ const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
+ console.error('Unhandled promise rejection:', event.reason);
+ setHasError(true);
+ setError(event.reason instanceof Error ? event.reason : new Error(String(event.reason)));
+ };
+
+ window.addEventListener('error', handleError);
+ window.addEventListener('unhandledrejection', handleUnhandledRejection);
+
+ return () => {
+ window.removeEventListener('error', handleError);
+ window.removeEventListener('unhandledrejection', handleUnhandledRejection);
+ };
+ }, []);
+
+ if (hasError) {
+ return (
+
+
+
+
+
+ Something went wrong while loading the application. Please try refreshing the page.
+
+
+ {error && (
+
+
+ Error Details
+
+
+ {error.message}
+ {error.stack && `\n\n${error.stack}`}
+
+
+ )}
+
+
+ window.location.reload()}
+ className="flex-1 px-4 py-2 bg-primary-600 text-white rounded-md hover:bg-primary-700 transition-colors"
+ >
+ Reload Page
+
+ {
+ setHasError(false);
+ setError(null);
+ }}
+ className="flex-1 px-4 py-2 bg-neutral-200 text-neutral-700 rounded-md hover:bg-neutral-300 transition-colors"
+ >
+ Try Again
+
+
+
+
+ );
+ }
+
+ return <>{children}>;
+};
+
+// 主应用组件
+const RootApp: React.FC = () => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+// 渲染应用
+const rootElement = document.getElementById('root');
+if (!rootElement) {
+ throw new Error('Root element not found');
+}
+
+const root = ReactDOM.createRoot(rootElement);
+root.render();
+
+// 开发环境下的热模块替换
+if (import.meta.hot) {
+ import.meta.hot.accept();
+}
+
+// 设计模式桥接(如果存在)
+declare global {
+ interface Window {
+ __APPDEV_DESIGN_MODE__?: {
+ store: typeof useDesignModeStore;
+ toggleDesignMode: () => void;
+ selectComponent: (component: any) => void;
+ highlightComponent: (component: any) => void;
+ };
+ }
+}
+
+// 初始化设计模式桥接
+if (typeof window !== 'undefined') {
+ window.__APPDEV_DESIGN_MODE__ = {
+ store: useDesignModeStore,
+ toggleDesignMode: () => useDesignModeStore.getState().toggleDesignMode(),
+ selectComponent: (component) => useDesignModeStore.getState().setSelectedComponent(component),
+ highlightComponent: (component) => useDesignModeStore.getState().setHoveredComponent(component),
+ };
+}
+
+export { useDesignModeStore };
+export default RootApp;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/ComponentEditor.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/ComponentEditor.tsx
new file mode 100644
index 00000000..6583e23b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/ComponentEditor.tsx
@@ -0,0 +1,711 @@
+/**
+ * @fileoverview 组件编辑器页面
+ * @description 提供可视化的组件编辑功能,支持拖拽、属性编辑和实时预览
+ */
+
+import React, { useState, useCallback, useRef } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import {
+ Plus,
+ Play,
+ Square,
+ Save,
+ Download,
+ Upload,
+ Eye,
+ EyeOff,
+ Settings,
+ Layers,
+ Code,
+ Smartphone,
+ Monitor,
+ Tablet
+} from 'lucide-react';
+import { Card, CardBody, CardHeader } from '../components/ui/Card';
+import { Button, IconButton } from '../components/ui/Button';
+import { LoadingSpinner } from '../components/ui/LoadingSpinner';
+import { useDesignModeStore } from '../main';
+
+// 编辑模式类型
+type EditMode = 'design' | 'code' | 'preview';
+
+// 设备类型
+type DeviceType = 'desktop' | 'tablet' | 'mobile';
+
+// 组件库数据
+const COMPONENT_LIBRARY = [
+ {
+ id: 'button',
+ name: 'Button',
+ category: 'Input',
+ icon: '🔘',
+ description: 'Interactive button component',
+ props: {
+ variant: 'primary',
+ size: 'md',
+ disabled: false,
+ loading: false
+ },
+ variants: ['primary', 'secondary', 'outline', 'ghost', 'danger'],
+ sizes: ['sm', 'md', 'lg', 'xl']
+ },
+ {
+ id: 'card',
+ name: 'Card',
+ category: 'Layout',
+ icon: '🃏',
+ description: 'Container component with header, body, and footer',
+ props: {
+ variant: 'default',
+ size: 'md',
+ hover: false,
+ interactive: false
+ },
+ variants: ['default', 'elevated', 'outlined', 'ghost', 'filled'],
+ sizes: ['sm', 'md', 'lg', 'xl']
+ },
+ {
+ id: 'input',
+ name: 'Input',
+ category: 'Form',
+ icon: '📝',
+ description: 'Text input field component',
+ props: {
+ type: 'text',
+ placeholder: 'Enter text...',
+ disabled: false,
+ required: false,
+ error: false
+ },
+ variants: ['default', 'filled', 'underlined'],
+ sizes: ['sm', 'md', 'lg']
+ },
+ {
+ id: 'modal',
+ name: 'Modal',
+ category: 'Overlay',
+ icon: '🪟',
+ description: 'Overlay modal dialog',
+ props: {
+ open: false,
+ size: 'md',
+ closable: true,
+ maskClosable: true
+ },
+ variants: ['default', 'fullscreen', 'drawer'],
+ sizes: ['sm', 'md', 'lg', 'xl']
+ }
+];
+
+// 组件预览组件
+const ComponentPreview: React.FC<{
+ component: any;
+ deviceType: DeviceType;
+ isPlaying: boolean;
+}> = ({ component, deviceType, isPlaying }) => {
+ const renderComponent = () => {
+ switch (component.id) {
+ case 'button':
+ return (
+
+ {isPlaying ? 'Loading...' : 'Button Component'}
+
+ );
+
+ case 'card':
+ return (
+
+
+ Card Header
+
+
+ This is a preview of the {component.name} component.
+
+
+ Card content goes here...
+
+
+ );
+
+ case 'input':
+ return (
+
+
+
+
+ );
+
+ case 'modal':
+ return (
+
+
+
+
+ 🪟
+
+
+ {component.props.open ? 'Modal is open' : 'Click to open modal'}
+
+
+ Size: {component.props.size} • Closable: {component.props.closable ? 'Yes' : 'No'}
+
+
+
+
+ );
+
+ default:
+ return (
+
+
+ Component preview not available
+
+
+ );
+ }
+ };
+
+ const getDeviceClasses = () => {
+ switch (deviceType) {
+ case 'mobile':
+ return 'max-w-sm mx-auto';
+ case 'tablet':
+ return 'max-w-2xl mx-auto';
+ default:
+ return 'w-full';
+ }
+ };
+
+ return (
+
+ {renderComponent()}
+
+ );
+};
+
+// 属性面板组件
+const PropertyPanel: React.FC<{
+ component: any;
+ onUpdate: (props: any) => void;
+}> = ({ component, onUpdate }) => {
+ const updateProp = (key: string, value: any) => {
+ onUpdate({
+ ...component.props,
+ [key]: value
+ });
+ };
+
+ return (
+
+
+
+ {/* 变体选择 */}
+ {component.variants && (
+
+
+
+
+ )}
+
+ {/* 尺寸选择 */}
+ {component.sizes && (
+
+
+
+
+ )}
+
+ {/* 布尔属性 */}
+ {['disabled', 'loading', 'hover', 'interactive', 'required', 'error'].map((prop) => {
+ if (!(prop in component.props)) return null;
+
+ return (
+
+
+ updateProp(prop, !component.props[prop])}
+ className={`
+ relative inline-flex h-6 w-11 items-center rounded-full transition-colors
+ ${component.props[prop] ? 'bg-primary-600' : 'bg-neutral-200 dark:bg-neutral-700'}
+ `}
+ data-component="Toggle"
+ data-element="boolean-toggle"
+ data-prop={prop}
+ data-value={component.props[prop] ? 'true' : 'false'}
+ >
+
+
+
+ );
+ })}
+
+ {/* 文本属性 */}
+ {['placeholder', 'type'].map((prop) => {
+ if (!(prop in component.props)) return null;
+
+ return (
+
+
+ updateProp(prop, e.target.value)}
+ className="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-md bg-white dark:bg-neutral-800 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
+ data-component="Input"
+ data-element="text-input"
+ data-component-prop={prop}
+ />
+
+ );
+ })}
+
+
+ );
+};
+
+/**
+ * 组件编辑器主页面
+ */
+const ComponentEditor: React.FC = () => {
+ const [selectedComponent, setSelectedComponent] = useState(COMPONENT_LIBRARY[0]);
+ const [editMode, setEditMode] = useState('design');
+ const [deviceType, setDeviceType] = useState('desktop');
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [code, setCode] = useState('');
+ const { isDesignModeEnabled } = useDesignModeStore();
+
+ // 组件属性更新处理
+ const handleComponentUpdate = useCallback((newProps: any) => {
+ setSelectedComponent((prev: any) => ({
+ ...prev,
+ props: newProps
+ }));
+ }, []);
+
+ // 开始/停止播放状态
+ const togglePlayState = () => {
+ setIsPlaying(!isPlaying);
+ };
+
+ // 生成代码
+ const generateCode = () => {
+ const propsString = Object.entries(selectedComponent.props)
+ .map(([key, value]) => `${key}="${value}"`)
+ .join(' ');
+
+ const codeString = `<${selectedComponent.id}${propsString ? ' ' + propsString : ''} />`;
+ setCode(codeString);
+ setEditMode('code');
+ };
+
+ // 复制代码
+ const copyCode = async () => {
+ try {
+ await navigator.clipboard.writeText(code);
+ // 可以添加成功提示
+ } catch (error) {
+ console.error('Failed to copy code:', error);
+ }
+ };
+
+ return (
+
+ {/* 编辑器工具栏 */}
+
+
+ {/* 左侧:组件库和模式切换 */}
+
+ {/* 组件库选择 */}
+
+
+
+
+
+ {/* 模式切换 */}
+
+ {(['design', 'code', 'preview'] as EditMode[]).map((mode) => (
+ setEditMode(mode)}
+ className={`
+ px-3 py-1.5 text-sm transition-colors flex items-center space-x-1
+ ${editMode === mode
+ ? 'bg-primary-500 text-white'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ }
+ `}
+ data-component="ModeButton"
+ data-element="mode-button"
+ data-mode={mode}
+ >
+ {mode === 'design' && }
+ {mode === 'code' && }
+ {mode === 'preview' && }
+ {mode}
+
+ ))}
+
+
+
+ {/* 右侧:操作按钮 */}
+
+ {/* 设备切换 */}
+ {editMode === 'preview' && (
+
+ {[
+ { type: 'desktop', icon: Monitor, label: 'Desktop' },
+ { type: 'tablet', icon: Tablet, label: 'Tablet' },
+ { type: 'mobile', icon: Smartphone, label: 'Mobile' }
+ ].map(({ type, icon: Icon, label }) => (
+ setDeviceType(type as DeviceType)}
+ className={`
+ px-3 py-1.5 text-sm transition-colors flex items-center space-x-1
+ ${deviceType === type
+ ? 'bg-primary-500 text-white'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ }
+ `}
+ data-component="DeviceButton"
+ data-element="device-button"
+ data-device={type}
+ title={label}
+ >
+
+
+ ))}
+
+ )}
+
+ {/* 播放按钮 */}
+ {selectedComponent.props.hasOwnProperty('loading') && (
+
+ {isPlaying ? : }
+
+ )}
+
+ {/* 生成代码 */}
+
+
+ Generate Code
+
+
+ {/* 保存 */}
+
+
+
+
+
+
+
+ {/* 编辑器主要内容 */}
+
+ {/* 组件库面板 */}
+
+
+
+ Component Library
+
+
+
+ {COMPONENT_LIBRARY.map((component) => (
+
setSelectedComponent(component)}
+ className={`
+ w-full text-left p-3 rounded-lg border transition-colors
+ ${selectedComponent.id === component.id
+ ? 'bg-primary-50 dark:bg-primary-900/20 border-primary-200 dark:border-primary-800'
+ : 'bg-white dark:bg-neutral-900 border-neutral-200 dark:border-neutral-700 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ }
+ `}
+ data-component="LibraryItem"
+ data-element="library-item"
+ data-component-id={component.id}
+ >
+
+
{component.icon}
+
+
+ {component.name}
+
+
+ {component.category}
+
+
+
+
+ ))}
+
+
+
+
+ {/* 主编辑区域 */}
+
+
+ {editMode === 'design' && (
+
+ {/* 设计画布 */}
+
+
+ {/* 属性面板 */}
+
+
+ )}
+
+ {editMode === 'code' && (
+
+ {/* 代码编辑器 */}
+
+
+
Generated Code
+
+ Copy
+
+
+
+
+ {code || '// Click "Generate Code" to create component code'}
+
+
+
+
+ {/* 代码预览 */}
+
+
+
+
+ )}
+
+ {editMode === 'preview' && (
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default ComponentEditor;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/Dashboard.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/Dashboard.tsx
new file mode 100644
index 00000000..58b49602
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/Dashboard.tsx
@@ -0,0 +1,527 @@
+/**
+ * @fileoverview 仪表盘页面组件
+ * @description 设计系统的概览仪表盘,展示系统状态、统计信息和快速入口
+ */
+
+import React, { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import {
+ Palette,
+ Type,
+ Layout,
+ Zap,
+ ArrowRight,
+ TrendingUp,
+ Users,
+ Activity,
+ CheckCircle,
+ AlertCircle,
+ Clock
+} from 'lucide-react';
+import { Card, CardBody, CardHeader, CardGroup } from '../components/ui/Card';
+import { Button } from '../components/ui/Button';
+import { LoadingSpinner } from '../components/ui/LoadingSpinner';
+import { useDesignTokens, useColors, useTypography, useSpacing, useEvents } from '../store/design-system-store';
+
+// 简化统计卡片组件
+interface StatsCardProps {
+ title: string;
+ value: string | number;
+ icon: React.ReactNode;
+ trend?: {
+ value: number;
+ label: string;
+ };
+ color: 'primary' | 'secondary' | 'success' | 'warning' | 'error';
+}
+
+// 统计卡片组件
+const StatsCard: React.FC = ({ title, value, icon, trend, color }) => {
+ const colorClasses = {
+ primary: 'from-primary-500 to-primary-600',
+ secondary: 'from-secondary-500 to-secondary-600',
+ success: 'from-success-500 to-success-600',
+ warning: 'from-warning-500 to-warning-600',
+ error: 'from-error-500 to-error-600',
+ };
+
+ return (
+
+
+
+
+
+ {icon}
+
+
+ {trend && (
+
+
+
+ +{trend.value}%
+
+
+ )}
+
+
+
+
+ {value}
+
+
+ {title}
+
+ {trend && (
+
+ {trend.label}
+
+ )}
+
+
+
+
+ );
+};
+
+// 活动项组件
+interface ActivityItemProps {
+ type: 'token' | 'component' | 'library' | 'theme';
+ title: string;
+ description: string;
+ timestamp: string;
+ status: 'success' | 'pending' | 'error';
+}
+
+// 活动项组件
+const ActivityItem: React.FC = ({ type, title, description, timestamp, status }) => {
+ const getTypeIcon = () => {
+ switch (type) {
+ case 'token':
+ return ;
+ case 'component':
+ return ;
+ case 'library':
+ return ;
+ case 'theme':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getStatusColor = () => {
+ switch (status) {
+ case 'success':
+ return 'text-success-500';
+ case 'pending':
+ return 'text-warning-500';
+ case 'error':
+ return 'text-error-500';
+ default:
+ return 'text-neutral-500';
+ }
+ };
+
+ const getStatusIcon = () => {
+ switch (status) {
+ case 'success':
+ return ;
+ case 'pending':
+ return ;
+ case 'error':
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {getTypeIcon()}
+
+
+
+
+
+ {title}
+
+
+ {getStatusIcon()}
+
+
+
+ {description}
+
+
+ {timestamp}
+
+
+
+ );
+};
+
+/**
+ * 仪表盘主组件
+ */
+const Dashboard: React.FC = () => {
+ const [isLoading, setIsLoading] = useState(true);
+
+ // 获取设计系统数据
+ const colors = useColors();
+ const typography = useTypography();
+ const spacing = useSpacing();
+ const events = useEvents();
+
+ // 模拟数据加载
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setIsLoading(false);
+ }, 1000);
+
+ return () => clearTimeout(timer);
+ }, []);
+
+ // 统计数据
+ const stats = {
+ totalTokens: colors.length + typography.length + spacing.length,
+ totalColors: colors.length,
+ totalTypography: typography.length,
+ totalSpacing: spacing.length,
+ totalComponents: 24, // 模拟数据
+ totalLibraries: 3, // 模拟数据
+ designScore: 94, // 模拟数据
+ };
+
+ // 最近活动(基于真实事件)
+ const recentActivities = events.slice(0, 5).map((event, index) => ({
+ type: event.type.includes('token') ? 'token' : event.type.includes('component') ? 'component' : 'theme',
+ title: `System ${event.type}`,
+ description: `System ${event.type} was updated`,
+ timestamp: new Date(event.timestamp).toLocaleTimeString(),
+ status: 'success' as const
+ }));
+
+ // 如果加载中,显示加载状态
+ if (isLoading) {
+ return (
+
+
+
+ Loading dashboard...
+
+
+ );
+ }
+
+ return (
+
+ {/* 欢迎区域 */}
+
+
+
+
+ Welcome to Design System Studio
+
+
+ Manage your design tokens, components, and build consistent user interfaces.
+
+
+
+ Get Started
+
+
+
+ Learn More
+
+
+
+
+
+
+ {/* 统计卡片 */}
+
+
+ System Overview
+
+
+ 🎨}
+ trend={{ value: 12, label: 'from last month' }}
+ color="primary"
+ />
+ ✅}
+ trend={{ value: 5, label: 'improvement' }}
+ color="success"
+ />
+ 🧩}
+ trend={{ value: 8, label: 'new this month' }}
+ color="secondary"
+ />
+ 📚}
+ color="warning"
+ />
+
+
+
+ {/* 内容区域 */}
+
+ {/* 左侧列 */}
+
+ {/* 快速访问 */}
+
+
+
+
+
+
+
+
+
+
Design System
+
Manage tokens & themes
+
+
+
+
+
+
+
+
+
Component Editor
+
Build & customize
+
+
+
+
+
+
+
+
+
Live Preview
+
Real-time testing
+
+
+
+
+
+
+
+
+
Libraries
+
Component collections
+
+
+
+
+
+
+
+
+ {/* 令牌统计 */}
+
+
+
+
+
+
+
+
{stats.totalColors} tokens
+
+
+
+
+
{stats.totalTypography} tokens
+
+
+
+
+
{stats.totalSpacing} tokens
+
+
+
+
+
+
+
+ {/* 右侧列 */}
+
+ {/* 最近活动 */}
+
+
+
+
+
+ {recentActivities.length > 0 ? (
+ recentActivities.map((activity, index) => (
+
+ ))
+ ) : (
+
+ )}
+
+
+ {events.length > 5 && (
+
+
+ View All Activity
+
+
+
+ )}
+
+
+
+
+ {/* 系统状态 */}
+
+
+
+
+
+
+
+
+ Design Mode
+
+
Active
+
+
+
+
+
+ Token Sync
+
+
Synced
+
+
+
+
+
+ Component Library
+
+
Updated
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Dashboard;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/DesignSystem.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/DesignSystem.tsx
new file mode 100644
index 00000000..96c095ec
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/DesignSystem.tsx
@@ -0,0 +1,496 @@
+/**
+ * @fileoverview 设计系统管理页面
+ * @description 展示和管理设计系统的所有令牌,包括颜色、字体、间距等
+ */
+
+import React, { useState, useMemo } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import {
+ Palette,
+ Type,
+ Ruler,
+ Shadow,
+ Circle,
+ Zap,
+ Search,
+ Filter,
+ Grid,
+ List,
+ Download,
+ Upload,
+ Settings,
+ Plus,
+ Eye,
+ EyeOff
+} from 'lucide-react';
+import { Card, CardBody, CardHeader } from '../components/ui/Card';
+import { Button, IconButton } from '../components/ui/Button';
+import { TokenCard } from '../components/design-system/TokenCard';
+import { ColorPalette } from '../components/design-system/ColorPalette';
+import {
+ useColors,
+ useTypography,
+ useSpacing,
+ useShadows,
+ useBorderRadius,
+ useAnimations,
+ useSelectedToken,
+ useDesignSystemStore
+} from '../store/design-system-store';
+import { clsx } from 'clsx';
+
+// 令牌类型定义
+type TokenType = 'all' | 'color' | 'typography' | 'spacing' | 'shadow' | 'border-radius' | 'animation';
+
+// 令牌类别配置
+const TOKEN_CATEGORIES = {
+ all: { label: 'All Tokens', icon: Settings, color: 'neutral' },
+ color: { label: 'Colors', icon: Palette, color: 'primary' },
+ typography: { label: 'Typography', icon: Type, color: 'secondary' },
+ spacing: { label: 'Spacing', icon: Ruler, color: 'success' },
+ shadow: { label: 'Shadows', icon: Shadow, color: 'warning' },
+ 'border-radius': { label: 'Border Radius', icon: Circle, color: 'error' },
+ animation: { label: 'Animation', icon: Zap, color: 'info' },
+} as const;
+
+/**
+ * 设计系统主页面组件
+ */
+const DesignSystem: React.FC = () => {
+ const [activeCategory, setActiveCategory] = useState('all');
+ const [searchQuery, setSearchQuery] = useState('');
+ const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
+ const [showDeprecated, setShowDeprecated] = useState(true);
+ const [selectedToken, setSelectedToken] = useState(null);
+
+ // 获取所有令牌数据
+ const colors = useColors();
+ const typography = useTypography();
+ const spacing = useSpacing();
+ const shadows = useShadows();
+ const borderRadius = useBorderRadius();
+ const animations = useAnimations();
+
+ // 合并所有令牌
+ const allTokens = useMemo(() => {
+ return [
+ ...colors,
+ ...typography,
+ ...spacing,
+ ...shadows,
+ ...borderRadius,
+ ...animations,
+ ];
+ }, [colors, typography, spacing, shadows, borderRadius, animations]);
+
+ // 过滤令牌
+ const filteredTokens = useMemo(() => {
+ return allTokens.filter(token => {
+ // 类别过滤
+ const matchesCategory = activeCategory === 'all' || token.category === activeCategory;
+
+ // 搜索过滤
+ const matchesSearch = searchQuery === '' ||
+ token.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ token.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ token.value.toString().toLowerCase().includes(searchQuery.toLowerCase());
+
+ // 废弃状态过滤
+ const matchesDeprecated = showDeprecated || !token.deprecated;
+
+ return matchesCategory && matchesSearch && matchesDeprecated;
+ });
+ }, [allTokens, activeCategory, searchQuery, showDeprecated]);
+
+ // 渲染特定类别的内容
+ const renderCategoryContent = () => {
+ switch (activeCategory) {
+ case 'color':
+ return (
+
+ );
+
+ case 'typography':
+ return (
+
+ {typography.map((token, index) => (
+
+
+
+ ))}
+
+ );
+
+ case 'spacing':
+ return (
+
+ {spacing.map((token, index) => (
+
+
+
+ ))}
+
+ );
+
+ case 'shadow':
+ return (
+
+ {shadows.map((token, index) => (
+
+
+
+ ))}
+
+ );
+
+ case 'border-radius':
+ return (
+
+ {borderRadius.map((token, index) => (
+
+
+
+ ))}
+
+ );
+
+ case 'animation':
+ return (
+
+ {animations.map((token, index) => (
+
+
+
+ ))}
+
+ );
+
+ default:
+ return (
+
+
+ {filteredTokens.map((token, index) => (
+
+
+
+ ))}
+
+
+ );
+ }
+ };
+
+ // 获取类别统计
+ const getCategoryStats = (type: TokenType) => {
+ switch (type) {
+ case 'color': return colors.length;
+ case 'typography': return typography.length;
+ case 'spacing': return spacing.length;
+ case 'shadow': return shadows.length;
+ case 'border-radius': return borderRadius.length;
+ case 'animation': return animations.length;
+ default: return allTokens.length;
+ }
+ };
+
+ return (
+
+ {/* 页面头部 */}
+
+
+
+
+ Design System
+
+
+ Manage your design tokens, typography, colors, and component guidelines
+
+
+
+
+ }
+ data-component="Button"
+ data-element="import-button"
+ data-action="import-tokens"
+ >
+ Import
+
+ }
+ data-component="Button"
+ data-element="export-button"
+ data-action="export-tokens"
+ >
+ Export
+
+ }
+ data-component="Button"
+ data-element="add-token-button"
+ data-action="add-token"
+ >
+ Add Token
+
+
+
+
+
+ {/* 控制面板 */}
+
+
+
+
+ {/* 搜索框 */}
+
+
+
+ setSearchQuery(e.target.value)}
+ className="pl-10 pr-4 py-2 w-full border border-neutral-300 dark:border-neutral-600 rounded-md bg-white dark:bg-neutral-800 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
+ data-component="Input"
+ data-element="search-input"
+ data-placeholder="search-tokens"
+ />
+
+
+
+ {/* 视图模式切换 */}
+
+
View:
+
+ setViewMode('grid')}
+ className={clsx(
+ 'px-3 py-1.5 text-sm transition-colors',
+ viewMode === 'grid'
+ ? 'bg-primary-500 text-white'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ )}
+ data-component="ToggleButton"
+ data-element="grid-view-button"
+ data-action="set-view-grid"
+ >
+
+
+ setViewMode('list')}
+ className={clsx(
+ 'px-3 py-1.5 text-sm transition-colors',
+ viewMode === 'list'
+ ? 'bg-primary-500 text-white'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ )}
+ data-component="ToggleButton"
+ data-element="list-view-button"
+ data-action="set-view-list"
+ >
+
+
+
+
+
+ {/* 废弃令牌切换 */}
+
+ setShowDeprecated(!showDeprecated)}
+ size="sm"
+ data-component="IconButton"
+ data-element="deprecated-toggle"
+ data-action="toggle-deprecated"
+ >
+ {showDeprecated ? : }
+
+
+ Show deprecated
+
+
+
+
+
+
+
+ {/* 类别选择器 */}
+
+
+ {Object.entries(TOKEN_CATEGORIES).map(([key, config]) => {
+ const Icon = config.icon;
+ const count = getCategoryStats(key as TokenType);
+ const isActive = activeCategory === key;
+
+ return (
+ setActiveCategory(key as TokenType)}
+ className={clsx(
+ 'flex items-center space-x-2 px-4 py-2 rounded-lg border transition-all duration-200',
+ isActive
+ ? 'bg-primary-500 text-white border-primary-500 shadow-md'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 border-neutral-300 dark:border-neutral-600 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ )}
+ data-component="CategoryTab"
+ data-element="category-tab"
+ data-category={key}
+ data-active={isActive ? 'true' : 'false'}
+ >
+
+ {config.label}
+
+ {count}
+
+
+ );
+ })}
+
+
+
+ {/* 主要内容区域 */}
+
+ {/* 空状态 */}
+ {filteredTokens.length === 0 && activeCategory !== 'color' ? (
+
+
+
+
+
+
+ No tokens found
+
+
+ Try adjusting your search criteria or browse different categories.
+
+ {
+ setSearchQuery('');
+ setActiveCategory('all');
+ }}
+ data-component="Button"
+ data-element="clear-filters-button"
+ data-action="clear-filters"
+ >
+ Clear Filters
+
+
+
+ ) : (
+
+
+ {renderCategoryContent()}
+
+
+ )}
+
+
+ {/* 统计信息 */}
+
+
+ {Object.entries(TOKEN_CATEGORIES).slice(1).map(([key, config]) => {
+ const Icon = config.icon;
+ const count = getCategoryStats(key as TokenType);
+
+ return (
+
+
+
+ {count}
+
+
+ {config.label}
+
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default DesignSystem;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/LivePreview.tsx b/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/LivePreview.tsx
new file mode 100644
index 00000000..3a0f6e95
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/pages/LivePreview.tsx
@@ -0,0 +1,566 @@
+/**
+ * @fileoverview 实时预览页面
+ * @description 展示组件的实时预览效果,支持不同设备和主题切换
+ */
+
+import React, { useState, useCallback, useRef } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import {
+ RefreshCw,
+ Maximize,
+ Smartphone,
+ Tablet,
+ Monitor,
+ Moon,
+ Sun,
+ Settings,
+ Eye,
+ EyeOff,
+ Play,
+ Pause,
+ Download,
+ Share2
+} from 'lucide-react';
+import { Card, CardBody, CardHeader } from '../components/ui/Card';
+import { Button, IconButton } from '../components/ui/Button';
+import { LoadingSpinner } from '../components/ui/LoadingSpinner';
+import { useDesignModeStore } from '../main';
+
+// 设备类型
+type DeviceType = 'desktop' | 'tablet' | 'mobile';
+
+// 主题类型
+type ThemeType = 'light' | 'dark' | 'system';
+
+// 预览模式
+type PreviewMode = 'component' | 'page' | 'interaction';
+
+// 示例组件数据
+const PREVIEW_COMPONENTS = [
+ {
+ id: 'hero-section',
+ name: 'Hero Section',
+ description: 'Landing page hero with call-to-action',
+ category: 'Layout',
+ component: (
+
+
Design System Studio
+
+ Build consistent and beautiful user interfaces with our comprehensive design system toolkit.
+
+
+
+ Get Started
+
+
+ Learn More
+
+
+
+ )
+ },
+ {
+ id: 'feature-grid',
+ name: 'Feature Grid',
+ description: 'Grid of features with icons and descriptions',
+ category: 'Content',
+ component: (
+
+
+
Powerful Features
+
+ Everything you need to build and maintain a consistent design system.
+
+
+
+ {[
+ { icon: '🎨', title: 'Design Tokens', desc: 'Centralized design decisions' },
+ { icon: '🔧', title: 'Component Library', desc: 'Reusable UI components' },
+ { icon: '📱', title: 'Responsive Design', desc: 'Mobile-first approach' },
+ { icon: '⚡', title: 'Fast Performance', desc: 'Optimized for speed' },
+ { icon: '🎯', title: 'Accessibility', desc: 'WCAG compliant design' },
+ { icon: '🔄', title: 'Real-time Sync', desc: 'Instant updates across team' }
+ ].map((feature, index) => (
+
+
{feature.icon}
+
{feature.title}
+
{feature.desc}
+
+ ))}
+
+
+ )
+ },
+ {
+ id: 'form-example',
+ name: 'Contact Form',
+ description: 'Complete contact form with validation',
+ category: 'Form',
+ component: (
+
+
+
Contact Us
+
We'd love to hear from you. Send us a message!
+
+
+
+ )
+ }
+];
+
+// 设备预览组件
+const DevicePreview: React.FC<{
+ component: React.ReactNode;
+ deviceType: DeviceType;
+ theme: ThemeType;
+ isLoading?: boolean;
+}> = ({ component, deviceType, theme, isLoading = false }) => {
+ const getDeviceStyles = () => {
+ switch (deviceType) {
+ case 'mobile':
+ return {
+ width: '375px',
+ height: '667px',
+ maxWidth: '100%',
+ borderRadius: '25px',
+ border: '1px solid #e5e5e5'
+ };
+ case 'tablet':
+ return {
+ width: '768px',
+ height: '1024px',
+ maxWidth: '100%',
+ borderRadius: '15px',
+ border: '1px solid #e5e5e5'
+ };
+ default:
+ return {
+ width: '100%',
+ height: '600px',
+ borderRadius: '8px',
+ border: '1px solid #e5e5e5'
+ };
+ }
+ };
+
+ const deviceStyles = getDeviceStyles();
+
+ return (
+
+
+ {isLoading ? (
+
+
+
+ ) : (
+
+ {component}
+
+ )}
+
+
+ );
+};
+
+/**
+ * 实时预览主页面
+ */
+const LivePreview: React.FC = () => {
+ const [selectedComponent, setSelectedComponent] = useState(PREVIEW_COMPONENTS[0]);
+ const [deviceType, setDeviceType] = useState('desktop');
+ const [theme, setTheme] = useState('light');
+ const [previewMode, setPreviewMode] = useState('component');
+ const [isFullscreen, setIsFullscreen] = useState(false);
+ const [isAutoRefresh, setIsAutoRefresh] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [lastUpdate, setLastUpdate] = useState(new Date());
+
+ const { isDesignModeEnabled } = useDesignModeStore();
+ const previewRef = useRef(null);
+
+ // 刷新预览
+ const refreshPreview = useCallback(async () => {
+ setIsLoading(true);
+ // 模拟加载延迟
+ await new Promise(resolve => setTimeout(resolve, 500));
+ setIsLoading(false);
+ setLastUpdate(new Date());
+ }, []);
+
+ // 切换全屏
+ const toggleFullscreen = useCallback(() => {
+ if (!document.fullscreenElement) {
+ previewRef.current?.requestFullscreen();
+ setIsFullscreen(true);
+ } else {
+ document.exitFullscreen();
+ setIsFullscreen(false);
+ }
+ }, []);
+
+ // 应用主题
+ const applyTheme = useCallback((newTheme: ThemeType) => {
+ setTheme(newTheme);
+ const root = document.documentElement;
+
+ switch (newTheme) {
+ case 'dark':
+ root.classList.add('dark');
+ break;
+ case 'light':
+ root.classList.remove('dark');
+ break;
+ case 'system':
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ root.classList.toggle('dark', prefersDark);
+ break;
+ }
+ }, []);
+
+ // 初始化主题
+ React.useEffect(() => {
+ applyTheme(theme);
+ }, [theme, applyTheme]);
+
+ // 设备切换时刷新
+ React.useEffect(() => {
+ if (isAutoRefresh) {
+ refreshPreview();
+ }
+ }, [deviceType, selectedComponent, isAutoRefresh, refreshPreview]);
+
+ return (
+
+ {/* 预览控制栏 */}
+
+
+ {/* 左侧:组件选择和模式 */}
+
+ {/* 组件选择 */}
+
+
+
+
+
+ {/* 预览模式切换 */}
+
+ {[
+ { mode: 'component' as PreviewMode, icon: Settings, label: 'Component' },
+ { mode: 'page' as PreviewMode, icon: Monitor, label: 'Page' },
+ { mode: 'interaction' as PreviewMode, icon: Play, label: 'Interaction' }
+ ].map(({ mode, icon: Icon, label }) => (
+ setPreviewMode(mode)}
+ className={`
+ px-3 py-1.5 text-sm transition-colors flex items-center space-x-1
+ ${previewMode === mode
+ ? 'bg-primary-500 text-white'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ }
+ `}
+ data-component="ModeButton"
+ data-element="mode-button"
+ data-mode={mode}
+ >
+
+ {label}
+
+ ))}
+
+
+
+ {/* 右侧:控制按钮 */}
+
+ {/* 设备切换 */}
+
+ {[
+ { type: 'mobile' as DeviceType, icon: Smartphone, label: 'Mobile' },
+ { type: 'tablet' as DeviceType, icon: Tablet, label: 'Tablet' },
+ { type: 'desktop' as DeviceType, icon: Monitor, label: 'Desktop' }
+ ].map(({ type, icon: Icon, label }) => (
+ setDeviceType(type)}
+ className={`
+ px-3 py-1.5 text-sm transition-colors flex items-center space-x-1
+ ${deviceType === type
+ ? 'bg-primary-500 text-white'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ }
+ `}
+ data-component="DeviceButton"
+ data-element="device-button"
+ data-device={type}
+ title={label}
+ >
+
+
+ ))}
+
+
+ {/* 主题切换 */}
+
+ {[
+ { type: 'light' as ThemeType, icon: Sun, label: 'Light' },
+ { type: 'dark' as ThemeType, icon: Moon, label: 'Dark' },
+ { type: 'system' as ThemeType, icon: Settings, label: 'System' }
+ ].map(({ type, icon: Icon, label }) => (
+ applyTheme(type)}
+ className={`
+ px-3 py-1.5 text-sm transition-colors flex items-center space-x-1
+ ${theme === type
+ ? 'bg-primary-500 text-white'
+ : 'bg-white dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700'
+ }
+ `}
+ data-component="ThemeButton"
+ data-element="theme-button"
+ data-theme={type}
+ title={label}
+ >
+
+
+ ))}
+
+
+ {/* 自动刷新切换 */}
+
setIsAutoRefresh(!isAutoRefresh)}
+ title="Auto Refresh"
+ data-component="IconButton"
+ data-element="auto-refresh-toggle"
+ data-action="toggle-auto-refresh"
+ >
+ {isAutoRefresh ? : }
+
+
+ {/* 刷新按钮 */}
+
+
+
+
+ {/* 全屏按钮 */}
+
+
+
+
+
+
+
+ {/* 预览信息栏 */}
+
+
+
+
+ {selectedComponent.name} • {selectedComponent.category}
+
+
+ {deviceType.charAt(0).toUpperCase() + deviceType.slice(1)} • {theme.charAt(0).toUpperCase() + theme.slice(1)} Mode
+
+
+
+
+ Last updated: {lastUpdate.toLocaleTimeString()}
+
+ {isAutoRefresh && (
+
+ ● Auto-refresh ON
+
+ )}
+
+
+
+
+ {/* 主要预览区域 */}
+
+
+
+
+
+
+
+ {/* 覆盖层指示器 */}
+
+ {selectedComponent.description}
+
+
+
+ {/* 操作栏 */}
+
+
+
+ }
+ data-component="Button"
+ data-element="download-button"
+ data-action="download-preview"
+ >
+ Download
+
+ }
+ data-component="Button"
+ data-element="share-button"
+ data-action="share-preview"
+ >
+ Share
+
+
+
+
+
+ Preview Mode: {previewMode.charAt(0).toUpperCase() + previewMode.slice(1)}
+
+
+
+
+
+
+ );
+};
+
+export default LivePreview;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/store/design-system-store.ts b/qiming-vite-plugin-design-mode/examples/full-featured/src/store/design-system-store.ts
new file mode 100644
index 00000000..a48a8a63
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/store/design-system-store.ts
@@ -0,0 +1,599 @@
+/**
+ * @fileoverview 设计系统状态管理
+ * @description 使用 Zustand 管理设计系统的全局状态
+ */
+
+import { create } from 'zustand';
+import { subscribeWithSelector } from 'zustand/middleware';
+import { devtools } from 'zustand/middleware';
+import {
+ DesignSystemState,
+ DesignSystemAction,
+ DesignToken,
+ ComponentVariant,
+ ComponentLibrary,
+ DesignSystemConfig,
+ DesignSystemEvent
+} from '../types/design-system';
+
+/**
+ * 设计系统默认配置
+ */
+const defaultDesignSystemConfig: DesignSystemConfig = {
+ name: 'Full Featured Design System',
+ version: '1.0.0',
+ description: 'A comprehensive design system with tokens, components, and utilities',
+ author: 'XAGI Team',
+ license: 'MIT',
+ tokens: {
+ colors: [
+ {
+ name: 'primary.50',
+ value: '#eff6ff',
+ category: 'color',
+ description: 'Lightest primary color',
+ palette: 'primary',
+ scale: '50',
+ tags: ['primary', 'light', 'background']
+ },
+ {
+ name: 'primary.500',
+ value: '#3b82f6',
+ category: 'color',
+ description: 'Main primary color',
+ palette: 'primary',
+ scale: '500',
+ tags: ['primary', 'main', 'interactive']
+ },
+ {
+ name: 'primary.900',
+ value: '#1e3a8a',
+ category: 'color',
+ description: 'Darkest primary color',
+ palette: 'primary',
+ scale: '900',
+ tags: ['primary', 'dark', 'text']
+ },
+ {
+ name: 'neutral.50',
+ value: '#fafafa',
+ category: 'color',
+ description: 'Lightest neutral color',
+ palette: 'neutral',
+ scale: '50',
+ tags: ['neutral', 'light', 'background']
+ },
+ {
+ name: 'neutral.500',
+ value: '#737373',
+ category: 'color',
+ description: 'Main neutral color',
+ palette: 'neutral',
+ scale: '500',
+ tags: ['neutral', 'main', 'text']
+ },
+ {
+ name: 'neutral.900',
+ value: '#171717',
+ category: 'color',
+ description: 'Darkest neutral color',
+ palette: 'neutral',
+ scale: '900',
+ tags: ['neutral', 'dark', 'text']
+ }
+ ],
+ typography: [
+ {
+ name: 'font-family.sans',
+ value: {
+ fontFamily: 'Inter, ui-sans-serif, system-ui, sans-serif',
+ },
+ category: 'typography',
+ description: 'Default sans-serif font family',
+ type: 'body',
+ tags: ['typography', 'font', 'sans-serif']
+ },
+ {
+ name: 'font-family.mono',
+ value: {
+ fontFamily: 'JetBrains Mono, ui-monospace, monospace',
+ },
+ category: 'typography',
+ description: 'Default monospace font family',
+ type: 'code',
+ tags: ['typography', 'font', 'monospace']
+ },
+ {
+ name: 'font-size.base',
+ value: {
+ fontSize: '1rem',
+ lineHeight: '1.5',
+ },
+ category: 'typography',
+ description: 'Base font size',
+ type: 'body',
+ scale: 'base',
+ tags: ['typography', 'font-size', 'base']
+ },
+ {
+ name: 'font-size.lg',
+ value: {
+ fontSize: '1.125rem',
+ lineHeight: '1.75',
+ },
+ category: 'typography',
+ description: 'Large font size',
+ type: 'body',
+ scale: 'lg',
+ tags: ['typography', 'font-size', 'large']
+ }
+ ],
+ spacing: [
+ {
+ name: 'spacing.0',
+ value: '0',
+ category: 'spacing',
+ description: 'No spacing',
+ scale: '0',
+ tags: ['spacing', 'zero']
+ },
+ {
+ name: 'spacing.1',
+ value: '0.25rem',
+ category: 'spacing',
+ description: 'Extra small spacing',
+ scale: '1',
+ tags: ['spacing', 'extra-small']
+ },
+ {
+ name: 'spacing.4',
+ value: '1rem',
+ category: 'spacing',
+ description: 'Medium spacing',
+ scale: '4',
+ tags: ['spacing', 'medium']
+ },
+ {
+ name: 'spacing.8',
+ value: '2rem',
+ category: 'spacing',
+ description: 'Large spacing',
+ scale: '8',
+ tags: ['spacing', 'large']
+ }
+ ],
+ shadows: [
+ {
+ name: 'shadow.sm',
+ value: {
+ offsetX: '0',
+ offsetY: '1px',
+ blurRadius: '2px',
+ spreadRadius: '0',
+ color: 'rgb(0 0 0 / 0.05)',
+ },
+ category: 'shadow',
+ description: 'Small shadow',
+ elevation: 1,
+ tags: ['shadow', 'small', 'subtle']
+ },
+ {
+ name: 'shadow.md',
+ value: {
+ offsetX: '0',
+ offsetY: '4px',
+ blurRadius: '6px',
+ spreadRadius: '-1px',
+ color: 'rgb(0 0 0 / 0.1)',
+ },
+ category: 'shadow',
+ description: 'Medium shadow',
+ elevation: 2,
+ tags: ['shadow', 'medium', 'elevated']
+ },
+ {
+ name: 'shadow.lg',
+ value: {
+ offsetX: '0',
+ offsetY: '10px',
+ blurRadius: '15px',
+ spreadRadius: '-3px',
+ color: 'rgb(0 0 0 / 0.1)',
+ },
+ category: 'shadow',
+ description: 'Large shadow',
+ elevation: 3,
+ tags: ['shadow', 'large', 'prominent']
+ }
+ ],
+ borderRadius: [
+ {
+ name: 'radius.sm',
+ value: '0.125rem',
+ category: 'border-radius',
+ description: 'Small border radius',
+ shape: 'small',
+ tags: ['border-radius', 'small', 'subtle']
+ },
+ {
+ name: 'radius.md',
+ value: '0.375rem',
+ category: 'border-radius',
+ description: 'Medium border radius',
+ shape: 'medium',
+ tags: ['border-radius', 'medium', 'standard']
+ },
+ {
+ name: 'radius.lg',
+ value: '0.5rem',
+ category: 'border-radius',
+ description: 'Large border radius',
+ shape: 'large',
+ tags: ['border-radius', 'large', 'prominent']
+ },
+ {
+ name: 'radius.full',
+ value: '9999px',
+ category: 'border-radius',
+ description: 'Full border radius',
+ shape: 'full',
+ tags: ['border-radius', 'full', 'circular']
+ }
+ ],
+ animations: [
+ {
+ name: 'duration.fast',
+ value: {
+ duration: '150ms',
+ timingFunction: 'ease-in-out',
+ },
+ category: 'animation',
+ description: 'Fast animation duration',
+ type: 'transition',
+ tags: ['animation', 'duration', 'fast']
+ },
+ {
+ name: 'duration.medium',
+ value: {
+ duration: '300ms',
+ timingFunction: 'ease-in-out',
+ },
+ category: 'animation',
+ description: 'Medium animation duration',
+ type: 'transition',
+ tags: ['animation', 'duration', 'medium']
+ },
+ {
+ name: 'duration.slow',
+ value: {
+ duration: '500ms',
+ timingFunction: 'ease-in-out',
+ },
+ category: 'animation',
+ description: 'Slow animation duration',
+ type: 'transition',
+ tags: ['animation', 'duration', 'slow']
+ }
+ ]
+ },
+ meta: {
+ created: new Date().toISOString(),
+ updated: new Date().toISOString(),
+ tags: ['design-system', 'tokens', 'components'],
+ category: 'light'
+ }
+};
+
+/**
+ * 设计系统状态管理 Store
+ */
+const useDesignSystemStore = create;
+ events: DesignSystemEvent[];
+ addEvent: (event: Omit) => void;
+ clearEvents: () => void;
+}>()(
+ devtools(
+ subscribeWithSelector((set, get) => ({
+ // 初始状态
+ config: defaultDesignSystemConfig,
+ libraries: [],
+ selectedToken: null,
+ selectedComponent: null,
+ selectedLibrary: null,
+ searchQuery: '',
+ filterBy: {
+ category: null,
+ status: null,
+ tags: []
+ },
+ viewMode: 'grid',
+ theme: 'light',
+ isLoading: false,
+ error: null,
+
+ // 事件历史
+ events: [],
+
+ // 分发器函数
+ dispatch: (action: DesignSystemAction) => {
+ set((state) => {
+ const newState = reducer(state, action);
+
+ // 添加事件
+ if (action.type !== 'RESET_STATE') {
+ get().addEvent({
+ type: getActionType(action),
+ payload: action.payload,
+ });
+ }
+
+ return newState;
+ });
+ },
+
+ // 事件管理
+ addEvent: (event: Omit) => {
+ set((state) => ({
+ events: [
+ {
+ ...event,
+ timestamp: new Date().toISOString()
+ },
+ ...state.events.slice(0, 99) // 保留最近100个事件
+ ]
+ }));
+ },
+
+ clearEvents: () => {
+ set({ events: [] });
+ },
+
+ // 便捷操作方法
+ setConfig: (config: DesignSystemConfig) => {
+ get().dispatch({ type: 'SET_CONFIG', payload: config });
+ },
+
+ setSelectedToken: (token: DesignToken | null) => {
+ get().dispatch({ type: 'SET_SELECTED_TOKEN', payload: token });
+ },
+
+ setSelectedComponent: (component: ComponentVariant | null) => {
+ get().dispatch({ type: 'SET_SELECTED_COMPONENT', payload: component });
+ },
+
+ setSearchQuery: (query: string) => {
+ get().dispatch({ type: 'SET_SEARCH_QUERY', payload: query });
+ },
+
+ setFilter: (filter: Partial) => {
+ get().dispatch({ type: 'SET_FILTER', payload: filter });
+ },
+
+ resetState: () => {
+ get().dispatch({ type: 'RESET_STATE' });
+ },
+ })),
+ {
+ name: 'design-system-store',
+ }
+ )
+);
+
+/**
+ * Reducer 函数处理状态更新
+ */
+function reducer(state: DesignSystemState, action: DesignSystemAction): DesignSystemState {
+ switch (action.type) {
+ case 'SET_CONFIG':
+ return {
+ ...state,
+ config: action.payload,
+ error: null,
+ };
+
+ case 'SET_LIBRARIES':
+ return {
+ ...state,
+ libraries: action.payload,
+ error: null,
+ };
+
+ case 'ADD_LIBRARY':
+ return {
+ ...state,
+ libraries: [...state.libraries, action.payload],
+ error: null,
+ };
+
+ case 'UPDATE_LIBRARY':
+ return {
+ ...state,
+ libraries: state.libraries.map(lib =>
+ lib.id === action.payload.id ? action.payload : lib
+ ),
+ error: null,
+ };
+
+ case 'DELETE_LIBRARY':
+ return {
+ ...state,
+ libraries: state.libraries.filter(lib => lib.id !== action.payload),
+ error: null,
+ };
+
+ case 'SET_SELECTED_TOKEN':
+ return {
+ ...state,
+ selectedToken: action.payload,
+ };
+
+ case 'SET_SELECTED_COMPONENT':
+ return {
+ ...state,
+ selectedComponent: action.payload,
+ };
+
+ case 'SET_SELECTED_LIBRARY':
+ return {
+ ...state,
+ selectedLibrary: action.payload,
+ };
+
+ case 'SET_SEARCH_QUERY':
+ return {
+ ...state,
+ searchQuery: action.payload,
+ };
+
+ case 'SET_FILTER':
+ return {
+ ...state,
+ filterBy: {
+ ...state.filterBy,
+ ...action.payload,
+ },
+ };
+
+ case 'SET_VIEW_MODE':
+ return {
+ ...state,
+ viewMode: action.payload,
+ };
+
+ case 'SET_THEME':
+ return {
+ ...state,
+ theme: action.payload,
+ };
+
+ case 'SET_LOADING':
+ return {
+ ...state,
+ isLoading: action.payload,
+ };
+
+ case 'SET_ERROR':
+ return {
+ ...state,
+ error: action.payload,
+ isLoading: false,
+ };
+
+ case 'RESET_STATE':
+ return {
+ ...state,
+ selectedToken: null,
+ selectedComponent: null,
+ selectedLibrary: null,
+ searchQuery: '',
+ filterBy: {
+ category: null,
+ status: null,
+ tags: [],
+ },
+ error: null,
+ isLoading: false,
+ };
+
+ default:
+ return state;
+ }
+}
+
+/**
+ * 获取操作类型
+ */
+function getActionType(action: DesignSystemAction): DesignSystemEvent['type'] {
+ switch (action.type) {
+ case 'SET_CONFIG':
+ return 'library_imported';
+ case 'ADD_LIBRARY':
+ return 'library_imported';
+ case 'UPDATE_LIBRARY':
+ return 'component_updated';
+ case 'DELETE_LIBRARY':
+ return 'component_deleted';
+ case 'SET_SELECTED_TOKEN':
+ return action.payload ? 'token_created' : 'component_deleted';
+ case 'SET_SELECTED_COMPONENT':
+ return action.payload ? 'component_created' : 'component_deleted';
+ case 'SET_THEME':
+ return 'theme_switched';
+ default:
+ return 'token_updated';
+ }
+}
+
+/**
+ * 选择器函数
+ */
+export const useDesignTokens = () => {
+ const config = useDesignSystemStore(state => state.config);
+ return config?.tokens || { colors: [], typography: [], spacing: [], shadows: [], borderRadius: [], animations: [] };
+};
+
+export const useColors = () => {
+ const tokens = useDesignTokens();
+ return tokens.colors || [];
+};
+
+export const useTypography = () => {
+ const tokens = useDesignTokens();
+ return tokens.typography || [];
+};
+
+export const useSpacing = () => {
+ const tokens = useDesignTokens();
+ return tokens.spacing || [];
+};
+
+export const useShadows = () => {
+ const tokens = useDesignTokens();
+ return tokens.shadows || [];
+};
+
+export const useBorderRadius = () => {
+ const tokens = useDesignTokens();
+ return tokens.borderRadius || [];
+};
+
+export const useAnimations = () => {
+ const tokens = useDesignTokens();
+ return tokens.animations || [];
+};
+
+export const useLibraries = () => {
+ return useDesignSystemStore(state => state.libraries);
+};
+
+export const useSelectedToken = () => {
+ return useDesignSystemStore(state => state.selectedToken);
+};
+
+export const useSelectedComponent = () => {
+ return useDesignSystemStore(state => state.selectedComponent);
+};
+
+export const useSearchAndFilters = () => {
+ return useDesignSystemStore(state => ({
+ searchQuery: state.searchQuery,
+ filterBy: state.filterBy,
+ viewMode: state.viewMode,
+ theme: state.theme,
+ }));
+};
+
+export const useLoadingAndError = () => {
+ return useDesignSystemStore(state => ({
+ isLoading: state.isLoading,
+ error: state.error,
+ }));
+};
+
+export const useEvents = () => {
+ return useDesignSystemStore(state => state.events);
+};
+
+// 导出 Store 和选择器
+export default useDesignSystemStore;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/types/design-system.ts b/qiming-vite-plugin-design-mode/examples/full-featured/src/types/design-system.ts
new file mode 100644
index 00000000..e8504470
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/types/design-system.ts
@@ -0,0 +1,364 @@
+/**
+ * @fileoverview 设计系统类型定义
+ * @description 定义设计系统相关的TypeScript类型和接口
+ */
+
+// 设计令牌基础类型
+export interface DesignToken {
+ name: string;
+ value: string | number | Record; // 允许复杂对象
+ category: 'color' | 'typography' | 'spacing' | 'shadow' | 'border-radius' | 'z-index' | 'animation';
+ description?: string;
+ deprecated?: boolean;
+ tags?: string[];
+}
+
+// 色彩令牌
+export interface ColorToken extends DesignToken {
+ category: 'color';
+ value: string;
+ hue?: number;
+ saturation?: number;
+ lightness?: number;
+ alpha?: number;
+ palette?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'neutral';
+ scale?: string; // e.g., '50', '100', '500', '900'
+}
+
+// 字体令牌
+export interface TypographyToken extends DesignToken {
+ category: 'typography';
+ value: {
+ fontFamily?: string;
+ fontSize?: string;
+ fontWeight?: number | string;
+ lineHeight?: string | number;
+ letterSpacing?: string;
+ textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize';
+ };
+ type?: 'heading' | 'body' | 'caption' | 'code' | 'display';
+ scale?: string;
+}
+
+// 间距令牌
+export interface SpacingToken extends DesignToken {
+ category: 'spacing';
+ value: string | number;
+ scale?: string;
+ unit?: 'px' | 'rem' | 'em' | 'vh' | 'vw' | '%';
+}
+
+// 阴影令牌
+export interface ShadowToken extends DesignToken {
+ category: 'shadow';
+ value: {
+ offsetX?: string | number;
+ offsetY?: string | number;
+ blurRadius?: string | number;
+ spreadRadius?: string | number;
+ color: string;
+ inset?: boolean;
+ };
+ elevation?: number;
+}
+
+// 圆角令牌
+export interface BorderRadiusToken extends DesignToken {
+ category: 'border-radius';
+ value: string | number;
+ unit?: 'px' | 'rem' | 'em' | '%';
+ shape?: 'none' | 'small' | 'medium' | 'large' | 'full';
+}
+
+// 动画令牌
+export interface AnimationToken extends DesignToken {
+ category: 'animation';
+ value: {
+ duration?: string;
+ timingFunction?: string;
+ delay?: string;
+ iterationCount?: number | 'infinite';
+ direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';
+ fillMode?: 'none' | 'forwards' | 'backwards' | 'both';
+ };
+ type?: 'transition' | 'animation' | 'transform';
+}
+
+// 设计系统配置
+export interface DesignSystemConfig {
+ name: string;
+ version: string;
+ description: string;
+ author: string;
+ license: string;
+ tokens: {
+ colors: ColorToken[];
+ typography: TypographyToken[];
+ spacing: SpacingToken[];
+ shadows: ShadowToken[];
+ borderRadius: BorderRadiusToken[];
+ animations: AnimationToken[];
+ };
+ meta: {
+ created: string;
+ updated: string;
+ tags: string[];
+ category: 'light' | 'dark' | 'auto';
+ };
+}
+
+// 组件变体
+export interface ComponentVariant {
+ id: string;
+ name: string;
+ description: string;
+ category: string;
+ props: Record;
+ styles: Record;
+ preview?: string;
+ code?: string;
+ usage?: string;
+ accessibility?: string[];
+ status: 'draft' | 'review' | 'approved' | 'deprecated';
+}
+
+// 组件库
+export interface ComponentLibrary {
+ id: string;
+ name: string;
+ version: string;
+ description: string;
+ author: string;
+ components: ComponentVariant[];
+ categories: string[];
+ tags: string[];
+ license: string;
+ dependencies?: Record;
+ peerDependencies?: Record;
+ devDependencies?: Record;
+}
+
+// 设计系统状态
+export interface DesignSystemState {
+ config: DesignSystemConfig | null;
+ libraries: ComponentLibrary[];
+ selectedToken: DesignToken | null;
+ selectedComponent: ComponentVariant | null;
+ selectedLibrary: ComponentLibrary | null;
+ searchQuery: string;
+ filterBy: {
+ category: string | null;
+ status: string | null;
+ tags: string[];
+ };
+ viewMode: 'grid' | 'list' | 'table';
+ theme: 'light' | 'dark' | 'auto';
+ isLoading: boolean;
+ error: string | null;
+}
+
+// 操作类型
+export type DesignSystemAction =
+ | { type: 'SET_CONFIG'; payload: DesignSystemConfig }
+ | { type: 'SET_LIBRARIES'; payload: ComponentLibrary[] }
+ | { type: 'ADD_LIBRARY'; payload: ComponentLibrary }
+ | { type: 'UPDATE_LIBRARY'; payload: ComponentLibrary }
+ | { type: 'DELETE_LIBRARY'; payload: string }
+ | { type: 'SET_SELECTED_TOKEN'; payload: DesignToken | null }
+ | { type: 'SET_SELECTED_COMPONENT'; payload: ComponentVariant | null }
+ | { type: 'SET_SELECTED_LIBRARY'; payload: ComponentLibrary | null }
+ | { type: 'SET_SEARCH_QUERY'; payload: string }
+ | { type: 'SET_FILTER'; payload: Partial }
+ | { type: 'SET_VIEW_MODE'; payload: 'grid' | 'list' | 'table' }
+ | { type: 'SET_THEME'; payload: 'light' | 'dark' | 'auto' }
+ | { type: 'SET_LOADING'; payload: boolean }
+ | { type: 'SET_ERROR'; payload: string | null }
+ | { type: 'RESET_STATE' };
+
+// 导出配置
+export interface ExportConfig {
+ format: 'json' | 'css' | 'scss' | 'tailwind' | 'typescript' | 'figma';
+ include: {
+ tokens: boolean;
+ components: boolean;
+ utilities: boolean;
+ comments: boolean;
+ };
+ output: {
+ fileName?: string;
+ directory?: string;
+ structure: 'flat' | 'nested';
+ };
+ options: {
+ prefix?: string;
+ suffix?: string;
+ transform?: (value: any, key: string) => any;
+ filter?: (token: DesignToken) => boolean;
+ };
+}
+
+// 导入配置
+export interface ImportConfig {
+ source: {
+ type: 'file' | 'url' | 'api' | 'clipboard';
+ path?: string;
+ url?: string;
+ api?: {
+ endpoint: string;
+ method: 'GET' | 'POST';
+ headers?: Record;
+ body?: any;
+ };
+ };
+ options: {
+ merge: boolean;
+ validate: boolean;
+ backup: boolean;
+ overwrite: boolean;
+ };
+}
+
+// 验证结果
+export interface ValidationResult {
+ isValid: boolean;
+ errors: ValidationError[];
+ warnings: ValidationWarning[];
+ suggestions: ValidationSuggestion[];
+}
+
+export interface ValidationError {
+ code: string;
+ message: string;
+ path: string;
+ severity: 'error' | 'critical';
+ fix?: string;
+}
+
+export interface ValidationWarning {
+ code: string;
+ message: string;
+ path: string;
+ severity: 'warning' | 'info';
+ suggestion?: string;
+}
+
+export interface ValidationSuggestion {
+ message: string;
+ action: string;
+ impact: 'low' | 'medium' | 'high';
+}
+
+// 设计系统分析
+export interface DesignSystemAnalytics {
+ overview: {
+ totalTokens: number;
+ totalComponents: number;
+ totalLibraries: number;
+ coverage: number;
+ };
+ usage: {
+ mostUsedTokens: { token: string; count: number }[];
+ mostUsedComponents: { component: string; count: number }[];
+ componentDistribution: { category: string; count: number }[];
+ };
+ quality: {
+ accessibilityScore: number;
+ consistencyScore: number;
+ documentationScore: number;
+ testCoverage: number;
+ };
+ performance: {
+ bundleSize: number;
+ loadTime: number;
+ renderTime: number;
+ };
+}
+
+// 主题配置
+export interface ThemeConfig {
+ id: string;
+ name: string;
+ displayName: string;
+ description: string;
+ type: 'light' | 'dark' | 'auto';
+ tokens: Partial;
+ overrides: Record;
+ meta: {
+ author: string;
+ version: string;
+ tags: string[];
+ category: 'default' | 'accessibility' | 'branded' | 'custom';
+ };
+}
+
+// 组件生成器配置
+export interface ComponentGeneratorConfig {
+ name: string;
+ template: string;
+ props: {
+ name: string;
+ type: string;
+ required: boolean;
+ default?: any;
+ description?: string;
+ }[];
+ styles: {
+ [key: string]: any;
+ };
+ variants: {
+ [key: string]: ComponentVariant;
+ };
+ accessibility: {
+ ariaRoles: string[];
+ ariaProperties: Record;
+ keyboardNavigation: string[];
+ screenReaderSupport: string[];
+ };
+}
+
+// 设计系统工具函数参数
+export interface ToolFunction {
+ id: string;
+ name: string;
+ description: string;
+ category: 'validation' | 'conversion' | 'generation' | 'analysis' | 'export';
+ parameters: {
+ name: string;
+ type: string;
+ required: boolean;
+ description: string;
+ default?: any;
+ }[];
+ returns: {
+ type: string;
+ description: string;
+ };
+ execute: (params: Record) => Promise;
+}
+
+// 设计系统事件
+export interface DesignSystemEvent {
+ type: 'token_created' | 'token_updated' | 'token_deleted' | 'component_created' | 'component_updated' | 'component_deleted' | 'theme_switched' | 'library_imported' | 'library_exported';
+ payload: any;
+ timestamp: string;
+ userId?: string;
+ sessionId?: string;
+}
+
+// 设计系统通知
+export interface DesignSystemNotification {
+ id: string;
+ type: 'info' | 'success' | 'warning' | 'error';
+ title: string;
+ message: string;
+ actions?: {
+ label: string;
+ action: string;
+ primary?: boolean;
+ }[];
+ timestamp: string;
+ read: boolean;
+ persistent?: boolean;
+}
+
+// 具体接口已经在上面定义了,这里不需要重复导出
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/src/utils/index.ts b/qiming-vite-plugin-design-mode/examples/full-featured/src/utils/index.ts
new file mode 100644
index 00000000..8b63a32b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/src/utils/index.ts
@@ -0,0 +1,395 @@
+/**
+ * @fileoverview 工具函数集合
+ * @description 提供项目中常用的工具函数
+ */
+
+import { clsx, type ClassValue } from 'clsx';
+
+/**
+ * 合并 CSS 类名
+ * @param inputs - CSS 类名数组
+ * @returns 合并后的 CSS 类名字符串
+ */
+export function cn(...inputs: ClassValue[]) {
+ return clsx(inputs);
+}
+
+/**
+ * 格式化日期
+ * @param date - 日期对象或字符串
+ * @param locale - 语言代码,默认 'zh-CN'
+ * @returns 格式化后的日期字符串
+ */
+export function formatDate(date: Date | string, locale: string = 'zh-CN'): string {
+ const dateObj = typeof date === 'string' ? new Date(date) : date;
+ return dateObj.toLocaleDateString(locale, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+}
+
+/**
+ * 格式化时间
+ * @param date - 日期对象或字符串
+ * @param locale - 语言代码,默认 'zh-CN'
+ * @returns 格式化后的时间字符串
+ */
+export function formatTime(date: Date | string, locale: string = 'zh-CN'): string {
+ const dateObj = typeof date === 'string' ? new Date(date) : date;
+ return dateObj.toLocaleTimeString(locale, {
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+}
+
+/**
+ * 格式化日期时间
+ * @param date - 日期对象或字符串
+ * @param locale - 语言代码,默认 'zh-CN'
+ * @returns 格式化后的日期时间字符串
+ */
+export function formatDateTime(
+ date: Date | string,
+ locale: string = 'zh-CN'
+): string {
+ const dateObj = typeof date === 'string' ? new Date(date) : date;
+ return `${formatDate(dateObj, locale)} ${formatTime(dateObj, locale)}`;
+}
+
+/**
+ * 生成随机 ID
+ * @param length - ID 长度,默认 8
+ * @returns 随机 ID 字符串
+ */
+export function generateId(length: number = 8): string {
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ let result = '';
+ for (let i = 0; i < length; i++) {
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
+ }
+ return result;
+}
+
+/**
+ * 防抖函数
+ * @param func - 要防抖的函数
+ * @param delay - 延迟时间(毫秒)
+ * @returns 防抖后的函数
+ */
+export function debounce any>(
+ func: T,
+ delay: number
+): (...args: Parameters) => void {
+ let timeoutId: NodeJS.Timeout;
+ return (...args: Parameters) => {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => func.apply(null, args), delay);
+ };
+}
+
+/**
+ * 节流函数
+ * @param func - 要节流的函数
+ * @param delay - 延迟时间(毫秒)
+ * @returns 节流后的函数
+ */
+export function throttle any>(
+ func: T,
+ delay: number
+): (...args: Parameters) => void {
+ let lastCall = 0;
+ return (...args: Parameters) => {
+ const now = new Date().getTime();
+ if (now - lastCall < delay) {
+ return;
+ }
+ lastCall = now;
+ return func.apply(null, args);
+ };
+}
+
+/**
+ * 深拷贝对象
+ * @param obj - 要拷贝的对象
+ * @returns 深拷贝后的对象
+ */
+export function deepClone(obj: T): T {
+ if (obj === null || typeof obj !== 'object') {
+ return obj;
+ }
+
+ if (obj instanceof Date) {
+ return new Date(obj.getTime()) as unknown as T;
+ }
+
+ if (obj instanceof Array) {
+ return obj.map(item => deepClone(item)) as unknown as T;
+ }
+
+ if (typeof obj === 'object') {
+ const clonedObj = {} as T;
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ clonedObj[key] = deepClone(obj[key]);
+ }
+ }
+ return clonedObj;
+ }
+
+ return obj;
+}
+
+/**
+ * 检查是否为移动设备
+ * @returns 是否为移动设备
+ */
+export function isMobile(): boolean {
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
+ navigator.userAgent
+ );
+}
+
+/**
+ * 检查是否为平板设备
+ * @returns 是否为平板设备
+ */
+export function isTablet(): boolean {
+ return /iPad|Android(?=.*\bTablet\b)|Windows(?=.*\bTouch\b)/i.test(
+ navigator.userAgent
+ );
+}
+
+/**
+ * 检查是否支持触摸
+ * @returns 是否支持触摸
+ */
+export function isTouchDevice(): boolean {
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
+}
+
+/**
+ * 获取文件扩展名
+ * @param filename - 文件名
+ * @returns 文件扩展名
+ */
+export function getFileExtension(filename: string): string {
+ return filename.slice(((filename.lastIndexOf('.') - 1) >>> 0) + 2);
+}
+
+/**
+ * 格式化文件大小
+ * @param bytes - 字节数
+ * @param decimals - 小数位数,默认 2
+ * @returns 格式化后的文件大小字符串
+ */
+export function formatFileSize(bytes: number, decimals: number = 2): string {
+ if (bytes === 0) return '0 Bytes';
+
+ const k = 1024;
+ const dm = decimals < 0 ? 0 : decimals;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+}
+
+/**
+ * 复制文本到剪贴板
+ * @param text - 要复制的文本
+ * @returns Promise 是否复制成功
+ */
+export async function copyToClipboard(text: string): Promise {
+ try {
+ if (navigator.clipboard && window.isSecureContext) {
+ await navigator.clipboard.writeText(text);
+ return true;
+ } else {
+ // 降级方案
+ const textArea = document.createElement('textarea');
+ textArea.value = text;
+ textArea.style.position = 'fixed';
+ textArea.style.left = '-999999px';
+ textArea.style.top = '-999999px';
+ document.body.appendChild(textArea);
+ textArea.focus();
+ textArea.select();
+ const result = document.execCommand('copy');
+ textArea.remove();
+ return result;
+ }
+ } catch (error) {
+ console.error('Failed to copy text:', error);
+ return false;
+ }
+}
+
+/**
+ * 下载文件
+ * @param content - 文件内容
+ * @param filename - 文件名
+ * @param mimeType - MIME 类型
+ */
+export function downloadFile(
+ content: string,
+ filename: string,
+ mimeType: string = 'text/plain'
+): void {
+ const blob = new Blob([content], { type: mimeType });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+}
+
+/**
+ * 获取 URL 参数
+ * @param key - 参数名
+ * @returns 参数值或 null
+ */
+export function getUrlParam(key: string): string | null {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.get(key);
+}
+
+/**
+ * 设置 URL 参数
+ * @param key - 参数名
+ * @param value - 参数值
+ */
+export function setUrlParam(key: string, value: string): void {
+ const url = new URL(window.location.href);
+ url.searchParams.set(key, value);
+ window.history.replaceState({}, '', url.toString());
+}
+
+/**
+ * 移除 URL 参数
+ * @param key - 参数名
+ */
+export function removeUrlParam(key: string): void {
+ const url = new URL(window.location.href);
+ url.searchParams.delete(key);
+ window.history.replaceState({}, '', url.toString());
+}
+
+/**
+ * 检查对象是否为空
+ * @param obj - 要检查的对象
+ * @returns 是否为空
+ */
+export function isEmpty(obj: any): boolean {
+ if (obj == null) return true;
+ if (Array.isArray(obj) || typeof obj === 'string') return obj.length === 0;
+ if (obj instanceof Date) return false;
+ return Object.keys(obj).length === 0;
+}
+
+/**
+ * 唯一数组
+ * @param array - 原数组
+ * @returns 去重后的数组
+ */
+export function uniqueArray(array: T[]): T[] {
+ return [...new Set(array)];
+}
+
+/**
+ * 数组分组
+ * @param array - 原数组
+ * @param size - 每组大小
+ * @returns 分组后的二维数组
+ */
+export function chunkArray(array: T[], size: number): T[][] {
+ const chunks: T[][] = [];
+ for (let i = 0; i < array.length; i += size) {
+ chunks.push(array.slice(i, i + size));
+ }
+ return chunks;
+}
+
+/**
+ * 对象数组根据某个属性分组
+ * @param array - 对象数组
+ * @param key - 分组键
+ * @returns 分组后的对象
+ */
+export function groupBy>(
+ array: T[],
+ key: keyof T
+): Record {
+ return array.reduce((groups, item) => {
+ const group = String(item[key]);
+ groups[group] = groups[group] || [];
+ groups[group].push(item);
+ return groups;
+ }, {} as Record);
+}
+
+/**
+ * 等待指定时间
+ * @param ms - 等待时间(毫秒)
+ * @returns Promise
+ */
+export function sleep(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+/**
+ * 重试函数
+ * @param fn - 要重试的函数
+ * @param retries - 重试次数
+ * @param delay - 重试间隔(毫秒)
+ * @returns Promise
+ */
+export async function retry(
+ fn: () => Promise,
+ retries: number = 3,
+ delay: number = 1000
+): Promise {
+ try {
+ return await fn();
+ } catch (error) {
+ if (retries > 0) {
+ await sleep(delay);
+ return retry(fn, retries - 1, delay);
+ }
+ throw error;
+ }
+}
+
+/**
+ * 获取随机颜色
+ * @returns 随机颜色十六进制字符串
+ */
+export function getRandomColor(): string {
+ return '#' + Math.floor(Math.random() * 16777215).toString(16);
+}
+
+/**
+ * 计算相对时间
+ * @param date - 日期
+ * @returns 相对时间字符串
+ */
+export function getRelativeTime(date: Date | string): string {
+ const now = new Date();
+ const target = typeof date === 'string' ? new Date(date) : date;
+ const diffInSeconds = Math.floor((now.getTime() - target.getTime()) / 1000);
+
+ if (diffInSeconds < 60) {
+ return '刚刚';
+ } else if (diffInSeconds < 3600) {
+ return `${Math.floor(diffInSeconds / 60)} 分钟前`;
+ } else if (diffInSeconds < 86400) {
+ return `${Math.floor(diffInSeconds / 3600)} 小时前`;
+ } else if (diffInSeconds < 2592000) {
+ return `${Math.floor(diffInSeconds / 86400)} 天前`;
+ } else {
+ return formatDate(target);
+ }
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/tailwind.config.js b/qiming-vite-plugin-design-mode/examples/full-featured/tailwind.config.js
new file mode 100644
index 00000000..b898356e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/tailwind.config.js
@@ -0,0 +1,139 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ // 设计系统色彩配置
+ colors: {
+ // 主色调 - 蓝色系
+ primary: {
+ 50: '#eff6ff',
+ 100: '#dbeafe',
+ 200: '#bfdbfe',
+ 300: '#93c5fd',
+ 400: '#60a5fa',
+ 500: '#3b82f6',
+ 600: '#2563eb',
+ 700: '#1d4ed8',
+ 800: '#1e40af',
+ 900: '#1e3a8a',
+ 950: '#172554',
+ },
+ // 次要色调 - 紫色系
+ secondary: {
+ 50: '#faf5ff',
+ 100: '#f3e8ff',
+ 200: '#e9d5ff',
+ 300: '#d8b4fe',
+ 400: '#c084fc',
+ 500: '#a855f7',
+ 600: '#9333ea',
+ 700: '#7c3aed',
+ 800: '#6b21a8',
+ 900: '#581c87',
+ 950: '#3b0764',
+ },
+ // 成功色调 - 绿色系
+ success: {
+ 50: '#f0fdf4',
+ 100: '#dcfce7',
+ 200: '#bbf7d0',
+ 300: '#86efac',
+ 400: '#4ade80',
+ 500: '#22c55e',
+ 600: '#16a34a',
+ 700: '#15803d',
+ 800: '#166534',
+ 900: '#14532d',
+ 950: '#052e16',
+ },
+ // 警告色调 - 黄色系
+ warning: {
+ 50: '#fefce8',
+ 100: '#fef9c3',
+ 200: '#fef08a',
+ 300: '#fde047',
+ 400: '#facc15',
+ 500: '#eab308',
+ 600: '#ca8a04',
+ 700: '#a16207',
+ 800: '#854d0e',
+ 900: '#713f12',
+ 950: '#422006',
+ },
+ // 错误色调 - 红色系
+ error: {
+ 50: '#fef2f2',
+ 100: '#fee2e2',
+ 200: '#fecaca',
+ 300: '#fca5a5',
+ 400: '#f87171',
+ 500: '#ef4444',
+ 600: '#dc2626',
+ 700: '#b91c1c',
+ 800: '#991b1b',
+ 900: '#7f1d1d',
+ 950: '#450a0a',
+ },
+ // 中性色调 - 灰色系
+ neutral: {
+ 50: '#fafafa',
+ 100: '#f5f5f5',
+ 200: '#e5e5e5',
+ 300: '#d4d4d4',
+ 400: '#a3a3a3',
+ 500: '#737373',
+ 600: '#525252',
+ 700: '#404040',
+ 800: '#262626',
+ 900: '#171717',
+ 950: '#0a0a0a',
+ },
+ },
+
+ // 字体配置
+ fontFamily: {
+ sans: [
+ 'Inter',
+ 'ui-sans-serif',
+ 'system-ui',
+ 'sans-serif'
+ ],
+ mono: [
+ 'JetBrains Mono',
+ 'ui-monospace',
+ 'monospace'
+ ],
+ },
+
+ // 间距配置
+ spacing: {
+ '18': '4.5rem',
+ '88': '22rem',
+ '128': '32rem',
+ '144': '36rem',
+ },
+
+ // 圆角配置
+ borderRadius: {
+ 'xs': '0.125rem',
+ 'sm': '0.25rem',
+ 'md': '0.375rem',
+ 'lg': '0.5rem',
+ 'xl': '0.75rem',
+ '2xl': '1rem',
+ '3xl': '1.5rem',
+ '4xl': '2rem',
+ },
+ },
+ },
+
+ // 插件配置 - 移除可能导致问题的插件
+ plugins: [],
+
+ // 暗色模式配置
+ darkMode: 'class',
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/tsconfig.json b/qiming-vite-plugin-design-mode/examples/full-featured/tsconfig.json
new file mode 100644
index 00000000..d5b45c32
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/tsconfig.json
@@ -0,0 +1,89 @@
+{
+ "compilerOptions": {
+ // 目标版本
+ "target": "ES2022",
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
+
+ // 模块系统
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ // 严格模式设置
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "exactOptionalPropertyTypes": true,
+
+ // 路径映射
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"],
+ "@components/*": ["./src/components/*"],
+ "@pages/*": ["./src/pages/*"],
+ "@hooks/*": ["./src/hooks/*"],
+ "@store/*": ["./src/store/*"],
+ "@utils/*": ["./src/utils/*"],
+ "@types/*": ["./src/types/*"]
+ },
+
+ // 类型检查选项
+ "skipLibCheck": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+
+ // 实验性功能
+ "allowJs": true,
+ "checkJs": false,
+
+ // React 相关配置
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+
+ // 装饰器支持(如果需要)
+ "experimentalDecorators": false,
+ "emitDecoratorMetadata": false,
+
+ // 输出配置
+ "declaration": false,
+ "declarationMap": false,
+ "sourceMap": true,
+
+ // 其他配置
+ "removeComments": false,
+ "preserveConstEnums": true,
+
+ // 性能优化
+ "incremental": true,
+ "tsBuildInfoFile": "./node_modules/.tmp/tsbuildinfo.json"
+ },
+
+ "include": [
+ "src/**/*",
+ "vite.config.ts",
+ "tailwind.config.js",
+ "postcss.config.js"
+ ],
+
+ "exclude": [
+ "node_modules",
+ "dist",
+ "build",
+ "**/*.test.ts",
+ "**/*.test.tsx",
+ "**/*.spec.ts",
+ "**/*.spec.tsx"
+ ],
+
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/tsconfig.node.json b/qiming-vite-plugin-design-mode/examples/full-featured/tsconfig.node.json
new file mode 100644
index 00000000..1f742271
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/tsconfig.node.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "skipLibCheck": true
+ },
+ "include": [
+ "vite.config.ts",
+ "tailwind.config.js",
+ "postcss.config.js"
+ ]
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/full-featured/vite.config.ts b/qiming-vite-plugin-design-mode/examples/full-featured/vite.config.ts
new file mode 100644
index 00000000..fb09e9f9
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/full-featured/vite.config.ts
@@ -0,0 +1,50 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import appdevDesignMode from '../../packages/plugin/src/index';
+
+export default defineConfig({
+ plugins: [
+ react(),
+ // AppDev Design Mode 插件配置
+ appdevDesignMode({
+ enabled: true,
+ enableInProduction: false,
+ attributePrefix: 'design-mode',
+ verbose: false, // 关闭详细日志避免频繁输出
+ exclude: [
+ 'node_modules',
+ '.git',
+ 'dist',
+ 'build',
+ '**/*.test.{ts,tsx,js,jsx}',
+ '**/*.spec.{ts,tsx,js,jsx}',
+ '**/__tests__/**',
+ '**/tests/**',
+ ],
+ include: [
+ 'src/**/*.{ts,tsx,js,jsx}',
+ ],
+ }),
+ ],
+
+ server: {
+ port: 5177,
+ host: true,
+ open: false, // 不自动打开浏览器
+ },
+
+ build: {
+ outDir: 'dist',
+ sourcemap: true,
+ },
+
+ resolve: {
+ alias: {
+ '@': '/src',
+ },
+ },
+
+ css: {
+ devSourcemap: true,
+ },
+});
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/react-tsx/debug_app.tsx b/qiming-vite-plugin-design-mode/examples/react-tsx/debug_app.tsx
new file mode 100644
index 00000000..420bc19f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-tsx/debug_app.tsx
@@ -0,0 +1,602 @@
+import { createHotContext as __vite__createHotContext } from "/@vite/client";import.meta.hot = __vite__createHotContext("/src/App.tsx");import __vite__cjsImport0_react_jsxDevRuntime from "/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=657b04ee"; const jsxDEV = __vite__cjsImport0_react_jsxDevRuntime["jsxDEV"];
+import * as RefreshRuntime from "/@react-refresh";
+const inWebWorker = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
+let prevRefreshReg;
+let prevRefreshSig;
+if (import.meta.hot && !inWebWorker) {
+ if (!window.$RefreshReg$) {
+ throw new Error(
+ "@vitejs/plugin-react can't detect preamble. Something is wrong."
+ );
+ }
+ prevRefreshReg = window.$RefreshReg$;
+ prevRefreshSig = window.$RefreshSig$;
+ window.$RefreshReg$ = RefreshRuntime.getRefreshReg("/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx");
+ window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
+}
+var _s = $RefreshSig$(), _s2 = $RefreshSig$();
+import __vite__cjsImport3_react from "/node_modules/.vite/deps/react.js?v=657b04ee"; const React = __vite__cjsImport3_react.__esModule ? __vite__cjsImport3_react.default : __vite__cjsImport3_react; const useState = __vite__cjsImport3_react["useState"];
+import "/src/App.css";
+function HeaderComponent() {
+ return /* @__PURE__ */ jsxDEV("header", { className: "main-header", "data-component": "header", children: [
+ /* @__PURE__ */ jsxDEV("h1", { className: "title", "data-element": "main-title", children: "Vite Design Mode Plugin Demo" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 27,
+ columnNumber: 7
+ }, this),
+ /* @__PURE__ */ jsxDEV("nav", { className: "navigation", "data-component": "nav", children: /* @__PURE__ */ jsxDEV("ul", { className: "nav-list", children: [
+ /* @__PURE__ */ jsxDEV("li", { children: /* @__PURE__ */ jsxDEV("a", { href: "#features", className: "nav-link", children: "Features" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 33,
+ columnNumber: 13
+ }, this) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 32,
+ columnNumber: 11
+ }, this),
+ /* @__PURE__ */ jsxDEV("li", { children: /* @__PURE__ */ jsxDEV("a", { href: "#examples", className: "nav-link", children: "Examples" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 38,
+ columnNumber: 13
+ }, this) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 37,
+ columnNumber: 11
+ }, this),
+ /* @__PURE__ */ jsxDEV("li", { children: /* @__PURE__ */ jsxDEV("a", { href: "#docs", className: "nav-link", children: "Documentation" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 43,
+ columnNumber: 13
+ }, this) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 42,
+ columnNumber: 11
+ }, this)
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 31,
+ columnNumber: 9
+ }, this) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 30,
+ columnNumber: 7
+ }, this)
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 26,
+ columnNumber: 5
+ }, this);
+}
+_c = HeaderComponent;
+class CounterComponent extends React.Component {
+ constructor() {
+ super(...arguments);
+ this.state = {
+ count: this.props.initial || 0
+ };
+ }
+ render() {
+ return /* @__PURE__ */ jsxDEV("div", { className: "counter-container", "data-component": "counter", children: [
+ /* @__PURE__ */ jsxDEV("h2", { "data-element": "counter-title", children: "计数器示例" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 62,
+ columnNumber: 9
+ }, this),
+ /* @__PURE__ */ jsxDEV("div", { className: "counter-display", children: /* @__PURE__ */ jsxDEV("span", { className: "counter-value", "data-element": "counter-value", children: [
+ "当前值: ",
+ this.state.count
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 64,
+ columnNumber: 11
+ }, this) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 63,
+ columnNumber: 9
+ }, this),
+ /* @__PURE__ */ jsxDEV("div", { className: "counter-buttons", children: [
+ /* @__PURE__ */ jsxDEV(
+ "button",
+ {
+ className: "counter-btn increment",
+ onClick: () => this.setState({ count: this.state.count + 1 }),
+ "data-action": "increment",
+ children: "+1"
+ },
+ void 0,
+ false,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 69,
+ columnNumber: 11
+ },
+ this
+ ),
+ /* @__PURE__ */ jsxDEV(
+ "button",
+ {
+ className: "counter-btn decrement",
+ onClick: () => this.setState({ count: this.state.count - 1 }),
+ "data-action": "decrement",
+ children: "-1"
+ },
+ void 0,
+ false,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 76,
+ columnNumber: 11
+ },
+ this
+ ),
+ /* @__PURE__ */ jsxDEV(
+ "button",
+ {
+ className: "counter-btn reset",
+ onClick: () => this.setState({ count: 0 }),
+ "data-action": "reset",
+ children: "重置"
+ },
+ void 0,
+ false,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 83,
+ columnNumber: 11
+ },
+ this
+ )
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 68,
+ columnNumber: 9
+ }, this)
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 61,
+ columnNumber: 7
+ }, this);
+ }
+}
+function FeatureSection() {
+ const features = [
+ {
+ id: "source-mapping",
+ title: "源码映射",
+ description: "自动注入源码位置信息到DOM元素",
+ icon: "🗺️"
+ },
+ {
+ id: "ast-analysis",
+ title: "AST分析",
+ description: "基于Babel AST的精确组件识别",
+ icon: "🔍"
+ },
+ {
+ id: "hot-reload",
+ title: "热更新",
+ description: "与Vite开发服务器无缝集成",
+ icon: "⚡"
+ }
+ ];
+ return /* @__PURE__ */ jsxDEV("section", { className: "features-section", "data-section": "features", children: [
+ /* @__PURE__ */ jsxDEV("h2", { className: "section-title", "data-element": "section-title", children: "插件特性" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 121,
+ columnNumber: 7
+ }, this),
+ /* @__PURE__ */ jsxDEV("div", { className: "features-grid", children: features.map(
+ (feature) => /* @__PURE__ */ jsxDEV(
+ "div",
+ {
+ className: "feature-card",
+ "data-feature": feature.id,
+ children: [
+ /* @__PURE__ */ jsxDEV("div", { className: "feature-icon", "data-element": "feature-icon", children: feature.icon }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 131,
+ columnNumber: 13
+ }, this),
+ /* @__PURE__ */ jsxDEV("h3", { className: "feature-title", "data-element": "feature-title", children: feature.title }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 134,
+ columnNumber: 13
+ }, this),
+ /* @__PURE__ */ jsxDEV(
+ "p",
+ {
+ className: "feature-description",
+ "data-element": "feature-description",
+ children: feature.description
+ },
+ void 0,
+ false,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 137,
+ columnNumber: 13
+ },
+ this
+ )
+ ]
+ },
+ feature.id,
+ true,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 126,
+ columnNumber: 9
+ },
+ this
+ )
+ ) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 124,
+ columnNumber: 7
+ }, this)
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 120,
+ columnNumber: 5
+ }, this);
+}
+_c2 = FeatureSection;
+function DynamicContent() {
+ _s();
+ const [isVisible, setIsVisible] = useState(false);
+ const [items, setItems] = useState(["项目1", "项目2", "项目3"]);
+ return /* @__PURE__ */ jsxDEV("div", { className: "dynamic-section", "data-section": "dynamic", children: [
+ /* @__PURE__ */ jsxDEV("h2", { className: "section-title", "data-element": "dynamic-title", children: "动态内容示例" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 157,
+ columnNumber: 7
+ }, this),
+ /* @__PURE__ */ jsxDEV("div", { className: "dynamic-controls", children: [
+ /* @__PURE__ */ jsxDEV(
+ "button",
+ {
+ className: "toggle-btn",
+ onClick: () => setIsVisible(!isVisible),
+ "data-action": "toggle-visibility",
+ children: [
+ isVisible ? "隐藏" : "显示",
+ "内容"
+ ]
+ },
+ void 0,
+ true,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 162,
+ columnNumber: 9
+ },
+ this
+ ),
+ /* @__PURE__ */ jsxDEV(
+ "button",
+ {
+ className: "add-item-btn",
+ onClick: () => setItems([...items, `项目${items.length + 1}`]),
+ "data-action": "add-item",
+ children: "添加项目"
+ },
+ void 0,
+ false,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 169,
+ columnNumber: 9
+ },
+ this
+ )
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 161,
+ columnNumber: 7
+ }, this),
+ isVisible && /* @__PURE__ */ jsxDEV("div", { className: "dynamic-content", "data-element": "dynamic-list", children: /* @__PURE__ */ jsxDEV("ul", { className: "items-list", children: items.map(
+ (item, index) => /* @__PURE__ */ jsxDEV("li", { className: "list-item", "data-item": index, children: [
+ /* @__PURE__ */ jsxDEV("span", { className: "item-text", children: item }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 183,
+ columnNumber: 17
+ }, this),
+ /* @__PURE__ */ jsxDEV(
+ "button",
+ {
+ className: "remove-btn",
+ onClick: () => setItems(items.filter((_, i) => i !== index)),
+ "data-action": "remove-item",
+ children: "×"
+ },
+ void 0,
+ false,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 184,
+ columnNumber: 17
+ },
+ this
+ )
+ ] }, index, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 182,
+ columnNumber: 11
+ }, this)
+ ) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 180,
+ columnNumber: 11
+ }, this) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 179,
+ columnNumber: 7
+ }, this)
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 156,
+ columnNumber: 5
+ }, this);
+}
+_s(DynamicContent, "ve3r1vIaFZxAaXG7Nau8RfGM2ms=");
+_c3 = DynamicContent;
+function ContactForm() {
+ _s2();
+ const [formData, setFormData] = useState({
+ name: "",
+ email: "",
+ message: ""
+ });
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ alert(`表单提交成功!
+姓名: ${formData.name}
+邮箱: ${formData.email}`);
+ };
+ const handleChange = (e) => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value
+ });
+ };
+ return /* @__PURE__ */ jsxDEV("section", { className: "form-section", "data-section": "contact", children: [
+ /* @__PURE__ */ jsxDEV("h2", { className: "section-title", "data-element": "form-title", children: "联系表单" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 224,
+ columnNumber: 7
+ }, this),
+ /* @__PURE__ */ jsxDEV(
+ "form",
+ {
+ className: "contact-form",
+ onSubmit: handleSubmit,
+ "data-form": "contact",
+ children: [
+ /* @__PURE__ */ jsxDEV("div", { className: "form-group", children: [
+ /* @__PURE__ */ jsxDEV("label", { htmlFor: "name", className: "form-label", children: "姓名" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 233,
+ columnNumber: 11
+ }, this),
+ /* @__PURE__ */ jsxDEV(
+ "input",
+ {
+ type: "text",
+ id: "name",
+ name: "name",
+ value: formData.name,
+ onChange: handleChange,
+ className: "form-input",
+ "data-element": "name-input",
+ required: true
+ },
+ void 0,
+ false,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 236,
+ columnNumber: 11
+ },
+ this
+ )
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 232,
+ columnNumber: 9
+ }, this),
+ /* @__PURE__ */ jsxDEV("div", { className: "form-group", children: [
+ /* @__PURE__ */ jsxDEV("label", { htmlFor: "email", className: "form-label", children: "邮箱" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 249,
+ columnNumber: 11
+ }, this),
+ /* @__PURE__ */ jsxDEV(
+ "input",
+ {
+ type: "email",
+ id: "email",
+ name: "email",
+ value: formData.email,
+ onChange: handleChange,
+ className: "form-input",
+ "data-element": "email-input",
+ required: true
+ },
+ void 0,
+ false,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 252,
+ columnNumber: 11
+ },
+ this
+ )
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 248,
+ columnNumber: 9
+ }, this),
+ /* @__PURE__ */ jsxDEV("div", { className: "form-group", children: [
+ /* @__PURE__ */ jsxDEV("label", { htmlFor: "message", className: "form-label", children: "消息" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 265,
+ columnNumber: 11
+ }, this),
+ /* @__PURE__ */ jsxDEV(
+ "textarea",
+ {
+ id: "message",
+ name: "message",
+ value: formData.message,
+ onChange: handleChange,
+ className: "form-textarea",
+ "data-element": "message-textarea",
+ rows: 4,
+ required: true
+ },
+ void 0,
+ false,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 268,
+ columnNumber: 11
+ },
+ this
+ )
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 264,
+ columnNumber: 9
+ }, this),
+ /* @__PURE__ */ jsxDEV("button", { type: "submit", className: "submit-btn", "data-action": "submit-form", children: "发送消息" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 280,
+ columnNumber: 9
+ }, this)
+ ]
+ },
+ void 0,
+ true,
+ {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 227,
+ columnNumber: 7
+ },
+ this
+ )
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 223,
+ columnNumber: 5
+ }, this);
+}
+_s2(ContactForm, "cRGivdu4kk0x+g1ddJGmIV4n560=");
+_c4 = ContactForm;
+function App() {
+ return /* @__PURE__ */ jsxDEV("div", { className: "App", "data-appdev-component": "App", children: [
+ /* @__PURE__ */ jsxDEV(HeaderComponent, {}, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 292,
+ columnNumber: 7
+ }, this),
+ /* @__PURE__ */ jsxDEV("main", { className: "main-content", children: [
+ /* @__PURE__ */ jsxDEV("section", { className: "hero-section", "data-section": "hero", children: /* @__PURE__ */ jsxDEV("div", { className: "hero-content", children: [
+ /* @__PURE__ */ jsxDEV("h1", { className: "hero-title", "data-element": "hero-title", children: "Vite Plugin AppDev Design Mode" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 297,
+ columnNumber: 13
+ }, this),
+ /* @__PURE__ */ jsxDEV("p", { className: "hero-description", "data-element": "hero-description", children: "一个强大的Vite插件,为React开发者提供源码映射和可视化编辑功能" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 300,
+ columnNumber: 13
+ }, this),
+ /* @__PURE__ */ jsxDEV("div", { className: "hero-buttons", children: [
+ /* @__PURE__ */ jsxDEV("button", { className: "hero-btn primary", "data-action": "get-started", children: "开始使用" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 304,
+ columnNumber: 15
+ }, this),
+ /* @__PURE__ */ jsxDEV("button", { className: "hero-btn secondary", "data-action": "view-docs", children: "查看文档" }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 307,
+ columnNumber: 15
+ }, this)
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 303,
+ columnNumber: 13
+ }, this)
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 296,
+ columnNumber: 11
+ }, this) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 295,
+ columnNumber: 9
+ }, this),
+ /* @__PURE__ */ jsxDEV(CounterComponent, { initial: 5 }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 314,
+ columnNumber: 9
+ }, this),
+ /* @__PURE__ */ jsxDEV(FeatureSection, {}, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 315,
+ columnNumber: 9
+ }, this),
+ /* @__PURE__ */ jsxDEV(DynamicContent, {}, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 316,
+ columnNumber: 9
+ }, this),
+ /* @__PURE__ */ jsxDEV(ContactForm, {}, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 317,
+ columnNumber: 9
+ }, this)
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 294,
+ columnNumber: 7
+ }, this),
+ /* @__PURE__ */ jsxDEV("footer", { className: "main-footer", "data-component": "footer", children: /* @__PURE__ */ jsxDEV("p", { className: "footer-text", "data-element": "footer-text", children: "© 2024 Vite Plugin AppDev Design Mode. All rights reserved." }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 321,
+ columnNumber: 9
+ }, this) }, void 0, false, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 320,
+ columnNumber: 7
+ }, this)
+ ] }, void 0, true, {
+ fileName: "/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx",
+ lineNumber: 291,
+ columnNumber: 5
+ }, this);
+}
+_c5 = App;
+export default App;
+var _c, _c2, _c3, _c4, _c5;
+$RefreshReg$(_c, "HeaderComponent");
+$RefreshReg$(_c2, "FeatureSection");
+$RefreshReg$(_c3, "DynamicContent");
+$RefreshReg$(_c4, "ContactForm");
+$RefreshReg$(_c5, "App");
+if (import.meta.hot && !inWebWorker) {
+ window.$RefreshReg$ = prevRefreshReg;
+ window.$RefreshSig$ = prevRefreshSig;
+}
+if (import.meta.hot && !inWebWorker) {
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
+ RefreshRuntime.registerExportsForReactRefresh("/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx", currentExports);
+ import.meta.hot.accept((nextExports) => {
+ if (!nextExports) return;
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate("/Users/a1234/www/vite-plugin-appdev-design-mode/examples/react-tsx/src/App.tsx", currentExports, nextExports);
+ if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage);
+ });
+ });
+}
+
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IkFBT007Ozs7Ozs7Ozs7Ozs7Ozs7O0FBUE4sT0FBT0EsU0FBU0MsZ0JBQWdCO0FBQ2hDLE9BQU87QUFHUCxTQUFTQyxrQkFBa0I7QUFDekIsU0FDRSx1QkFBQyxZQUFPLFdBQVUsZUFBYyxrQkFBZSxVQUM3QztBQUFBLDJCQUFDLFFBQUcsV0FBVSxTQUFRLGdCQUFhLGNBQVksNENBQS9DO0FBQUE7QUFBQTtBQUFBO0FBQUEsV0FFQTtBQUFBLElBQ0EsdUJBQUMsU0FBSSxXQUFVLGNBQWEsa0JBQWUsT0FDekMsaUNBQUMsUUFBRyxXQUFVLFlBQ1o7QUFBQSw2QkFBQyxRQUNDLGlDQUFDLE9BQUUsTUFBSyxhQUFZLFdBQVUsWUFBVSx3QkFBeEM7QUFBQTtBQUFBO0FBQUE7QUFBQSxhQUVBLEtBSEY7QUFBQTtBQUFBO0FBQUE7QUFBQSxhQUlBO0FBQUEsTUFDQSx1QkFBQyxRQUNDLGlDQUFDLE9BQUUsTUFBSyxhQUFZLFdBQVUsWUFBVSx3QkFBeEM7QUFBQTtBQUFBO0FBQUE7QUFBQSxhQUVBLEtBSEY7QUFBQTtBQUFBO0FBQUE7QUFBQSxhQUlBO0FBQUEsTUFDQSx1QkFBQyxRQUNDLGlDQUFDLE9BQUUsTUFBSyxTQUFRLFdBQVUsWUFBVSw2QkFBcEM7QUFBQTtBQUFBO0FBQUE7QUFBQSxhQUVBLEtBSEY7QUFBQTtBQUFBO0FBQUE7QUFBQSxhQUlBO0FBQUEsU0FmRjtBQUFBO0FBQUE7QUFBQTtBQUFBLFdBZ0JBLEtBakJGO0FBQUE7QUFBQTtBQUFBO0FBQUEsV0FrQkE7QUFBQSxPQXRCRjtBQUFBO0FBQUE7QUFBQTtBQUFBLFNBdUJBO0FBRUo7QUFFQUMsS0E3QlNEO0FBOEJULE1BQU1FLHlCQUF5QkosTUFBTUssVUFBZ0M7QUFBQSxFQUFyRTtBQUFBO0FBQ0VDLGlCQUFRO0FBQUEsTUFDTkMsT0FBTyxLQUFLQyxNQUFNQyxXQUFXO0FBQUEsSUFDL0I7QUFBQTtBQUFBLEVBRUFDLFNBQVM7QUFDUCxXQUNFLHVCQUFDLFNBQUksV0FBVSxxQkFBb0Isa0JBQWUsV0FDaEQ7QUFBQSw2QkFBQyxRQUFHLGdCQUFhLGlCQUFnQixxQkFBakM7QUFBQTtBQUFBO0FBQUE7QUFBQSxhQUFzQztBQUFBLE1BQ3RDLHVCQUFDLFNBQUksV0FBVSxtQkFDYixpQ0FBQyxVQUFLLFdBQVUsaUJBQWdCLGdCQUFhLGlCQUFlO0FBQUE7QUFBQSxRQUNwRCxLQUFLSixNQUFNQztBQUFBQSxXQURuQjtBQUFBO0FBQUE7QUFBQTtBQUFBLGFBRUEsS0FIRjtBQUFBO0FBQUE7QUFBQTtBQUFBLGFBSUE7QUFBQSxNQUNBLHVCQUFDLFNBQUksV0FBVSxtQkFDYjtBQUFBO0FBQUEsVUFBQztBQUFBO0FBQUEsWUFDQyxXQUFVO0FBQUEsWUFDVixTQUFTLE1BQU0sS0FBS0ksU0FBUyxFQUFFSixPQUFPLEtBQUtELE1BQU1DLFFBQVEsRUFBRSxDQUFDO0FBQUEsWUFDNUQsZUFBWTtBQUFBLFlBQVc7QUFBQTtBQUFBLFVBSHpCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxRQU1BO0FBQUEsUUFDQTtBQUFBLFVBQUM7QUFBQTtBQUFBLFlBQ0MsV0FBVTtBQUFBLFlBQ1YsU0FBUyxNQUFNLEtBQUtJLFNBQVMsRUFBRUosT0FBTyxLQUFLRCxNQUFNQyxRQUFRLEVBQUUsQ0FBQztBQUFBLFlBQzVELGVBQVk7QUFBQSxZQUFXO0FBQUE7QUFBQSxVQUh6QjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsUUFNQTtBQUFBLFFBQ0E7QUFBQSxVQUFDO0FBQUE7QUFBQSxZQUNDLFdBQVU7QUFBQSxZQUNWLFNBQVMsTUFBTSxLQUFLSSxTQUFTLEVBQUVKLE9BQU8sRUFBRSxDQUFDO0FBQUEsWUFDekMsZUFBWTtBQUFBLFlBQU87QUFBQTtBQUFBLFVBSHJCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxRQU1BO0FBQUEsV0FyQkY7QUFBQTtBQUFBO0FBQUE7QUFBQSxhQXNCQTtBQUFBLFNBN0JGO0FBQUE7QUFBQTtBQUFBO0FBQUEsV0E4QkE7QUFBQSxFQUVKO0FBQ0Y7QUFHQSxTQUFTSyxpQkFBaUI7QUFDeEIsUUFBTUMsV0FBVztBQUFBLElBQ2Y7QUFBQSxNQUNFQyxJQUFJO0FBQUEsTUFDSkMsT0FBTztBQUFBLE1BQ1BDLGFBQWE7QUFBQSxNQUNiQyxNQUFNO0FBQUEsSUFDUjtBQUFBLElBQ0E7QUFBQSxNQUNFSCxJQUFJO0FBQUEsTUFDSkMsT0FBTztBQUFBLE1BQ1BDLGFBQWE7QUFBQSxNQUNiQyxNQUFNO0FBQUEsSUFDUjtBQUFBLElBQ0E7QUFBQSxNQUNFSCxJQUFJO0FBQUEsTUFDSkMsT0FBTztBQUFBLE1BQ1BDLGFBQWE7QUFBQSxNQUNiQyxNQUFNO0FBQUEsSUFDUjtBQUFBLEVBQUM7QUFHSCxTQUNFLHVCQUFDLGFBQVEsV0FBVSxvQkFBbUIsZ0JBQWEsWUFDakQ7QUFBQSwyQkFBQyxRQUFHLFdBQVUsaUJBQWdCLGdCQUFhLGlCQUFlLG9CQUExRDtBQUFBO0FBQUE7QUFBQTtBQUFBLFdBRUE7QUFBQSxJQUNBLHVCQUFDLFNBQUksV0FBVSxpQkFDWkosbUJBQVNLO0FBQUFBLE1BQUksQ0FBQUMsWUFDWjtBQUFBLFFBQUM7QUFBQTtBQUFBLFVBRUMsV0FBVTtBQUFBLFVBQ1YsZ0JBQWNBLFFBQVFMO0FBQUFBLFVBRXRCO0FBQUEsbUNBQUMsU0FBSSxXQUFVLGdCQUFlLGdCQUFhLGdCQUN4Q0ssa0JBQVFGLFFBRFg7QUFBQTtBQUFBO0FBQUE7QUFBQSxtQkFFQTtBQUFBLFlBQ0EsdUJBQUMsUUFBRyxXQUFVLGlCQUFnQixnQkFBYSxpQkFDeENFLGtCQUFRSixTQURYO0FBQUE7QUFBQTtBQUFBO0FBQUEsbUJBRUE7QUFBQSxZQUNBO0FBQUEsY0FBQztBQUFBO0FBQUEsZ0JBQ0MsV0FBVTtBQUFBLGdCQUNWLGdCQUFhO0FBQUEsZ0JBRVpJLGtCQUFRSDtBQUFBQTtBQUFBQSxjQUpYO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxZQUtBO0FBQUE7QUFBQTtBQUFBLFFBZktHLFFBQVFMO0FBQUFBLFFBRGY7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQWlCQTtBQUFBLElBQ0QsS0FwQkg7QUFBQTtBQUFBO0FBQUE7QUFBQSxXQXFCQTtBQUFBLE9BekJGO0FBQUE7QUFBQTtBQUFBO0FBQUEsU0EwQkE7QUFFSjtBQUVBTSxNQXJEU1I7QUFzRFQsU0FBU1MsaUJBQWlCO0FBQUFDLEtBQUE7QUFDeEIsUUFBTSxDQUFDQyxXQUFXQyxZQUFZLElBQUl2QixTQUFTLEtBQUs7QUFDaEQsUUFBTSxDQUFDd0IsT0FBT0MsUUFBUSxJQUFJekIsU0FBUyxDQUFDLE9BQU8sT0FBTyxLQUFLLENBQUM7QUFFeEQsU0FDRSx1QkFBQyxTQUFJLFdBQVUsbUJBQWtCLGdCQUFhLFdBQzVDO0FBQUEsMkJBQUMsUUFBRyxXQUFVLGlCQUFnQixnQkFBYSxpQkFBZSxzQkFBMUQ7QUFBQTtBQUFBO0FBQUE7QUFBQSxXQUVBO0FBQUEsSUFFQSx1QkFBQyxTQUFJLFdBQVUsb0JBQ2I7QUFBQTtBQUFBLFFBQUM7QUFBQTtBQUFBLFVBQ0MsV0FBVTtBQUFBLFVBQ1YsU0FBUyxNQUFNdUIsYUFBYSxDQUFDRCxTQUFTO0FBQUEsVUFDdEMsZUFBWTtBQUFBLFVBRVhBO0FBQUFBLHdCQUFZLE9BQU87QUFBQSxZQUFLO0FBQUE7QUFBQTtBQUFBLFFBTDNCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU1BO0FBQUEsTUFDQTtBQUFBLFFBQUM7QUFBQTtBQUFBLFVBQ0MsV0FBVTtBQUFBLFVBQ1YsU0FBUyxNQUFNRyxTQUFTLENBQUMsR0FBR0QsT0FBTyxLQUFLQSxNQUFNRSxTQUFTLENBQUMsRUFBRSxDQUFDO0FBQUEsVUFDM0QsZUFBWTtBQUFBLFVBQVU7QUFBQTtBQUFBLFFBSHhCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU1BO0FBQUEsU0FkRjtBQUFBO0FBQUE7QUFBQTtBQUFBLFdBZUE7QUFBQSxJQUVDSixhQUNDLHVCQUFDLFNBQUksV0FBVSxtQkFBa0IsZ0JBQWEsZ0JBQzVDLGlDQUFDLFFBQUcsV0FBVSxjQUNYRSxnQkFBTVA7QUFBQUEsTUFBSSxDQUFDVSxNQUFNQyxVQUNoQix1QkFBQyxRQUFlLFdBQVUsYUFBWSxhQUFXQSxPQUMvQztBQUFBLCtCQUFDLFVBQUssV0FBVSxhQUFhRCxrQkFBN0I7QUFBQTtBQUFBO0FBQUE7QUFBQSxlQUFrQztBQUFBLFFBQ2xDO0FBQUEsVUFBQztBQUFBO0FBQUEsWUFDQyxXQUFVO0FBQUEsWUFDVixTQUFTLE1BQU1GLFNBQVNELE1BQU1LLE9BQU8sQ0FBQ0MsR0FBR0MsTUFBTUEsTUFBTUgsS0FBSyxDQUFDO0FBQUEsWUFDM0QsZUFBWTtBQUFBLFlBQWE7QUFBQTtBQUFBLFVBSDNCO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxRQU1BO0FBQUEsV0FST0EsT0FBVDtBQUFBO0FBQUE7QUFBQTtBQUFBLGFBU0E7QUFBQSxJQUNELEtBWkg7QUFBQTtBQUFBO0FBQUE7QUFBQSxXQWFBLEtBZEY7QUFBQTtBQUFBO0FBQUE7QUFBQSxXQWVBO0FBQUEsT0F0Q0o7QUFBQTtBQUFBO0FBQUE7QUFBQSxTQXdDQTtBQUVKO0FBRUFQLEdBakRTRCxnQkFBYztBQUFBWSxNQUFkWjtBQWtEVCxTQUFTYSxjQUFjO0FBQUFDLE1BQUE7QUFDckIsUUFBTSxDQUFDQyxVQUFVQyxXQUFXLElBQUlwQyxTQUFTO0FBQUEsSUFDdkNxQyxNQUFNO0FBQUEsSUFDTkMsT0FBTztBQUFBLElBQ1BDLFNBQVM7QUFBQSxFQUNYLENBQUM7QUFFRCxRQUFNQyxlQUFlQSxDQUFDQyxNQUF1QjtBQUMzQ0EsTUFBRUMsZUFBZTtBQUNqQkMsVUFBTTtBQUFBLE1BQWdCUixTQUFTRSxJQUFJO0FBQUEsTUFBU0YsU0FBU0csS0FBSyxFQUFFO0FBQUEsRUFDOUQ7QUFFQSxRQUFNTSxlQUFlQSxDQUNuQkgsTUFDRztBQUNITCxnQkFBWTtBQUFBLE1BQ1YsR0FBR0Q7QUFBQUEsTUFDSCxDQUFDTSxFQUFFSSxPQUFPUixJQUFJLEdBQUdJLEVBQUVJLE9BQU9DO0FBQUFBLElBQzVCLENBQUM7QUFBQSxFQUNIO0FBRUEsU0FDRSx1QkFBQyxhQUFRLFdBQVUsZ0JBQWUsZ0JBQWEsV0FDN0M7QUFBQSwyQkFBQyxRQUFHLFdBQVUsaUJBQWdCLGdCQUFhLGNBQVksb0JBQXZEO0FBQUE7QUFBQTtBQUFBO0FBQUEsV0FFQTtBQUFBLElBQ0E7QUFBQSxNQUFDO0FBQUE7QUFBQSxRQUNDLFdBQVU7QUFBQSxRQUNWLFVBQVVOO0FBQUFBLFFBQ1YsYUFBVTtBQUFBLFFBRVY7QUFBQSxpQ0FBQyxTQUFJLFdBQVUsY0FDYjtBQUFBLG1DQUFDLFdBQU0sU0FBUSxRQUFPLFdBQVUsY0FBWSxrQkFBNUM7QUFBQTtBQUFBO0FBQUE7QUFBQSxtQkFFQTtBQUFBLFlBQ0E7QUFBQSxjQUFDO0FBQUE7QUFBQSxnQkFDQyxNQUFLO0FBQUEsZ0JBQ0wsSUFBRztBQUFBLGdCQUNILE1BQUs7QUFBQSxnQkFDTCxPQUFPTCxTQUFTRTtBQUFBQSxnQkFDaEIsVUFBVU87QUFBQUEsZ0JBQ1YsV0FBVTtBQUFBLGdCQUNWLGdCQUFhO0FBQUEsZ0JBQ2IsVUFBUTtBQUFBO0FBQUEsY0FSVjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsWUFRVTtBQUFBLGVBWlo7QUFBQTtBQUFBO0FBQUE7QUFBQSxpQkFjQTtBQUFBLFVBRUEsdUJBQUMsU0FBSSxXQUFVLGNBQ2I7QUFBQSxtQ0FBQyxXQUFNLFNBQVEsU0FBUSxXQUFVLGNBQVksa0JBQTdDO0FBQUE7QUFBQTtBQUFBO0FBQUEsbUJBRUE7QUFBQSxZQUNBO0FBQUEsY0FBQztBQUFBO0FBQUEsZ0JBQ0MsTUFBSztBQUFBLGdCQUNMLElBQUc7QUFBQSxnQkFDSCxNQUFLO0FBQUEsZ0JBQ0wsT0FBT1QsU0FBU0c7QUFBQUEsZ0JBQ2hCLFVBQVVNO0FBQUFBLGdCQUNWLFdBQVU7QUFBQSxnQkFDVixnQkFBYTtBQUFBLGdCQUNiLFVBQVE7QUFBQTtBQUFBLGNBUlY7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLFlBUVU7QUFBQSxlQVpaO0FBQUE7QUFBQTtBQUFBO0FBQUEsaUJBY0E7QUFBQSxVQUVBLHVCQUFDLFNBQUksV0FBVSxjQUNiO0FBQUEsbUNBQUMsV0FBTSxTQUFRLFdBQVUsV0FBVSxjQUFZLGtCQUEvQztBQUFBO0FBQUE7QUFBQTtBQUFBLG1CQUVBO0FBQUEsWUFDQTtBQUFBLGNBQUM7QUFBQTtBQUFBLGdCQUNDLElBQUc7QUFBQSxnQkFDSCxNQUFLO0FBQUEsZ0JBQ0wsT0FBT1QsU0FBU0k7QUFBQUEsZ0JBQ2hCLFVBQVVLO0FBQUFBLGdCQUNWLFdBQVU7QUFBQSxnQkFDVixnQkFBYTtBQUFBLGdCQUNiLE1BQU07QUFBQSxnQkFDTixVQUFRO0FBQUE7QUFBQSxjQVJWO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxZQVFVO0FBQUEsZUFaWjtBQUFBO0FBQUE7QUFBQTtBQUFBLGlCQWNBO0FBQUEsVUFFQSx1QkFBQyxZQUFPLE1BQUssVUFBUyxXQUFVLGNBQWEsZUFBWSxlQUFhLG9CQUF0RTtBQUFBO0FBQUE7QUFBQTtBQUFBLGlCQUVBO0FBQUE7QUFBQTtBQUFBLE1BdkRGO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQXdEQTtBQUFBLE9BNURGO0FBQUE7QUFBQTtBQUFBO0FBQUEsU0E2REE7QUFFSjtBQUVBVixJQXZGU0QsYUFBVztBQUFBYyxNQUFYZDtBQXdGVCxTQUFTZSxNQUFNO0FBQ2IsU0FDRSx1QkFBQyxTQUFJLFdBQVUsT0FBTSx5QkFBc0IsT0FDekM7QUFBQSwyQkFBQyxxQkFBRDtBQUFBO0FBQUE7QUFBQTtBQUFBLFdBQWdCO0FBQUEsSUFFaEIsdUJBQUMsVUFBSyxXQUFVLGdCQUNkO0FBQUEsNkJBQUMsYUFBUSxXQUFVLGdCQUFlLGdCQUFhLFFBQzdDLGlDQUFDLFNBQUksV0FBVSxnQkFDYjtBQUFBLCtCQUFDLFFBQUcsV0FBVSxjQUFhLGdCQUFhLGNBQVksOENBQXBEO0FBQUE7QUFBQTtBQUFBO0FBQUEsZUFFQTtBQUFBLFFBQ0EsdUJBQUMsT0FBRSxXQUFVLG9CQUFtQixnQkFBYSxvQkFBa0IsbURBQS9EO0FBQUE7QUFBQTtBQUFBO0FBQUEsZUFFQTtBQUFBLFFBQ0EsdUJBQUMsU0FBSSxXQUFVLGdCQUNiO0FBQUEsaUNBQUMsWUFBTyxXQUFVLG9CQUFtQixlQUFZLGVBQWEsb0JBQTlEO0FBQUE7QUFBQTtBQUFBO0FBQUEsaUJBRUE7QUFBQSxVQUNBLHVCQUFDLFlBQU8sV0FBVSxzQkFBcUIsZUFBWSxhQUFXLG9CQUE5RDtBQUFBO0FBQUE7QUFBQTtBQUFBLGlCQUVBO0FBQUEsYUFORjtBQUFBO0FBQUE7QUFBQTtBQUFBLGVBT0E7QUFBQSxXQWRGO0FBQUE7QUFBQTtBQUFBO0FBQUEsYUFlQSxLQWhCRjtBQUFBO0FBQUE7QUFBQTtBQUFBLGFBaUJBO0FBQUEsTUFFQSx1QkFBQyxvQkFBaUIsU0FBUyxLQUEzQjtBQUFBO0FBQUE7QUFBQTtBQUFBLGFBQTZCO0FBQUEsTUFDN0IsdUJBQUMsb0JBQUQ7QUFBQTtBQUFBO0FBQUE7QUFBQSxhQUFlO0FBQUEsTUFDZix1QkFBQyxvQkFBRDtBQUFBO0FBQUE7QUFBQTtBQUFBLGFBQWU7QUFBQSxNQUNmLHVCQUFDLGlCQUFEO0FBQUE7QUFBQTtBQUFBO0FBQUEsYUFBWTtBQUFBLFNBdkJkO0FBQUE7QUFBQTtBQUFBO0FBQUEsV0F3QkE7QUFBQSxJQUVBLHVCQUFDLFlBQU8sV0FBVSxlQUFjLGtCQUFlLFVBQzdDLGlDQUFDLE9BQUUsV0FBVSxlQUFjLGdCQUFhLGVBQWEsMkVBQXJEO0FBQUE7QUFBQTtBQUFBO0FBQUEsV0FFQSxLQUhGO0FBQUE7QUFBQTtBQUFBO0FBQUEsV0FJQTtBQUFBLE9BakNGO0FBQUE7QUFBQTtBQUFBO0FBQUEsU0FrQ0E7QUFFSjtBQUFDQyxNQXRDUUQ7QUF3Q1QsZUFBZUE7QUFBSSxJQUFBOUMsSUFBQWlCLEtBQUFhLEtBQUFlLEtBQUFFO0FBQUFDLGFBQUFoRCxJQUFBO0FBQUFnRCxhQUFBL0IsS0FBQTtBQUFBK0IsYUFBQWxCLEtBQUE7QUFBQWtCLGFBQUFILEtBQUE7QUFBQUcsYUFBQUQsS0FBQSIsIm5hbWVzIjpbIlJlYWN0IiwidXNlU3RhdGUiLCJIZWFkZXJDb21wb25lbnQiLCJfYyIsIkNvdW50ZXJDb21wb25lbnQiLCJDb21wb25lbnQiLCJzdGF0ZSIsImNvdW50IiwicHJvcHMiLCJpbml0aWFsIiwicmVuZGVyIiwic2V0U3RhdGUiLCJGZWF0dXJlU2VjdGlvbiIsImZlYXR1cmVzIiwiaWQiLCJ0aXRsZSIsImRlc2NyaXB0aW9uIiwiaWNvbiIsIm1hcCIsImZlYXR1cmUiLCJfYzIiLCJEeW5hbWljQ29udGVudCIsIl9zIiwiaXNWaXNpYmxlIiwic2V0SXNWaXNpYmxlIiwiaXRlbXMiLCJzZXRJdGVtcyIsImxlbmd0aCIsIml0ZW0iLCJpbmRleCIsImZpbHRlciIsIl8iLCJpIiwiX2MzIiwiQ29udGFjdEZvcm0iLCJfczIiLCJmb3JtRGF0YSIsInNldEZvcm1EYXRhIiwibmFtZSIsImVtYWlsIiwibWVzc2FnZSIsImhhbmRsZVN1Ym1pdCIsImUiLCJwcmV2ZW50RGVmYXVsdCIsImFsZXJ0IiwiaGFuZGxlQ2hhbmdlIiwidGFyZ2V0IiwidmFsdWUiLCJfYzQiLCJBcHAiLCJfYzUiLCIkUmVmcmVzaFJlZyQiXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOlsiQXBwLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgUmVhY3QsIHsgdXNlU3RhdGUgfSBmcm9tICdyZWFjdCc7XG5pbXBvcnQgJy4vQXBwLmNzcyc7XG5cbi8vIOWHveaVsOe7hOS7tuekuuS+i1xuZnVuY3Rpb24gSGVhZGVyQ29tcG9uZW50KCkge1xuICByZXR1cm4gKFxuICAgIDxoZWFkZXIgY2xhc3NOYW1lPSdtYWluLWhlYWRlcicgZGF0YS1jb21wb25lbnQ9J2hlYWRlcic+XG4gICAgICA8aDEgY2xhc3NOYW1lPSd0aXRsZScgZGF0YS1lbGVtZW50PSdtYWluLXRpdGxlJz5cbiAgICAgICAgVml0ZSBEZXNpZ24gTW9kZSBQbHVnaW4gRGVtb1xuICAgICAgPC9oMT5cbiAgICAgIDxuYXYgY2xhc3NOYW1lPSduYXZpZ2F0aW9uJyBkYXRhLWNvbXBvbmVudD0nbmF2Jz5cbiAgICAgICAgPHVsIGNsYXNzTmFtZT0nbmF2LWxpc3QnPlxuICAgICAgICAgIDxsaT5cbiAgICAgICAgICAgIDxhIGhyZWY9JyNmZWF0dXJlcycgY2xhc3NOYW1lPSduYXYtbGluayc+XG4gICAgICAgICAgICAgIEZlYXR1cmVzXG4gICAgICAgICAgICA8L2E+XG4gICAgICAgICAgPC9saT5cbiAgICAgICAgICA8bGk+XG4gICAgICAgICAgICA8YSBocmVmPScjZXhhbXBsZXMnIGNsYXNzTmFtZT0nbmF2LWxpbmsnPlxuICAgICAgICAgICAgICBFeGFtcGxlc1xuICAgICAgICAgICAgPC9hPlxuICAgICAgICAgIDwvbGk+XG4gICAgICAgICAgPGxpPlxuICAgICAgICAgICAgPGEgaHJlZj0nI2RvY3MnIGNsYXNzTmFtZT0nbmF2LWxpbmsnPlxuICAgICAgICAgICAgICBEb2N1bWVudGF0aW9uXG4gICAgICAgICAgICA8L2E+XG4gICAgICAgICAgPC9saT5cbiAgICAgICAgPC91bD5cbiAgICAgIDwvbmF2PlxuICAgIDwvaGVhZGVyPlxuICApO1xufVxuXG4vLyDnsbvnu4Tku7bnpLrkvotcbmNsYXNzIENvdW50ZXJDb21wb25lbnQgZXh0ZW5kcyBSZWFjdC5Db21wb25lbnQ8eyBpbml0aWFsPzogbnVtYmVyIH0+IHtcbiAgc3RhdGUgPSB7XG4gICAgY291bnQ6IHRoaXMucHJvcHMuaW5pdGlhbCB8fCAwLFxuICB9O1xuXG4gIHJlbmRlcigpIHtcbiAgICByZXR1cm4gKFxuICAgICAgPGRpdiBjbGFzc05hbWU9J2NvdW50ZXItY29udGFpbmVyJyBkYXRhLWNvbXBvbmVudD0nY291bnRlcic+XG4gICAgICAgIDxoMiBkYXRhLWVsZW1lbnQ9J2NvdW50ZXItdGl0bGUnPuiuoeaVsOWZqOekuuS+izwvaDI+XG4gICAgICAgIDxkaXYgY2xhc3NOYW1lPSdjb3VudGVyLWRpc3BsYXknPlxuICAgICAgICAgIDxzcGFuIGNsYXNzTmFtZT0nY291bnRlci12YWx1ZScgZGF0YS1lbGVtZW50PSdjb3VudGVyLXZhbHVlJz5cbiAgICAgICAgICAgIOW9k+WJjeWAvDoge3RoaXMuc3RhdGUuY291bnR9XG4gICAgICAgICAgPC9zcGFuPlxuICAgICAgICA8L2Rpdj5cbiAgICAgICAgPGRpdiBjbGFzc05hbWU9J2NvdW50ZXItYnV0dG9ucyc+XG4gICAgICAgICAgPGJ1dHRvblxuICAgICAgICAgICAgY2xhc3NOYW1lPSdjb3VudGVyLWJ0biBpbmNyZW1lbnQnXG4gICAgICAgICAgICBvbkNsaWNrPXsoKSA9PiB0aGlzLnNldFN0YXRlKHsgY291bnQ6IHRoaXMuc3RhdGUuY291bnQgKyAxIH0pfVxuICAgICAgICAgICAgZGF0YS1hY3Rpb249J2luY3JlbWVudCdcbiAgICAgICAgICA+XG4gICAgICAgICAgICArMVxuICAgICAgICAgIDwvYnV0dG9uPlxuICAgICAgICAgIDxidXR0b25cbiAgICAgICAgICAgIGNsYXNzTmFtZT0nY291bnRlci1idG4gZGVjcmVtZW50J1xuICAgICAgICAgICAgb25DbGljaz17KCkgPT4gdGhpcy5zZXRTdGF0ZSh7IGNvdW50OiB0aGlzLnN0YXRlLmNvdW50IC0gMSB9KX1cbiAgICAgICAgICAgIGRhdGEtYWN0aW9uPSdkZWNyZW1lbnQnXG4gICAgICAgICAgPlxuICAgICAgICAgICAgLTFcbiAgICAgICAgICA8L2J1dHRvbj5cbiAgICAgICAgICA8YnV0dG9uXG4gICAgICAgICAgICBjbGFzc05hbWU9J2NvdW50ZXItYnRuIHJlc2V0J1xuICAgICAgICAgICAgb25DbGljaz17KCkgPT4gdGhpcy5zZXRTdGF0ZSh7IGNvdW50OiAwIH0pfVxuICAgICAgICAgICAgZGF0YS1hY3Rpb249J3Jlc2V0J1xuICAgICAgICAgID5cbiAgICAgICAgICAgIOmHjee9rlxuICAgICAgICAgIDwvYnV0dG9uPlxuICAgICAgICA8L2Rpdj5cbiAgICAgIDwvZGl2PlxuICAgICk7XG4gIH1cbn1cblxuLy8g5aSN5p2C5bWM5aWX57uE5Lu256S65L6LXG5mdW5jdGlvbiBGZWF0dXJlU2VjdGlvbigpIHtcbiAgY29uc3QgZmVhdHVyZXMgPSBbXG4gICAge1xuICAgICAgaWQ6ICdzb3VyY2UtbWFwcGluZycsXG4gICAgICB0aXRsZTogJ+a6kOeggeaYoOWwhCcsXG4gICAgICBkZXNjcmlwdGlvbjogJ+iHquWKqOazqOWFpea6kOeggeS9jee9ruS/oeaBr+WIsERPTeWFg+e0oCcsXG4gICAgICBpY29uOiAn8J+Xuu+4jycsXG4gICAgfSxcbiAgICB7XG4gICAgICBpZDogJ2FzdC1hbmFseXNpcycsXG4gICAgICB0aXRsZTogJ0FTVOWIhuaekCcsXG4gICAgICBkZXNjcmlwdGlvbjogJ+WfuuS6jkJhYmVsIEFTVOeahOeyvuehrue7hOS7tuivhuWIqycsXG4gICAgICBpY29uOiAn8J+UjScsXG4gICAgfSxcbiAgICB7XG4gICAgICBpZDogJ2hvdC1yZWxvYWQnLFxuICAgICAgdGl0bGU6ICfng63mm7TmlrAnLFxuICAgICAgZGVzY3JpcHRpb246ICfkuI5WaXRl5byA5Y+R5pyN5Yqh5Zmo5peg57yd6ZuG5oiQJyxcbiAgICAgIGljb246ICfimqEnLFxuICAgIH0sXG4gIF07XG5cbiAgcmV0dXJuIChcbiAgICA8c2VjdGlvbiBjbGFzc05hbWU9J2ZlYXR1cmVzLXNlY3Rpb24nIGRhdGEtc2VjdGlvbj0nZmVhdHVyZXMnPlxuICAgICAgPGgyIGNsYXNzTmFtZT0nc2VjdGlvbi10aXRsZScgZGF0YS1lbGVtZW50PSdzZWN0aW9uLXRpdGxlJz5cbiAgICAgICAg5o+S5Lu254m55oCnXG4gICAgICA8L2gyPlxuICAgICAgPGRpdiBjbGFzc05hbWU9J2ZlYXR1cmVzLWdyaWQnPlxuICAgICAgICB7ZmVhdHVyZXMubWFwKGZlYXR1cmUgPT4gKFxuICAgICAgICAgIDxkaXZcbiAgICAgICAgICAgIGtleT17ZmVhdHVyZS5pZH1cbiAgICAgICAgICAgIGNsYXNzTmFtZT0nZmVhdHVyZS1jYXJkJ1xuICAgICAgICAgICAgZGF0YS1mZWF0dXJlPXtmZWF0dXJlLmlkfVxuICAgICAgICAgID5cbiAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPSdmZWF0dXJlLWljb24nIGRhdGEtZWxlbWVudD0nZmVhdHVyZS1pY29uJz5cbiAgICAgICAgICAgICAge2ZlYXR1cmUuaWNvbn1cbiAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgPGgzIGNsYXNzTmFtZT0nZmVhdHVyZS10aXRsZScgZGF0YS1lbGVtZW50PSdmZWF0dXJlLXRpdGxlJz5cbiAgICAgICAgICAgICAge2ZlYXR1cmUudGl0bGV9XG4gICAgICAgICAgICA8L2gzPlxuICAgICAgICAgICAgPHBcbiAgICAgICAgICAgICAgY2xhc3NOYW1lPSdmZWF0dXJlLWRlc2NyaXB0aW9uJ1xuICAgICAgICAgICAgICBkYXRhLWVsZW1lbnQ9J2ZlYXR1cmUtZGVzY3JpcHRpb24nXG4gICAgICAgICAgICA+XG4gICAgICAgICAgICAgIHtmZWF0dXJlLmRlc2NyaXB0aW9ufVxuICAgICAgICAgICAgPC9wPlxuICAgICAgICAgIDwvZGl2PlxuICAgICAgICApKX1cbiAgICAgIDwvZGl2PlxuICAgIDwvc2VjdGlvbj5cbiAgKTtcbn1cblxuLy8g5Yqo5oCB5YaF5a6556S65L6LXG5mdW5jdGlvbiBEeW5hbWljQ29udGVudCgpIHtcbiAgY29uc3QgW2lzVmlzaWJsZSwgc2V0SXNWaXNpYmxlXSA9IHVzZVN0YXRlKGZhbHNlKTtcbiAgY29uc3QgW2l0ZW1zLCBzZXRJdGVtc10gPSB1c2VTdGF0ZShbJ+mhueebrjEnLCAn6aG555uuMicsICfpobnnm64zJ10pO1xuXG4gIHJldHVybiAoXG4gICAgPGRpdiBjbGFzc05hbWU9J2R5bmFtaWMtc2VjdGlvbicgZGF0YS1zZWN0aW9uPSdkeW5hbWljJz5cbiAgICAgIDxoMiBjbGFzc05hbWU9J3NlY3Rpb24tdGl0bGUnIGRhdGEtZWxlbWVudD0nZHluYW1pYy10aXRsZSc+XG4gICAgICAgIOWKqOaAgeWGheWuueekuuS+i1xuICAgICAgPC9oMj5cblxuICAgICAgPGRpdiBjbGFzc05hbWU9J2R5bmFtaWMtY29udHJvbHMnPlxuICAgICAgICA8YnV0dG9uXG4gICAgICAgICAgY2xhc3NOYW1lPSd0b2dnbGUtYnRuJ1xuICAgICAgICAgIG9uQ2xpY2s9eygpID0+IHNldElzVmlzaWJsZSghaXNWaXNpYmxlKX1cbiAgICAgICAgICBkYXRhLWFjdGlvbj0ndG9nZ2xlLXZpc2liaWxpdHknXG4gICAgICAgID5cbiAgICAgICAgICB7aXNWaXNpYmxlID8gJ+makOiXjycgOiAn5pi+56S6J33lhoXlrrlcbiAgICAgICAgPC9idXR0b24+XG4gICAgICAgIDxidXR0b25cbiAgICAgICAgICBjbGFzc05hbWU9J2FkZC1pdGVtLWJ0bidcbiAgICAgICAgICBvbkNsaWNrPXsoKSA9PiBzZXRJdGVtcyhbLi4uaXRlbXMsIGDpobnnm64ke2l0ZW1zLmxlbmd0aCArIDF9YF0pfVxuICAgICAgICAgIGRhdGEtYWN0aW9uPSdhZGQtaXRlbSdcbiAgICAgICAgPlxuICAgICAgICAgIOa3u+WKoOmhueebrlxuICAgICAgICA8L2J1dHRvbj5cbiAgICAgIDwvZGl2PlxuXG4gICAgICB7aXNWaXNpYmxlICYmIChcbiAgICAgICAgPGRpdiBjbGFzc05hbWU9J2R5bmFtaWMtY29udGVudCcgZGF0YS1lbGVtZW50PSdkeW5hbWljLWxpc3QnPlxuICAgICAgICAgIDx1bCBjbGFzc05hbWU9J2l0ZW1zLWxpc3QnPlxuICAgICAgICAgICAge2l0ZW1zLm1hcCgoaXRlbSwgaW5kZXgpID0+IChcbiAgICAgICAgICAgICAgPGxpIGtleT17aW5kZXh9IGNsYXNzTmFtZT0nbGlzdC1pdGVtJyBkYXRhLWl0ZW09e2luZGV4fT5cbiAgICAgICAgICAgICAgICA8c3BhbiBjbGFzc05hbWU9J2l0ZW0tdGV4dCc+e2l0ZW19PC9zcGFuPlxuICAgICAgICAgICAgICAgIDxidXR0b25cbiAgICAgICAgICAgICAgICAgIGNsYXNzTmFtZT0ncmVtb3ZlLWJ0bidcbiAgICAgICAgICAgICAgICAgIG9uQ2xpY2s9eygpID0+IHNldEl0ZW1zKGl0ZW1zLmZpbHRlcigoXywgaSkgPT4gaSAhPT0gaW5kZXgpKX1cbiAgICAgICAgICAgICAgICAgIGRhdGEtYWN0aW9uPSdyZW1vdmUtaXRlbSdcbiAgICAgICAgICAgICAgICA+XG4gICAgICAgICAgICAgICAgICDDl1xuICAgICAgICAgICAgICAgIDwvYnV0dG9uPlxuICAgICAgICAgICAgICA8L2xpPlxuICAgICAgICAgICAgKSl9XG4gICAgICAgICAgPC91bD5cbiAgICAgICAgPC9kaXY+XG4gICAgICApfVxuICAgIDwvZGl2PlxuICApO1xufVxuXG4vLyDooajljZXnu4Tku7bnpLrkvotcbmZ1bmN0aW9uIENvbnRhY3RGb3JtKCkge1xuICBjb25zdCBbZm9ybURhdGEsIHNldEZvcm1EYXRhXSA9IHVzZVN0YXRlKHtcbiAgICBuYW1lOiAnJyxcbiAgICBlbWFpbDogJycsXG4gICAgbWVzc2FnZTogJycsXG4gIH0pO1xuXG4gIGNvbnN0IGhhbmRsZVN1Ym1pdCA9IChlOiBSZWFjdC5Gb3JtRXZlbnQpID0+IHtcbiAgICBlLnByZXZlbnREZWZhdWx0KCk7XG4gICAgYWxlcnQoYOihqOWNleaPkOS6pOaIkOWKnyFcXG7lp5PlkI06ICR7Zm9ybURhdGEubmFtZX1cXG7pgq7nrrE6ICR7Zm9ybURhdGEuZW1haWx9YCk7XG4gIH07XG5cbiAgY29uc3QgaGFuZGxlQ2hhbmdlID0gKFxuICAgIGU6IFJlYWN0LkNoYW5nZUV2ZW50PEhUTUxJbnB1dEVsZW1lbnQgfCBIVE1MVGV4dEFyZWFFbGVtZW50PlxuICApID0+IHtcbiAgICBzZXRGb3JtRGF0YSh7XG4gICAgICAuLi5mb3JtRGF0YSxcbiAgICAgIFtlLnRhcmdldC5uYW1lXTogZS50YXJnZXQudmFsdWUsXG4gICAgfSk7XG4gIH07XG5cbiAgcmV0dXJuIChcbiAgICA8c2VjdGlvbiBjbGFzc05hbWU9J2Zvcm0tc2VjdGlvbicgZGF0YS1zZWN0aW9uPSdjb250YWN0Jz5cbiAgICAgIDxoMiBjbGFzc05hbWU9J3NlY3Rpb24tdGl0bGUnIGRhdGEtZWxlbWVudD0nZm9ybS10aXRsZSc+XG4gICAgICAgIOiBlOezu+ihqOWNlVxuICAgICAgPC9oMj5cbiAgICAgIDxmb3JtXG4gICAgICAgIGNsYXNzTmFtZT0nY29udGFjdC1mb3JtJ1xuICAgICAgICBvblN1Ym1pdD17aGFuZGxlU3VibWl0fVxuICAgICAgICBkYXRhLWZvcm09J2NvbnRhY3QnXG4gICAgICA+XG4gICAgICAgIDxkaXYgY2xhc3NOYW1lPSdmb3JtLWdyb3VwJz5cbiAgICAgICAgICA8bGFiZWwgaHRtbEZvcj0nbmFtZScgY2xhc3NOYW1lPSdmb3JtLWxhYmVsJz5cbiAgICAgICAgICAgIOWnk+WQjVxuICAgICAgICAgIDwvbGFiZWw+XG4gICAgICAgICAgPGlucHV0XG4gICAgICAgICAgICB0eXBlPSd0ZXh0J1xuICAgICAgICAgICAgaWQ9J25hbWUnXG4gICAgICAgICAgICBuYW1lPSduYW1lJ1xuICAgICAgICAgICAgdmFsdWU9e2Zvcm1EYXRhLm5hbWV9XG4gICAgICAgICAgICBvbkNoYW5nZT17aGFuZGxlQ2hhbmdlfVxuICAgICAgICAgICAgY2xhc3NOYW1lPSdmb3JtLWlucHV0J1xuICAgICAgICAgICAgZGF0YS1lbGVtZW50PSduYW1lLWlucHV0J1xuICAgICAgICAgICAgcmVxdWlyZWRcbiAgICAgICAgICAvPlxuICAgICAgICA8L2Rpdj5cblxuICAgICAgICA8ZGl2IGNsYXNzTmFtZT0nZm9ybS1ncm91cCc+XG4gICAgICAgICAgPGxhYmVsIGh0bWxGb3I9J2VtYWlsJyBjbGFzc05hbWU9J2Zvcm0tbGFiZWwnPlxuICAgICAgICAgICAg6YKu566xXG4gICAgICAgICAgPC9sYWJlbD5cbiAgICAgICAgICA8aW5wdXRcbiAgICAgICAgICAgIHR5cGU9J2VtYWlsJ1xuICAgICAgICAgICAgaWQ9J2VtYWlsJ1xuICAgICAgICAgICAgbmFtZT0nZW1haWwnXG4gICAgICAgICAgICB2YWx1ZT17Zm9ybURhdGEuZW1haWx9XG4gICAgICAgICAgICBvbkNoYW5nZT17aGFuZGxlQ2hhbmdlfVxuICAgICAgICAgICAgY2xhc3NOYW1lPSdmb3JtLWlucHV0J1xuICAgICAgICAgICAgZGF0YS1lbGVtZW50PSdlbWFpbC1pbnB1dCdcbiAgICAgICAgICAgIHJlcXVpcmVkXG4gICAgICAgICAgLz5cbiAgICAgICAgPC9kaXY+XG5cbiAgICAgICAgPGRpdiBjbGFzc05hbWU9J2Zvcm0tZ3JvdXAnPlxuICAgICAgICAgIDxsYWJlbCBodG1sRm9yPSdtZXNzYWdlJyBjbGFzc05hbWU9J2Zvcm0tbGFiZWwnPlxuICAgICAgICAgICAg5raI5oGvXG4gICAgICAgICAgPC9sYWJlbD5cbiAgICAgICAgICA8dGV4dGFyZWFcbiAgICAgICAgICAgIGlkPSdtZXNzYWdlJ1xuICAgICAgICAgICAgbmFtZT0nbWVzc2FnZSdcbiAgICAgICAgICAgIHZhbHVlPXtmb3JtRGF0YS5tZXNzYWdlfVxuICAgICAgICAgICAgb25DaGFuZ2U9e2hhbmRsZUNoYW5nZX1cbiAgICAgICAgICAgIGNsYXNzTmFtZT0nZm9ybS10ZXh0YXJlYSdcbiAgICAgICAgICAgIGRhdGEtZWxlbWVudD0nbWVzc2FnZS10ZXh0YXJlYSdcbiAgICAgICAgICAgIHJvd3M9ezR9XG4gICAgICAgICAgICByZXF1aXJlZFxuICAgICAgICAgIC8+XG4gICAgICAgIDwvZGl2PlxuXG4gICAgICAgIDxidXR0b24gdHlwZT0nc3VibWl0JyBjbGFzc05hbWU9J3N1Ym1pdC1idG4nIGRhdGEtYWN0aW9uPSdzdWJtaXQtZm9ybSc+XG4gICAgICAgICAg5Y+R6YCB5raI5oGvXG4gICAgICAgIDwvYnV0dG9uPlxuICAgICAgPC9mb3JtPlxuICAgIDwvc2VjdGlvbj5cbiAgKTtcbn1cblxuLy8g5Li75bqU55So57uE5Lu2XG5mdW5jdGlvbiBBcHAoKSB7XG4gIHJldHVybiAoXG4gICAgPGRpdiBjbGFzc05hbWU9J0FwcCcgZGF0YS1hcHBkZXYtY29tcG9uZW50PSdBcHAnPlxuICAgICAgPEhlYWRlckNvbXBvbmVudCAvPlxuXG4gICAgICA8bWFpbiBjbGFzc05hbWU9J21haW4tY29udGVudCc+XG4gICAgICAgIDxzZWN0aW9uIGNsYXNzTmFtZT0naGVyby1zZWN0aW9uJyBkYXRhLXNlY3Rpb249J2hlcm8nPlxuICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPSdoZXJvLWNvbnRlbnQnPlxuICAgICAgICAgICAgPGgxIGNsYXNzTmFtZT0naGVyby10aXRsZScgZGF0YS1lbGVtZW50PSdoZXJvLXRpdGxlJz5cbiAgICAgICAgICAgICAgVml0ZSBQbHVnaW4gQXBwRGV2IERlc2lnbiBNb2RlXG4gICAgICAgICAgICA8L2gxPlxuICAgICAgICAgICAgPHAgY2xhc3NOYW1lPSdoZXJvLWRlc2NyaXB0aW9uJyBkYXRhLWVsZW1lbnQ9J2hlcm8tZGVzY3JpcHRpb24nPlxuICAgICAgICAgICAgICDkuIDkuKrlvLrlpKfnmoRWaXRl5o+S5Lu277yM5Li6UmVhY3TlvIDlj5HogIXmj5DkvpvmupDnoIHmmKDlsITlkozlj6/op4bljJbnvJbovpHlip/og71cbiAgICAgICAgICAgIDwvcD5cbiAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPSdoZXJvLWJ1dHRvbnMnPlxuICAgICAgICAgICAgICA8YnV0dG9uIGNsYXNzTmFtZT0naGVyby1idG4gcHJpbWFyeScgZGF0YS1hY3Rpb249J2dldC1zdGFydGVkJz5cbiAgICAgICAgICAgICAgICDlvIDlp4vkvb/nlKhcbiAgICAgICAgICAgICAgPC9idXR0b24+XG4gICAgICAgICAgICAgIDxidXR0b24gY2xhc3NOYW1lPSdoZXJvLWJ0biBzZWNvbmRhcnknIGRhdGEtYWN0aW9uPSd2aWV3LWRvY3MnPlxuICAgICAgICAgICAgICAgIOafpeeci+aWh+aho1xuICAgICAgICAgICAgICA8L2J1dHRvbj5cbiAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgIDwvZGl2PlxuICAgICAgICA8L3NlY3Rpb24+XG5cbiAgICAgICAgPENvdW50ZXJDb21wb25lbnQgaW5pdGlhbD17NX0gLz5cbiAgICAgICAgPEZlYXR1cmVTZWN0aW9uIC8+XG4gICAgICAgIDxEeW5hbWljQ29udGVudCAvPlxuICAgICAgICA8Q29udGFjdEZvcm0gLz5cbiAgICAgIDwvbWFpbj5cblxuICAgICAgPGZvb3RlciBjbGFzc05hbWU9J21haW4tZm9vdGVyJyBkYXRhLWNvbXBvbmVudD0nZm9vdGVyJz5cbiAgICAgICAgPHAgY2xhc3NOYW1lPSdmb290ZXItdGV4dCcgZGF0YS1lbGVtZW50PSdmb290ZXItdGV4dCc+XG4gICAgICAgICAgwqkgMjAyNCBWaXRlIFBsdWdpbiBBcHBEZXYgRGVzaWduIE1vZGUuIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gICAgICAgIDwvcD5cbiAgICAgIDwvZm9vdGVyPlxuICAgIDwvZGl2PlxuICApO1xufVxuXG5leHBvcnQgZGVmYXVsdCBBcHA7XG4iXSwiZmlsZSI6Ii9Vc2Vycy9hMTIzNC93d3cvdml0ZS1wbHVnaW4tYXBwZGV2LWRlc2lnbi1tb2RlL2V4YW1wbGVzL3JlYWN0LXRzeC9zcmMvQXBwLnRzeCJ9
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/react-tsx/index.html b/qiming-vite-plugin-design-mode/examples/react-tsx/index.html
new file mode 100644
index 00000000..387f68c4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-tsx/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ AppDev Design Mode Example1
+
+
+
+
+
+
+
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-tsx/package-lock.json b/qiming-vite-plugin-design-mode/examples/react-tsx/package-lock.json
new file mode 100644
index 00000000..0c234d0f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-tsx/package-lock.json
@@ -0,0 +1,1681 @@
+{
+ "name": "vite-plugin-appdev-design-mode-react-example",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "vite-plugin-appdev-design-mode-react-example",
+ "version": "1.0.0",
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "@vitejs/plugin-react": "^4.0.0",
+ "typescript": "^5.0.0",
+ "vite": "^5.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@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"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.27",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
+ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "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.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.30",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz",
+ "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
+ "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.8.25",
+ "caniuse-lite": "^1.0.30001754",
+ "electron-to-chromium": "^1.5.249",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.1.4"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001756",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz",
+ "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.259",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz",
+ "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
+ "@rollup/rollup-android-arm64": "4.53.3",
+ "@rollup/rollup-darwin-arm64": "4.53.3",
+ "@rollup/rollup-darwin-x64": "4.53.3",
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
+ "@rollup/rollup-freebsd-x64": "4.53.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
+ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-tsx/package.json b/qiming-vite-plugin-design-mode/examples/react-tsx/package.json
new file mode 100644
index 00000000..50eae735
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-tsx/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "vite-plugin-appdev-design-mode-react-example",
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "@vitejs/plugin-react": "^4.0.0",
+ "typescript": "^5.0.0",
+ "vite": "^5.0.0"
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-tsx/src/App.css b/qiming-vite-plugin-design-mode/examples/react-tsx/src/App.css
new file mode 100644
index 00000000..2f3e3627
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-tsx/src/App.css
@@ -0,0 +1,473 @@
+/* 全局样式重置 */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+}
+
+.App {
+ min-height: 100vh;
+ color: #333;
+}
+
+/* 头部样式 */
+.main-header {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(10px);
+ padding: 1rem 2rem;
+ box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
+ position: sticky;
+ top: 0;
+ z-index: 100;
+}
+
+.main-header .title {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: #4a5568;
+ margin-bottom: 0.5rem;
+}
+
+.navigation .nav-list {
+ display: flex;
+ list-style: none;
+ gap: 2rem;
+}
+
+.nav-link {
+ text-decoration: none;
+ color: #4a5568;
+ font-weight: 500;
+ transition: color 0.3s ease;
+}
+
+.nav-link:hover {
+ color: #667eea;
+}
+
+/* 主要内容区域 */
+.main-content {
+ padding: 2rem;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+/* 英雄区域 */
+.hero-section {
+ text-align: center;
+ padding: 4rem 0;
+ margin-bottom: 3rem;
+}
+
+.hero-content {
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(20px);
+ border-radius: 20px;
+ padding: 3rem;
+ border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.hero-title {
+ font-size: 3rem;
+ font-weight: 800;
+ color: white;
+ margin-bottom: 1.5rem;
+ text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
+}
+
+.hero-description {
+ font-size: 1.2rem;
+ color: rgba(255, 255, 255, 0.9);
+ margin-bottom: 2rem;
+ line-height: 1.6;
+}
+
+.hero-buttons {
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+.hero-btn {
+ padding: 0.75rem 2rem;
+ border: none;
+ border-radius: 50px;
+ font-weight: 600;
+ font-size: 1rem;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ text-decoration: none;
+ display: inline-block;
+}
+
+.hero-btn.primary {
+ background: linear-gradient(45deg, #ff6b6b, #ee5a24);
+ color: white;
+ box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
+}
+
+.hero-btn.secondary {
+ background: rgba(255, 255, 255, 0.2);
+ color: white;
+ border: 2px solid rgba(255, 255, 255, 0.3);
+}
+
+.hero-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
+}
+
+/* 计数器组件 */
+.counter-container {
+ background: white;
+ border-radius: 15px;
+ padding: 2rem;
+ margin: 2rem 0;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+ text-align: center;
+}
+
+.counter-container h2 {
+ margin-bottom: 1.5rem;
+ color: #4a5568;
+}
+
+.counter-display {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #667eea;
+ margin-bottom: 2rem;
+ padding: 1rem;
+ background: #f7fafc;
+ border-radius: 10px;
+}
+
+.counter-buttons {
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+.counter-btn {
+ padding: 0.75rem 1.5rem;
+ border: none;
+ border-radius: 10px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ min-width: 80px;
+}
+
+.counter-btn.increment {
+ background: linear-gradient(45deg, #48bb78, #38a169);
+ color: white;
+}
+
+.counter-btn.decrement {
+ background: linear-gradient(45deg, #f56565, #e53e3e);
+ color: white;
+}
+
+.counter-btn.reset {
+ background: linear-gradient(45deg, #a0aec0, #718096);
+ color: white;
+}
+
+.counter-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
+}
+
+/* 特性区域 */
+.features-section {
+ margin: 3rem 0;
+}
+
+.section-title {
+ text-align: center;
+ font-size: 2.5rem;
+ font-weight: 700;
+ color: white;
+ margin-bottom: 3rem;
+ text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+}
+
+.features-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 2rem;
+ padding: 0 1rem;
+}
+
+.feature-card {
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(20px);
+ border-radius: 15px;
+ padding: 2rem;
+ text-align: center;
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ transition: all 0.3s ease;
+}
+
+.feature-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
+ background: rgba(255, 255, 255, 0.15);
+}
+
+.feature-icon {
+ font-size: 3rem;
+ margin-bottom: 1rem;
+}
+
+.feature-title {
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: white;
+ margin-bottom: 1rem;
+}
+
+.feature-description {
+ color: rgba(255, 255, 255, 0.9);
+ line-height: 1.6;
+}
+
+/* 动态内容区域 */
+.dynamic-section {
+ background: white;
+ border-radius: 15px;
+ padding: 2rem;
+ margin: 2rem 0;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+}
+
+.dynamic-controls {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 2rem;
+ flex-wrap: wrap;
+}
+
+.toggle-btn, .add-item-btn {
+ padding: 0.75rem 1.5rem;
+ border: none;
+ border-radius: 10px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.toggle-btn {
+ background: linear-gradient(45deg, #667eea, #764ba2);
+ color: white;
+}
+
+.add-item-btn {
+ background: linear-gradient(45deg, #48bb78, #38a169);
+ color: white;
+}
+
+.dynamic-content {
+ animation: fadeIn 0.3s ease-in;
+}
+
+.items-list {
+ list-style: none;
+}
+
+.list-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.75rem 1rem;
+ margin-bottom: 0.5rem;
+ background: #f7fafc;
+ border-radius: 8px;
+ border-left: 4px solid #667eea;
+}
+
+.item-text {
+ font-weight: 500;
+}
+
+.remove-btn {
+ background: #f56565;
+ color: white;
+ border: none;
+ border-radius: 50%;
+ width: 24px;
+ height: 24px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: all 0.3s ease;
+}
+
+.remove-btn:hover {
+ background: #e53e3e;
+ transform: scale(1.1);
+}
+
+/* 表单区域 */
+.form-section {
+ background: white;
+ border-radius: 15px;
+ padding: 2rem;
+ margin: 2rem 0;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+}
+
+.contact-form {
+ max-width: 500px;
+ margin: 0 auto;
+}
+
+.form-group {
+ margin-bottom: 1.5rem;
+}
+
+.form-label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+ color: #4a5568;
+}
+
+.form-input, .form-textarea {
+ width: 100%;
+ padding: 0.75rem;
+ border: 2px solid #e2e8f0;
+ border-radius: 8px;
+ font-size: 1rem;
+ transition: border-color 0.3s ease;
+ font-family: inherit;
+}
+
+.form-input:focus, .form-textarea:focus {
+ outline: none;
+ border-color: #667eea;
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+}
+
+.form-textarea {
+ resize: vertical;
+ min-height: 100px;
+}
+
+.submit-btn {
+ width: 100%;
+ padding: 0.75rem;
+ background: linear-gradient(45deg, #667eea, #764ba2);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.submit-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
+}
+
+/* 页脚 */
+.main-footer {
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(20px);
+ text-align: center;
+ padding: 2rem;
+ color: white;
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+/* 动画 */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .main-content {
+ padding: 1rem;
+ }
+
+ .hero-title {
+ font-size: 2rem;
+ }
+
+ .hero-content {
+ padding: 2rem 1rem;
+ }
+
+ .hero-buttons {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .hero-btn {
+ width: 200px;
+ }
+
+ .features-grid {
+ grid-template-columns: 1fr;
+ gap: 1rem;
+ }
+
+ .counter-buttons {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .dynamic-controls {
+ flex-direction: column;
+ }
+
+ .navigation .nav-list {
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+}
+
+/* 设计模式高亮样式(当启用设计模式时显示) */
+[data-source-info]:hover {
+ outline: 2px dashed #667eea !important;
+ outline-offset: 2px !important;
+ cursor: pointer !important;
+ position: relative !important;
+}
+
+[data-source-info]:hover::after {
+ content: attr(data-source-info);
+ position: absolute;
+ bottom: 100%;
+ left: 0;
+ background: #333;
+ color: white;
+ padding: 0.5rem;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ white-space: nowrap;
+ z-index: 1000;
+ opacity: 0;
+ animation: fadeIn 0.3s ease-in forwards;
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-tsx/src/App.tsx b/qiming-vite-plugin-design-mode/examples/react-tsx/src/App.tsx
new file mode 100644
index 00000000..9d768394
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-tsx/src/App.tsx
@@ -0,0 +1,310 @@
+import React, { useState } from 'react';
+import './App.css';
+
+// 函数组件示例
+function HeaderComponent() {
+ return (
+
+
+ Vite Design Mode Plugin Demo
+
+
+
+ );
+}
+
+// 类组件示例
+class CounterComponent extends React.Component<{ initial?: number }> {
+ state = {
+ count: this.props.initial || 0,
+ };
+
+ render() {
+ return (
+
+
计数器示例
+
+
+ 当前值: {this.state.count}
+
+
+
+ this.setState({ count: this.state.count + 1 })}
+ data-action='increment'
+ >
+ +1
+
+ this.setState({ count: this.state.count - 1 })}
+ data-action='decrement'
+ >
+ -1
+
+ this.setState({ count: 0 })}
+ data-action='reset'
+ >
+ 重置
+
+
+
+ );
+ }
+}
+
+// 复杂嵌套组件示例
+function FeatureSection() {
+ const features = [
+ {
+ id: 'source-mapping',
+ title: '源码映射',
+ description: '自动注入源码位置信息到DOM元素',
+ icon: '🗺️',
+ },
+ {
+ id: 'ast-analysis',
+ title: 'AST分析',
+ description: '基于Babel AST的精确组件识别',
+ icon: '🔍',
+ },
+ {
+ id: 'hot-reload',
+ title: '热更新',
+ description: '与Vite开发服务器无缝集成',
+ icon: '⚡',
+ },
+ ];
+
+ return (
+
+
+ 插件特性
+
+
+ {features.map(feature => (
+
+
+ {feature.icon}
+
+
+ {feature.title}
+
+
+ {feature.description}
+
+
+ ))}
+
+
+ );
+}
+
+// 动态内容示例
+function DynamicContent() {
+ const [isVisible, setIsVisible] = useState(false);
+ const [items, setItems] = useState(['项目1', '项目2', '项目3']);
+
+ return (
+
+
+ 动态内容示例
+
+
+
+ setIsVisible(!isVisible)}
+ data-action='toggle-visibility'
+ >
+ {isVisible ? '隐藏' : '显示'}内容
+
+ setItems([...items, `项目${items.length + 1}`])}
+ data-action='add-item'
+ >
+ 添加项目
+
+
+
+ {isVisible && (
+
+
+ {items.map((item, index) => (
+ -
+ {item}
+ setItems(items.filter((_, i) => i !== index))}
+ data-action='remove-item'
+ >
+ ×
+
+
+ ))}
+
+
+ )}
+
+ );
+}
+
+// 表单组件示例
+function ContactForm() {
+ const [formData, setFormData] = useState({
+ name: '',
+ email: '',
+ message: '',
+ });
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ alert(`表单提交成功!\n姓名: ${formData.name}\n邮箱: ${formData.email}`);
+ };
+
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value,
+ });
+ };
+
+ return (
+
+ );
+}
+
+// 主应用组件
+function App() {
+ return (
+
+
+
+
+
+
+
+ Vite Plugin AppDev Design Mode
+
+
+ 一个强大的Vite插件,为React开发者提供源码映射和可视化编辑功能
+
+
+
+ 开始使用
+
+
+ 查看文档
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/qiming-vite-plugin-design-mode/examples/react-tsx/src/main.tsx b/qiming-vite-plugin-design-mode/examples/react-tsx/src/main.tsx
new file mode 100644
index 00000000..9707d827
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-tsx/src/main.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/qiming-vite-plugin-design-mode/examples/react-tsx/vite.config.ts b/qiming-vite-plugin-design-mode/examples/react-tsx/vite.config.ts
new file mode 100644
index 00000000..4e33c22e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-tsx/vite.config.ts
@@ -0,0 +1,16 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import appdevDesignMode from '../../packages/plugin/src/index';
+
+export default defineConfig({
+ plugins: [
+ react(),
+ appdevDesignMode({
+ enabled: true,
+ verbose: true,
+ attributePrefix: 'data-source',
+ exclude: ['node_modules'],
+ include: ['**/*.{tsx,ts,jsx,js}'],
+ }),
+ ],
+});
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/.npmrc b/qiming-vite-plugin-design-mode/examples/react-vite-md2/.npmrc
new file mode 100644
index 00000000..6ce23873
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/.npmrc
@@ -0,0 +1,6 @@
+# pnpm 磁盘空间优化配置
+# 自动生成于 2025-11-22 16:14:26
+
+package-import-method=hardlink
+auto-install-peers=true
+registry=https://registry.npmmirror.com
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/biome.json b/qiming-vite-plugin-design-mode/examples/react-vite-md2/biome.json
new file mode 100644
index 00000000..2fee5acb
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/biome.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
+ "files": {
+ "includes": ["src/**/*.{js,jsx,ts,tsx}"]
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": false,
+ "correctness": {
+ "noUndeclaredDependencies": "error"
+ },
+ "suspicious": {
+ "noRedeclare": "error"
+ }
+ }
+ },
+ "formatter": {
+ "enabled": false
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/components.json b/qiming-vite-plugin-design-mode/examples/react-vite-md2/components.json
new file mode 100644
index 00000000..7dfce35b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/index.css",
+ "baseColor": "slate",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/cpage_config.json b/qiming-vite-plugin-design-mode/examples/react-vite-md2/cpage_config.json
new file mode 100644
index 00000000..1748a68f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/cpage_config.json
@@ -0,0 +1,12 @@
+{
+ "name": "公众号推文助手",
+ "description": "专为微信公众号内容创作者设计,提供图文生成、图片配文、视频脚本创作等功能",
+ "icon": "https://agent-statics-tc.nuwax.com/store/cbc0aac4615e4665b31e199a55fb4870.png",
+ "coverImg": "https://agent-statics-tc.nuwax.com/store/4ef409a57fd04c589437058da1aee15e.png",
+ "coverImgSourceType": "SYSTEM",
+ "needLogin": true,
+ "proxyConfigs": null,
+ "pageArgConfigs": null,
+ "dataSourcePlugins": null,
+ "dataSourceWorkflows": null
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/index.html b/qiming-vite-plugin-design-mode/examples/react-vite-md2/index.html
new file mode 100644
index 00000000..c8cc40e1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ 公众号推文助手
+
+
+
+
+
+
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/package-lock.json b/qiming-vite-plugin-design-mode/examples/react-vite-md2/package-lock.json
new file mode 100644
index 00000000..d3bfad55
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/package-lock.json
@@ -0,0 +1,9904 @@
+{
+ "name": "react-admin",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "react-admin",
+ "version": "0.0.1",
+ "dependencies": {
+ "@hookform/resolvers": "^5.2.2",
+ "@radix-ui/react-accordion": "^1.2.12",
+ "@radix-ui/react-alert-dialog": "^1.1.15",
+ "@radix-ui/react-aspect-ratio": "^1.1.7",
+ "@radix-ui/react-avatar": "^1.1.10",
+ "@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-collapsible": "^1.1.12",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-icons": "^1.3.2",
+ "@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-menubar": "^1.1.16",
+ "@radix-ui/react-navigation-menu": "^1.2.14",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.7",
+ "@radix-ui/react-radio-group": "^1.3.8",
+ "@radix-ui/react-scroll-area": "^1.2.10",
+ "@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.7",
+ "@radix-ui/react-slider": "^1.3.6",
+ "@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tabs": "^1.1.13",
+ "@radix-ui/react-toast": "^1.2.15",
+ "@radix-ui/react-toggle": "^1.1.10",
+ "@radix-ui/react-toggle-group": "^1.1.11",
+ "@radix-ui/react-tooltip": "^1.2.8",
+ "@supabase/supabase-js": "^2.76.1",
+ "axios": "^1.12.2",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
+ "date-fns": "^3.6.0",
+ "embla-carousel-react": "^8.6.0",
+ "eventsource-parser": "^3.0.6",
+ "input-otp": "^1.4.2",
+ "ky": "^1.13.0",
+ "lucide-react": "^0.548.0",
+ "next-themes": "^0.4.6",
+ "qrcode": "^1.5.4",
+ "react": "^18.0.0",
+ "react-day-picker": "^8.10.1",
+ "react-dom": "^18.0.0",
+ "react-dropzone": "^14.3.8",
+ "react-helmet-async": "^2.0.5",
+ "react-hook-form": "^7.65.0",
+ "react-resizable-panels": "^2.1.8",
+ "react-router": "^7.9.4",
+ "react-router-dom": "^6.30.0",
+ "recharts": "^2.15.3",
+ "sonner": "^2.0.7",
+ "streamdown": "^1.4.0",
+ "tailwind-merge": "^3.3.1",
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^1.1.2",
+ "video-react": "^0.16.0",
+ "zod": "^3.25.76"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "2.3.0",
+ "@types/lodash": "^4.17.20",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.2",
+ "@types/video-react": "^0.15.8",
+ "@typescript/native-preview": "7.0.0-dev.20251024.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "@xagi/vite-plugin-design-mode": "^1.0.24",
+ "autoprefixer": "^10.4.21",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^3.4.11",
+ "typescript": "~5.9.3",
+ "vite": "^5.1.4",
+ "vite-plugin-svgr": "^4.5.0"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@antfu/install-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
+ "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "package-manager-detector": "^1.3.0",
+ "tinyexec": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@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"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/standalone": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/standalone/-/standalone-7.28.5.tgz",
+ "integrity": "sha512-1DViPYJpRU50irpGMfLBQ9B4kyfQuL6X7SS7pwTeWeZX0mNkjzPi0XFqxCjSdddZXUQy4AhnQnnesA/ZHnvAdw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@biomejs/biome": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@biomejs/biome/-/biome-2.3.0.tgz",
+ "integrity": "sha512-shdUY5H3S3tJVUWoVWo5ua+GdPW5lRHf+b0IwZ4OC1o2zOKQECZ6l2KbU6t89FNhtd3Qx5eg5N7/UsQWGQbAFw==",
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "bin": {
+ "biome": "bin/biome"
+ },
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/biome"
+ },
+ "optionalDependencies": {
+ "@biomejs/cli-darwin-arm64": "2.3.0",
+ "@biomejs/cli-darwin-x64": "2.3.0",
+ "@biomejs/cli-linux-arm64": "2.3.0",
+ "@biomejs/cli-linux-arm64-musl": "2.3.0",
+ "@biomejs/cli-linux-x64": "2.3.0",
+ "@biomejs/cli-linux-x64-musl": "2.3.0",
+ "@biomejs/cli-win32-arm64": "2.3.0",
+ "@biomejs/cli-win32-x64": "2.3.0"
+ }
+ },
+ "node_modules/@biomejs/cli-darwin-arm64": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.0.tgz",
+ "integrity": "sha512-3cJVT0Z5pbTkoBmbjmDZTDFYxIkRcrs9sYVJbIBHU8E6qQxgXAaBfSVjjCreG56rfDuQBr43GzwzmaHPcu4vlw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=14.21.3"
+ }
+ },
+ "node_modules/@biomejs/cli-darwin-x64": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.0.tgz",
+ "integrity": "sha512-6LIkhglh3UGjuDqJXsK42qCA0XkD1Ke4K/raFOii7QQPbM8Pia7Qj2Hji4XuF2/R78hRmEx7uKJH3t/Y9UahtQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=14.21.3"
+ }
+ },
+ "node_modules/@biomejs/cli-linux-arm64": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.0.tgz",
+ "integrity": "sha512-uhAsbXySX7xsXahegDg5h3CDgfMcRsJvWLFPG0pjkylgBb9lErbK2C0UINW52zhwg0cPISB09lxHPxCau4e2xA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.21.3"
+ }
+ },
+ "node_modules/@biomejs/cli-linux-arm64-musl": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.0.tgz",
+ "integrity": "sha512-nDksoFdwZ2YrE7NiYDhtMhL2UgFn8Kb7Y0bYvnTAakHnqEdb4lKindtBc1f+xg2Snz0JQhJUYO7r9CDBosRU5w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.21.3"
+ }
+ },
+ "node_modules/@biomejs/cli-linux-x64": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.0.tgz",
+ "integrity": "sha512-uxa8reA2s1VgoH8MhbGlCmMOt3JuSE1vJBifkh1ulaPiuk0SPx8cCdpnm9NWnTe2x/LfWInWx4sZ7muaXTPGGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.21.3"
+ }
+ },
+ "node_modules/@biomejs/cli-linux-x64-musl": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.0.tgz",
+ "integrity": "sha512-+i9UcJwl99uAhtRQDz9jUAh+Xkb097eekxs/D9j4deWDg5/yB/jPWzISe1nBHvlzTXsdUSj0VvB4Go2DSpKIMw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=14.21.3"
+ }
+ },
+ "node_modules/@biomejs/cli-win32-arm64": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.0.tgz",
+ "integrity": "sha512-ynjmsJLIKrAjC3CCnKMMhzcnNy8dbQWjKfSU5YA0mIruTxBNMbkAJp+Pr2iV7/hFou+66ZSD/WV8hmLEmhUaXA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=14.21.3"
+ }
+ },
+ "node_modules/@biomejs/cli-win32-x64": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.0.tgz",
+ "integrity": "sha512-zOCYmCRVkWXc9v8P7OLbLlGGMxQTKMvi+5IC4v7O8DkjLCOHRzRVK/Lno2pGZNo0lzKM60pcQOhH8HVkXMQdFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT OR Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=14.21.3"
+ }
+ },
+ "node_modules/@braintree/sanitize-url": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz",
+ "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==",
+ "license": "MIT"
+ },
+ "node_modules/@chevrotain/cst-dts-gen": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmmirror.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
+ "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/gast": "11.0.3",
+ "@chevrotain/types": "11.0.3",
+ "lodash-es": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/gast": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmmirror.com/@chevrotain/gast/-/gast-11.0.3.tgz",
+ "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/types": "11.0.3",
+ "lodash-es": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/regexp-to-ast": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmmirror.com/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
+ "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/types": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmmirror.com/@chevrotain/types/-/types-11.0.3.tgz",
+ "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/utils": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmmirror.com/@chevrotain/utils/-/utils-11.0.3.tgz",
+ "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.3.tgz",
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.4.tgz",
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.3",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
+ "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
+ "node_modules/@hookform/resolvers": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmmirror.com/@hookform/resolvers/-/resolvers-5.2.2.tgz",
+ "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "react-hook-form": "^7.55.0"
+ }
+ },
+ "node_modules/@iconify/types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz",
+ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
+ "license": "MIT"
+ },
+ "node_modules/@iconify/utils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-3.1.0.tgz",
+ "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==",
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/install-pkg": "^1.1.0",
+ "@iconify/types": "^2.0.0",
+ "mlly": "^1.8.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mermaid-js/parser": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmmirror.com/@mermaid-js/parser/-/parser-0.6.3.tgz",
+ "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==",
+ "license": "MIT",
+ "dependencies": {
+ "langium": "3.3.1"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/number/-/number-1.1.1.tgz",
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-accordion": {
+ "version": "1.2.12",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz",
+ "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collapsible": "1.1.12",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-alert-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz",
+ "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dialog": "1.1.15",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-aspect-ratio": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.8.tgz",
+ "integrity": "sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-aspect-ratio/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-avatar": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz",
+ "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.4",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-is-hydrated": "0.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.3.tgz",
+ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz",
+ "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz",
+ "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
+ "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-icons": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-icons/-/react-icons-1.3.2.tgz",
+ "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-label/-/react-label-2.1.8.tgz",
+ "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+ "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menubar": {
+ "version": "1.1.16",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz",
+ "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-navigation-menu": {
+ "version": "1.2.14",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz",
+ "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
+ "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-progress/-/react-progress-1.1.8.tgz",
+ "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.3.tgz",
+ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz",
+ "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz",
+ "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-select/-/react-select-2.2.6.tgz",
+ "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-separator/-/react-separator-1.1.8.tgz",
+ "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slider": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slider/-/react-slider-1.3.6.tgz",
+ "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
+ "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-switch/-/react-switch-1.2.6.tgz",
+ "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
+ "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.15",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.15.tgz",
+ "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz",
+ "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle-group": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz",
+ "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-toggle": "1.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
+ "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-is-hydrated": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz",
+ "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.5.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.23.1",
+ "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.23.1.tgz",
+ "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
+ "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@shikijs/core": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/core/-/core-3.19.0.tgz",
+ "integrity": "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.19.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4",
+ "hast-util-to-html": "^9.0.5"
+ }
+ },
+ "node_modules/@shikijs/engine-javascript": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-3.19.0.tgz",
+ "integrity": "sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.19.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "oniguruma-to-es": "^4.3.4"
+ }
+ },
+ "node_modules/@shikijs/engine-oniguruma": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-3.19.0.tgz",
+ "integrity": "sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.19.0",
+ "@shikijs/vscode-textmate": "^10.0.2"
+ }
+ },
+ "node_modules/@shikijs/langs": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/langs/-/langs-3.19.0.tgz",
+ "integrity": "sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.19.0"
+ }
+ },
+ "node_modules/@shikijs/themes": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/themes/-/themes-3.19.0.tgz",
+ "integrity": "sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.19.0"
+ }
+ },
+ "node_modules/@shikijs/types": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmmirror.com/@shikijs/types/-/types-3.19.0.tgz",
+ "integrity": "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmmirror.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
+ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmmirror.com/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@supabase/auth-js": {
+ "version": "2.86.0",
+ "resolved": "https://registry.npmmirror.com/@supabase/auth-js/-/auth-js-2.86.0.tgz",
+ "integrity": "sha512-3xPqMvBWC6Haqpr6hEWmSUqDq+6SA1BAEdbiaHdAZM9QjZ5uiQJ+6iD9pZOzOa6MVXZh4GmwjhC9ObIG0K1NcA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/functions-js": {
+ "version": "2.86.0",
+ "resolved": "https://registry.npmmirror.com/@supabase/functions-js/-/functions-js-2.86.0.tgz",
+ "integrity": "sha512-AlOoVfeaq9XGlBFIyXTmb+y+CZzxNO4wWbfgRM6iPpNU5WCXKawtQYSnhivi3UVxS7GA0rWovY4d6cIAxZAojA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/postgrest-js": {
+ "version": "2.86.0",
+ "resolved": "https://registry.npmmirror.com/@supabase/postgrest-js/-/postgrest-js-2.86.0.tgz",
+ "integrity": "sha512-QVf+wIXILcZJ7IhWhWn+ozdf8B+oO0Ulizh2AAPxD/6nQL+x3r9lJ47a+fpc/jvAOGXMbkeW534Kw6jz7e8iIA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/realtime-js": {
+ "version": "2.86.0",
+ "resolved": "https://registry.npmmirror.com/@supabase/realtime-js/-/realtime-js-2.86.0.tgz",
+ "integrity": "sha512-dyS8bFoP29R/sj5zLi0AP3JfgG8ar1nuImcz5jxSx7UIW7fbFsXhUCVrSY2Ofo0+Ev6wiATiSdBOzBfWaiFyPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/phoenix": "^1.6.6",
+ "@types/ws": "^8.18.1",
+ "tslib": "2.8.1",
+ "ws": "^8.18.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/storage-js": {
+ "version": "2.86.0",
+ "resolved": "https://registry.npmmirror.com/@supabase/storage-js/-/storage-js-2.86.0.tgz",
+ "integrity": "sha512-PM47jX/Mfobdtx7NNpoj9EvlrkapAVTQBZgGGslEXD6NS70EcGjhgRPBItwHdxZPM5GwqQ0cGMN06uhjeY2mHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "iceberg-js": "^0.8.0",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@supabase/supabase-js": {
+ "version": "2.86.0",
+ "resolved": "https://registry.npmmirror.com/@supabase/supabase-js/-/supabase-js-2.86.0.tgz",
+ "integrity": "sha512-BaC9sv5+HGNy1ulZwY8/Ev7EjfYYmWD4fOMw9bDBqTawEj6JHAiOHeTwXLRzVaeSay4p17xYLN2NSCoGgXMQnw==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/auth-js": "2.86.0",
+ "@supabase/functions-js": "2.86.0",
+ "@supabase/postgrest-js": "2.86.0",
+ "@supabase/realtime-js": "2.86.0",
+ "@supabase/storage-js": "2.86.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-add-jsx-attribute": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
+ "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz",
+ "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz",
+ "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz",
+ "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-dynamic-title": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz",
+ "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-svg-em-dimensions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz",
+ "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-react-native-svg": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz",
+ "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-plugin-transform-svg-component": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz",
+ "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/babel-preset": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz",
+ "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@svgr/babel-plugin-add-jsx-attribute": "8.0.0",
+ "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0",
+ "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0",
+ "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0",
+ "@svgr/babel-plugin-svg-dynamic-title": "8.0.0",
+ "@svgr/babel-plugin-svg-em-dimensions": "8.0.0",
+ "@svgr/babel-plugin-transform-react-native-svg": "8.1.0",
+ "@svgr/babel-plugin-transform-svg-component": "8.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@svgr/core": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/core/-/core-8.1.0.tgz",
+ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "8.1.0",
+ "camelcase": "^6.2.0",
+ "cosmiconfig": "^8.1.3",
+ "snake-case": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/hast-util-to-babel-ast": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
+ "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.21.3",
+ "entities": "^4.4.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ }
+ },
+ "node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/@svgr/plugin-jsx": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmmirror.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
+ "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.21.3",
+ "@svgr/babel-preset": "8.1.0",
+ "@svgr/hast-util-to-babel-ast": "8.0.0",
+ "svg-parser": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/gregberge"
+ },
+ "peerDependencies": {
+ "@svgr/core": "*"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/d3": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmmirror.com/@types/d3/-/d3-7.4.3.tgz",
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-delaunay": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-zoom": "*"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-axis": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmmirror.com/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-brush": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmmirror.com/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-chord": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmmirror.com/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-contour": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmmirror.com/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-dispatch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
+ "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-dsv": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmmirror.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-fetch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmmirror.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-dsv": "*"
+ }
+ },
+ "node_modules/@types/d3-force": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmmirror.com/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-format": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.4.tgz",
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-geo": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-hierarchy": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmmirror.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-polygon": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-quadtree": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmmirror.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-random": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/@types/d3-random/-/d3-random-3.0.3.tgz",
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+ "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-time-format": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree-jsx": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmmirror.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/katex": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.16.7.tgz",
+ "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.10.1",
+ "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.1.tgz",
+ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/phoenix": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmmirror.com/@types/phoenix/-/phoenix-1.6.6.tgz",
+ "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.7",
+ "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.7.tgz",
+ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/@types/video-react": {
+ "version": "0.15.8",
+ "resolved": "https://registry.npmmirror.com/@types/video-react/-/video-react-0.15.8.tgz",
+ "integrity": "sha512-ZFm57z6bwJ1FWMKAMXyDar0OAQCchals2T4mDG//JXeToW3C2dADI2MzX5y53tFL77Y2QAA1YQZh5XTL1rjiqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@typescript/native-preview": {
+ "version": "7.0.0-dev.20251024.1",
+ "resolved": "https://registry.npmmirror.com/@typescript/native-preview/-/native-preview-7.0.0-dev.20251024.1.tgz",
+ "integrity": "sha512-ZoJSN/ymGSBtNim3QQt85UmEHk4rN93fL6wiNHoN/84f/ZD/1RRbCQqo885DnQy+doh8fes90E7a2iJGKlU9ng==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsgo": "bin/tsgo.js"
+ },
+ "optionalDependencies": {
+ "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251024.1",
+ "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251024.1",
+ "@typescript/native-preview-linux-arm": "7.0.0-dev.20251024.1",
+ "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251024.1",
+ "@typescript/native-preview-linux-x64": "7.0.0-dev.20251024.1",
+ "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251024.1",
+ "@typescript/native-preview-win32-x64": "7.0.0-dev.20251024.1"
+ }
+ },
+ "node_modules/@typescript/native-preview-darwin-arm64": {
+ "version": "7.0.0-dev.20251024.1",
+ "resolved": "https://registry.npmmirror.com/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20251024.1.tgz",
+ "integrity": "sha512-r6fQMgUlNmpJBw0DOt+pxaLSoSeH7GraK4c9Q/HCaAaPO6AjwkgadURhj4AMpxzdTE62Ra3Xs28q0N2ybXTC+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@typescript/native-preview-darwin-x64": {
+ "version": "7.0.0-dev.20251024.1",
+ "resolved": "https://registry.npmmirror.com/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20251024.1.tgz",
+ "integrity": "sha512-bI+oyej2pY/k0AZhYdSbvxuUACVenXRhEjqAbjxBivElqk6gfsTTItMPZRVgRP0QLlKsEPVI75/jaugewqKkJw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@typescript/native-preview-linux-arm": {
+ "version": "7.0.0-dev.20251024.1",
+ "resolved": "https://registry.npmmirror.com/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20251024.1.tgz",
+ "integrity": "sha512-WFclQefBXD+bfD3vhZpPUc01dQnQ1GLZj8K5ksGEqHLTQF5xH5C9aa2yGia4BXWuOT1PIYX+/AyM7sa+lXuX+g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@typescript/native-preview-linux-arm64": {
+ "version": "7.0.0-dev.20251024.1",
+ "resolved": "https://registry.npmmirror.com/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20251024.1.tgz",
+ "integrity": "sha512-HjfUIyGGGcA8os/MpS2AX+JowqEIVsra7d95S5oAKGpcGBjZCggH/bn9DvijIp42D+eXYuGVA1OwL0hgLWLLSw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@typescript/native-preview-linux-x64": {
+ "version": "7.0.0-dev.20251024.1",
+ "resolved": "https://registry.npmmirror.com/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20251024.1.tgz",
+ "integrity": "sha512-I3/00X+yh5esr4F7cq4Qis5JKeznNBzIWjQDY/KnPyi3pRcEhXNlF38C0Cle6pxis3SQTRIZ548DfWmrq0KtFA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@typescript/native-preview-win32-arm64": {
+ "version": "7.0.0-dev.20251024.1",
+ "resolved": "https://registry.npmmirror.com/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20251024.1.tgz",
+ "integrity": "sha512-8tWfDf/7dGXZXQaaK+l0G8qJoYhnRe2bN+CH87LCvuw5crzd/jJYiHW1VUxTSTYsS0mAanJ0PvXcDYu7BrjDZQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@typescript/native-preview-win32-x64": {
+ "version": "7.0.0-dev.20251024.1",
+ "resolved": "https://registry.npmmirror.com/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20251024.1.tgz",
+ "integrity": "sha512-cYIc6FiS5xCDNBzs9X0MuV/IS1sfg5WV//m9u7PcYLOv4zdQsHsEfqneTddtAjhjk2fiagO8PAkEEnT0CGd4jg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "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.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@xagi/vite-plugin-design-mode": {
+ "version": "1.0.27",
+ "resolved": "https://registry.npmmirror.com/@xagi/vite-plugin-design-mode/-/vite-plugin-design-mode-1.0.27.tgz",
+ "integrity": "sha512-ENfY/JOnMfi9PusMDJBwD3dJSRejWw8phIVvbnWdFCfhbhEeOl1gpSR1iUyyndV5VqkR2Z26IhyBjCDmBRJURg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/standalone": "^7.23.0",
+ "@babel/traverse": "^7.24.0",
+ "clsx": "^2.1.1",
+ "tailwind-merge": "^3.4.0"
+ },
+ "bin": {
+ "vite-plugin-design-mode": "dist/cli/index.js"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0",
+ "@babel/types": "^7.0.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0",
+ "vite": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": false
+ },
+ "react-dom": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmmirror.com/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/attr-accept": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmmirror.com/attr-accept/-/attr-accept-2.2.5.tgz",
+ "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.22",
+ "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.22.tgz",
+ "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.27.0",
+ "caniuse-lite": "^1.0.30001754",
+ "fraction.js": "^5.3.4",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.0.tgz",
+ "integrity": "sha512-Mh++g+2LPfzZToywfE1BUzvZbfOY52Nil0rn9H1CPC5DJ7fX+Vir7nToBeoiSbB1zTNeGYbELEvJESujgGrzXw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001759",
+ "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz",
+ "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chevrotain": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmmirror.com/chevrotain/-/chevrotain-11.0.3.tgz",
+ "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/cst-dts-gen": "11.0.3",
+ "@chevrotain/gast": "11.0.3",
+ "@chevrotain/regexp-to-ast": "11.0.3",
+ "@chevrotain/types": "11.0.3",
+ "@chevrotain/utils": "11.0.3",
+ "lodash-es": "4.17.21"
+ }
+ },
+ "node_modules/chevrotain-allstar": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmmirror.com/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz",
+ "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash-es": "^4.17.21"
+ },
+ "peerDependencies": {
+ "chevrotain": "^11.0.0"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cmdk": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/cmdk/-/cmdk-1.1.1.tgz",
+ "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "^1.1.1",
+ "@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-id": "^1.1.0",
+ "@radix-ui/react-primitive": "^2.0.2"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cose-base": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz",
+ "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+ "license": "MIT",
+ "dependencies": {
+ "layout-base": "^1.0.0"
+ }
+ },
+ "node_modules/cosmiconfig": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/cytoscape": {
+ "version": "3.33.1",
+ "resolved": "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.33.1.tgz",
+ "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/cytoscape-cose-bilkent": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+ "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cose-base": "^1.0.0"
+ },
+ "peerDependencies": {
+ "cytoscape": "^3.2.0"
+ }
+ },
+ "node_modules/cytoscape-fcose": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
+ "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cose-base": "^2.2.0"
+ },
+ "peerDependencies": {
+ "cytoscape": "^3.2.0"
+ }
+ },
+ "node_modules/cytoscape-fcose/node_modules/cose-base": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-2.2.0.tgz",
+ "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
+ "license": "MIT",
+ "dependencies": {
+ "layout-base": "^2.0.0"
+ }
+ },
+ "node_modules/cytoscape-fcose/node_modules/layout-base": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-2.0.1.tgz",
+ "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
+ "license": "MIT"
+ },
+ "node_modules/d3": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz",
+ "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "3",
+ "d3-axis": "3",
+ "d3-brush": "3",
+ "d3-chord": "3",
+ "d3-color": "3",
+ "d3-contour": "4",
+ "d3-delaunay": "6",
+ "d3-dispatch": "3",
+ "d3-drag": "3",
+ "d3-dsv": "3",
+ "d3-ease": "3",
+ "d3-fetch": "3",
+ "d3-force": "3",
+ "d3-format": "3",
+ "d3-geo": "3",
+ "d3-hierarchy": "3",
+ "d3-interpolate": "3",
+ "d3-path": "3",
+ "d3-polygon": "3",
+ "d3-quadtree": "3",
+ "d3-random": "3",
+ "d3-scale": "4",
+ "d3-scale-chromatic": "3",
+ "d3-selection": "3",
+ "d3-shape": "3",
+ "d3-time": "3",
+ "d3-time-format": "4",
+ "d3-timer": "3",
+ "d3-transition": "3",
+ "d3-zoom": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-axis": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz",
+ "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-brush": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz",
+ "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "3",
+ "d3-transition": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-chord": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz",
+ "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-contour": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz",
+ "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+ "license": "ISC",
+ "dependencies": {
+ "delaunator": "5"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dsv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz",
+ "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+ "license": "ISC",
+ "dependencies": {
+ "commander": "7",
+ "iconv-lite": "0.6",
+ "rw": "1"
+ },
+ "bin": {
+ "csv2json": "bin/dsv2json.js",
+ "csv2tsv": "bin/dsv2dsv.js",
+ "dsv2dsv": "bin/dsv2dsv.js",
+ "dsv2json": "bin/dsv2json.js",
+ "json2csv": "bin/json2dsv.js",
+ "json2dsv": "bin/json2dsv.js",
+ "json2tsv": "bin/json2dsv.js",
+ "tsv2csv": "bin/dsv2dsv.js",
+ "tsv2json": "bin/dsv2json.js"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dsv/node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-fetch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz",
+ "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dsv": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-force": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz",
+ "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-quadtree": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz",
+ "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-geo": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz",
+ "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.5.0 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-hierarchy": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+ "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-polygon": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz",
+ "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-quadtree": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+ "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-random": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz",
+ "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-sankey": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmmirror.com/d3-sankey/-/d3-sankey-0.12.3.tgz",
+ "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "d3-array": "1 - 2",
+ "d3-shape": "^1.2.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-path": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-1.0.9.tgz",
+ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/d3-sankey/node_modules/d3-shape": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-1.3.7.tgz",
+ "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "d3-path": "1"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
+ "license": "ISC"
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-interpolate": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dagre-d3-es": {
+ "version": "7.0.13",
+ "resolved": "https://registry.npmmirror.com/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz",
+ "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "d3": "^7.9.0",
+ "lodash-es": "^4.17.21"
+ }
+ },
+ "node_modules/date-fns": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz",
+ "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.19",
+ "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz",
+ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
+ "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/delaunator": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz",
+ "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
+ "license": "ISC",
+ "dependencies": {
+ "robust-predicates": "^3.0.2"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+ "license": "MIT"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "license": "MIT"
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/dompurify": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.0.tgz",
+ "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
+ "node_modules/dot-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/dot-case/-/dot-case-3.0.4.tgz",
+ "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "no-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.264",
+ "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.264.tgz",
+ "integrity": "sha512-1tEf0nLgltC3iy9wtlYDlQDc5Rg9lEKVjEmIHJ21rI9OcqkvD45K1oyNIRA4rR1z3LgJ7KeGzEBojVcV6m4qjA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/embla-carousel": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmmirror.com/embla-carousel/-/embla-carousel-8.6.0.tgz",
+ "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
+ "license": "MIT"
+ },
+ "node_modules/embla-carousel-react": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmmirror.com/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz",
+ "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==",
+ "license": "MIT",
+ "dependencies": {
+ "embla-carousel": "8.6.0",
+ "embla-carousel-reactive-utils": "8.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/embla-carousel-reactive-utils": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmmirror.com/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz",
+ "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "embla-carousel": "8.6.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-util-is-identifier-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
+ "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/fast-equals": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-5.3.3.tgz",
+ "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-selector": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmmirror.com/file-selector/-/file-selector-2.1.2.tgz",
+ "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.7.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hachure-fill": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmmirror.com/hachure-fill/-/hachure-fill-0.5.2.tgz",
+ "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
+ "license": "MIT"
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hast": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/hast/-/hast-1.0.0.tgz",
+ "integrity": "sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA==",
+ "deprecated": "Renamed to rehype",
+ "license": "MIT"
+ },
+ "node_modules/hast-util-from-dom": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz",
+ "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==",
+ "license": "ISC",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hastscript": "^9.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
+ "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.1.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "parse5": "^7.0.0",
+ "vfile": "^6.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html-isomorphic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz",
+ "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-from-dom": "^5.0.0",
+ "hast-util-from-html": "^2.0.0",
+ "unist-util-remove-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmmirror.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
+ "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "devlop": "^1.0.0",
+ "hastscript": "^9.0.0",
+ "property-information": "^7.0.0",
+ "vfile": "^6.0.0",
+ "vfile-location": "^5.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-is-element": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+ "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-parse-selector": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
+ "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-raw": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmmirror.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz",
+ "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "hast-util-to-parse5": "^8.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "parse5": "^7.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-html": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmmirror.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
+ "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "stringify-entities": "^4.0.0",
+ "zwitch": "^2.0.4"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-jsx-runtime": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-js": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz",
+ "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-parse5/node_modules/property-information": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmmirror.com/property-information/-/property-information-6.5.0.tgz",
+ "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/hast-util-to-text": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
+ "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "hast-util-is-element": "^3.0.0",
+ "unist-util-find-after": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hastscript": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-9.0.1.tgz",
+ "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^4.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/html-url-attributes": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/iceberg-js": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmmirror.com/iceberg-js/-/iceberg-js-0.8.0.tgz",
+ "integrity": "sha512-kmgmea2nguZEvRqW79gDqNXyxA3OS5WIgMVffrHpqXV4F/J4UmNIw2vstixioLTNSkd5rFB8G0s3Lwzogm6OFw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
+ "license": "MIT"
+ },
+ "node_modules/input-otp": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmmirror.com/input-otp/-/input-otp-1.4.2.tgz",
+ "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/katex": {
+ "version": "0.16.25",
+ "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.25.tgz",
+ "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==",
+ "funding": [
+ "https://opencollective.com/katex",
+ "https://github.com/sponsors/katex"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^8.3.0"
+ },
+ "bin": {
+ "katex": "cli.js"
+ }
+ },
+ "node_modules/khroma": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/khroma/-/khroma-2.1.0.tgz",
+ "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
+ },
+ "node_modules/ky": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmmirror.com/ky/-/ky-1.14.0.tgz",
+ "integrity": "sha512-Rczb6FMM6JT0lvrOlP5WUOCB7s9XKxzwgErzhKlKde1bEV90FXplV1o87fpt4PU/asJFiqjYJxAJyzJhcrxOsQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/ky?sponsor=1"
+ }
+ },
+ "node_modules/langium": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/langium/-/langium-3.3.1.tgz",
+ "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==",
+ "license": "MIT",
+ "dependencies": {
+ "chevrotain": "~11.0.3",
+ "chevrotain-allstar": "~0.3.0",
+ "vscode-languageserver": "~9.0.1",
+ "vscode-languageserver-textdocument": "~1.0.11",
+ "vscode-uri": "~3.0.8"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/layout-base": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-1.0.2.tgz",
+ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
+ "license": "MIT"
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.throttle": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+ "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
+ "license": "MIT"
+ },
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lower-case": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/lower-case/-/lower-case-2.0.2.tgz",
+ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.548.0",
+ "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.548.0.tgz",
+ "integrity": "sha512-63b16z63jM9yc1MwxajHeuu0FRZFsDtljtDjYm26Kd86UQ5HQzu9ksEtoUUw4RBuewodw/tGFmvipePvRsKeDA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/markdown-table": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.4.tgz",
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/marked": {
+ "version": "16.4.2",
+ "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz",
+ "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
+ "mdast-util-gfm-footnote": "^2.0.0",
+ "mdast-util-gfm-strikethrough": "^2.0.0",
+ "mdast-util-gfm-table": "^2.0.0",
+ "mdast-util-gfm-task-list-item": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-find-and-replace": "^3.0.0",
+ "micromark-util-character": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-math": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-math/-/mdast-util-math-3.0.0.tgz",
+ "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.1.0",
+ "unist-util-remove-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-expression": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdxjs-esm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/mermaid": {
+ "version": "11.12.2",
+ "resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-11.12.2.tgz",
+ "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@braintree/sanitize-url": "^7.1.1",
+ "@iconify/utils": "^3.0.1",
+ "@mermaid-js/parser": "^0.6.3",
+ "@types/d3": "^7.4.3",
+ "cytoscape": "^3.29.3",
+ "cytoscape-cose-bilkent": "^4.1.0",
+ "cytoscape-fcose": "^2.2.0",
+ "d3": "^7.9.0",
+ "d3-sankey": "^0.12.3",
+ "dagre-d3-es": "7.0.13",
+ "dayjs": "^1.11.18",
+ "dompurify": "^3.2.5",
+ "katex": "^0.16.22",
+ "khroma": "^2.1.0",
+ "lodash-es": "^4.17.21",
+ "marked": "^16.2.1",
+ "roughjs": "^4.6.6",
+ "stylis": "^4.3.6",
+ "ts-dedent": "^2.2.0",
+ "uuid": "^11.1.0"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-cjk-friendly": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-cjk-friendly/-/micromark-extension-cjk-friendly-1.2.3.tgz",
+ "integrity": "sha512-gRzVLUdjXBLX6zNPSnHGDoo+ZTp5zy+MZm0g3sv+3chPXY7l9gW+DnrcHcZh/jiPR6MjPKO4AEJNp4Aw6V9z5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.1.0",
+ "micromark-extension-cjk-friendly-util": "2.1.1",
+ "micromark-util-chunked": "^2.0.1",
+ "micromark-util-resolve-all": "^2.0.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "micromark": "^4.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "micromark-util-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/micromark-extension-cjk-friendly-gfm-strikethrough": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-cjk-friendly-gfm-strikethrough/-/micromark-extension-cjk-friendly-gfm-strikethrough-1.2.3.tgz",
+ "integrity": "sha512-gSPnxgHDDqXYOBvQRq6lerrq9mjDhdtKn+7XETuXjxWcL62yZEfUdA28Ml1I2vDIPfAOIKLa0h2XDSGkInGHFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.1.0",
+ "get-east-asian-width": "^1.3.0",
+ "micromark-extension-cjk-friendly-util": "2.1.1",
+ "micromark-util-character": "^2.1.1",
+ "micromark-util-chunked": "^2.0.1",
+ "micromark-util-resolve-all": "^2.0.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "micromark": "^4.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "micromark-util-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/micromark-extension-cjk-friendly-util": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-cjk-friendly-util/-/micromark-extension-cjk-friendly-util-2.1.1.tgz",
+ "integrity": "sha512-egs6+12JU2yutskHY55FyR48ZiEcFOJFyk9rsiyIhcJ6IvWB6ABBqVrBw8IobqJTDZ/wdSr9eoXDPb5S2nW1bg==",
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.0",
+ "micromark-util-character": "^2.1.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependenciesMeta": {
+ "micromark-util-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/micromark-extension-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
+ "micromark-extension-gfm-footnote": "^2.0.0",
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
+ "micromark-extension-gfm-table": "^2.0.0",
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-math": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz",
+ "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/katex": "^0.16.0",
+ "devlop": "^1.0.0",
+ "katex": "^0.16.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz",
+ "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.1"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmmirror.com/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/no-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/no-case/-/no-case-3.0.4.tgz",
+ "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lower-case": "^2.0.2",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/oniguruma-parser": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmmirror.com/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
+ "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==",
+ "license": "MIT"
+ },
+ "node_modules/oniguruma-to-es": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz",
+ "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==",
+ "license": "MIT",
+ "dependencies": {
+ "oniguruma-parser": "^0.12.1",
+ "regex": "^6.0.1",
+ "regex-recursion": "^6.0.2"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/package-manager-detector": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.6.0.tgz",
+ "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
+ "license": "MIT"
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-entities": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz",
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-entities/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/path-data-parser": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmmirror.com/path-data-parser/-/path-data-parser-0.1.0.tgz",
+ "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==",
+ "license": "MIT"
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/points-on-curve": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmmirror.com/points-on-curve/-/points-on-curve-0.2.0.tgz",
+ "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==",
+ "license": "MIT"
+ },
+ "node_modules/points-on-path": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmmirror.com/points-on-path/-/points-on-path-0.2.1.tgz",
+ "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==",
+ "license": "MIT",
+ "dependencies": {
+ "path-data-parser": "0.1.0",
+ "points-on-curve": "0.2.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/property-information": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.1.0.tgz",
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/qrcode": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz",
+ "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+ "license": "MIT",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-day-picker": {
+ "version": "8.10.1",
+ "resolved": "https://registry.npmmirror.com/react-day-picker/-/react-day-picker-8.10.1.tgz",
+ "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==",
+ "license": "MIT",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/gpbl"
+ },
+ "peerDependencies": {
+ "date-fns": "^2.28.0 || ^3.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-dropzone": {
+ "version": "14.3.8",
+ "resolved": "https://registry.npmmirror.com/react-dropzone/-/react-dropzone-14.3.8.tgz",
+ "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "attr-accept": "^2.2.4",
+ "file-selector": "^2.1.0",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">= 10.13"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8 || 18.0.0"
+ }
+ },
+ "node_modules/react-fast-compare": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-helmet-async": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmmirror.com/react-helmet-async/-/react-helmet-async-2.0.5.tgz",
+ "integrity": "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "invariant": "^2.2.4",
+ "react-fast-compare": "^3.2.2",
+ "shallowequal": "^1.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.6.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-hook-form": {
+ "version": "7.68.0",
+ "resolved": "https://registry.npmmirror.com/react-hook-form/-/react-hook-form-7.68.0.tgz",
+ "integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmmirror.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-resizable-panels": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmmirror.com/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz",
+ "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.10.0",
+ "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.10.0.tgz",
+ "integrity": "sha512-FVyCOH4IZ0eDDRycODfUqoN8ZSR2LbTvtx6RPsBgzvJ8xAXlMZNCrOFpu+jb8QbtZnpAd/cEki2pwE848pNGxw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.30.2",
+ "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.30.2.tgz",
+ "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.1",
+ "react-router": "6.30.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom/node_modules/react-router": {
+ "version": "6.30.2",
+ "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.30.2.tgz",
+ "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-equals": "^5.0.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmmirror.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmmirror.com/recharts/-/recharts-2.15.4.tgz",
+ "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmmirror.com/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "license": "MIT",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/recharts/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmmirror.com/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
+ "node_modules/regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/regex/-/regex-6.0.1.tgz",
+ "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==",
+ "license": "MIT",
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-recursion": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmmirror.com/regex-recursion/-/regex-recursion-6.0.2.tgz",
+ "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
+ "license": "MIT",
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-utilities": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/regex-utilities/-/regex-utilities-2.3.0.tgz",
+ "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
+ "license": "MIT"
+ },
+ "node_modules/rehype-harden": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmmirror.com/rehype-harden/-/rehype-harden-1.1.6.tgz",
+ "integrity": "sha512-5WyX6BFEWYmmbCF/S2gNRklfgPGTiGjviAjbseO4XlpqEilWBkvWwve6uU/JB3C0JvG/qxCZa3rBn8+ajy4i/A==",
+ "license": "MIT",
+ "dependencies": {
+ "unist-util-visit": "^5.0.0"
+ }
+ },
+ "node_modules/rehype-katex": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmmirror.com/rehype-katex/-/rehype-katex-7.0.1.tgz",
+ "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/katex": "^0.16.0",
+ "hast-util-from-html-isomorphic": "^2.0.0",
+ "hast-util-to-text": "^4.0.0",
+ "katex": "^0.16.0",
+ "unist-util-visit-parents": "^6.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-raw": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/rehype-raw/-/rehype-raw-7.0.0.tgz",
+ "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-raw": "^9.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-cjk-friendly": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/remark-cjk-friendly/-/remark-cjk-friendly-1.2.3.tgz",
+ "integrity": "sha512-UvAgxwlNk+l9Oqgl/9MWK2eWRS7zgBW/nXX9AthV7nd/3lNejF138E7Xbmk9Zs4WjTJGs721r7fAEc7tNFoH7g==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-cjk-friendly": "1.2.3"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "@types/mdast": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/mdast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/remark-cjk-friendly-gfm-strikethrough": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/remark-cjk-friendly-gfm-strikethrough/-/remark-cjk-friendly-gfm-strikethrough-1.2.3.tgz",
+ "integrity": "sha512-bXfMZtsaomK6ysNN/UGRIcasQAYkC10NtPmP0oOHOV8YOhA2TXmwRXCku4qOzjIFxAPfish5+XS0eIug2PzNZA==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-cjk-friendly-gfm-strikethrough": "1.2.3"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "@types/mdast": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/mdast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/remark-gfm": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-4.0.1.tgz",
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-gfm": "^3.0.0",
+ "micromark-extension-gfm": "^3.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-stringify": "^11.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-math": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/remark-math/-/remark-math-6.0.0.tgz",
+ "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-math": "^3.0.0",
+ "micromark-extension-math": "^3.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz",
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "11.1.2",
+ "resolved": "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-11.1.2.tgz",
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-stringify": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmmirror.com/remark-stringify/-/remark-stringify-11.0.0.tgz",
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remend": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/remend/-/remend-1.0.1.tgz",
+ "integrity": "sha512-152puVH0qMoRJQFnaMG+rVDdf01Jq/CaED+MBuXExurJgdbkLp0c3TIe4R12o28Klx8uyGsjvFNG05aFG69G9w==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "license": "ISC"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/robust-predicates": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz",
+ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
+ "license": "Unlicense"
+ },
+ "node_modules/rollup": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.53.3.tgz",
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
+ "@rollup/rollup-android-arm64": "4.53.3",
+ "@rollup/rollup-darwin-arm64": "4.53.3",
+ "@rollup/rollup-darwin-x64": "4.53.3",
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
+ "@rollup/rollup-freebsd-x64": "4.53.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/roughjs": {
+ "version": "4.6.6",
+ "resolved": "https://registry.npmmirror.com/roughjs/-/roughjs-4.6.6.tgz",
+ "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "hachure-fill": "^0.5.2",
+ "path-data-parser": "^0.1.0",
+ "points-on-curve": "^0.2.0",
+ "points-on-path": "^0.2.1"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "license": "ISC"
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
+ "node_modules/shiki": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmmirror.com/shiki/-/shiki-3.19.0.tgz",
+ "integrity": "sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/core": "3.19.0",
+ "@shikijs/engine-javascript": "3.19.0",
+ "@shikijs/engine-oniguruma": "3.19.0",
+ "@shikijs/langs": "3.19.0",
+ "@shikijs/themes": "3.19.0",
+ "@shikijs/types": "3.19.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/snake-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/snake-case/-/snake-case-3.0.4.tgz",
+ "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dot-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/sonner": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmmirror.com/sonner/-/sonner-2.0.7.tgz",
+ "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/streamdown": {
+ "version": "1.6.10",
+ "resolved": "https://registry.npmmirror.com/streamdown/-/streamdown-1.6.10.tgz",
+ "integrity": "sha512-B4Y3Z/qiXl1Dc+LzAB5c52Cd1QGRiFjaDwP+ERoj1JtCykdRDM8X6HwQnn3YkpkSk0x3R7S/6LrGe1nQiElHQQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "hast": "^1.0.0",
+ "hast-util-to-jsx-runtime": "^2.3.6",
+ "html-url-attributes": "^3.0.1",
+ "katex": "^0.16.22",
+ "lucide-react": "^0.542.0",
+ "marked": "^16.2.1",
+ "mermaid": "^11.11.0",
+ "rehype-harden": "^1.1.6",
+ "rehype-katex": "^7.0.1",
+ "rehype-raw": "^7.0.0",
+ "remark-cjk-friendly": "^1.2.3",
+ "remark-cjk-friendly-gfm-strikethrough": "^1.2.3",
+ "remark-gfm": "^4.0.1",
+ "remark-math": "^6.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.1.2",
+ "remend": "1.0.1",
+ "shiki": "^3.12.2",
+ "tailwind-merge": "^3.3.1",
+ "unified": "^11.0.5",
+ "unist-util-visit": "^5.0.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/streamdown/node_modules/lucide-react": {
+ "version": "0.542.0",
+ "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.542.0.tgz",
+ "integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/style-to-js": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.21.tgz",
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "style-to-object": "1.0.14"
+ }
+ },
+ "node_modules/style-to-object": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.14.tgz",
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
+ "license": "MIT",
+ "dependencies": {
+ "inline-style-parser": "0.2.7"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz",
+ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
+ "license": "MIT"
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/svg-parser": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmmirror.com/svg-parser/-/svg-parser-2.0.4.tgz",
+ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tailwind-merge": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
+ "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.18",
+ "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.18.tgz",
+ "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss-animate": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
+ "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.2.tgz",
+ "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/ts-dedent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/ts-dedent/-/ts-dedent-2.2.0.tgz",
+ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.10"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz",
+ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "license": "MIT"
+ },
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmmirror.com/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-find-after": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
+ "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz",
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-remove-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz",
+ "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz",
+ "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/uuid": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
+ "node_modules/vaul": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/vaul/-/vaul-1.1.2.tgz",
+ "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-location": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmmirror.com/vfile-location/-/vfile-location-5.0.3.tgz",
+ "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz",
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/victory-vendor": {
+ "version": "36.9.2",
+ "resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "license": "MIT AND ISC",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
+ "node_modules/video-react": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmmirror.com/video-react/-/video-react-0.16.0.tgz",
+ "integrity": "sha512-138NHPS8bmgqCYVCdbv2GVFhXntemNHWGw9AN8iJSzr3jizXMmWJd2LTBppr4hZJUbyW1A1tPZ3CQXZUaexMVA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.4.5",
+ "classnames": "^2.2.6",
+ "lodash.throttle": "^4.1.1",
+ "prop-types": "^15.7.2",
+ "redux": "^4.0.1"
+ },
+ "peerDependencies": {
+ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-svgr": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmmirror.com/vite-plugin-svgr/-/vite-plugin-svgr-4.5.0.tgz",
+ "integrity": "sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.2.0",
+ "@svgr/core": "^8.1.0",
+ "@svgr/plugin-jsx": "^8.1.0"
+ },
+ "peerDependencies": {
+ "vite": ">=2.6.0"
+ }
+ },
+ "node_modules/vscode-jsonrpc": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
+ "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/vscode-languageserver": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmmirror.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
+ "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-languageserver-protocol": "3.17.5"
+ },
+ "bin": {
+ "installServerIntoExtension": "bin/installServerIntoExtension"
+ }
+ },
+ "node_modules/vscode-languageserver-protocol": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
+ "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-jsonrpc": "8.2.0",
+ "vscode-languageserver-types": "3.17.5"
+ }
+ },
+ "node_modules/vscode-languageserver-textdocument": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmmirror.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
+ "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-languageserver-types": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
+ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-uri": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.0.8.tgz",
+ "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
+ "license": "MIT"
+ },
+ "node_modules/web-namespaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/web-namespaces/-/web-namespaces-2.0.1.tgz",
+ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "license": "ISC"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "license": "ISC"
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs-parser/node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/package.json b/qiming-vite-plugin-design-mode/examples/react-vite-md2/package.json
new file mode 100644
index 00000000..81e51004
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/package.json
@@ -0,0 +1,90 @@
+{
+ "name": "react-admin",
+ "version": "0.0.1",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "build:production": "NODE_ENV=production vite build"
+ },
+ "dependencies": {
+ "@hookform/resolvers": "^5.2.2",
+ "@radix-ui/react-accordion": "^1.2.12",
+ "@radix-ui/react-alert-dialog": "^1.1.15",
+ "@radix-ui/react-aspect-ratio": "^1.1.7",
+ "@radix-ui/react-avatar": "^1.1.10",
+ "@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-collapsible": "^1.1.12",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-icons": "^1.3.2",
+ "@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-menubar": "^1.1.16",
+ "@radix-ui/react-navigation-menu": "^1.2.14",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.7",
+ "@radix-ui/react-radio-group": "^1.3.8",
+ "@radix-ui/react-scroll-area": "^1.2.10",
+ "@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.7",
+ "@radix-ui/react-slider": "^1.3.6",
+ "@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tabs": "^1.1.13",
+ "@radix-ui/react-toast": "^1.2.15",
+ "@radix-ui/react-toggle": "^1.1.10",
+ "@radix-ui/react-toggle-group": "^1.1.11",
+ "@radix-ui/react-tooltip": "^1.2.8",
+ "@supabase/supabase-js": "^2.76.1",
+ "axios": "^1.12.2",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
+ "date-fns": "^3.6.0",
+ "embla-carousel-react": "^8.6.0",
+ "eventsource-parser": "^3.0.6",
+ "input-otp": "^1.4.2",
+ "ky": "^1.13.0",
+ "lucide-react": "^0.548.0",
+ "next-themes": "^0.4.6",
+ "qrcode": "^1.5.4",
+ "react": "^18.0.0",
+ "react-day-picker": "^8.10.1",
+ "react-dom": "^18.0.0",
+ "react-dropzone": "^14.3.8",
+ "react-helmet-async": "^2.0.5",
+ "react-hook-form": "^7.65.0",
+ "react-resizable-panels": "^2.1.8",
+ "react-router": "^7.9.4",
+ "react-router-dom": "^6.30.0",
+ "recharts": "^2.15.3",
+ "sonner": "^2.0.7",
+ "streamdown": "^1.4.0",
+ "tailwind-merge": "^3.3.1",
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^1.1.2",
+ "video-react": "^0.16.0",
+ "zod": "^3.25.76"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "2.3.0",
+ "@types/lodash": "^4.17.20",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.2",
+ "@types/video-react": "^0.15.8",
+ "@typescript/native-preview": "7.0.0-dev.20251024.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "^10.4.21",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^3.4.11",
+ "typescript": "~5.9.3",
+ "vite": "^5.1.4",
+ "vite-plugin-svgr": "^4.5.0",
+ "@xagi/vite-plugin-design-mode": "^1.1.0-beta.8"
+ },
+ "overrides": {
+ "react-helmet-async": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/pnpm-workspace.yaml b/qiming-vite-plugin-design-mode/examples/react-vite-md2/pnpm-workspace.yaml
new file mode 100644
index 00000000..2918d4ef
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+catalog:
+ '@react-three/drei': 9.122.0
+ '@react-three/fiber': 8.18.0
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/postcss.config.js b/qiming-vite-plugin-design-mode/examples/react-vite-md2/postcss.config.js
new file mode 100644
index 00000000..2aa7205d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/favicon.png b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/favicon.png
new file mode 100644
index 00000000..49529ec3
Binary files /dev/null and b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/favicon.png differ
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/404-dark.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/404-dark.svg
new file mode 100644
index 00000000..4d14ec9a
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/404-dark.svg
@@ -0,0 +1,20 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/404.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/404.svg
new file mode 100644
index 00000000..ff8b8a2b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/404.svg
@@ -0,0 +1,20 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/500-dark.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/500-dark.svg
new file mode 100644
index 00000000..c5ac764e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/500-dark.svg
@@ -0,0 +1,24 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/500.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/500.svg
new file mode 100644
index 00000000..82f51590
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/500.svg
@@ -0,0 +1,24 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/503-dark.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/503-dark.svg
new file mode 100644
index 00000000..8df2a94a
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/503-dark.svg
@@ -0,0 +1,26 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/503.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/503.svg
new file mode 100644
index 00000000..a27a7143
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/error/503.svg
@@ -0,0 +1,26 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/favicon.ico b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/favicon.ico
new file mode 100644
index 00000000..cf3128aa
Binary files /dev/null and b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/favicon.ico differ
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/auth-logo.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/auth-logo.svg
new file mode 100644
index 00000000..eb11cc7c
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/auth-logo.svg
@@ -0,0 +1,53 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/logo-dark.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/logo-dark.svg
new file mode 100644
index 00000000..4b94dacc
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/logo-dark.svg
@@ -0,0 +1,53 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/logo-icon.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/logo-icon.svg
new file mode 100644
index 00000000..11d52ca4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/logo/logo-icon.svg
@@ -0,0 +1,44 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/shape/grid-01.svg b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/shape/grid-01.svg
new file mode 100644
index 00000000..64903671
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/public/images/shape/grid-01.svg
@@ -0,0 +1,71 @@
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/sgconfig.yml b/qiming-vite-plugin-design-mode/examples/react-vite-md2/sgconfig.yml
new file mode 100644
index 00000000..b2ea77dd
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/sgconfig.yml
@@ -0,0 +1,5 @@
+ruleDirs:
+- rules
+languageGlobs:
+ TypeScript: ["*.ts"]
+ Tsx: ["*.tsx"]
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/App.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/App.tsx
new file mode 100644
index 00000000..40de4865
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/App.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { HashRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
+import { Toaster } from '@/components/ui/sonner';
+
+import routes from './routes';
+
+const App: React.FC = () => {
+ return (
+
+
+
+
+
+ {routes.map((route, index) => (
+
+ ))}
+ } />
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/Footer.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/Footer.tsx
new file mode 100644
index 00000000..483aedf6
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/Footer.tsx
@@ -0,0 +1,71 @@
+import React from "react";
+
+const Footer: React.FC = () => {
+ const currentYear = new Date().getFullYear();
+
+ return (
+
+ );
+};
+
+export default Footer;
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/Header.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/Header.tsx
new file mode 100644
index 00000000..abcc4253
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/Header.tsx
@@ -0,0 +1,51 @@
+import React, { useState } from "react";
+import { Link, useLocation } from "react-router-dom";
+import routes from "../../routes";
+
+const Header: React.FC = () => {
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+ const location = useLocation();
+ const navigation = routes.filter((route) => route.visible !== false);
+
+ return (
+
+
+
+ );
+};
+
+export default Header;
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/PageMeta.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/PageMeta.tsx
new file mode 100644
index 00000000..007a806b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/common/PageMeta.tsx
@@ -0,0 +1,20 @@
+import { HelmetProvider, Helmet } from "react-helmet-async";
+
+const PageMeta = ({
+ title,
+ description,
+}: {
+ title: string;
+ description: string;
+}) => (
+
+ {title}
+
+
+);
+
+export const AppWrapper = ({ children }: { children: React.ReactNode }) => (
+ {children}
+);
+
+export default PageMeta;
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/dropzone.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/dropzone.tsx
new file mode 100644
index 00000000..122bd348
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/dropzone.tsx
@@ -0,0 +1,227 @@
+import { cn } from '@/lib/utils'
+import { type UseSupabaseUploadReturn } from '@/hooks/use-supabase-upload'
+import { Button } from '@/components/ui/button'
+import { CheckCircle, File, Loader2, Upload, X } from 'lucide-react'
+import { createContext, type PropsWithChildren, useCallback, useContext } from 'react'
+
+export const formatBytes = (
+ bytes: number,
+ decimals = 2,
+ size?: 'bytes' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB' | 'EB' | 'ZB' | 'YB'
+) => {
+ const k = 1000
+ const dm = decimals < 0 ? 0 : decimals
+ const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+
+ if (bytes === 0 || bytes === undefined) return size !== undefined ? `0 ${size}` : '0 bytes'
+ const i = size !== undefined ? sizes.indexOf(size) : Math.floor(Math.log(bytes) / Math.log(k))
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
+}
+
+type DropzoneContextType = Omit
+
+const DropzoneContext = createContext(undefined)
+
+type DropzoneProps = UseSupabaseUploadReturn & {
+ className?: string
+}
+
+const Dropzone = ({
+ className,
+ children,
+ getRootProps,
+ getInputProps,
+ ...restProps
+}: PropsWithChildren) => {
+ const isSuccess = restProps.isSuccess
+ const isActive = restProps.isDragActive
+ const isInvalid =
+ (restProps.isDragActive && restProps.isDragReject) ||
+ (restProps.errors.length > 0 && !restProps.isSuccess) ||
+ restProps.files.some((file) => file.errors.length !== 0)
+
+ return (
+
+
+
+ {children}
+
+
+ )
+}
+const DropzoneContent = ({ className }: { className?: string }) => {
+ const {
+ files,
+ setFiles,
+ onUpload,
+ loading,
+ successes,
+ errors,
+ maxFileSize,
+ maxFiles,
+ isSuccess,
+ } = useDropzoneContext()
+
+ const exceedMaxFiles = files.length > maxFiles
+
+ const handleRemoveFile = useCallback(
+ (fileName: string) => {
+ setFiles(files.filter((file) => file.name !== fileName))
+ },
+ [files, setFiles]
+ )
+
+ if (isSuccess) {
+ return (
+
+
+
+ Successfully uploaded {files.length} file{files.length > 1 ? 's' : ''}
+
+
+ )
+ }
+
+ return (
+
+ {files.map((file, idx) => {
+ const fileError = errors.find((e) => e.name === file.name)
+ const isSuccessfullyUploaded = !!successes.find((e) => e === file.name)
+
+ return (
+
+ {file.type.startsWith('image/') ? (
+
+

+
+ ) : (
+
+
+
+ )}
+
+
+
+ {file.name}
+
+ {file.errors.length > 0 ? (
+
+ {file.errors
+ .map((e) =>
+ e.message.startsWith('File is larger than')
+ ? `File is larger than ${formatBytes(maxFileSize, 2)} (Size: ${formatBytes(file.size, 2)})`
+ : e.message
+ )
+ .join(', ')}
+
+ ) : loading && !isSuccessfullyUploaded ? (
+
Uploading file...
+ ) : !!fileError ? (
+
Failed to upload: {fileError.message}
+ ) : isSuccessfullyUploaded ? (
+
Successfully uploaded file
+ ) : (
+
{formatBytes(file.size, 2)}
+ )}
+
+
+ {!loading && !isSuccessfullyUploaded && (
+
handleRemoveFile(file.name)}
+ >
+
+
+ )}
+
+ )
+ })}
+ {exceedMaxFiles && (
+
+ You may upload only up to {maxFiles} files, please remove {files.length - maxFiles} file
+ {files.length - maxFiles > 1 ? 's' : ''}.
+
+ )}
+ {files.length > 0 && !exceedMaxFiles && (
+
+ file.errors.length !== 0) || loading}
+ >
+ {loading ? (
+ <>
+
+ Uploading...
+ >
+ ) : (
+ <>Upload files>
+ )}
+
+
+ )}
+
+ )
+}
+
+const DropzoneEmptyState = ({ className }: { className?: string }) => {
+ const { maxFiles, maxFileSize, inputRef, isSuccess } = useDropzoneContext()
+
+ if (isSuccess) {
+ return null
+ }
+
+ return (
+
+
+
+ Upload{!!maxFiles && maxFiles > 1 ? ` ${maxFiles}` : ''} file
+ {!maxFiles || maxFiles > 1 ? 's' : ''}
+
+
+
+ )
+}
+
+const useDropzoneContext = () => {
+ const context = useContext(DropzoneContext)
+
+ if (!context) {
+ throw new Error('useDropzoneContext must be used within a Dropzone')
+ }
+
+ return context
+}
+
+export { Dropzone, DropzoneContent, DropzoneEmptyState, useDropzoneContext }
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/editor/RichTextEditor.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/editor/RichTextEditor.tsx
new file mode 100644
index 00000000..c350690b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/editor/RichTextEditor.tsx
@@ -0,0 +1,128 @@
+import { useRef, useEffect } from 'react';
+import { Button } from '@/components/ui/button';
+import { Separator } from '@/components/ui/separator';
+import {
+ Bold,
+ Italic,
+ Underline,
+ AlignLeft,
+ AlignCenter,
+ AlignRight,
+ List,
+ ListOrdered,
+ Quote,
+ Heading1,
+ Heading2,
+ Heading3
+} from 'lucide-react';
+import { cn } from '@/lib/utils';
+
+interface RichTextEditorProps {
+ content: string;
+ onChange: (content: string) => void;
+ className?: string;
+}
+
+export default function RichTextEditor({ content, onChange, className }: RichTextEditorProps) {
+ const editorRef = useRef(null);
+
+ useEffect(() => {
+ if (editorRef.current && editorRef.current.innerHTML !== content) {
+ editorRef.current.innerHTML = content;
+ }
+ }, [content]);
+
+ const handleInput = () => {
+ if (editorRef.current) {
+ onChange(editorRef.current.innerHTML);
+ }
+ };
+
+ const execCommand = (command: string, value?: string) => {
+ document.execCommand(command, false, value);
+ editorRef.current?.focus();
+ };
+
+ const formatBlock = (tag: string) => {
+ document.execCommand('formatBlock', false, tag);
+ editorRef.current?.focus();
+ };
+
+ const toolbarButtons = [
+ {
+ group: 'text',
+ buttons: [
+ { icon: Bold, command: 'bold', title: '粗体' },
+ { icon: Italic, command: 'italic', title: '斜体' },
+ { icon: Underline, command: 'underline', title: '下划线' }
+ ]
+ },
+ {
+ group: 'heading',
+ buttons: [
+ { icon: Heading1, command: 'h1', title: '一级标题', isFormatBlock: true },
+ { icon: Heading2, command: 'h2', title: '二级标题', isFormatBlock: true },
+ { icon: Heading3, command: 'h3', title: '三级标题', isFormatBlock: true }
+ ]
+ },
+ {
+ group: 'align',
+ buttons: [
+ { icon: AlignLeft, command: 'justifyLeft', title: '左对齐' },
+ { icon: AlignCenter, command: 'justifyCenter', title: '居中' },
+ { icon: AlignRight, command: 'justifyRight', title: '右对齐' }
+ ]
+ },
+ {
+ group: 'list',
+ buttons: [
+ { icon: List, command: 'insertUnorderedList', title: '无序列表' },
+ { icon: ListOrdered, command: 'insertOrderedList', title: '有序列表' },
+ { icon: Quote, command: 'formatBlock', value: 'blockquote', title: '引用' }
+ ]
+ }
+ ];
+
+ return (
+
+
+ {toolbarButtons.map((group, groupIndex) => (
+
+ {group.buttons.map((btn) => {
+ const Icon = btn.icon;
+ return (
+ {
+ if (btn.isFormatBlock) {
+ formatBlock(btn.command);
+ } else {
+ execCommand(btn.command, btn.value);
+ }
+ }}
+ >
+
+
+ );
+ })}
+ {groupIndex < toolbarButtons.length - 1 && (
+
+ )}
+
+ ))}
+
+
+
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/accordion.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/accordion.tsx
new file mode 100644
index 00000000..62705e3d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/accordion.tsx
@@ -0,0 +1,64 @@
+import * as React from "react";
+import * as AccordionPrimitive from "@radix-ui/react-accordion";
+import { ChevronDownIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Accordion({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ );
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/alert-dialog.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/alert-dialog.tsx
new file mode 100644
index 00000000..69499798
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,155 @@
+import * as React from "react";
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
+
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/components/ui/button";
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ );
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+};
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/alert.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/alert.tsx
new file mode 100644
index 00000000..14213546
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/alert.tsx
@@ -0,0 +1,66 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+ {
+ variants: {
+ variant: {
+ default: "bg-card text-card-foreground",
+ destructive:
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/aspect-ratio.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 00000000..01d045dd
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,9 @@
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
+
+function AspectRatio({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+export { AspectRatio };
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/avatar.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/avatar.tsx
new file mode 100644
index 00000000..02305fd4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/avatar.tsx
@@ -0,0 +1,51 @@
+import * as React from "react";
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
+
+import { cn } from "@/lib/utils";
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/badge.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/badge.tsx
new file mode 100644
index 00000000..83750ed1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const badgeVariants = cva(
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+);
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span";
+
+ return (
+
+ );
+}
+
+export { Badge, badgeVariants };
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/breadcrumb.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/breadcrumb.tsx
new file mode 100644
index 00000000..0c581f7f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { ChevronRight, MoreHorizontal } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
+ return ;
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+ return (
+
+ );
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : "a";
+
+ return (
+
+ );
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+ );
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More
+
+ );
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+};
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/button.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/button.tsx
new file mode 100644
index 00000000..21409a06
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/button.tsx
@@ -0,0 +1,60 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ "icon-sm": "size-8",
+ "icon-lg": "size-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/calendar.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/calendar.tsx
new file mode 100644
index 00000000..962e816f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/calendar.tsx
@@ -0,0 +1,73 @@
+import * as React from "react";
+import { ChevronLeft, ChevronRight } from "lucide-react";
+import { DayPicker } from "react-day-picker";
+
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/components/ui/button";
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+ .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
+ : "[&:has([aria-selected])]:rounded-md"
+ ),
+ day: cn(
+ buttonVariants({ variant: "ghost" }),
+ "size-8 p-0 font-normal aria-selected:opacity-100"
+ ),
+ day_range_start:
+ "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
+ day_range_end:
+ "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
+ day_selected:
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+ day_today: "bg-accent text-accent-foreground",
+ day_outside:
+ "day-outside text-muted-foreground aria-selected:text-muted-foreground",
+ day_disabled: "text-muted-foreground opacity-50",
+ day_range_middle:
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
+ day_hidden: "invisible",
+ ...classNames,
+ }}
+ components={{
+ IconLeft: ({ className, ...props }) => (
+
+ ),
+ IconRight: ({ className, ...props }) => (
+
+ ),
+ }}
+ {...props}
+ />
+ );
+}
+
+export { Calendar };
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/card.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/card.tsx
new file mode 100644
index 00000000..e9d7e464
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+};
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/carousel.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/carousel.tsx
new file mode 100644
index 00000000..efdff8ca
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/carousel.tsx
@@ -0,0 +1,239 @@
+import * as React from "react";
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react";
+import { ArrowLeft, ArrowRight } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: "horizontal" | "vertical"
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext(null);
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext);
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ");
+ }
+
+ return context;
+}
+
+function Carousel({
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return;
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext]
+ );
+
+ React.useEffect(() => {
+ if (!api || !setApi) return;
+ setApi(api);
+ }, [api, setApi]);
+
+ React.useEffect(() => {
+ if (!api) return;
+ onSelect(api);
+ api.on("reInit", onSelect);
+ api.on("select", onSelect);
+
+ return () => {
+ api?.off("select", onSelect);
+ };
+ }, [api, onSelect]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
+ const { carouselRef, orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
+ const { orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselPrevious({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
+
+ return (
+
+
+ Previous slide
+
+ );
+}
+
+function CarouselNext({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
+
+ return (
+
+
+ Next slide
+
+ );
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+};
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/chart.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/chart.tsx
new file mode 100644
index 00000000..e26c5ce6
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/components/ui/chart.tsx
@@ -0,0 +1,351 @@
+import * as React from "react";
+import * as RechartsPrimitive from "recharts";
+
+import { cn } from "@/lib/utils";
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const;
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ )
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext(null);
+
+function useChart() {
+ const context = React.useContext(ChartContext);
+
+ if (!context) {
+ throw new Error("useChart must be used within a ");
+ }
+
+ return context;
+}
+
+function ChartContainer({
+ id,
+ className,
+ children,
+ config,
+ ...props
+}: React.ComponentProps<"div"> & {
+ config: ChartConfig
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"]
+}) {
+ const uniqueId = React.useId();
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color
+ );
+
+ if (!colorConfig.length) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/global.d.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/global.d.ts
new file mode 100644
index 00000000..8547fb6b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/global.d.ts
@@ -0,0 +1 @@
+// global types
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-debounce.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-debounce.ts
new file mode 100644
index 00000000..bd6d2a78
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-debounce.ts
@@ -0,0 +1,15 @@
+import * as React from "react";
+
+export function useDebounce(value: T, delay?: number): T {
+ const [debouncedValue, setDebouncedValue] = React.useState(value);
+
+ React.useEffect(() => {
+ const timer = setTimeout(() => setDebouncedValue(value), delay ?? 500);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-go-back.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-go-back.ts
new file mode 100644
index 00000000..8cbec301
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-go-back.ts
@@ -0,0 +1,17 @@
+import { useNavigate } from "react-router-dom";
+
+const useGoBack = () => {
+ const navigate = useNavigate();
+
+ const goBack = () => {
+ if (window.history.state && window.history.state.idx > 0) {
+ navigate(-1); // Go back to the previous page
+ } else {
+ navigate("/"); // Redirect to home if no history exists
+ }
+ };
+
+ return goBack;
+};
+
+export default useGoBack;
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-mobile.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-mobile.ts
new file mode 100644
index 00000000..502fd323
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-mobile.ts
@@ -0,0 +1,19 @@
+import * as React from "react";
+
+const MOBILE_BREAKPOINT = 768;
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined);
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ mql.addEventListener("change", onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => mql.removeEventListener("change", onChange);
+ }, []);
+
+ return !!isMobile;
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-supabase-upload.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-supabase-upload.ts
new file mode 100644
index 00000000..b0bbc0e6
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-supabase-upload.ts
@@ -0,0 +1,197 @@
+import { useCallback, useEffect, useMemo, useState } from 'react'
+import { type FileError, type FileRejection, useDropzone } from 'react-dropzone'
+import {type SupabaseClient} from '@supabase/supabase-js'
+
+interface FileWithPreview extends File {
+ preview?: string
+ errors: readonly FileError[]
+}
+
+type UseSupabaseUploadOptions = {
+ /**
+ * Name of bucket to upload files to in your Supabase project
+ */
+ bucketName: string
+ /**
+ * Folder to upload files to in the specified bucket within your Supabase project.
+ *
+ * Defaults to uploading files to the root of the bucket
+ *
+ * e.g If specified path is `test`, your file will be uploaded as `test/file_name`
+ */
+ path?: string
+ /**
+ * Allowed MIME types for each file upload (e.g `image/png`, `text/html`, etc). Wildcards are also supported (e.g `image/*`).
+ *
+ * Defaults to allowing uploading of all MIME types.
+ */
+ allowedMimeTypes?: string[]
+ /**
+ * Maximum upload size of each file allowed in bytes. (e.g 1000 bytes = 1 KB)
+ */
+ maxFileSize?: number
+ /**
+ * Maximum number of files allowed per upload.
+ */
+ maxFiles?: number
+ /**
+ * The number of seconds the asset is cached in the browser and in the Supabase CDN.
+ *
+ * This is set in the Cache-Control: max-age= header. Defaults to 3600 seconds.
+ */
+ cacheControl?: number
+ /**
+ * When set to true, the file is overwritten if it exists.
+ *
+ * When set to false, an error is thrown if the object already exists. Defaults to `false`
+ */
+ upsert?: boolean
+
+ /**
+ * initialized Supabase client instance
+ */
+ supabase: SupabaseClient
+}
+
+type UseSupabaseUploadReturn = ReturnType
+
+const useSupabaseUpload = (options: UseSupabaseUploadOptions) => {
+ const {
+ bucketName,
+ path,
+ allowedMimeTypes = [],
+ maxFileSize = Number.POSITIVE_INFINITY,
+ maxFiles = 1,
+ cacheControl = 3600,
+ upsert = false,
+ supabase
+ } = options
+
+ const [files, setFiles] = useState([])
+ const [loading, setLoading] = useState(false)
+ const [errors, setErrors] = useState<{ name: string; message: string }[]>([])
+ const [successes, setSuccesses] = useState([])
+
+ const isSuccess = useMemo(() => {
+ if (errors.length === 0 && successes.length === 0) {
+ return false
+ }
+ if (errors.length === 0 && successes.length === files.length) {
+ return true
+ }
+ return false
+ }, [errors.length, successes.length, files.length])
+
+ const onDrop = useCallback(
+ (acceptedFiles: File[], fileRejections: FileRejection[]) => {
+ const validFiles = acceptedFiles
+ .filter((file) => !files.find((x) => x.name === file.name))
+ .map((file) => {
+ ;(file as FileWithPreview).preview = URL.createObjectURL(file)
+ ;(file as FileWithPreview).errors = []
+ return file as FileWithPreview
+ })
+
+ const invalidFiles = fileRejections.map(({ file, errors }) => {
+ ;(file as FileWithPreview).preview = URL.createObjectURL(file)
+ ;(file as FileWithPreview).errors = errors
+ return file as FileWithPreview
+ })
+
+ const newFiles = [...files, ...validFiles, ...invalidFiles]
+
+ setFiles(newFiles)
+ },
+ [files, setFiles]
+ )
+
+ const dropzoneProps = useDropzone({
+ onDrop,
+ noClick: true,
+ accept: allowedMimeTypes.reduce((acc, type) => ({ ...acc, [type]: [] }), {}),
+ maxSize: maxFileSize,
+ maxFiles: maxFiles,
+ multiple: maxFiles !== 1,
+ })
+
+ const onUpload = useCallback(async () => {
+ setLoading(true)
+
+ // [Joshen] This is to support handling partial successes
+ // If any files didn't upload for any reason, hitting "Upload" again will only upload the files that had errors
+ const filesWithErrors = errors.map((x) => x.name)
+ const filesToUpload =
+ filesWithErrors.length > 0
+ ? [
+ ...files.filter((f) => filesWithErrors.includes(f.name)),
+ ...files.filter((f) => !successes.includes(f.name)),
+ ]
+ : files
+
+ const responses = await Promise.all(
+ filesToUpload.map(async (file) => {
+ const { error } = await supabase.storage
+ .from(bucketName)
+ .upload(!!path ? `${path}/${file.name}` : file.name, file, {
+ cacheControl: cacheControl.toString(),
+ upsert,
+ })
+ if (error) {
+ return { name: file.name, message: error.message }
+ } else {
+ return { name: file.name, message: undefined }
+ }
+ })
+ )
+
+ const responseErrors = responses.filter((x) => x.message !== undefined)
+ // if there were errors previously, this function tried to upload the files again so we should clear/overwrite the existing errors.
+ setErrors(responseErrors)
+
+ const responseSuccesses = responses.filter((x) => x.message === undefined)
+ const newSuccesses = Array.from(
+ new Set([...successes, ...responseSuccesses.map((x) => x.name)])
+ )
+ setSuccesses(newSuccesses)
+
+ setLoading(false)
+ }, [files, path, bucketName, errors, successes])
+
+ useEffect(() => {
+ if (files.length === 0) {
+ setErrors([])
+ }
+
+ // If the number of files doesn't exceed the maxFiles parameter, remove the error 'Too many files' from each file
+ if (files.length <= maxFiles) {
+ let changed = false
+ const newFiles = files.map((file) => {
+ if (file.errors.some((e) => e.code === 'too-many-files')) {
+ file.errors = file.errors.filter((e) => e.code !== 'too-many-files')
+ changed = true
+ }
+ return file
+ })
+ if (changed) {
+ setFiles(newFiles)
+ }
+ }
+ }, [files.length, setFiles, maxFiles])
+
+ return {
+ files,
+ setFiles,
+ successes,
+ isSuccess,
+ loading,
+ errors,
+ setErrors,
+ onUpload,
+ maxFileSize: maxFileSize,
+ maxFiles: maxFiles,
+ allowedMimeTypes,
+ ...dropzoneProps,
+ }
+}
+
+export { useSupabaseUpload, type UseSupabaseUploadOptions, type UseSupabaseUploadReturn }
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-toast.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-toast.tsx
new file mode 100644
index 00000000..9a391450
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/hooks/use-toast.tsx
@@ -0,0 +1,188 @@
+import * as React from "react";
+
+import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
+
+const TOAST_LIMIT = 1;
+const TOAST_REMOVE_DELAY = 1000000;
+
+type ToasterToast = ToastProps & {
+ id: string;
+ title?: React.ReactNode;
+ description?: React.ReactNode;
+ action?: ToastActionElement;
+};
+
+const actionTypes = {
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST",
+} as const;
+
+let count = 0;
+
+function genId() {
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
+ return count.toString();
+}
+
+type ActionType = typeof actionTypes;
+
+type Action =
+ | {
+ type: ActionType["ADD_TOAST"];
+ toast: ToasterToast;
+ }
+ | {
+ type: ActionType["UPDATE_TOAST"];
+ toast: Partial;
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"];
+ toastId?: ToasterToast["id"];
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"];
+ toastId?: ToasterToast["id"];
+ };
+
+interface State {
+ toasts: ToasterToast[];
+}
+
+const toastTimeouts = new Map>();
+
+const addToRemoveQueue = (toastId: string) => {
+ if (toastTimeouts.has(toastId)) {
+ return;
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId);
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ });
+ }, TOAST_REMOVE_DELAY);
+
+ toastTimeouts.set(toastId, timeout);
+};
+
+export const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ };
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
+ ),
+ };
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action;
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId);
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id);
+ });
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t
+ ),
+ };
+ }
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ };
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ };
+ }
+};
+
+const listeners: Array<(state: State) => void> = [];
+
+let memoryState: State = { toasts: [] };
+
+function dispatch(action: Action) {
+ memoryState = reducer(memoryState, action);
+ listeners.forEach((listener) => {
+ listener(memoryState);
+ });
+}
+
+type Toast = Omit;
+
+function toast({ ...props }: Toast) {
+ const id = genId();
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ });
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss();
+ },
+ },
+ });
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ };
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState);
+
+ React.useEffect(() => {
+ listeners.push(setState);
+ return () => {
+ const index = listeners.indexOf(setState);
+ if (index > -1) {
+ listeners.splice(index, 1);
+ }
+ };
+ }, [state]);
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
+ };
+}
+
+export { useToast, toast };
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/index.css b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/index.css
new file mode 100644
index 00000000..dd3bf3c9
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/index.css
@@ -0,0 +1,93 @@
+/* stylelint-disable */
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Definition of the design system. All colors, gradients, fonts, etc should be defined here.
+All colors MUST be HSL.
+*/
+
+@layer base {
+ :root {
+ --radius: 0.75rem;
+ --background: 0 0% 98%;
+ --foreground: 0 0% 15%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 15%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 15%;
+ --primary: 160 100% 38%;
+ --primary-foreground: 0 0% 100%;
+ --primary-light: 160 100% 95%;
+ --secondary: 0 0% 96%;
+ --secondary-foreground: 0 0% 15%;
+ --muted: 0 0% 96%;
+ --muted-foreground: 0 0% 45%;
+ --accent: 160 100% 95%;
+ --accent-foreground: 160 100% 38%;
+ --destructive: 0 84% 60%;
+ --destructive-foreground: 0 0% 100%;
+ --border: 0 0% 90%;
+ --input: 0 0% 90%;
+ --ring: 160 100% 38%;
+ --chart-1: 160 100% 38%;
+ --chart-2: 200 100% 45%;
+ --chart-3: 140 100% 35%;
+ --chart-4: 180 100% 40%;
+ --chart-5: 120 100% 30%;
+ --sidebar: 0 0% 98%;
+ --sidebar-foreground: 0 0% 15%;
+ --sidebar-primary: 160 100% 38%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-accent: 160 100% 95%;
+ --sidebar-accent-foreground: 160 100% 38%;
+ --sidebar-border: 0 0% 90%;
+ --sidebar-ring: 160 100% 38%;
+ }
+
+ .dark {
+ --background: 0 0% 10%;
+ --foreground: 0 0% 95%;
+ --card: 0 0% 15%;
+ --card-foreground: 0 0% 95%;
+ --popover: 0 0% 15%;
+ --popover-foreground: 0 0% 95%;
+ --primary: 160 100% 45%;
+ --primary-foreground: 0 0% 100%;
+ --primary-light: 160 100% 20%;
+ --secondary: 0 0% 20%;
+ --secondary-foreground: 0 0% 95%;
+ --muted: 0 0% 20%;
+ --muted-foreground: 0 0% 60%;
+ --accent: 160 100% 20%;
+ --accent-foreground: 160 100% 45%;
+ --destructive: 0 84% 60%;
+ --destructive-foreground: 0 0% 100%;
+ --border: 0 0% 25%;
+ --input: 0 0% 25%;
+ --ring: 160 100% 45%;
+ --chart-1: 160 100% 45%;
+ --chart-2: 200 100% 50%;
+ --chart-3: 140 100% 40%;
+ --chart-4: 180 100% 45%;
+ --chart-5: 120 100% 35%;
+ --sidebar: 0 0% 15%;
+ --sidebar-foreground: 0 0% 95%;
+ --sidebar-primary: 160 100% 45%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-accent: 160 100% 20%;
+ --sidebar-accent-foreground: 160 100% 45%;
+ --sidebar-border: 0 0% 25%;
+ --sidebar-ring: 160 100% 45%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/lib/utils.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/lib/utils.ts
new file mode 100644
index 00000000..4f8f70a8
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/lib/utils.ts
@@ -0,0 +1,39 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+
+export type Params = Partial<
+ Record
+>;
+
+export function createQueryString(
+ params: Params,
+ searchParams: URLSearchParams
+) {
+ const newSearchParams = new URLSearchParams(searchParams?.toString());
+
+ for (const [key, value] of Object.entries(params)) {
+ if (value === null || value === undefined) {
+ newSearchParams.delete(key);
+ } else {
+ newSearchParams.set(key, String(value));
+ }
+ }
+
+ return newSearchParams.toString();
+}
+
+export function formatDate(
+ date: Date | string | number,
+ opts: Intl.DateTimeFormatOptions = {}
+) {
+ return new Intl.DateTimeFormat("zh-CN", {
+ month: opts.month ?? "long",
+ day: opts.day ?? "numeric",
+ year: opts.year ?? "numeric",
+ ...opts,
+ }).format(new Date(date));
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/main.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/main.tsx
new file mode 100644
index 00000000..3cfac3b5
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/main.tsx
@@ -0,0 +1,13 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import "./index.css";
+import App from "./App.tsx";
+import { AppWrapper } from "./components/common/PageMeta.tsx";
+
+createRoot(document.getElementById("root")!).render(
+
+
+
+
+
+);
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/ArticleGenerator.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/ArticleGenerator.tsx
new file mode 100644
index 00000000..700932f8
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/ArticleGenerator.tsx
@@ -0,0 +1,242 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Textarea } from '@/components/ui/textarea';
+import { Label } from '@/components/ui/label';
+import { ArrowLeft, Sparkles, Copy, Check, Loader2 } from 'lucide-react';
+import { toast } from 'sonner';
+import RichTextEditor from '@/components/editor/RichTextEditor';
+import { sendChatStream } from '@/services/chatStream';
+import PageMeta from '@/components/common/PageMeta';
+
+const APP_ID = import.meta.env.VITE_APP_ID;
+
+const markdownToHtml = (markdown: string): string => {
+ let html = markdown;
+
+ html = html.replace(/^### (.*$)/gim, '$1
');
+ html = html.replace(/^## (.*$)/gim, '$1
');
+ html = html.replace(/^# (.*$)/gim, '$1
');
+
+ html = html.replace(/\*\*(.*?)\*\*/g, '$1');
+ html = html.replace(/\*(.*?)\*/g, '$1');
+
+ html = html.replace(/^\* (.*$)/gim, '$1');
+ html = html.replace(/(.*<\/li>)/s, '');
+
+ html = html.replace(/\n\n/g, '');
+ html = '
' + html + '
';
+
+ return html;
+};
+
+export default function ArticleGenerator() {
+ const navigate = useNavigate();
+ const [topic, setTopic] = useState('');
+ const [content, setContent] = useState('');
+ const [isGenerating, setIsGenerating] = useState(false);
+ const [isCopied, setIsCopied] = useState(false);
+ const [abortController, setAbortController] = useState(null);
+
+ const handleGenerate = async () => {
+ if (!topic.trim()) {
+ toast.error('请输入文章主题');
+ return;
+ }
+
+ setIsGenerating(true);
+ setContent('');
+ const controller = new AbortController();
+ setAbortController(controller);
+
+ let markdownContent = '';
+
+ try {
+ await sendChatStream({
+ endpoint: 'https://api-integrations.appmiaoda.com/app-7a7nlc9zki69/api-2bk93oeO9NlE/v2/chat/completions',
+ apiId: APP_ID,
+ messages: [
+ {
+ role: 'system',
+ content: '你是一个专业的公众号文章写手,擅长创作吸引人的图文内容。请根据用户提供的主题,生成一篇完整的公众号文章,包括标题、引言、正文和结尾。文章要有吸引力,语言生动,适合微信公众号阅读。使用 Markdown 格式输出。'
+ },
+ {
+ role: 'user',
+ content: `请为我创作一篇关于"${topic}"的公众号文章`
+ }
+ ],
+ onUpdate: (newContent: string) => {
+ markdownContent = newContent;
+ const htmlContent = markdownToHtml(markdownContent);
+ setContent(htmlContent);
+ },
+ onComplete: () => {
+ setIsGenerating(false);
+ setAbortController(null);
+ toast.success('文章生成完成');
+ },
+ onError: (error: Error) => {
+ setIsGenerating(false);
+ setAbortController(null);
+ toast.error(`生成失败: ${error.message}`);
+ },
+ signal: controller.signal
+ });
+ } catch (error) {
+ if (!controller.signal.aborted) {
+ setIsGenerating(false);
+ setAbortController(null);
+ toast.error('生成失败,请重试');
+ }
+ }
+ };
+
+ const handleStop = () => {
+ if (abortController) {
+ abortController.abort();
+ setAbortController(null);
+ setIsGenerating(false);
+ toast.info('已停止生成');
+ }
+ };
+
+ const handleCopy = async () => {
+ try {
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = content;
+
+ const blob = new Blob([tempDiv.innerHTML], { type: 'text/html' });
+ const clipboardItem = new ClipboardItem({
+ 'text/html': blob,
+ 'text/plain': new Blob([tempDiv.innerText], { type: 'text/plain' })
+ });
+
+ await navigator.clipboard.write([clipboardItem]);
+ setIsCopied(true);
+ toast.success('内容已复制,可直接粘贴到公众号编辑器');
+
+ setTimeout(() => setIsCopied(false), 2000);
+ } catch (error) {
+ toast.error('复制失败,请手动选择复制');
+ }
+ };
+
+ return (
+ <>
+
+
+
+
navigate('/')}
+ >
+
+ 返回首页
+
+
+
+
+
+
+
+ 图文生成
+
+
+ 输入文章主题,AI 将为您生成完整的公众号图文内容
+
+
+
+
+
+
+
+ {isGenerating ? (
+
+
+ 停止生成
+
+ ) : (
+
+
+ 生成文章
+
+ )}
+
+ {content && !isGenerating && (
+
+ {isCopied ? (
+ <>
+
+ 已复制
+ >
+ ) : (
+ <>
+
+ 复制内容
+ >
+ )}
+
+ )}
+
+
+
使用提示:
+
+ - 主题描述越详细,生成的内容越精准
+ - 可以在编辑器中修改生成的内容
+ - 点击"复制内容"后可直接粘贴到公众号
+
+
+
+
+
+
+
+ 内容预览与编辑
+
+ 在这里查看和编辑生成的内容
+
+
+
+ {content ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ >
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/Home.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/Home.tsx
new file mode 100644
index 00000000..302d3d13
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/Home.tsx
@@ -0,0 +1,116 @@
+import { useNavigate } from 'react-router-dom';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { FileText, Image, Video, Code2 } from 'lucide-react';
+import PageMeta from '@/components/common/PageMeta';
+
+export default function Home() {
+ const navigate = useNavigate();
+
+ const features = [
+ {
+ icon: FileText,
+ title: '图文生成',
+ description: '输入主题,AI 自动生成完整的图文推送内容',
+ path: '/article-generator',
+ color: 'text-primary'
+ },
+ {
+ icon: Image,
+ title: '图片配文',
+ description: '上传图片,AI 为您生成精准的文案内容',
+ path: '/image-caption',
+ color: 'text-primary'
+ },
+ {
+ icon: Video,
+ title: '视频脚本',
+ description: '创建视频拍摄和制作的详细脚本文案',
+ path: '/video-script',
+ color: 'text-primary'
+ },
+ {
+ icon: Code2,
+ title: '设计模式演示',
+ description: '体验 Iframe 设计模式,实时编辑页面元素',
+ path: '/iframe-demo',
+ color: 'text-primary'
+ }
+ ];
+
+ return (
+ <>
+
+
+
+
+
+ 公众号推文助手
+
+
+ 专为微信公众号内容创作者设计,提供图文生成、图片配文、视频脚本创作等功能
+
+
+
+
+ {features.map((feature) => {
+ const Icon = feature.icon;
+ return (
+
navigate(feature.path)}
+ >
+
+
+
+
+ {feature.title}
+
+ {feature.description}
+
+
+
+
+ 开始创作
+
+
+
+ );
+ })}
+
+
+
+
+
+ 功能特色
+
+
+
+
+
⚡ 快速生成
+
+ AI 驱动,秒级生成高质量内容
+
+
+
+
✨ 在线编辑
+
+ 富文本编辑器,支持样式自定义
+
+
+
+
📋 一键复制
+
+ 保留格式,直接粘贴到公众号
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/IframeDemoPage.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/IframeDemoPage.tsx
new file mode 100644
index 00000000..e37e9a75
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/IframeDemoPage.tsx
@@ -0,0 +1,567 @@
+import { useState, useEffect, useRef } from 'react';
+import { useNavigate } from 'react-router-dom';
+import type { ElementInfo } from '@/types/messages';
+import { Button } from '@/components/ui/button';
+import { Textarea } from '@/components/ui/textarea';
+import { Label } from '@/components/ui/label';
+import { ArrowLeft } from 'lucide-react';
+import PageMeta from '@/components/common/PageMeta';
+
+export default function IframeDemoPage() {
+ const navigate = useNavigate();
+ const [iframeDesignMode, setIframeDesignMode] = useState(false);
+ const [selectedElement, setSelectedElement] = useState(
+ null
+ );
+ const [editingContent, setEditingContent] = useState('');
+ const [editingClass, setEditingClass] = useState('');
+ const [pendingChanges, setPendingChanges] = useState<
+ Array<{
+ type: 'style' | 'content';
+ sourceInfo: any;
+ newValue: string;
+ originalValue?: string;
+ }>
+ >([]);
+
+ const iframeRef = useRef(null);
+
+ // Listen for messages from iframe
+ useEffect(() => {
+ const handleMessage = (event: MessageEvent) => {
+ // Debug log for message source
+ if (event.data.type === 'ELEMENT_SELECTED') {
+ console.log('[Parent] Received ELEMENT_SELECTED', {
+ source: event.source,
+ iframeWindow: iframeRef.current?.contentWindow,
+ isMatch: iframeRef.current && event.source === iframeRef.current.contentWindow,
+ data: event.data
+ });
+ }
+ if (event.data.type === 'ADD_TO_CHAT') {
+ return console.log('[Parent] Add to chat:', event.data.payload);
+ }
+
+ if (event.data.type === 'COPY_ELEMENT') {
+ return console.log('[Parent] Copy element:', event.data.payload);
+ }
+
+ // Only accept messages from the iframe
+ if (iframeRef.current && event.source !== iframeRef.current.contentWindow) {
+ if (event.data.type === 'ELEMENT_SELECTED') {
+ console.warn('[Parent] Ignoring message from non-iframe source');
+ }
+ return;
+ }
+
+ const { type, payload } = event.data;
+
+ switch (type) {
+ case 'DESIGN_MODE_CHANGED':
+ setIframeDesignMode(event.data.enabled);
+ break;
+
+ case 'ELEMENT_SELECTED':
+ console.log('[Parent] Processing ELEMENT_SELECTED', payload);
+
+ // 验证 sourceInfo 是否有效
+ if (
+ !payload.elementInfo?.sourceInfo ||
+ !payload.elementInfo.sourceInfo.fileName ||
+ payload.elementInfo.sourceInfo.lineNumber === 0
+ ) {
+ console.warn(
+ '[Parent] Invalid sourceInfo received:',
+ payload.elementInfo?.sourceInfo
+ );
+ console.warn('[Parent] This may cause update operations to fail');
+ }
+
+ setSelectedElement(payload.elementInfo);
+ setEditingContent(payload.elementInfo.textContent);
+ setEditingClass(payload.elementInfo.className);
+ break;
+
+ case 'ELEMENT_DESELECTED':
+ setSelectedElement(null);
+ console.log('[Parent] Element deselected');
+ break;
+
+ case 'CONTENT_UPDATED':
+ console.log('[Parent] Content updated:', payload);
+ break;
+
+ case 'STYLE_UPDATED':
+ console.log('[Parent] Style updated:', payload);
+ break;
+
+ }
+ };
+
+ window.addEventListener('message', handleMessage);
+ return () => window.removeEventListener('message', handleMessage);
+ }, []);
+
+ // Debounce hook
+ const useDebounce = (value: T, delay: number): T => {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+ return () => clearTimeout(handler);
+ }, [value, delay]);
+ return debouncedValue;
+ };
+
+ const debouncedContent = useDebounce(editingContent, 300);
+ const debouncedClass = useDebounce(editingClass, 300);
+
+ // Upsert pending change
+ const upsertPendingChange = (
+ type: 'style' | 'content',
+ newValue: string,
+ originalValue?: string
+ ) => {
+ if (!selectedElement) return;
+ setPendingChanges(prev => {
+ const existingIndex = prev.findIndex(
+ item =>
+ item.type === type &&
+ item.sourceInfo.fileName === selectedElement.sourceInfo.fileName &&
+ item.sourceInfo.lineNumber === selectedElement.sourceInfo.lineNumber
+ );
+
+ const newChange = {
+ type,
+ sourceInfo: selectedElement.sourceInfo,
+ newValue,
+ originalValue: originalValue || (type === 'style' ? selectedElement.className : selectedElement.textContent),
+ };
+
+ if (existingIndex >= 0) {
+ const newList = [...prev];
+ newList[existingIndex] = newChange;
+ return newList;
+ } else {
+ return [...prev, newChange];
+ }
+ });
+ };
+
+ const lastSelectedElementRef = useRef(selectedElement);
+
+ // Real-time content update
+ useEffect(() => {
+ if (!selectedElement) return;
+
+ // If selection changed, skip this update cycle
+ if (selectedElement !== lastSelectedElementRef.current) {
+ console.log('[Parent] Skipping content update due to selection change');
+ return;
+ }
+
+ console.log('[Parent] Content effect triggered', {
+ debouncedContent,
+ currentContent: selectedElement.textContent,
+ shouldUpdate: debouncedContent !== selectedElement.textContent
+ });
+
+ if (debouncedContent === selectedElement.textContent) return;
+
+ console.log('[Parent] Sending UPDATE_CONTENT', debouncedContent);
+ upsertPendingChange('content', debouncedContent);
+
+ const iframe = iframeRef.current;
+ if (iframe && iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ {
+ type: 'UPDATE_CONTENT',
+ payload: {
+ sourceInfo: selectedElement.sourceInfo,
+ newContent: debouncedContent,
+ persist: false,
+ },
+ timestamp: Date.now(),
+ },
+ '*'
+ );
+ }
+ }, [debouncedContent, selectedElement]); // Removed upsertPendingChange to fix infinite loop
+
+ // Real-time style update
+ useEffect(() => {
+ if (!selectedElement) return;
+
+ // If selection changed, skip this update cycle
+ if (selectedElement !== lastSelectedElementRef.current) {
+ console.log('[Parent] Skipping style update due to selection change');
+ return;
+ }
+
+ if (debouncedClass === selectedElement.className) return;
+
+ upsertPendingChange('style', debouncedClass);
+
+ const iframe = iframeRef.current;
+ if (iframe && iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ {
+ type: 'UPDATE_STYLE',
+ payload: {
+ sourceInfo: selectedElement.sourceInfo,
+ newClass: debouncedClass,
+ persist: false,
+ },
+ timestamp: Date.now(),
+ },
+ '*'
+ );
+ }
+ }, [debouncedClass, selectedElement]); // Removed upsertPendingChange to fix infinite loop
+
+ // Update lastSelectedElementRef AFTER other effects
+ useEffect(() => {
+ lastSelectedElementRef.current = selectedElement;
+ }, [selectedElement]);
+
+ // Style Manager Logic
+ const toggleStyle = (newStyle: string, categoryRegex: RegExp) => {
+ let currentClasses = editingClass.split(' ').filter(c => c.trim());
+
+ // Remove existing class in the same category
+ currentClasses = currentClasses.filter(c => !categoryRegex.test(c));
+
+ // Add new style if it's not empty (allows clearing style)
+ if (newStyle) {
+ currentClasses.push(newStyle);
+ }
+
+ setEditingClass(currentClasses.join(' '));
+ };
+
+ const hasStyle = (style: string) => {
+ return editingClass.split(' ').includes(style);
+ };
+
+ // UI Controls
+ const renderColorPicker = (prefix: string, colors: string[], label: string) => (
+
+
+
+ {colors.map(color => {
+ const styleClass = `${prefix}-${color}`;
+ const isActive = hasStyle(styleClass);
+ return (
+ toggleStyle(styleClass, new RegExp(`^${prefix}-[a-z]+(-\\d+)?$`))}
+ className={`w-8 h-8 rounded-full border-2 transition-all ${isActive ? 'border-gray-900 scale-110' : 'border-transparent hover:scale-105'
+ } ${styleClass.replace('text-', 'bg-')}`} // Use bg color for preview
+ title={color}
+ />
+ );
+ })}
+ toggleStyle('', new RegExp(`^${prefix}-[a-z]+(-\\d+)?$`))}
+ className="px-2 py-1 text-xs text-gray-500 border border-gray-300 rounded hover:bg-gray-100"
+ >
+ 清除
+
+
+
+ );
+
+ const renderSelect = (prefix: string, options: { label: string; value: string }[], label: string) => (
+
+
+
+ {options.map(opt => {
+ const styleClass = opt.value ? `${prefix}-${opt.value}` : '';
+ const isActive = styleClass ? hasStyle(styleClass) : !options.some(o => o.value && hasStyle(`${prefix}-${o.value}`));
+ return (
+ toggleStyle(styleClass, new RegExp(`^${prefix}(-[a-z0-9]+)?$`))}
+ className={`px-3 py-1 text-sm rounded border transition-all ${isActive
+ ? 'bg-blue-600 text-white border-blue-600'
+ : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
+ }`}
+ >
+ {opt.label}
+
+ )
+ })}
+
+
+ );
+
+ // Toggle design mode in iframe
+ const toggleIframeDesignMode = () => {
+ const iframe = iframeRef.current;
+ console.log('[Parent] Toggling iframe design mode...', iframe);
+ if (iframe && iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ {
+ type: 'TOGGLE_DESIGN_MODE',
+ enabled: !iframeDesignMode,
+ timestamp: Date.now(),
+ },
+ '*'
+ );
+ }
+ };
+
+ // 保存所有更改
+ const saveChanges = async () => {
+ if (pendingChanges.length === 0) return;
+
+ console.log('[Parent] Saving changes...', pendingChanges);
+
+ try {
+ const response = await fetch('/__appdev_design_mode/batch-update', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ updates: pendingChanges.map(change => ({
+ filePath: change.sourceInfo.fileName,
+ line: change.sourceInfo.lineNumber,
+ column: change.sourceInfo.columnNumber,
+ newValue: change.newValue,
+ type: change.type,
+ originalValue: change.originalValue,
+ })),
+ }),
+ });
+
+ if (response.ok) {
+ const result = await response.json();
+ console.log('[Parent] Batch update success:', result);
+ alert(`成功保存 ${result.summary.successful} 个更改!`);
+ setPendingChanges([]); // 清空待保存列表
+ } else {
+ const error = await response.json();
+ console.error('[Parent] Batch update failed:', error);
+ alert('保存失败,请查看控制台错误信息。');
+ }
+ } catch (error) {
+ console.error('[Parent] Error saving changes:', error);
+ alert('保存出错,请检查网络连接。');
+ }
+ };
+
+ return (
+ <>
+
+
+ {/* Top Control Bar */}
+
+
+
+
navigate(-1)}
+ className="mr-2"
+ >
+
+
+
+ Iframe 设计模式演示
+
+
+
+ {pendingChanges.length > 0 && (
+
+ 保存更改 ({pendingChanges.length})
+
+ )}
+
+ {iframeDesignMode ? '✓ 设计模式已启用' : '启用设计模式'}
+
+
+
+
+
+
+ {/* Left: External Property Panel */}
+
+
+
+ 外部属性面板
+
+
+ {selectedElement ? (
+
+ {/* Element Info */}
+
+
+ 选中元素
+
+
+ {/* 基本信息 */}
+
+
基本信息
+
标签: {selectedElement.tagName}
+ {selectedElement.sourceInfo.elementType && selectedElement.sourceInfo.elementType !== selectedElement.tagName && (
+
组件类型: {selectedElement.sourceInfo.elementType}
+ )}
+ {selectedElement.componentName && (
+
所属组件: {selectedElement.componentName}
+ )}
+ {selectedElement.sourceInfo.importPath && (
+
+ 组件定义:{' '}
+ {selectedElement.sourceInfo.importPath}
+
+ )}
+
+ 使用位置:{' '}
+ {selectedElement.sourceInfo.fileName.split('/').pop()}
+
+
+ {selectedElement.sourceInfo.fileName}
+
+
+
+ {/* 属性列表 */}
+ {selectedElement.props && Object.keys(selectedElement.props).length > 0 && (
+
+
属性 (Props)
+
+ {Object.entries(selectedElement.props).map(([key, value]) => (
+
+ {key}:
+
+ {value as string}
+
+
+ ))}
+
+
+ )}
+
+ {/* 组件层级 */}
+ {selectedElement.hierarchy && selectedElement.hierarchy.length > 0 && (
+
+
组件层级
+
+ {selectedElement.hierarchy.map((item, index) => (
+
+ {item.componentName ? (
+ {item.componentName}
+ ) : (
+ {item.tagName}
+ )}
+ {item.fileName && (
+
+ ({item.fileName.split('/').pop()})
+
+ )}
+
+ ))}
+
+
+ )}
+
+
+
+ {/* Content Editor - 仅当 isStaticText 为 true 时显示 */}
+ {selectedElement.isStaticText && (
+
+
+
+ )}
+
+
+
+ {/* Smart Style Controls */}
+
+
样式设置
+
+ {renderColorPicker('bg', ['white', 'gray-100', 'red-100', 'blue-100', 'green-100', 'yellow-100', 'purple-100'], '背景颜色')}
+ {renderColorPicker('text', ['gray-900', 'gray-500', 'red-600', 'blue-600', 'green-600', 'purple-600', 'white'], '文字颜色')}
+
+ {renderSelect('p', [
+ { label: '无', value: '0' },
+ { label: '小', value: '2' },
+ { label: '中', value: '4' },
+ { label: '大', value: '8' },
+ { label: '特大', value: '12' }
+ ], '内边距 (Padding)')}
+
+ {renderSelect('rounded', [
+ { label: '直角', value: 'none' },
+ { label: '小圆角', value: 'sm' },
+ { label: '圆角', value: 'md' },
+ { label: '大圆角', value: 'lg' },
+ { label: '全圆角', value: 'full' }
+ ], '圆角 (Border Radius)')}
+
+ {renderSelect('text', [
+ { label: '小', value: 'sm' },
+ { label: '默认', value: 'base' },
+ { label: '中', value: 'lg' },
+ { label: '大', value: 'xl' },
+ { label: '特大', value: '2xl' }
+ ], '文字大小 (Font Size)')}
+
+
+ {/* Raw Style Editor */}
+
+
+
+
+ ) : (
+
+
+ {iframeDesignMode
+ ? '点击 iframe 内的元素开始编辑'
+ : '请先启用设计模式'}
+
+
+ )}
+
+
+
+ {/* Right: Iframe Preview */}
+
+
+
+ >
+ );
+}
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/ImageCaption.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/ImageCaption.tsx
new file mode 100644
index 00000000..d3e9ceef
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/ImageCaption.tsx
@@ -0,0 +1,318 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Label } from '@/components/ui/label';
+import { ArrowLeft, Sparkles, Copy, Check, Loader2, Upload, X } from 'lucide-react';
+import { toast } from 'sonner';
+import RichTextEditor from '@/components/editor/RichTextEditor';
+import { sendChatStream } from '@/services/chatStream';
+import PageMeta from '@/components/common/PageMeta';
+
+const APP_ID = import.meta.env.VITE_APP_ID;
+
+const markdownToHtml = (markdown: string): string => {
+ let html = markdown;
+
+ html = html.replace(/^### (.*$)/gim, '$1
');
+ html = html.replace(/^## (.*$)/gim, '$1
');
+ html = html.replace(/^# (.*$)/gim, '$1
');
+
+ html = html.replace(/\*\*(.*?)\*\*/g, '$1');
+ html = html.replace(/\*(.*?)\*/g, '$1');
+
+ html = html.replace(/^\* (.*$)/gim, '$1');
+ html = html.replace(/(.*<\/li>)/s, '');
+
+ html = html.replace(/\n\n/g, '');
+ html = '
' + html + '
';
+
+ return html;
+};
+
+export default function ImageCaption() {
+ const navigate = useNavigate();
+ const [imageFile, setImageFile] = useState(null);
+ const [imagePreview, setImagePreview] = useState('');
+ const [imageBase64, setImageBase64] = useState('');
+ const [content, setContent] = useState('');
+ const [isGenerating, setIsGenerating] = useState(false);
+ const [isCopied, setIsCopied] = useState(false);
+ const [abortController, setAbortController] = useState(null);
+
+ const handleImageUpload = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ if (!file.type.match(/^image\/(jpeg|jpg|png|bmp)$/)) {
+ toast.error('仅支持 JPG、PNG、BMP 格式的图片');
+ return;
+ }
+
+ if (file.size > 10 * 1024 * 1024) {
+ toast.error('图片大小不能超过 10MB');
+ return;
+ }
+
+ setImageFile(file);
+ const reader = new FileReader();
+
+ reader.onload = (event) => {
+ const result = event.target?.result as string;
+ setImagePreview(result);
+
+ const base64Data = result.split(',')[1];
+ const mimeType = file.type;
+ setImageBase64(`data:${mimeType};base64,${base64Data}`);
+ };
+
+ reader.readAsDataURL(file);
+ };
+
+ const handleRemoveImage = () => {
+ setImageFile(null);
+ setImagePreview('');
+ setImageBase64('');
+ setContent('');
+ };
+
+ const handleGenerate = async () => {
+ if (!imageBase64) {
+ toast.error('请先上传图片');
+ return;
+ }
+
+ setIsGenerating(true);
+ setContent('');
+ const controller = new AbortController();
+ setAbortController(controller);
+
+ let markdownContent = '';
+
+ try {
+ await sendChatStream({
+ endpoint: 'https://api-integrations.appmiaoda.com/app-7a7nlc9zki69/api-2jBYdN3A9Jyz/v2/chat/completions',
+ apiId: APP_ID,
+ messages: [
+ {
+ role: 'system',
+ content: '你是一个专业的公众号文案写手,擅长为图片配写简洁有力的文案。请根据图片内容,生成适合公众号发布的配文,包括标题和正文。文案要简洁、有吸引力,能够引起读者共鸣。使用 Markdown 格式输出。'
+ },
+ {
+ role: 'user',
+ content: [
+ {
+ type: 'text',
+ text: '请为这张图片生成适合公众号发布的配文'
+ },
+ {
+ type: 'image_url',
+ image_url: {
+ url: imageBase64
+ }
+ }
+ ]
+ }
+ ],
+ onUpdate: (newContent: string) => {
+ markdownContent = newContent;
+ const htmlContent = markdownToHtml(markdownContent);
+ setContent(htmlContent);
+ },
+ onComplete: () => {
+ setIsGenerating(false);
+ setAbortController(null);
+ toast.success('配文生成完成');
+ },
+ onError: (error: Error) => {
+ setIsGenerating(false);
+ setAbortController(null);
+ toast.error(`生成失败: ${error.message}`);
+ },
+ signal: controller.signal
+ });
+ } catch (error) {
+ if (!controller.signal.aborted) {
+ setIsGenerating(false);
+ setAbortController(null);
+ toast.error('生成失败,请重试');
+ }
+ }
+ };
+
+ const handleStop = () => {
+ if (abortController) {
+ abortController.abort();
+ setAbortController(null);
+ setIsGenerating(false);
+ toast.info('已停止生成');
+ }
+ };
+
+ const handleCopy = async () => {
+ try {
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = content;
+
+ const blob = new Blob([tempDiv.innerHTML], { type: 'text/html' });
+ const clipboardItem = new ClipboardItem({
+ 'text/html': blob,
+ 'text/plain': new Blob([tempDiv.innerText], { type: 'text/plain' })
+ });
+
+ await navigator.clipboard.write([clipboardItem]);
+ setIsCopied(true);
+ toast.success('内容已复制,可直接粘贴到公众号编辑器');
+
+ setTimeout(() => setIsCopied(false), 2000);
+ } catch (error) {
+ toast.error('复制失败,请手动选择复制');
+ }
+ };
+
+ return (
+ <>
+
+
+
+
navigate('/')}
+ >
+
+ 返回首页
+
+
+
+
+
+
+
+ 图片配文
+
+
+ 上传图片,AI 将为您生成精准的文案内容
+
+
+
+
+
+ {imagePreview ? (
+
+

+
+
+
+
+ ) : (
+
+ )}
+
+
+ {isGenerating ? (
+
+
+ 停止生成
+
+ ) : (
+
+
+ 生成配文
+
+ )}
+
+ {content && !isGenerating && (
+
+ {isCopied ? (
+ <>
+
+ 已复制
+ >
+ ) : (
+ <>
+
+ 复制内容
+ >
+ )}
+
+ )}
+
+
+
使用提示:
+
+ - 图片内容越清晰,生成的配文越精准
+ - 支持 JPG、PNG、BMP 格式
+ - 可以在编辑器中修改生成的配文
+
+
+
+
+
+
+
+ 配文预览与编辑
+
+ 在这里查看和编辑生成的配文
+
+
+
+ {content ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ >
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/NotFound.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/NotFound.tsx
new file mode 100644
index 00000000..a9462a29
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/NotFound.tsx
@@ -0,0 +1,39 @@
+import { Link } from "react-router-dom";
+import PageMeta from "@/components/common/PageMeta";
+
+export default function NotFound() {
+ return (
+ <>
+
+
+
+
+ 错误
+
+
+

+

+
+
+ 页面可能已被删除或不存在,请检查网址是否正确。
+
+
+
+ 返回首页
+
+
+ {/* */}
+
+ © {new Date().getFullYear()}
+
+
+ >
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/SamplePage.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/SamplePage.tsx
new file mode 100644
index 00000000..ee5b10e1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/SamplePage.tsx
@@ -0,0 +1,16 @@
+/**
+ * Sample Page
+ */
+
+import PageMeta from "../components/common/PageMeta";
+
+export default function SamplePage() {
+ return (
+ <>
+
+
+
This is a sample page
+
+ >
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/UserSet.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/UserSet.tsx
new file mode 100644
index 00000000..115e2084
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/UserSet.tsx
@@ -0,0 +1,245 @@
+import React, { useState, useRef } from 'react';
+import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Label } from '@/components/ui/label';
+import { toast } from 'sonner';
+
+interface UserProfile {
+ name: string;
+ email: string;
+ phone: string;
+ avatar?: string;
+}
+
+const UserSet: React.FC = () => {
+ const [profile, setProfile] = useState({
+ name: '张三',
+ email: 'zhangsan@example.com',
+ phone: '13800138000',
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=zhangsan'
+ });
+
+ const [isLoading, setIsLoading] = useState(false);
+ const fileInputRef = useRef(null);
+
+ const handleAvatarChange = async (event: React.ChangeEvent) => {
+ const file = event.target.files?.[0];
+ if (!file) return;
+
+ if (file.size > 5 * 1024 * 1024) {
+ toast.error('头像文件不能超过 5MB');
+ return;
+ }
+
+ if (!file.type.startsWith('image/')) {
+ toast.error('请选择有效的图片文件');
+ return;
+ }
+
+ setIsLoading(true);
+
+ try {
+ // 创建临时 URL 用于预览
+ const tempUrl = URL.createObjectURL(file);
+
+ // 模拟上传过程
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // 实际项目中这里应该上传到服务器
+ // 这里我们创建一个更可靠的头像 URL
+ const newAvatarUrl = `https://api.dicebear.com/7.x/avataaars/svg?seed=${Date.now()}`;
+
+ setProfile(prev => ({
+ ...prev,
+ avatar: newAvatarUrl
+ }));
+
+ // 清理临时 URL
+ URL.revokeObjectURL(tempUrl);
+
+ toast.success('头像更新成功');
+ } catch (error) {
+ console.error('头像上传失败:', error);
+ toast.error('头像上传失败,请重试');
+ } finally {
+ setIsLoading(false);
+ if (fileInputRef.current) {
+ fileInputRef.current.value = '';
+ }
+ }
+ };
+
+ const handleProfileUpdate = async () => {
+ setIsLoading(true);
+
+ try {
+ // 模拟保存过程
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ toast.success('个人信息保存成功');
+ } catch (error) {
+ console.error('保存失败:', error);
+ toast.error('保存失败,请重试');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleInputChange = (field: keyof UserProfile) => (event: React.ChangeEvent) => {
+ setProfile(prev => ({
+ ...prev,
+ [field]: event.target.value
+ }));
+ };
+
+ const getAvatarSrc = (avatarUrl?: string) => {
+ // 如果没有头像URL,返回空
+ if (!avatarUrl) return '';
+
+ // 如果是相对路径,返回绝对路径
+ if (avatarUrl.startsWith('/')) {
+ return avatarUrl;
+ }
+
+ // 如果已经是完整的URL,直接返回
+ if (avatarUrl.startsWith('http://') || avatarUrl.startsWith('https://')) {
+ return avatarUrl;
+ }
+
+ // 其他情况,添加默认URL前缀
+ return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(avatarUrl)}`;
+ };
+
+ return (
+
+
+
+ 个人设置
+
+
+ {/* 头像设置 */}
+
+
+
+ {
+ // 图片加载失败时使用备用头像
+ e.currentTarget.style.display = 'none';
+ const fallback = e.currentTarget.parentElement?.querySelector('.avatar-fallback');
+ if (fallback) {
+ fallback.style.display = 'flex';
+ }
+ }}
+ />
+
+ {profile.name.charAt(0)}
+
+
+
+
fileInputRef.current?.click()}
+ disabled={isLoading}
+ >
+
+
+
+
+
+
+
+ 支持 JPG、PNG 格式,文件大小不超过 5MB
+
+
+
+ {/* 个人信息 */}
+
+
+ {/* 保存按钮 */}
+
+
+ {isLoading ? (
+
+ ) : (
+ '保存修改'
+ )}
+
+
+
+
+
+ );
+};
+
+export default UserSet;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/VideoScript.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/VideoScript.tsx
new file mode 100644
index 00000000..14378bf1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/pages/VideoScript.tsx
@@ -0,0 +1,243 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Textarea } from '@/components/ui/textarea';
+import { Label } from '@/components/ui/label';
+import { ArrowLeft, Sparkles, Copy, Check, Loader2 } from 'lucide-react';
+import { toast } from 'sonner';
+import RichTextEditor from '@/components/editor/RichTextEditor';
+import { sendChatStream } from '@/services/chatStream';
+import PageMeta from '@/components/common/PageMeta';
+
+const APP_ID = import.meta.env.VITE_APP_ID;
+
+const markdownToHtml = (markdown: string): string => {
+ let html = markdown;
+
+ html = html.replace(/^### (.*$)/gim, '$1
');
+ html = html.replace(/^## (.*$)/gim, '$1
');
+ html = html.replace(/^# (.*$)/gim, '$1
');
+
+ html = html.replace(/\*\*(.*?)\*\*/g, '$1');
+ html = html.replace(/\*(.*?)\*/g, '$1');
+
+ html = html.replace(/^\* (.*$)/gim, '$1');
+ html = html.replace(/(.*<\/li>)/s, '');
+
+ html = html.replace(/\n\n/g, '');
+ html = '
' + html + '
';
+
+ return html;
+};
+
+export default function VideoScript() {
+ const navigate = useNavigate();
+ const [topic, setTopic] = useState('');
+ const [content, setContent] = useState('');
+ const [isGenerating, setIsGenerating] = useState(false);
+ const [isCopied, setIsCopied] = useState(false);
+ const [abortController, setAbortController] = useState(null);
+
+ const handleGenerate = async () => {
+ if (!topic.trim()) {
+ toast.error('请输入视频主题');
+ return;
+ }
+
+ setIsGenerating(true);
+ setContent('');
+ const controller = new AbortController();
+ setAbortController(controller);
+
+ let markdownContent = '';
+
+ try {
+ await sendChatStream({
+ endpoint: 'https://api-integrations.appmiaoda.com/app-7a7nlc9zki69/api-2bk93oeO9NlE/v2/chat/completions',
+ apiId: APP_ID,
+ messages: [
+ {
+ role: 'system',
+ content: '你是一个专业的视频脚本创作者,擅长为公众号视频内容创作详细的拍摄脚本。请根据用户提供的主题,生成一个完整的视频脚本,包括:标题、简介、分镜脚本(场景描述、台词、时长、拍摄要点)。脚本要详细、实用,便于执行。使用 Markdown 格式输出,用表格展示分镜脚本。'
+ },
+ {
+ role: 'user',
+ content: `请为我创作一个关于"${topic}"的视频拍摄脚本`
+ }
+ ],
+ onUpdate: (newContent: string) => {
+ markdownContent = newContent;
+ const htmlContent = markdownToHtml(markdownContent);
+ setContent(htmlContent);
+ },
+ onComplete: () => {
+ setIsGenerating(false);
+ setAbortController(null);
+ toast.success('脚本生成完成');
+ },
+ onError: (error: Error) => {
+ setIsGenerating(false);
+ setAbortController(null);
+ toast.error(`生成失败: ${error.message}`);
+ },
+ signal: controller.signal
+ });
+ } catch (error) {
+ if (!controller.signal.aborted) {
+ setIsGenerating(false);
+ setAbortController(null);
+ toast.error('生成失败,请重试');
+ }
+ }
+ };
+
+ const handleStop = () => {
+ if (abortController) {
+ abortController.abort();
+ setAbortController(null);
+ setIsGenerating(false);
+ toast.info('已停止生成');
+ }
+ };
+
+ const handleCopy = async () => {
+ try {
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = content;
+
+ const blob = new Blob([tempDiv.innerHTML], { type: 'text/html' });
+ const clipboardItem = new ClipboardItem({
+ 'text/html': blob,
+ 'text/plain': new Blob([tempDiv.innerText], { type: 'text/plain' })
+ });
+
+ await navigator.clipboard.write([clipboardItem]);
+ setIsCopied(true);
+ toast.success('脚本已复制');
+
+ setTimeout(() => setIsCopied(false), 2000);
+ } catch (error) {
+ toast.error('复制失败,请手动选择复制');
+ }
+ };
+
+ return (
+ <>
+
+
+
+
navigate('/')}
+ >
+
+ 返回首页
+
+
+
+
+
+
+
+ 视频脚本生成
+
+
+ 输入视频主题,AI 将为您生成详细的拍摄脚本
+
+
+
+
+
+
+
+ {isGenerating ? (
+
+
+ 停止生成
+
+ ) : (
+
+
+ 生成脚本
+
+ )}
+
+ {content && !isGenerating && (
+
+ {isCopied ? (
+ <>
+
+ 已复制
+ >
+ ) : (
+ <>
+
+ 复制脚本
+ >
+ )}
+
+ )}
+
+
+
使用提示:
+
+ - 详细描述视频主题和目标受众
+ - 脚本包含分镜、台词、时长等信息
+ - 可以在编辑器中修改和完善脚本
+ - 适合短视频和中长视频制作
+
+
+
+
+
+
+
+ 脚本预览与编辑
+
+ 在这里查看和编辑生成的视频脚本
+
+
+
+ {content ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ >
+ );
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/routes.tsx b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/routes.tsx
new file mode 100644
index 00000000..37082cbf
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/routes.tsx
@@ -0,0 +1,49 @@
+import Home from './pages/Home';
+import ArticleGenerator from './pages/ArticleGenerator';
+import ImageCaption from './pages/ImageCaption';
+import VideoScript from './pages/VideoScript';
+import UserSet from './pages/UserSet';
+import IframeDemoPage from './pages/IframeDemoPage';
+import type { ReactNode } from 'react';
+
+interface RouteConfig {
+ name: string;
+ path: string;
+ element: ReactNode;
+ visible?: boolean;
+}
+
+const routes: RouteConfig[] = [
+ {
+ name: '首页',
+ path: '/',
+ element:
+ },
+ {
+ name: '图文生成',
+ path: '/article-generator',
+ element:
+ },
+ {
+ name: '图片配文',
+ path: '/image-caption',
+ element:
+ },
+ {
+ name: '视频脚本',
+ path: '/video-script',
+ element:
+ },
+ {
+ name: '设计模式演示',
+ path: '/iframe-demo',
+ element:
+ },
+ {
+ name: '个人设置',
+ path: '/user-set',
+ element:
+ }
+];
+
+export default routes;
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/services/chatStream.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/services/chatStream.ts
new file mode 100644
index 00000000..62b3af55
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/services/chatStream.ts
@@ -0,0 +1,150 @@
+import ky, { type KyResponse, type AfterResponseHook, type NormalizedOptions } from 'ky';
+import { createParser, type EventSourceParser } from 'eventsource-parser';
+
+export interface SSEOptions {
+ onData: (data: string) => void;
+ onEvent?: (event: any) => void;
+ onCompleted?: (error?: Error) => void;
+ onAborted?: () => void;
+ onReconnectInterval?: (interval: number) => void;
+}
+
+export const createSSEHook = (options: SSEOptions): AfterResponseHook => {
+ const hook: AfterResponseHook = async (request: Request, _options: NormalizedOptions, response: KyResponse) => {
+ if (!response.ok || !response.body) {
+ return;
+ }
+
+ let completed = false;
+ const innerOnCompleted = (error?: Error): void => {
+ if (completed) {
+ return;
+ }
+
+ completed = true;
+ options.onCompleted?.(error);
+ };
+
+ const isAborted = false;
+
+ const reader: ReadableStreamDefaultReader = response.body.getReader();
+
+ const decoder = new TextDecoder('utf8');
+
+ const parser: EventSourceParser = createParser({
+ onEvent: (event) => {
+ if (event.data) {
+ options.onEvent?.(event);
+ const dataArray: string[] = event.data.split('\n');
+ for (const data of dataArray) {
+ options.onData(data);
+ }
+ }
+ }
+ });
+
+ const read = (): void => {
+ if (isAborted) {
+ return;
+ }
+
+ reader.read().then((result: ReadableStreamReadResult) => {
+ if (result.done) {
+ innerOnCompleted();
+ return;
+ }
+
+ parser.feed(decoder.decode(result.value, { stream: true }));
+
+ read();
+ }).catch(error => {
+ if (request.signal.aborted) {
+ options.onAborted?.();
+ return;
+ }
+
+ innerOnCompleted(error as Error);
+ });
+ };
+
+ read();
+
+ return response;
+ };
+
+ return hook;
+};
+
+type ContentPart =
+ | { type: 'text'; text: string }
+ | { type: 'image_url'; image_url: { url: string } };
+
+export interface ChatMessage {
+ role: 'user' | 'assistant' | 'system';
+ content: string | ContentPart[];
+ id?: string;
+}
+
+export interface ChatStreamOptions {
+ endpoint: string;
+ messages: ChatMessage[];
+ apiId: string;
+ onUpdate: (content: string) => void;
+ onComplete: () => void;
+ onError: (error: Error) => void;
+ signal?: AbortSignal;
+}
+
+export const sendChatStream = async (options: ChatStreamOptions): Promise => {
+ const { messages, onUpdate, onComplete, onError, signal } = options;
+
+ let currentContent = '';
+
+ const sseHook = createSSEHook({
+ onData: (data: string) => {
+ try {
+ const parsed = JSON.parse(data);
+ if (parsed.choices?.[0]?.delta?.content) {
+ currentContent += parsed.choices[0].delta.content;
+ onUpdate(currentContent);
+ }
+ } catch {
+ console.warn('Failed to parse SSE data:', data);
+ }
+ },
+ onCompleted: (error?: Error) => {
+ if (error) {
+ onError(error);
+ } else {
+ onComplete();
+ }
+ },
+ onAborted: () => {
+ console.log('Stream aborted');
+ }
+ });
+
+ try {
+ await ky.post(options.endpoint, {
+ json: {
+ messages: messages.map(msg => ({
+ role: msg.role,
+ content: msg.content
+ })),
+ enable_thinking: false
+ },
+ headers: {
+ 'X-App-Id': options.apiId,
+ 'Content-Type': 'application/json'
+ },
+ signal,
+ hooks: {
+ afterResponse: [sseHook]
+ }
+ });
+ } catch (error) {
+ if (!signal?.aborted) {
+ onError(error as Error);
+ }
+ }
+};
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/services/imageGeneration.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/services/imageGeneration.ts
new file mode 100644
index 00000000..b2e0c5c1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/services/imageGeneration.ts
@@ -0,0 +1,100 @@
+import axios from 'axios';
+
+const APP_ID = import.meta.env.VITE_APP_ID;
+
+const apiClient = axios.create({
+ timeout: 60000,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-App-Id': APP_ID
+ }
+});
+
+apiClient.interceptors.response.use(
+ (response) => response.data,
+ (error) => {
+ console.error('API 请求错误:', error);
+ if (error.response?.data?.status === 999) {
+ throw new Error(error.response.data.msg);
+ }
+ return Promise.reject(error);
+ }
+);
+
+export interface ImageGenerationParams {
+ prompt: string;
+ width?: number;
+ height?: number;
+ image_num?: number;
+}
+
+export interface ImageGenerationResponse {
+ status: number;
+ msg: string;
+ data: {
+ task_id: number;
+ };
+}
+
+export interface ImageQueryResponse {
+ status: number;
+ msg: string;
+ data: {
+ task_status: string;
+ task_progress: number;
+ sub_task_result_list?: Array<{
+ sub_task_status: string;
+ final_image_list?: Array<{
+ img_url: string;
+ width: number;
+ height: number;
+ }>;
+ }>;
+ };
+}
+
+export const generateImage = async (params: ImageGenerationParams): Promise => {
+ return apiClient.post(
+ 'https://api-integrations.appmiaoda.com/app-7a7nlc9zki69/api-2bk93oeO9Nyz/v1/bce/wenxinworkshop/ai_custom_image_creation/v2/text2image',
+ {
+ prompt: params.prompt,
+ width: params.width || 768,
+ height: params.height || 1360,
+ image_num: params.image_num || 1
+ }
+ );
+};
+
+export const queryImageResult = async (taskId: number): Promise => {
+ return apiClient.post(
+ 'https://api-integrations.appmiaoda.com/app-7a7nlc9zki69/api-2jBYdN3A9Nyz/v1/bce/wenxinworkshop/ai_custom_image_creation/v2/query_task',
+ {
+ task_id: taskId
+ }
+ );
+};
+
+export const pollImageResult = async (
+ taskId: number,
+ maxAttempts = 30,
+ interval = 2000
+): Promise => {
+ for (let i = 0; i < maxAttempts; i++) {
+ const result = await queryImageResult(taskId);
+
+ if (result.data.task_status === 'SUCCESS') {
+ const imageUrl = result.data.sub_task_result_list?.[0]?.final_image_list?.[0]?.img_url;
+ if (imageUrl) {
+ return imageUrl;
+ }
+ }
+
+ if (result.data.task_status === 'FAILED') {
+ throw new Error('图片生成失败');
+ }
+
+ await new Promise(resolve => setTimeout(resolve, interval));
+ }
+
+ throw new Error('图片生成超时');
+};
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/svg.d.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/svg.d.ts
new file mode 100644
index 00000000..789797fa
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/svg.d.ts
@@ -0,0 +1,6 @@
+declare module "*.svg?react" {
+ import React = require("react");
+ export const ReactComponent: React.FC>;
+ const src: string;
+ export default src;
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/article.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/article.ts
new file mode 100644
index 00000000..09801365
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/article.ts
@@ -0,0 +1,20 @@
+export interface ArticleContent {
+ title: string;
+ content: string;
+ images?: string[];
+}
+
+export interface VideoScript {
+ title: string;
+ scenes: SceneScript[];
+}
+
+export interface SceneScript {
+ sceneNumber: number;
+ description: string;
+ dialogue: string;
+ duration: string;
+ notes: string;
+}
+
+export type ContentType = 'article' | 'image-caption' | 'video-script';
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/index.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/index.ts
new file mode 100644
index 00000000..14cc2d85
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/index.ts
@@ -0,0 +1,6 @@
+export interface Option {
+ label: string;
+ value: string;
+ icon?: React.ComponentType<{ className?: string }>;
+ withCount?: boolean;
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/messages.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/messages.ts
new file mode 100644
index 00000000..fa8b6ed2
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/types/messages.ts
@@ -0,0 +1,29 @@
+// Message types for iframe ↔ parent window communication
+
+export interface SourceInfo {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+ elementType?: string;
+ componentName?: string;
+ functionName?: string;
+ elementId?: string;
+ importPath?: string;
+}
+
+export interface ElementInfo {
+ tagName: string;
+ className: string;
+ textContent: string;
+ sourceInfo: SourceInfo;
+ isStaticText: boolean;
+ componentName?: string;
+ componentPath?: string;
+ props?: Record;
+ hierarchy?: {
+ tagName: string;
+ componentName?: string;
+ fileName?: string;
+ }[];
+}
+
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/vite-env.d.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/tailwind.config.js b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tailwind.config.js
new file mode 100644
index 00000000..4634ac78
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tailwind.config.js
@@ -0,0 +1,159 @@
+export default {
+ darkMode: ['class'],
+ content: [
+ './index.html',
+ './pages/**/*.{ts,tsx}',
+ './components/**/*.{ts,tsx}',
+ './app/**/*.{ts,tsx}',
+ './src/**/*.{ts,tsx}',
+ './node_modules/streamdown/dist/**/*.js'
+ ],
+ safelist: ['border', 'border-border'],
+ prefix: '',
+ theme: {
+ container: {
+ center: true,
+ padding: '2rem',
+ screens: {
+ '2xl': '1400px',
+ },
+ },
+ extend: {
+ colors: {
+ border: 'hsl(var(--border))',
+ borderColor: {
+ border: 'hsl(var(--border))',
+ },
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))',
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))',
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))',
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))',
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))',
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))',
+ },
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))',
+ },
+ education: {
+ blue: 'hsl(var(--education-blue))',
+ green: 'hsl(var(--education-green))',
+ },
+ success: 'hsl(var(--success))',
+ warning: 'hsl(var(--warning))',
+ info: 'hsl(var(--info))',
+ sidebar: {
+ DEFAULT: 'hsl(var(--sidebar-background))',
+ foreground: 'hsl(var(--sidebar-foreground))',
+ primary: 'hsl(var(--sidebar-primary))',
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
+ accent: 'hsl(var(--sidebar-accent))',
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
+ border: 'hsl(var(--sidebar-border))',
+ ring: 'hsl(var(--sidebar-ring))',
+ },
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)',
+ },
+ backgroundImage: {
+ 'gradient-primary': 'var(--gradient-primary)',
+ 'gradient-card': 'var(--gradient-card)',
+ 'gradient-background': 'var(--gradient-background)',
+ },
+ boxShadow: {
+ card: 'var(--shadow-card)',
+ hover: 'var(--shadow-hover)',
+ },
+ keyframes: {
+ 'accordion-down': {
+ from: {
+ height: '0',
+ },
+ to: {
+ height: 'var(--radix-accordion-content-height)',
+ },
+ },
+ 'accordion-up': {
+ from: {
+ height: 'var(--radix-accordion-content-height)',
+ },
+ to: {
+ height: '0',
+ },
+ },
+ 'fade-in': {
+ from: {
+ opacity: '0',
+ transform: 'translateY(10px)',
+ },
+ to: {
+ opacity: '1',
+ transform: 'translateY(0)',
+ },
+ },
+ 'slide-in': {
+ from: {
+ opacity: '0',
+ transform: 'translateX(-20px)',
+ },
+ to: {
+ opacity: '1',
+ transform: 'translateX(0)',
+ },
+ },
+ },
+ animation: {
+ 'accordion-down': 'accordion-down 0.2s ease-out',
+ 'accordion-up': 'accordion-up 0.2s ease-out',
+ 'fade-in': 'fade-in 0.5s ease-out',
+ 'slide-in': 'slide-in 0.5s ease-out',
+ },
+ },
+ },
+ plugins: [
+ require('tailwindcss-animate'),
+ function ({ addUtilities }) {
+ addUtilities(
+ {
+ '.border-t-solid': { 'border-top-style': 'solid' },
+ '.border-r-solid': { 'border-right-style': 'solid' },
+ '.border-b-solid': { 'border-bottom-style': 'solid' },
+ '.border-l-solid': { 'border-left-style': 'solid' },
+ '.border-t-dashed': { 'border-top-style': 'dashed' },
+ '.border-r-dashed': { 'border-right-style': 'dashed' },
+ '.border-b-dashed': { 'border-bottom-style': 'dashed' },
+ '.border-l-dashed': { 'border-left-style': 'dashed' },
+ '.border-t-dotted': { 'border-top-style': 'dotted' },
+ '.border-r-dotted': { 'border-right-style': 'dotted' },
+ '.border-b-dotted': { 'border-bottom-style': 'dotted' },
+ '.border-l-dotted': { 'border-left-style': 'dotted' },
+ },
+ ['responsive']
+ );
+ },
+ ],
+};
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.app.json b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.app.json
new file mode 100644
index 00000000..d6f64d52
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.app.json
@@ -0,0 +1,33 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+ "typeRoots": ["./node_modules/**/*"]
+ },
+ "include": ["src"]
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.check.json b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.check.json
new file mode 100644
index 00000000..c8c939bb
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.check.json
@@ -0,0 +1,17 @@
+{
+ "include": ["./src"],
+ "exclude": ["./src/**/*.test.ts", "./src/**/*.spec.ts", "./src/components/ui"],
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "target": "ES2020",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "noEmit": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.json b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.json
new file mode 100644
index 00000000..c9052ee1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.node.json b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.node.json
new file mode 100644
index 00000000..db0becc8
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/tsconfig.node.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/qiming-vite-plugin-design-mode/examples/react-vite-md2/vite.config.ts b/qiming-vite-plugin-design-mode/examples/react-vite-md2/vite.config.ts
new file mode 100644
index 00000000..97d18c6b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/examples/react-vite-md2/vite.config.ts
@@ -0,0 +1,49 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import svgr from 'vite-plugin-svgr';
+import path from 'path';
+import appdevDesignMode from '@xagi/vite-plugin-design-mode';
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [
+
+ //
+ {
+ name: 'dev-inject',
+ enforce: 'post', // 确保在 HTML 注入阶段最后执行
+ transformIndexHtml(html) {
+ if (!html.includes('data-id="dev-inject-monitor"')) {
+ return html.replace("", `
+
+ \n`);
+ }
+ return html;
+ }
+ },
+ //
+
+ react(), svgr({
+ svgrOptions: {
+ icon: true, exportType: 'named', namedExport: 'ReactComponent', }, }),
+ appdevDesignMode()
+ ],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
+});
diff --git a/qiming-vite-plugin-design-mode/package.json b/qiming-vite-plugin-design-mode/package.json
new file mode 100644
index 00000000..384440ec
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "vite-plugin-appdev-design-mode-monorepo",
+ "version": "1.2.0",
+ "description": "Vite plugin for Design Mode - enables source mapping and visual editing for React/Vue projects",
+ "private": true,
+ "type": "module",
+ "packageManager": "pnpm@10.18.3",
+ "keywords": [
+ "vite",
+ "plugin",
+ "react",
+ "vue",
+ "design-mode",
+ "source-mapping",
+ "hot-reload"
+ ],
+ "author": "XAGI Team",
+ "license": "MIT",
+ "devDependencies": {
+ "@types/babel__core": "^7.20.0",
+ "@types/babel__standalone": "^7.1.9",
+ "@types/babel__traverse": "^7.20.0",
+ "@types/node": "^20.19.39",
+ "@types/react": "^18.3.28",
+ "@types/react-dom": "^18.3.1",
+ "typescript": "^5.0.0",
+ "vite": "^5.4.21",
+ "vitest": "^1.0.0"
+ },
+ "scripts": {
+ "build": "pnpm -r --filter './packages/**' build",
+ "build:plugin": "pnpm --filter @xagi/vite-plugin-design-mode build",
+ "build:shared": "pnpm --filter @xagi/design-mode-shared build",
+ "build:react": "pnpm --filter @xagi/design-mode-client-react build",
+ "build:vue": "pnpm --filter @xagi/design-mode-client-vue build",
+ "clean": "rm -rf packages/*/dist",
+ "release:build": "pnpm build",
+ "release:preflight": "bash ./scripts/release-preflight.sh",
+ "release:next:dry-run": "bash ./scripts/release-dry-run.sh",
+ "release:beta:dry-run": "bash ./scripts/release-dry-run-beta.sh",
+ "release:publish:next": "bash ./scripts/release-publish-next.sh",
+ "release:publish:beta": "bash ./scripts/release-publish-beta.sh",
+ "release:verify:next": "bash ./scripts/release-verify-next.sh",
+ "release:verify:beta": "bash ./scripts/release-verify-beta.sh",
+ "release:sync:npmmirror": "bash ./scripts/release-sync-npmmirror.sh",
+ "release:version:sync": "bash ./scripts/release-version-sync.sh",
+ "release:next": "pnpm run release:preflight && pnpm run release:build && pnpm run release:publish:next && pnpm run release:verify:next",
+ "release:beta": "pnpm run release:preflight && pnpm run release:build && pnpm run release:publish:beta && pnpm run release:verify:beta",
+ "release": "pnpm run release:next",
+ "test": "vitest",
+ "test:run": "vitest run",
+ "test:coverage": "vitest run --coverage",
+ "test:ui": "vitest --ui"
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/package.json b/qiming-vite-plugin-design-mode/packages/client-react/package.json
new file mode 100644
index 00000000..33c2f83f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@xagi/design-mode-client-react",
+ "version": "1.2.0",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsc"
+ },
+ "dependencies": {
+ "@xagi/design-mode-shared": "1.2.0",
+ "tailwind-merge": "^3.4.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "typescript": "^5.0.0"
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeBridge.tsx b/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeBridge.tsx
new file mode 100644
index 00000000..06ccdf80
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeBridge.tsx
@@ -0,0 +1,209 @@
+import React, { useEffect, useCallback } from 'react';
+import { useDesignMode } from './DesignModeContext';
+import { bridge } from './bridge';
+import {
+ UpdateStyleMessage,
+ UpdateContentMessage,
+ ToggleDesignModeMessage,
+ ElementSelectedMessage,
+ ElementDeselectedMessage
+} from '@xagi/design-mode-shared/messages';
+import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
+import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
+import { resolveSourceInfo } from '@xagi/design-mode-shared/sourceInfoResolver';
+import { extractSourceInfo } from '@xagi/design-mode-shared/sourceInfo';
+
+export const DesignModeBridge: React.FC = () => {
+ const { selectedElement, modifyElementClass, updateElementContent } =
+ useDesignMode();
+
+ /** True in iframe with an active bridge target. */
+ const isBridgeReady = useCallback(() => {
+ if (window.self === window.top) {
+ return false;
+ }
+
+ if (!bridge.isConnectedToTarget()) {
+ return false;
+ }
+
+ return true;
+ }, []);
+
+ const safeSend = useCallback(
+ async (type: string, payload: any) => {
+ if (!isBridgeReady()) {
+ return;
+ }
+
+ try {
+ const message = {
+ type,
+ payload,
+ timestamp: Date.now(),
+ };
+
+ await bridge.send(message as any);
+ } catch (error) {
+ // Failed to send message
+ }
+ },
+ [isBridgeReady]
+ );
+
+ /** Push ELEMENT_SELECTED / DESELECTED to parent after a short delay (bridge init). */
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ if (selectedElement) {
+ const sourceInfo = resolveSourceInfo(selectedElement);
+ // console.log('[DesignModeBridge] resolveSourceInfo', window.location.href, sourceInfo);
+
+ // Static text: static-content attr + DOM is text-only
+ const hasStaticContentAttr = selectedElement.hasAttribute(AttributeNames.staticContent);
+ const isActuallyPureText = isPureStaticText(selectedElement);
+ const isStaticText = hasStaticContentAttr && isActuallyPureText;
+
+ // Editable class when static-class is present
+ const isStaticClass = selectedElement.hasAttribute(AttributeNames.staticClass);
+
+ const elementData = {
+ tagName: selectedElement.tagName.toLowerCase(),
+ className: selectedElement.className || '',
+ textContent: (
+ selectedElement.textContent ||
+ selectedElement.innerText ||
+ ''
+ ).substring(0, 100),
+ sourceInfo: sourceInfo || {
+ fileName: '',
+ lineNumber: 0,
+ columnNumber: 0,
+ },
+ isStaticText: isStaticText || false,
+ isStaticClass: isStaticClass,
+ };
+
+ if (
+ !elementData.sourceInfo.fileName ||
+ elementData.sourceInfo.lineNumber === 0
+ ) {
+ console.warn(
+ '[DesignModeBridge] Warning: Element missing source mapping attributes. Updates may fail.'
+ );
+ }
+
+ safeSend('ELEMENT_SELECTED', { elementInfo: elementData });
+ } else {
+ safeSend('ELEMENT_DESELECTED', null);
+ }
+ }, 100);
+
+ return () => clearTimeout(timer);
+ }, [selectedElement, safeSend]);
+
+ /** Parent → iframe: UPDATE_STYLE / UPDATE_CONTENT / TOGGLE */
+ useEffect(() => {
+ if (!isBridgeReady()) {
+ return;
+ }
+
+ const unsubscribeStyle = bridge.on('UPDATE_STYLE', (message) => {
+ const payload = message.payload;
+
+ if (selectedElement && payload?.sourceInfo && payload?.newClass) {
+ // Require matching file:line:col
+ const elementSourceInfo = extractSourceInfo(selectedElement);
+
+ if (!elementSourceInfo) {
+ console.warn('[DesignModeBridge] Selected element missing source info');
+ return;
+ }
+
+ const sourceMatches =
+ elementSourceInfo.fileName === payload.sourceInfo.fileName &&
+ elementSourceInfo.lineNumber === payload.sourceInfo.lineNumber &&
+ elementSourceInfo.columnNumber === payload.sourceInfo.columnNumber;
+
+ if (sourceMatches) {
+ modifyElementClass(selectedElement, payload.newClass);
+ } else {
+ console.warn(
+ '[DesignModeBridge] Source info mismatch, ignoring style update'
+ );
+ }
+ }
+ });
+
+ const unsubscribeContent = bridge.on('UPDATE_CONTENT', (message) => {
+ const payload = message.payload;
+
+ if (
+ selectedElement &&
+ payload?.sourceInfo &&
+ payload?.newContent !== undefined
+ ) {
+ // Require matching file:line:col
+ const elementSourceInfo = extractSourceInfo(selectedElement);
+
+ if (!elementSourceInfo) {
+ console.warn('[DesignModeBridge] Selected element missing source info');
+ return;
+ }
+
+ const sourceMatches =
+ elementSourceInfo.fileName === payload.sourceInfo.fileName &&
+ elementSourceInfo.lineNumber === payload.sourceInfo.lineNumber &&
+ elementSourceInfo.columnNumber === payload.sourceInfo.columnNumber;
+
+ if (sourceMatches) {
+ updateElementContent(selectedElement, payload.newContent);
+ } else {
+ console.warn(
+ '[DesignModeBridge] Source info mismatch, ignoring content update'
+ );
+ }
+ }
+ });
+
+ const unsubscribeToggle = bridge.on(
+ 'TOGGLE_DESIGN_MODE',
+ (message) => {
+ }
+ );
+
+ return () => {
+ unsubscribeStyle();
+ unsubscribeContent();
+ unsubscribeToggle();
+ };
+ }, [
+ selectedElement,
+ modifyElementClass,
+ updateElementContent,
+ isBridgeReady,
+ ]);
+
+ /** Periodic bridge.healthCheck in iframe */
+ useEffect(() => {
+ if (!isBridgeReady()) {
+ return;
+ }
+
+ const checkBridgeHealth = async () => {
+ try {
+ const health = await bridge.healthCheck();
+ console.log('[DesignModeBridge] Bridge health check:', health);
+ } catch (error) {
+ console.warn('[DesignModeBridge] Bridge health check failed:', error);
+ }
+ };
+
+ checkBridgeHealth();
+
+ const healthTimer = setInterval(checkBridgeHealth, 30000);
+
+ return () => clearInterval(healthTimer);
+ }, [isBridgeReady]);
+
+ return null;
+};
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeContext.tsx b/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeContext.tsx
new file mode 100644
index 00000000..6de24195
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeContext.tsx
@@ -0,0 +1,1074 @@
+import React, {
+ createContext,
+ useContext,
+ useState,
+ useCallback,
+ useEffect,
+} from 'react';
+import { twMerge } from 'tailwind-merge';
+import {
+ DesignModeMessage,
+ ParentToIframeMessage,
+ IframeToParentMessage,
+ BridgeConfig,
+ ElementInfo,
+ SourceInfo,
+ ToggleDesignModeMessage,
+ UpdateStyleMessage,
+ UpdateContentMessage,
+ BatchUpdateMessage,
+ HeartbeatMessage,
+ HealthCheckMessage,
+ HealthCheckResponseMessage,
+} from '@xagi/design-mode-shared/messages';
+import { bridge, messageValidator } from './bridge';
+import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
+import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
+import { extractSourceInfo as extractSourceInfoFromAttributes } from '@xagi/design-mode-shared/sourceInfo';
+import { resolveSourceInfo } from '@xagi/design-mode-shared/sourceInfoResolver';
+
+export interface Modification {
+ id: string;
+ element: string;
+ type: 'class' | 'style';
+ oldValue: string;
+ newValue: string;
+ timestamp: number;
+}
+
+export interface DesignModeConfig {
+ enabled?: boolean;
+ iframeMode?: {
+ enabled: boolean;
+ hideUI: boolean;
+ enableSelection: boolean;
+ enableDirectEdit: boolean;
+ };
+ batchUpdate?: {
+ enabled: boolean;
+ debounceMs: number;
+ };
+ bridge?: Partial;
+}
+
+interface DesignModeContextType {
+ // State
+ isDesignMode: boolean;
+ selectedElement: HTMLElement | null;
+ modifications: Modification[];
+ isConnected: boolean;
+ bridgeStatus: 'connected' | 'disconnected' | 'connecting' | 'error';
+
+ // Config
+ config: DesignModeConfig;
+
+ // Actions
+ toggleDesignMode: () => void;
+ selectElement: (element: HTMLElement | null) => void;
+ modifyElementClass: (element: HTMLElement, newClass: string) => Promise;
+ updateElementContent: (
+ element: HTMLElement,
+ newContent: string
+ ) => Promise;
+ batchUpdateElements: (
+ updates: Array<{
+ element: HTMLElement;
+ type: 'style' | 'content';
+ newValue: string;
+ originalValue?: string;
+ }>
+ ) => Promise;
+ resetModifications: () => void;
+
+ // Bridge helpers
+ sendMessage: (message: T) => Promise;
+ sendMessageWithResponse: <
+ T extends DesignModeMessage,
+ R extends DesignModeMessage,
+ >(
+ message: T,
+ responseType: R['type']
+ ) => Promise;
+ healthCheck: () => Promise;
+}
+
+const DesignModeContext = createContext(
+ undefined
+);
+
+export const DesignModeProvider: React.FC<{
+ children: React.ReactNode;
+ config?: DesignModeConfig;
+}> = ({ children, config: userConfig = {} }) => {
+ // Defaults
+ const defaultConfig: DesignModeConfig = {
+ enabled: true,
+ iframeMode: {
+ enabled: true,
+ hideUI: false,
+ enableSelection: true,
+ enableDirectEdit: true,
+ },
+ batchUpdate: {
+ enabled: true,
+ debounceMs: 300,
+ },
+ bridge: {
+ timeout: 10000,
+ retryAttempts: 3,
+ heartbeatInterval: 30000,
+ debug: process.env.NODE_ENV === 'development',
+ },
+ ...userConfig,
+ };
+
+ // State
+ const [isDesignMode, setIsDesignMode] = useState(false);
+ const [selectedElement, setSelectedElement] = useState(
+ null
+ );
+ const [modifications, setModifications] = useState([]);
+ const [isConnected, setIsConnected] = useState(false);
+ const [bridgeStatus, setBridgeStatus] = useState<
+ 'connected' | 'disconnected' | 'connecting' | 'error'
+ >('connecting');
+ const [config] = useState(defaultConfig);
+
+ // Batch debounce state
+ const [batchUpdateTimer, setBatchUpdateTimer] =
+ useState(null);
+ const [pendingBatchUpdates, setPendingBatchUpdates] = useState<
+ Array<{
+ element: HTMLElement;
+ type: 'style' | 'content';
+ newValue: string;
+ originalValue?: string;
+ }>
+ >([]);
+
+ /**
+ * Wire iframe bridge listeners
+ */
+ useEffect(() => {
+ // Bridge setup
+ if (config.iframeMode?.enabled) {
+ setBridgeStatus('connecting');
+
+ // Poll isConnected
+ const connectionCheck = setInterval(() => {
+ const connected = bridge.isConnected();
+ setIsConnected(connected);
+ setBridgeStatus(connected ? 'connected' : 'disconnected');
+ }, 1000);
+
+ // Subscriptions
+ const unsubscribeHandlers: (() => void)[] = [];
+
+ // TOGGLE_DESIGN_MODE
+ unsubscribeHandlers.push(
+ bridge.on('TOGGLE_DESIGN_MODE', message => {
+ const newState = message.enabled;
+ setIsDesignMode(newState);
+
+ // Echo DESIGN_MODE_CHANGED
+ if (window.self !== window.top) {
+ sendToParent({
+ type: 'DESIGN_MODE_CHANGED',
+ enabled: newState,
+ requestId: message.requestId,
+ timestamp: Date.now(),
+ });
+ }
+ })
+ );
+
+ // UPDATE_STYLE
+ unsubscribeHandlers.push(
+ bridge.on('UPDATE_STYLE', async message => {
+ await handleExternalStyleUpdate(message);
+ })
+ );
+
+ // UPDATE_CONTENT
+ unsubscribeHandlers.push(
+ bridge.on('UPDATE_CONTENT', async message => {
+ await handleExternalContentUpdate(message);
+ })
+ );
+
+ // BATCH_UPDATE
+ unsubscribeHandlers.push(
+ bridge.on('BATCH_UPDATE', async message => {
+ await handleExternalBatchUpdate(message);
+ })
+ );
+
+ // HEARTBEAT
+ unsubscribeHandlers.push(
+ bridge.on('HEARTBEAT', _ => {
+ // Echo HEARTBEAT
+ bridge.send({
+ type: 'HEARTBEAT',
+ payload: { timestamp: Date.now() },
+ timestamp: Date.now(),
+ });
+ })
+ );
+
+ // HEALTH_CHECK
+ unsubscribeHandlers.push(
+ bridge.on('HEALTH_CHECK', async message => {
+ const healthStatus = await bridge.healthCheck();
+ // Use a type assertion or construct the object carefully to match HealthCheckResponseMessage
+ const response: HealthCheckResponseMessage = {
+ type: 'HEALTH_CHECK_RESPONSE',
+ payload: {
+ status: healthStatus.status === 'healthy' ? 'healthy' : 'unhealthy',
+ version: '2.0.0',
+ uptime: Date.now() - ((window as any).__startTime || 0),
+ },
+ requestId: message.requestId || '', // Provide requestId
+ timestamp: Date.now(),
+ };
+ bridge.send(response);
+ })
+ );
+
+ // Initial health probe
+ const initialHealthCheck = setTimeout(async () => {
+ try {
+ const health = await bridge.healthCheck();
+ setBridgeStatus(health.status === 'healthy' ? 'connected' : 'error');
+ } catch (error) {
+ setBridgeStatus('error');
+ }
+ }, 1000);
+
+ return () => {
+ clearInterval(connectionCheck);
+ clearTimeout(initialHealthCheck);
+ unsubscribeHandlers.forEach(unsubscribe => unsubscribe());
+ };
+ }
+ }, [config]);
+
+ /**
+ * postMessage when iframe + connected
+ */
+ const sendToParent = useCallback(
+ (message: IframeToParentMessage) => {
+ if (!config.iframeMode?.enabled) {
+ return;
+ }
+
+ // 优先走 bridge;bridge 未连通或发送失败时兜底直发 parent,避免关键回执丢失。
+ if (bridge.isConnected()) {
+ bridge.send(message).catch(error => {
+ if (window.self !== window.top) {
+ window.parent.postMessage(message, '*');
+ }
+ });
+ return;
+ }
+
+ if (window.self !== window.top) {
+ window.parent.postMessage(message, '*');
+ }
+ },
+ [config.iframeMode?.enabled]
+ );
+
+ /**
+ * findElementBySourceInfo
+ */
+ const findElementBySourceInfo = useCallback(
+ (sourceInfo: SourceInfo): HTMLElement | null => {
+ // 1) element-id
+ if (sourceInfo.elementId) {
+ const element = document.querySelector(`[${AttributeNames.elementId}="${sourceInfo.elementId}"]`);
+ if (element) return element as HTMLElement;
+ }
+
+ // 2) legacy file/line/column attrs
+ const selector = `[${AttributeNames.file}="${sourceInfo.fileName}"][${AttributeNames.line}="${sourceInfo.lineNumber}"][${AttributeNames.column}="${sourceInfo.columnNumber}"]`;
+ const element = document.querySelector(selector);
+ if (element) return element as HTMLElement;
+
+ // 3) children-source
+ const childrenSourceValue = `${sourceInfo.fileName}:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`;
+ const elementByChildrenSource = document.querySelector(`[${AttributeNames.childrenSource}="${childrenSourceValue}"]`);
+ if (elementByChildrenSource) return elementByChildrenSource as HTMLElement;
+
+ // 4) scan all -info
+ const allElements = document.querySelectorAll(`[${AttributeNames.info}]`);
+ for (let i = 0; i < allElements.length; i++) {
+ const el = allElements[i] as HTMLElement;
+ try {
+ const infoStr = el.getAttribute(AttributeNames.info);
+ if (infoStr) {
+ const info = JSON.parse(infoStr);
+ if (
+ info.fileName === sourceInfo.fileName &&
+ info.lineNumber === sourceInfo.lineNumber &&
+ info.columnNumber === sourceInfo.columnNumber
+ ) {
+ return el;
+ }
+ }
+ } catch (e) {
+ // ignore parse error
+ }
+ }
+
+ return null;
+ },
+ []
+ );
+
+ /**
+ * Parent-driven style patch
+ */
+ const handleExternalStyleUpdate = useCallback(
+ async (message: UpdateStyleMessage) => {
+ if (!config.iframeMode?.enabled) return;
+
+ const updateMessage = message;
+ const { sourceInfo, newClass } = updateMessage.payload;
+
+ try {
+ // validate
+ const validation = messageValidator.validate(updateMessage);
+ if (!validation.isValid) {
+ console.error(
+ '[DesignMode] Invalid style update message:',
+ validation.errors
+ );
+ return;
+ }
+
+ // resolve element
+ const element = findElementBySourceInfo(sourceInfo);
+ if (!element) {
+ console.error(
+ '[DesignMode] Element not found for sourceInfo:',
+ sourceInfo
+ );
+
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'ELEMENT_NOT_FOUND',
+ message: `Element not found: ${sourceInfo.fileName}:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`,
+ details: { sourceInfo },
+ },
+ timestamp: Date.now(),
+ });
+ return;
+ }
+
+ const oldClass = element.className;
+
+ // Same element-id → list sync
+ const elementId = element.getAttribute(AttributeNames.elementId);
+ let relatedElements: HTMLElement[] = [element];
+
+ if (elementId) {
+ // query all [element-id]
+ const allElementsWithId = Array.from(
+ document.querySelectorAll(`[${AttributeNames.elementId}]`)
+ ) as HTMLElement[];
+
+ relatedElements = allElementsWithId.filter(el => {
+ const elId = el.getAttribute(AttributeNames.elementId);
+ return elId === elementId;
+ });
+ } else {
+ console.warn('[DesignMode] Element missing element-id attribute, only updating current element');
+ }
+
+ // Apply class to all peers
+ relatedElements.forEach(el => {
+ el.setAttribute('data-ignore-mutation', 'true');
+ el.className = newClass;
+ // Use setTimeout to ensure MutationObserver sees the attribute
+ setTimeout(() => {
+ el.removeAttribute('data-ignore-mutation');
+ }, 0);
+ });
+
+
+ // Refresh selection ref
+ if (selectedElement === element) {
+ setSelectedElement(element);
+ }
+
+ // (persist via API — commented)
+ // try {
+ // const response = await fetch('/__appdev_design_mode/update', {
+ // method: 'POST',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
+ // body: JSON.stringify({
+ // filePath: sourceInfo.fileName,
+ // line: sourceInfo.lineNumber,
+ // column: sourceInfo.columnNumber,
+ // newValue: newClass,
+ // type: 'style',
+ // originalValue: oldClass,
+ // }),
+ // });
+
+ // if (!response.ok) {
+ // throw new Error('Failed to update source');
+ // }
+ // } catch (error) {
+ // console.error('[DesignMode] Error updating source:', error);
+ // throw error;
+ // }
+
+ // STYLE_UPDATED
+ sendToParent({
+ type: 'STYLE_UPDATED',
+ payload: {
+ sourceInfo,
+ oldClass,
+ newClass,
+ },
+ timestamp: Date.now(),
+ });
+
+ } catch (error) {
+ console.error(
+ '[DesignMode] Error handling external style update:',
+ error
+ );
+
+ // ERROR
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'STYLE_UPDATE_FAILED',
+ message: error instanceof Error ? error.message : 'Unknown error',
+ details: { sourceInfo: updateMessage.payload.sourceInfo },
+ },
+ timestamp: Date.now(),
+ });
+ }
+ },
+ [
+ selectedElement,
+ config.iframeMode?.enabled,
+ sendToParent,
+ setSelectedElement,
+ findElementBySourceInfo
+ ]
+ );
+
+ /**
+ * Parent-driven content patch
+ */
+ const handleExternalContentUpdate = useCallback(
+ async (message: UpdateContentMessage) => {
+ if (!config.iframeMode?.enabled) return;
+
+ const updateMessage = message;
+ const { sourceInfo, newContent } = updateMessage.payload;
+
+ try {
+ // validate
+ const validation = messageValidator.validate(updateMessage);
+ if (!validation.isValid) {
+ console.error(
+ '[DesignMode] Invalid content update message:',
+ validation.errors
+ );
+ return;
+ }
+
+ // resolve element
+ const element = findElementBySourceInfo(sourceInfo);
+ if (!element) {
+ console.error(
+ '[DesignMode] Element not found for sourceInfo:',
+ sourceInfo
+ );
+
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'ELEMENT_NOT_FOUND',
+ message: `Element not found: ${sourceInfo.fileName}:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`,
+ details: { sourceInfo },
+ },
+ timestamp: Date.now(),
+ });
+ return;
+ }
+
+ const originalContent = element.innerText || element.textContent || '';
+
+ // Same element-id → list sync
+ const elementId = element.getAttribute(AttributeNames.elementId);
+ let relatedElements: HTMLElement[] = [element];
+
+ if (elementId) {
+ // query all [element-id]
+ const allElementsWithId = Array.from(
+ document.querySelectorAll(`[${AttributeNames.elementId}]`)
+ ) as HTMLElement[];
+
+ relatedElements = allElementsWithId.filter(el => {
+ const elId = el.getAttribute(AttributeNames.elementId);
+ return elId === elementId;
+ });
+ } else {
+ console.warn('[DesignMode] Element missing element-id attribute, only updating current element');
+ }
+
+ // Apply text to all peers (list sync)
+ relatedElements.forEach(el => {
+ el.setAttribute('data-ignore-mutation', 'true');
+ el.innerText = newContent;
+ // Use setTimeout to ensure MutationObserver sees the attribute
+ setTimeout(() => {
+ el.removeAttribute('data-ignore-mutation');
+ }, 0);
+ });
+
+
+ // Refresh selection ref
+ if (selectedElement === element) {
+ setSelectedElement(element);
+ }
+
+ // persist flag (commented)
+ // if (updateMessage.payload.persist !== false) {
+ // // Persist via API using extractSourceInfo (commented)
+ // const elementSourceInfo = extractSourceInfo(element);
+ // if (elementSourceInfo) {
+ // try {
+ // const response = await fetch('/__appdev_design_mode/update', {
+ // method: 'POST',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
+ // body: JSON.stringify({
+ // filePath: elementSourceInfo.fileName,
+ // line: elementSourceInfo.lineNumber,
+ // column: elementSourceInfo.columnNumber,
+ // newValue: newContent,
+ // type: 'content',
+ // originalValue: originalContent,
+ // }),
+ // });
+
+ // if (!response.ok) {
+ // throw new Error('Failed to update source');
+ // }
+ // } catch (error) {
+ // console.error('[DesignMode] Error updating source:', error);
+ // throw error;
+ // }
+ // }
+ // }
+
+ // STYLE_UPDATED
+ sendToParent({
+ type: 'CONTENT_UPDATED_CALLBACK',
+ payload: {
+ sourceInfo,
+ oldValue: originalContent,
+ newValue: newContent,
+ },
+ timestamp: Date.now(),
+ });
+
+ } catch (error) {
+ console.error(
+ '[DesignMode] Error handling external content update:',
+ error
+ );
+
+ // ERROR
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'CONTENT_UPDATE_FAILED',
+ message: error instanceof Error ? error.message : 'Unknown error',
+ details: { sourceInfo: updateMessage.payload.sourceInfo },
+ },
+ timestamp: Date.now(),
+ });
+ }
+ },
+ [
+ selectedElement,
+ config.iframeMode?.enabled,
+ sendToParent,
+ setSelectedElement,
+ findElementBySourceInfo
+ ]
+ );
+
+ /**
+ * updateSource (HTTP)
+ */
+ const updateSource = useCallback(
+ async (
+ element: HTMLElement,
+ newValue: string,
+ type: 'style' | 'content',
+ originalValue?: string
+ ) => {
+ const sourceInfo = extractSourceInfo(element);
+ if (!sourceInfo) {
+ throw new Error('Element does not have source mapping data');
+ }
+
+ try {
+ const response = await fetch('/__appdev_design_mode/update', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ filePath: sourceInfo.fileName,
+ line: sourceInfo.lineNumber,
+ column: sourceInfo.columnNumber,
+ newValue,
+ type,
+ originalValue,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to update source');
+ }
+
+ } catch (error) {
+ console.error('[DesignMode] Error updating source:', error);
+ throw error;
+ }
+ },
+ []
+ );
+
+ /**
+ * Parent BATCH_UPDATE
+ */
+ const handleExternalBatchUpdate = useCallback(
+ async (message: BatchUpdateMessage) => {
+ const updateMessage = message;
+ const { updates } = updateMessage.payload;
+
+ try {
+ // validate
+ const validation = messageValidator.validate(updateMessage);
+ if (!validation.isValid) {
+ console.error(
+ '[DesignMode] Invalid batch update message:',
+ validation.errors
+ );
+ return;
+ }
+
+ // Promise.allSettled items
+ const results = await Promise.allSettled(
+ updates.map(async (update: any) => {
+ const element = findElementBySourceInfo(update.sourceInfo);
+ if (!element) {
+ throw new Error(
+ `Element not found: ${update.sourceInfo.fileName}:${update.sourceInfo.lineNumber}`
+ );
+ }
+
+ if (update.type === 'style') {
+ element.setAttribute('data-ignore-mutation', 'true');
+ element.className = update.newValue;
+ setTimeout(() => element.removeAttribute('data-ignore-mutation'), 0);
+ } else if (update.type === 'content') {
+ element.setAttribute('data-ignore-mutation', 'true');
+ element.innerText = update.newValue;
+ setTimeout(() => element.removeAttribute('data-ignore-mutation'), 0);
+ }
+
+ await updateSource(
+ element,
+ update.newValue,
+ update.type,
+ update.originalValue
+ );
+
+ return { success: true, sourceInfo: update.sourceInfo };
+ })
+ );
+
+ } catch (error) {
+ console.error('[DesignMode] Error handling batch update:', error);
+
+ // ERROR
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'BATCH_UPDATE_FAILED',
+ message: error instanceof Error ? error.message : 'Unknown error',
+ details: { updatesCount: updates?.length || 0 },
+ },
+ timestamp: Date.now(),
+ });
+ }
+ },
+ [findElementBySourceInfo, sendToParent, updateSource]
+ );
+
+ /**
+ * selectElement
+ */
+ const selectElement = useCallback(
+ async (element: HTMLElement | null) => {
+ if (element && (element.hasAttribute(AttributeNames.staticContent)
+ || element.hasAttribute(AttributeNames.staticClass))) {
+ setSelectedElement(element);
+ } else {
+ setSelectedElement(null);
+ }
+
+ // iframe: ELEMENT_SELECTED
+ if (element && config.iframeMode?.enabled) {
+ // resolveSourceInfo for usage site
+
+ const sourceInfo = resolveSourceInfo(element);
+ // console.log('[DesignModeContext] selectElement - resolveSourceInfo:', sourceInfo);
+
+ if (sourceInfo) {
+ // Static text if attr + pure text
+
+
+ const hasStaticContentAttr = element.hasAttribute(AttributeNames.staticContent);
+ const isActuallyPureText = isPureStaticText(element);
+ const isStaticText = hasStaticContentAttr && isActuallyPureText;
+
+ // static-class attr
+
+ const isStaticClass = element.hasAttribute(AttributeNames.staticClass);
+
+
+ let textContent = '';
+ if (isStaticText) {
+
+ textContent = element.textContent || element.innerText || '';
+ } else {
+
+ textContent = element.innerText || element.textContent || '';
+ }
+
+ const elementInfo: ElementInfo = {
+ tagName: element.tagName.toLowerCase(),
+ className: element.className,
+ textContent: textContent,
+ sourceInfo,
+ isStaticText: isStaticText || false,
+ isStaticClass: isStaticClass,
+ };
+
+ sendToParent({
+ type: 'ELEMENT_SELECTED',
+ payload: { elementInfo },
+ timestamp: Date.now(),
+ });
+
+ } else {
+ console.warn(
+ `[DesignMode] Element selected but could not resolve source info:`,
+ element
+ );
+ }
+ } else if (!element && config.iframeMode?.enabled) {
+ sendToParent({
+ type: 'ELEMENT_DESELECTED',
+ timestamp: Date.now(),
+ });
+ }
+ },
+ [config.iframeMode?.enabled]
+ );
+
+ /**
+ * toggleDesignMode
+ */
+ const toggleDesignMode = useCallback(() => {
+ setIsDesignMode(prev => {
+ const next = !prev;
+ if (!next) {
+ setSelectedElement(null);
+ }
+ return next;
+ });
+ }, []);
+
+ /**
+ * Local extractSourceInfo helper
+ */
+ const extractSourceInfo = useCallback(
+ (element: HTMLElement): SourceInfo | null => {
+ return extractSourceInfoFromAttributes(element);
+ },
+ []
+ );
+
+ /**
+ * modifyElementClass
+ */
+ const modifyElementClass = useCallback(
+ async (element: HTMLElement, newClass: string) => {
+ const oldClasses = element.className;
+ const mergedClasses = twMerge(oldClasses, newClass);
+
+ // twMerge + DOM
+ element.className = mergedClasses;
+
+ // PATCH /update
+ await updateSource(element, mergedClasses, 'style', oldClasses);
+
+ // modifications list
+ const modification: Modification = {
+ id: Date.now().toString(),
+ element: element.id || 'unknown',
+ type: 'class',
+ oldValue: oldClasses,
+ newValue: mergedClasses,
+ timestamp: Date.now(),
+ };
+
+ setModifications(prev => [modification, ...prev]);
+
+ // STYLE_UPDATED to parent
+ if (config.iframeMode?.enabled) {
+ const sourceInfo = extractSourceInfo(element);
+ if (sourceInfo) {
+ sendToParent({
+ type: 'STYLE_UPDATED',
+ payload: {
+ sourceInfo,
+ oldClass: oldClasses,
+ newClass: mergedClasses,
+ },
+ timestamp: Date.now(),
+ });
+ }
+ }
+ },
+ [updateSource, config.iframeMode?.enabled]
+ );
+
+ /**
+ * updateElementContent
+ */
+ const updateElementContent = useCallback(
+ async (element: HTMLElement, newContent: string) => {
+ const sourceInfo = extractSourceInfo(element);
+ const originalContent = element.innerText;
+
+
+ // twMerge + DOM
+ element.innerText = newContent;
+
+ // PATCH /update
+ await updateSource(element, newContent, 'content', originalContent);
+
+ // STYLE_UPDATED to parent
+ if (config.iframeMode?.enabled) {
+ const sourceInfo = extractSourceInfo(element);
+ if (sourceInfo) {
+ sendToParent({
+ type: 'CONTENT_UPDATED',
+ payload: {
+ sourceInfo,
+ oldValue: originalContent,
+ newValue: newContent,
+ },
+ timestamp: Date.now(),
+ });
+ }
+ }
+ },
+ [updateSource, config.iframeMode?.enabled]
+ );
+
+ /**
+ * batchUpdateElements
+ */
+ const batchUpdateElements = useCallback(
+ async (
+ updates: Array<{
+ element: HTMLElement;
+ type: 'style' | 'content';
+ newValue: string;
+ originalValue?: string;
+ }>
+ ) => {
+ if (!config.batchUpdate?.enabled) {
+ // Sequential fallback
+ await Promise.all(
+ updates.map(update => {
+ if (update.type === 'style') {
+ return modifyElementClass(update.element, update.newValue);
+ } else {
+ return updateElementContent(update.element, update.newValue);
+ }
+ })
+ );
+ return;
+ }
+
+ // Debounced queue
+ const newUpdates = [...pendingBatchUpdates, ...updates];
+ setPendingBatchUpdates(newUpdates);
+
+ // Reset debounce timer
+ if (batchUpdateTimer) {
+ clearTimeout(batchUpdateTimer);
+ }
+
+ // Schedule flush
+ const timer = setTimeout(async () => {
+ try {
+ // Build payload
+ const batchUpdateItems = newUpdates.map(update => {
+ const sourceInfo = extractSourceInfo(update.element);
+ if (!sourceInfo) {
+ throw new Error('Element missing source mapping');
+ }
+
+ return {
+ type: update.type,
+ sourceInfo,
+ newValue: update.newValue,
+ originalValue: update.originalValue,
+ };
+ });
+
+ // iframe: bridge BATCH_UPDATE
+ if (config.iframeMode?.enabled) {
+ await bridge.send({
+ type: 'BATCH_UPDATE',
+ payload: { updates: batchUpdateItems },
+ timestamp: Date.now(),
+ });
+ } else {
+ // top: fetch batch endpoint
+ await fetch('/__appdev_design_mode/batch-update', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ updates: batchUpdateItems,
+ }),
+ });
+ }
+
+ // Clear queue
+ setPendingBatchUpdates([]);
+ } catch (error) {
+ console.error('[DesignMode] Batch update failed:', error);
+ setPendingBatchUpdates([]);
+ throw error;
+ }
+ }, config.batchUpdate.debounceMs);
+
+ setBatchUpdateTimer(timer);
+ },
+ [
+ config.batchUpdate,
+ pendingBatchUpdates,
+ batchUpdateTimer,
+ config.iframeMode?.enabled,
+ modifyElementClass,
+ updateElementContent,
+ ]
+ );
+
+ /**
+ * resetModifications
+ */
+ const resetModifications = useCallback(() => {
+ window.location.reload();
+ }, []);
+
+ /**
+ * postMessage when iframe + connected
+ */
+ const sendMessage = useCallback(
+ async (message: T) => {
+ if (config.iframeMode?.enabled) {
+ await bridge.send(message);
+ }
+ },
+ [config.iframeMode?.enabled]
+ );
+
+ /**
+ * sendMessageWithResponse
+ */
+ const sendMessageWithResponse = useCallback(
+ async (
+ message: T,
+ responseType: R['type']
+ ): Promise => {
+ if (config.iframeMode?.enabled) {
+ return await bridge.sendWithResponse(message, responseType);
+ }
+ throw new Error('Iframe mode is not enabled');
+ },
+ [config.iframeMode?.enabled]
+ );
+
+ /**
+ * healthCheck
+ */
+ const healthCheck = useCallback(async () => {
+ if (config.iframeMode?.enabled) {
+ return await bridge.healthCheck();
+ }
+ return { status: 'healthy', details: { mode: 'standalone' } };
+ }, [config.iframeMode?.enabled]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useDesignMode = () => {
+ const context = useContext(DesignModeContext);
+ if (context === undefined) {
+ throw new Error('useDesignMode must be used within a DesignModeProvider');
+ }
+ return context;
+};
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeManager.tsx b/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeManager.tsx
new file mode 100644
index 00000000..f7152d49
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeManager.tsx
@@ -0,0 +1,224 @@
+import React, { useEffect } from 'react';
+import { useDesignMode } from './DesignModeContext';
+import { useUpdateManager } from './UpdateManager';
+import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
+
+export const DesignModeManager: React.FC = () => {
+ const { isDesignMode, selectElement, selectedElement, updateElementContent } = useDesignMode();
+
+ // Initialize UpdateManager to enable context menu and direct editing features
+ useUpdateManager();
+
+
+ useEffect(() => {
+ if (!isDesignMode) {
+ document.querySelectorAll('[data-design-selected]').forEach(el => {
+ el.removeAttribute('data-design-selected');
+ });
+ return;
+ }
+
+ const handleClick = (e: MouseEvent) => {
+ // Don't select the overlay itself or controls (assuming they are in shadow DOM or have specific attribute)
+ // Since this manager runs in the main document context (event listeners), we need to be careful.
+ // The UI will be in a separate root, likely in Shadow DOM, so events might not bubble up the same way
+ // OR we need to check if target is part of our UI.
+ if ((e.target as HTMLElement).closest('#__vite_plugin_design_mode__')) return;
+
+ // Don't handle clicks on context menu
+ if ((e.target as HTMLElement).closest(`[${AttributeNames.contextMenu}="true"]`)) return;
+ if (
+ (e.target as HTMLElement).closest(`[${AttributeNames.staticContent}="true"]`)
+ || (e.target as HTMLElement).closest(`[${AttributeNames.staticClass}="true"]`)
+ ) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const target = e.target as HTMLElement;
+ selectElement(target);;
+
+ }
+
+
+ };
+
+ // Double-click handling is now managed by UpdateManager → EditManager
+ // This prevents conflicts and ensures data-ignore-mutation is properly set
+ /*
+ const handleDoubleClick = (e: MouseEvent) => {
+ if ((e.target as HTMLElement).closest('#__vite_plugin_design_mode__')) return;
+ if ((e.target as HTMLElement).closest(`[${AttributeNames.contextMenu}="true"]`)) return;
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ const target = e.target as HTMLElement;
+
+ // Check if element is marked as static content
+ if (!target.hasAttribute(AttributeNames.staticContent)) {
+ // alert('Not editable: only plain static text (no expressions).');
+ return;
+ }
+
+ // IMPORTANT: Save original content BEFORE enabling editing
+ const originalContent = target.innerText;
+
+ // Enable content editing
+ target.contentEditable = 'true';
+ target.focus();
+
+ const cleanup = () => {
+ target.contentEditable = 'false';
+ target.removeEventListener('blur', handleBlur);
+ target.removeEventListener('keydown', handleKeyDown);
+ };
+
+ const handleBlur = () => {
+ cleanup();
+ // Use the saved original content, not current innerText
+ const newContent = target.innerText;
+ if (newContent !== originalContent) {
+ // Create a custom version of updateElementContent that uses our saved original
+ const sourceInfoStr = target.getAttribute(AttributeNames.info);
+ let filePath: string | null = null;
+ let line: number | null = null;
+ let column: number | null = null;
+
+ if (sourceInfoStr) {
+ try {
+ const sourceInfo = JSON.parse(sourceInfoStr);
+ filePath = sourceInfo.fileName;
+ line = sourceInfo.lineNumber;
+ column = sourceInfo.columnNumber;
+ } catch (e) {
+ console.warn(`Failed to parse ${AttributeNames.info}:`, e);
+ }
+ }
+
+ if (filePath && line !== null && column !== null) {
+ fetch('/__appdev_design_mode/update', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ filePath,
+ line,
+ column,
+ newValue: newContent,
+ type: 'content',
+ originalValue: originalContent,
+ }),
+ }).then(response => {
+ if (response.ok) {
+ console.log('[DesignMode] Content updated successfully');
+ } else {
+ console.error('[DesignMode] Failed to update content');
+ }
+ }).catch(error => {
+ console.error('[DesignMode] Error updating content:', error);
+ });
+ }
+ }
+ };
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ target.blur(); // Will trigger handleBlur
+ }
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ target.innerText = originalContent;
+ cleanup();
+ }
+ };
+
+ target.addEventListener('blur', handleBlur);
+ target.addEventListener('keydown', handleKeyDown);
+ };
+ */
+
+ const handleMouseOver = (e: MouseEvent) => {
+ if ((e.target as HTMLElement).closest('#__vite_plugin_design_mode__')) return;
+ if ((e.target as HTMLElement).closest(`[${AttributeNames.contextMenu}="true"]`)) return;
+ if (
+ (e.target as HTMLElement).hasAttribute(AttributeNames.staticContent)
+ || (e.target as HTMLElement).hasAttribute(AttributeNames.staticClass)
+ ) {
+ const target = e.target as HTMLElement;
+ target.setAttribute('data-design-hover', 'true');
+ };
+ };
+
+ const handleMouseOut = (e: MouseEvent) => {
+ const target = e.target as HTMLElement;
+ // Keep hover while context-menu hover flag is set
+ if (!target.hasAttribute(AttributeNames.contextMenuHover)) {
+ target.removeAttribute('data-design-hover');
+ }
+ };
+
+ document.addEventListener('click', handleClick, true);
+ // document.addEventListener('dblclick', handleDoubleClick, true);
+ document.addEventListener('mouseover', handleMouseOver);
+ document.addEventListener('mouseout', handleMouseOut);
+
+ // Inject global styles for design mode into the MAIN document head
+ const styleId = 'appdev-design-mode-styles';
+ if (!document.getElementById(styleId)) {
+ const style = document.createElement('style');
+ style.id = styleId;
+ style.textContent = `
+ [data-design-hover="true"] {
+ outline: 2px dashed #60a5fa !important; /* blue-400 */
+ outline-offset: 2px;
+ cursor: pointer;
+ }
+ [data-design-selected="true"] {
+ outline: 2px solid #2563eb !important; /* blue-600 */
+ outline-offset: 2px;
+ }
+ [contenteditable="true"] {
+ outline: 2px solid #22c55e !important; /* green-500 */
+ cursor: text;
+ background-color: rgba(34, 197, 94, 0.1);
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ return () => {
+ // Optional: remove styles when unmounting?
+ // For now, let's keep them or we can remove them if we want to be clean.
+ // Since this component might be mounted/unmounted, maybe better to keep them
+ // or manage them carefully.
+ // Let's remove them to be clean.
+ const style = document.getElementById(styleId);
+ if (style) {
+ style.remove();
+ }
+
+ document.removeEventListener('click', handleClick, true);
+ // document.removeEventListener('dblclick', handleDoubleClick, true);
+ document.removeEventListener('mouseover', handleMouseOver);
+ document.removeEventListener('mouseout', handleMouseOut);
+
+ document.querySelectorAll('[data-design-hover]').forEach(el => {
+ el.removeAttribute('data-design-hover');
+ });
+ };
+ }, [isDesignMode, selectElement, updateElementContent]);
+
+ useEffect(() => {
+ document.querySelectorAll('[data-design-selected]').forEach(el => {
+ el.removeAttribute('data-design-selected');
+ });
+
+ if (selectedElement) {
+ selectedElement.setAttribute('data-design-selected', 'true');
+ }
+ }, [selectedElement]);
+
+ return null; // No UI to render, just logic and global side effects
+};
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeUI.tsx b/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeUI.tsx
new file mode 100644
index 00000000..a49f6ce0
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/DesignModeUI.tsx
@@ -0,0 +1,280 @@
+import React, { useState, useEffect } from 'react';
+import { useDesignMode } from './DesignModeContext';
+
+// Tailwind Presets (Copied from InteractiveDemo)
+const TAILWIND_PRESETS = {
+ bgColors: [
+ { label: 'White', value: 'bg-white' },
+ { label: 'Slate 50', value: 'bg-slate-50' },
+ { label: 'Blue 50', value: 'bg-blue-50' },
+ { label: 'Blue 100', value: 'bg-blue-100' },
+ { label: 'Blue 600', value: 'bg-blue-600' },
+ { label: 'Red 50', value: 'bg-red-50' },
+ { label: 'Green 50', value: 'bg-green-50' },
+ ],
+ textColors: [
+ { label: 'Slate 900', value: 'text-slate-900' },
+ { label: 'Slate 600', value: 'text-slate-600' },
+ { label: 'Blue 600', value: 'text-blue-600' },
+ { label: 'White', value: 'text-white' },
+ { label: 'Red 600', value: 'text-red-600' },
+ ],
+ fontSizes: [
+ { label: 'Small', value: 'text-sm' },
+ { label: 'Base', value: 'text-base' },
+ { label: 'Large', value: 'text-lg' },
+ { label: 'XL', value: 'text-xl' },
+ { label: '2XL', value: 'text-2xl' },
+ { label: '4XL', value: 'text-4xl' },
+ ],
+ paddings: [
+ { label: '0', value: 'p-0' },
+ { label: '2', value: 'p-2' },
+ { label: '4', value: 'p-4' },
+ { label: '6', value: 'p-6' },
+ { label: '8', value: 'p-8' },
+ { label: '12', value: 'p-12' },
+ ],
+ rounded: [
+ { label: 'None', value: 'rounded-none' },
+ { label: 'Small', value: 'rounded-sm' },
+ { label: 'Medium', value: 'rounded-md' },
+ { label: 'Large', value: 'rounded-lg' },
+ { label: 'Full', value: 'rounded-full' },
+ ]
+};
+
+export const DesignModeUI: React.FC = () => {
+ const { isDesignMode, toggleDesignMode, selectedElement, modifyElementClass, modifications, resetModifications } = useDesignMode();
+ const [currentClasses, setCurrentClasses] = useState('');
+
+ useEffect(() => {
+ if (selectedElement) {
+ setCurrentClasses(selectedElement.className);
+ } else {
+ setCurrentClasses('');
+ }
+ }, [selectedElement, modifications]);
+
+ const modifyClass = (newClass: string) => {
+ if (!selectedElement) return;
+ modifyElementClass(selectedElement, newClass);
+ };
+
+ return (
+
+ {/* Main Toggle */}
+
+
+
+
+
+
+
+ {/* Edit Panel */}
+ {isDesignMode && selectedElement && (
+
+
+
+ Edit Element
+
+
+ {selectedElement.tagName.toLowerCase()}{selectedElement.id ? `#${selectedElement.id}` : ''}
+
+
+
+
+ {/* Background */}
+
+
+
+ {TAILWIND_PRESETS.bgColors.map(preset => (
+ modifyClass(preset.value)}
+ title={preset.label}
+ style={{
+ width: '24px',
+ height: '24px',
+ borderRadius: '4px',
+ border: '1px solid #e2e8f0',
+ cursor: 'pointer',
+ backgroundColor: preset.value === 'bg-white' ? '#ffffff' :
+ preset.value === 'bg-slate-50' ? '#f8fafc' :
+ preset.value === 'bg-blue-50' ? '#eff6ff' :
+ preset.value === 'bg-blue-100' ? '#dbeafe' :
+ preset.value === 'bg-blue-600' ? '#2563eb' :
+ preset.value === 'bg-red-50' ? '#fef2f2' :
+ preset.value === 'bg-green-50' ? '#f0fdf4' : '#eee',
+ boxShadow: currentClasses.includes(preset.value) ? '0 0 0 2px #3b82f6' : 'none'
+ }}
+ />
+ ))}
+
+
+
+ {/* Text Color */}
+
+
+
+ {TAILWIND_PRESETS.textColors.map(preset => (
+ modifyClass(preset.value)}
+ title={preset.label}
+ style={{
+ width: '24px',
+ height: '24px',
+ borderRadius: '4px',
+ border: '1px solid #e2e8f0',
+ cursor: 'pointer',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ fontWeight: 'bold',
+ fontSize: '12px',
+ backgroundColor: '#f8fafc',
+ color: preset.value === 'text-white' ? '#000' : // Show black 'A' for white text option for visibility
+ preset.value === 'text-slate-900' ? '#0f172a' :
+ preset.value === 'text-slate-600' ? '#475569' :
+ preset.value === 'text-blue-600' ? '#2563eb' :
+ preset.value === 'text-red-600' ? '#dc2626' : '#000',
+ boxShadow: currentClasses.includes(preset.value) ? '0 0 0 2px #3b82f6' : 'none'
+ }}
+ >
+ A
+
+ ))}
+
+
+
+ {/* Padding */}
+
+
+
+ {TAILWIND_PRESETS.paddings.map(preset => (
+ modifyClass(preset.value)}
+ style={{
+ padding: '4px 8px',
+ fontSize: '11px',
+ borderRadius: '4px',
+ border: '1px solid #e2e8f0',
+ cursor: 'pointer',
+ backgroundColor: currentClasses.includes(preset.value) ? '#eff6ff' : 'white',
+ color: currentClasses.includes(preset.value) ? '#1d4ed8' : '#64748b',
+ borderColor: currentClasses.includes(preset.value) ? '#93c5fd' : '#e2e8f0'
+ }}
+ >
+ {preset.label}
+
+ ))}
+
+
+
+ {/* Rounded */}
+
+
+
+ {TAILWIND_PRESETS.rounded.map(preset => (
+ modifyClass(preset.value)}
+ style={{
+ padding: '4px 8px',
+ fontSize: '11px',
+ borderRadius: '4px',
+ border: '1px solid #e2e8f0',
+ cursor: 'pointer',
+ backgroundColor: currentClasses.includes(preset.value) ? '#eff6ff' : 'white',
+ color: currentClasses.includes(preset.value) ? '#1d4ed8' : '#64748b',
+ borderColor: currentClasses.includes(preset.value) ? '#93c5fd' : '#e2e8f0'
+ }}
+ >
+ {preset.label}
+
+ ))}
+
+
+
+
+
+ Reset All Modifications
+
+
+
+
+ )}
+
+ );
+};
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/SelectionManager.tsx b/qiming-vite-plugin-design-mode/packages/client-react/src/SelectionManager.tsx
new file mode 100644
index 00000000..e7e7e609
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/SelectionManager.tsx
@@ -0,0 +1,596 @@
+import React, { useEffect, useCallback, useRef } from 'react';
+import { useDesignMode } from './DesignModeContext';
+import { ElementInfo, SourceInfo } from '@xagi/design-mode-shared/messages';
+import { AttributeNames, isSourceMappingAttribute } from '@xagi/design-mode-shared/attributeNames';
+import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
+import { extractSourceInfo as extractSourceInfoFromAttributes } from '@xagi/design-mode-shared/sourceInfo';
+
+/**
+ * Click/hover selection for mapped elements; emits to listeners / iframe.
+ */
+export class SelectionManager {
+ private selectedElement: HTMLElement | null = null;
+ private selectionListeners: Set<(element: HTMLElement | null) => void> = new Set();
+ private hoverElement: HTMLElement | null = null;
+ private isSelecting = false;
+ private selectionStartTime = 0;
+ private preventNextClick = false;
+
+ constructor(
+ private container: HTMLElement,
+ private config: {
+ enableSelection: boolean;
+ enableHover: boolean;
+ selectionDelay: number;
+ excludeSelectors: string[];
+ includeOnlyElements: boolean;
+ } = {
+ enableSelection: true,
+ enableHover: true,
+ selectionDelay: 0,
+ excludeSelectors: [
+ 'script',
+ 'style',
+ 'meta',
+ 'link',
+ 'head',
+ 'title',
+ 'html',
+ 'body',
+ '[data-selection-exclude="true"]',
+ '.no-selection'
+ ],
+ includeOnlyElements: false
+ }
+ ) {
+ this.initializeEventListeners();
+ }
+
+ private initializeEventListeners() {
+ if (!this.config.enableSelection) return;
+
+ this.container.addEventListener('click', this.handleClick.bind(this), true);
+ this.container.addEventListener('mousedown', this.handleMouseDown.bind(this), true);
+ this.container.addEventListener('mouseup', this.handleMouseUp.bind(this), true);
+
+ if (this.config.enableHover) {
+ this.container.addEventListener('mouseenter', this.handleMouseEnter.bind(this), true);
+ this.container.addEventListener('mouseleave', this.handleMouseLeave.bind(this), true);
+ }
+
+ // Keyboard shortcuts when selection enabled
+ if (this.config.enableSelection) {
+ document.addEventListener('keydown', this.handleKeyDown.bind(this));
+ document.addEventListener('keyup', this.handleKeyUp.bind(this));
+ }
+ }
+
+ /**
+ * Click handler
+ */
+ private handleClick(event: MouseEvent) {
+ if (!this.config.enableSelection) return;
+ if (this.preventNextClick) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.preventNextClick = false;
+ return;
+ }
+
+ const target = event.target as HTMLElement;
+ if (!this.isValidElement(target)) return;
+
+ // Optional selectionDelay
+ if (this.config.selectionDelay > 0) {
+ setTimeout(() => {
+ this.selectElement(target);
+ }, this.config.selectionDelay);
+ } else {
+ this.selectElement(target);
+ }
+ }
+
+ /**
+ * mousedown
+ */
+ private handleMouseDown(event: MouseEvent) {
+ const target = event.target as HTMLElement;
+ if (!this.isValidElement(target)) return;
+
+ this.isSelecting = true;
+ this.selectionStartTime = Date.now();
+ this.preventNextClick = false;
+ }
+
+ /**
+ * mouseup
+ */
+ private handleMouseUp(event: MouseEvent) {
+ if (!this.isSelecting) return;
+
+ const target = event.target as HTMLElement;
+ const duration = Date.now() - this.selectionStartTime;
+
+ // Long-press (>500ms) suppresses click selection
+ if (duration > 500) {
+ this.preventNextClick = true;
+ }
+
+ this.isSelecting = false;
+ }
+
+ /**
+ * mouseenter
+ */
+ private handleMouseEnter(event: MouseEvent) {
+ if (!this.config.enableHover) return;
+
+ const target = event.target as HTMLElement;
+ if (!this.isValidElement(target)) return;
+
+ this.hoverElement = target;
+ this.onHoverElement(target);
+ }
+
+ /**
+ * mouseleave
+ */
+ private handleMouseLeave(event: MouseEvent) {
+ if (!this.config.enableHover) return;
+
+ const target = event.target as HTMLElement;
+ if (this.hoverElement === target) {
+ this.hoverElement = null;
+ this.onHoverElement(null);
+ }
+ }
+
+ /**
+ * keydown
+ */
+ private handleKeyDown(event: KeyboardEvent) {
+ // Esc clears selection
+ if (event.key === 'Escape') {
+ this.clearSelection();
+ }
+
+ // Ctrl/Cmd+A: select first match
+ if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
+ event.preventDefault();
+ this.selectAll();
+ }
+
+ // Ctrl/Cmd+D: clear
+ if ((event.ctrlKey || event.metaKey) && event.key === 'd') {
+ event.preventDefault();
+ this.clearSelection();
+ }
+ }
+
+ /**
+ * keyup
+ */
+ private handleKeyUp(event: KeyboardEvent) {
+ // noop
+ }
+
+ /**
+ * Whether element may be selected (mapped + static-content or static-class)
+ */
+ private isValidElement(element: HTMLElement): boolean {
+ if (!element || !element.tagName) return false;
+
+ // Exclude context menu
+ if (element.closest(`[${AttributeNames.contextMenu}="true"]`)) return false;
+
+ // Exclusion list
+ for (const selector of this.config.excludeSelectors) {
+ if (element.matches(selector) || element.closest(selector)) {
+ return false;
+ }
+ }
+
+ // Optional tag whitelist
+ if (this.config.includeOnlyElements) {
+ const validElements = ['DIV', 'SPAN', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BUTTON', 'A'];
+ if (!validElements.includes(element.tagName)) {
+ return false;
+ }
+ }
+
+ // Must have -info JSON
+ if (!element.hasAttribute(AttributeNames.info)) {
+ return false;
+ }
+
+ // Need static-content or static-class
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ const hasStaticClass = element.hasAttribute(AttributeNames.staticClass);
+
+ console.log('hasStaticContent', element, hasStaticContent);
+ console.log('hasStaticClass', element, hasStaticClass);
+
+ if (!hasStaticContent && !hasStaticClass) {
+ // Dynamic content/style — skip
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Apply selection highlight
+ */
+ private selectElement(element: HTMLElement) {
+ if (this.selectedElement === element) return;
+
+ // Clear previous outline
+ if (this.selectedElement) {
+ this.clearElementHighlighting(this.selectedElement);
+ }
+
+ this.selectedElement = element;
+
+ // Highlight new node
+ this.highlightElement(element);
+
+ // Notify subscribers
+ this.selectionListeners.forEach(listener => listener(element));
+ }
+
+ /**
+ * clearSelection
+ */
+ public clearSelection() {
+ if (this.selectedElement) {
+ this.clearElementHighlighting(this.selectedElement);
+ this.selectedElement = null;
+ this.selectionListeners.forEach(listener => listener(null));
+ }
+ }
+
+ /**
+ * highlightElement
+ */
+ private highlightElement(element: HTMLElement) {
+ // Restore prior inline styles if any
+ const existingHighlight = element.getAttribute('data-selection-highlight');
+ if (existingHighlight) {
+ const existingStyles = JSON.parse(existingHighlight);
+ Object.entries(existingStyles).forEach(([property, value]) => {
+ (element.style as any)[property] = value;
+ });
+ }
+
+ // Snapshot pre-highlight
+ const originalStyles = {
+ outline: element.style.outline,
+ boxShadow: element.style.boxShadow,
+ backgroundColor: element.style.backgroundColor,
+ cursor: element.style.cursor
+ };
+
+ // Selection chrome
+ element.style.outline = '2px solid #007acc';
+ element.style.boxShadow = '0 0 0 2px rgba(0, 122, 204, 0.3)';
+ element.style.backgroundColor = 'rgba(0, 122, 204, 0.1)';
+ element.style.cursor = 'pointer';
+
+ // Persist snapshot on data-selection-highlight
+ element.setAttribute('data-selection-highlight', JSON.stringify(originalStyles));
+ }
+
+ /**
+ * clearElementHighlighting
+ */
+ private clearElementHighlighting(element: HTMLElement) {
+ const highlightData = element.getAttribute('data-selection-highlight');
+ if (highlightData) {
+ try {
+ const originalStyles = JSON.parse(highlightData);
+ Object.entries(originalStyles).forEach(([property, value]) => {
+ (element.style as any)[property] = value;
+ });
+ element.removeAttribute('data-selection-highlight');
+ } catch (e) {
+ console.warn('[SelectionManager] Failed to restore original styles:', e);
+ }
+ }
+ }
+
+ /**
+ * onHoverElement
+ */
+ private onHoverElement(element: HTMLElement | null) {
+ // Hook for hover UX
+ }
+
+ /**
+ * selectAll (first candidate only)
+ */
+ private selectAll() {
+ const selectableElements = Array.from(
+ this.container.querySelectorAll('*')
+ ).filter(el => this.isValidElement(el as HTMLElement));
+
+ // Future: multi-select
+ if (selectableElements.length > 0) {
+ this.selectElement(selectableElements[0] as HTMLElement);
+ }
+ }
+
+ /**
+ * addSelectionListener
+ */
+ public addSelectionListener(listener: (element: HTMLElement | null) => void) {
+ this.selectionListeners.add(listener);
+ return () => this.selectionListeners.delete(listener);
+ }
+
+ /**
+ * getSelectedElement
+ */
+ public getSelectedElement(): HTMLElement | null {
+ return this.selectedElement;
+ }
+
+ /**
+ * getHoverElement
+ */
+ public getHoverElement(): HTMLElement | null {
+ return this.hoverElement;
+ }
+
+ /**
+ * extractSourceInfo
+ */
+ private extractSourceInfo(element: HTMLElement): SourceInfo | null {
+ return extractSourceInfoFromAttributes(element);
+ }
+
+ /**
+ * Walk up for library component root
+ */
+ private findComponentRoot(element: HTMLElement): HTMLElement {
+ const sourceInfo = this.extractSourceInfo(element);
+ if (!sourceInfo || !sourceInfo.fileName) return element;
+
+ // Heuristic: components/ui or common
+ const isLibraryComponent = sourceInfo.fileName.includes('/components/ui/') ||
+ sourceInfo.fileName.includes('/components/common/');
+
+ if (!isLibraryComponent) return element;
+
+ // Stop at file boundary
+ let current = element;
+ let componentRoot = element;
+
+ while (current.parentElement) {
+ const parent = current.parentElement;
+ const parentSourceInfo = this.extractSourceInfo(parent);
+
+ // Boundary: different file or no mapping
+ if (!parentSourceInfo || parentSourceInfo.fileName !== sourceInfo.fileName) {
+ componentRoot = current;
+ break;
+ }
+
+ current = parent;
+ }
+
+ return componentRoot;
+ }
+
+ /**
+ * extractElementInfo
+ */
+ public extractElementInfo(element: HTMLElement): ElementInfo | null {
+ if (!element) return null;
+
+ // Prefer library root
+ const targetElement = this.findComponentRoot(element);
+ const sourceInfo = this.extractSourceInfo(targetElement);
+
+ if (!sourceInfo) {
+ console.warn('[SelectionManager] Element has no source mapping:', targetElement);
+ return null;
+ }
+
+ // Layout (rect unused below — kept for future)
+ const rect = targetElement.getBoundingClientRect();
+ const computedStyle = window.getComputedStyle(targetElement);
+
+ // Static text flags
+ const hasStaticContentAttr = targetElement.hasAttribute(AttributeNames.staticContent);
+ const isActuallyPureText = isPureStaticText(targetElement);
+ const isStaticText = hasStaticContentAttr && isActuallyPureText;
+
+
+ const isStaticClass = targetElement.hasAttribute(AttributeNames.staticClass);
+
+ // Text snapshot
+ let textContent = '';
+ if (isStaticText) {
+ textContent = this.getElementTextContent(targetElement);
+ } else {
+ textContent = targetElement.innerText || targetElement.textContent || '';
+ }
+
+ // Ancestor chain with mappings
+ const hierarchy: { tagName: string; componentName?: string; fileName?: string }[] = [];
+ let current: HTMLElement | null = targetElement;
+ while (current && current !== document.body) {
+ const info = this.extractSourceInfo(current);
+ if (info) {
+ hierarchy.push({
+ tagName: current.tagName.toLowerCase(),
+ componentName: info.componentName,
+ fileName: info.fileName
+ });
+ }
+ current = current.parentElement;
+ }
+
+ // DOM attrs as pseudo-props
+ const props = this.getElementAttributes(targetElement);
+
+ return {
+ tagName: targetElement.tagName.toLowerCase(),
+ className: targetElement.className || '',
+ textContent: textContent,
+ sourceInfo,
+ isStaticText: isStaticText || false,
+ isStaticClass: isStaticClass,
+ componentName: sourceInfo.componentName,
+ componentPath: sourceInfo.fileName,
+ props,
+ hierarchy
+ };
+ }
+
+ /**
+ * getElementTextContent
+ */
+ private getElementTextContent(element: HTMLElement): string {
+
+ let textContent = element.textContent || '';
+
+ // Truncate preview
+ if (textContent.length > 100) {
+ textContent = textContent.substring(0, 100) + '...';
+ }
+
+ return textContent.trim();
+ }
+
+ /**
+ * getElementAttributes
+ */
+ private getElementAttributes(element: HTMLElement): Record {
+ const attributes: Record = {};
+ const elementAttributes = Array.from(element.attributes);
+
+ elementAttributes.forEach(attr => {
+ // Strip mapping + selection attrs
+ if (!isSourceMappingAttribute(attr.name) &&
+ !attr.name.startsWith('data-selection-')) {
+ attributes[attr.name] = attr.value;
+ }
+ });
+
+ return attributes;
+ }
+
+ /**
+ * getElementDomPath
+ */
+ private getElementDomPath(element: HTMLElement): string {
+ const path: string[] = [];
+ let current: HTMLElement | null = element;
+
+ while (current && current !== this.container) {
+ let selector = current.tagName.toLowerCase();
+
+ if (current.id) {
+ selector += `#${current.id}`;
+ path.unshift(selector);
+ break;
+ }
+
+ if (current.className) {
+ const classes = Array.from(current.classList).slice(0, 3);
+ selector += `.${classes.join('.')}`;
+ }
+
+ // nth-of-type disambiguation
+ const siblings = Array.from(current.parentNode?.children || []);
+ const sameTagSiblings = siblings.filter(sibling =>
+ sibling.tagName === current!.tagName
+ );
+
+ if (sameTagSiblings.length > 1) {
+ const index = sameTagSiblings.indexOf(current);
+ selector += `:nth-of-type(${index + 1})`;
+ }
+
+ path.unshift(selector);
+ current = current.parentElement;
+ }
+
+ return path.join(' > ');
+ }
+
+ /**
+ * destroy
+ */
+ public destroy() {
+
+ this.clearSelection();
+
+
+ this.container.removeEventListener('click', this.handleClick.bind(this), true);
+ this.container.removeEventListener('mousedown', this.handleMouseDown.bind(this), true);
+ this.container.removeEventListener('mouseup', this.handleMouseUp.bind(this), true);
+ this.container.removeEventListener('mouseenter', this.handleMouseEnter.bind(this), true);
+ this.container.removeEventListener('mouseleave', this.handleMouseLeave.bind(this), true);
+ document.removeEventListener('keydown', this.handleKeyDown.bind(this));
+ document.removeEventListener('keyup', this.handleKeyUp.bind(this));
+
+
+ this.selectionListeners.clear();
+ }
+}
+
+/**
+ * SelectionManager React Hook
+ */
+export const useSelectionManager = (config?: {
+ enableSelection?: boolean;
+ enableHover?: boolean;
+ selectionDelay?: number;
+ excludeSelectors?: string[];
+ includeOnlyElements?: boolean;
+}) => {
+ const selectionManagerRef = useRef(null);
+ const { selectElement, config: designModeConfig } = useDesignMode();
+
+ useEffect(() => {
+ const container = document.body;
+ selectionManagerRef.current = new SelectionManager(container, {
+ enableSelection: designModeConfig.iframeMode?.enableSelection ?? true,
+ enableHover: true,
+ selectionDelay: 0,
+ excludeSelectors: [
+ 'script', 'style', 'meta', 'link', 'head', 'title', 'html', 'body',
+ '[data-selection-exclude="true"]', '.no-selection'
+ ],
+ includeOnlyElements: false,
+ ...config
+ });
+
+ // Bridge to React context
+ const unsubscribe = selectionManagerRef.current.addSelectionListener((element) => {
+ if (element && designModeConfig.iframeMode?.enabled) {
+ // extractElementInfo + selectElement
+ const elementInfo = selectionManagerRef.current?.extractElementInfo(element);
+ if (elementInfo) {
+ selectElement(element);
+ }
+ } else if (!element) {
+ selectElement(null);
+ }
+ });
+
+ return () => {
+ unsubscribe();
+ selectionManagerRef.current?.destroy();
+ selectionManagerRef.current = null;
+ };
+ }, [selectElement, designModeConfig]);
+
+ return {
+ selectionManager: selectionManagerRef.current
+ };
+};
+
+export default SelectionManager;
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/UpdateManager.tsx b/qiming-vite-plugin-design-mode/packages/client-react/src/UpdateManager.tsx
new file mode 100644
index 00000000..75169cb1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/UpdateManager.tsx
@@ -0,0 +1,816 @@
+import React, { useRef, useState } from 'react';
+import { useDesignMode } from './DesignModeContext';
+import { SourceInfo, AddToChatMessage, CopyElementMessage } from '@xagi/design-mode-shared/messages';
+import { extractSourceInfo, hasSourceMapping } from '@xagi/design-mode-shared/sourceInfo';
+import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
+import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
+import { showContextMenu, MenuItem } from './ui/ContextMenu';
+import { UpdateOperation, UpdateState, UpdateResult, BatchUpdateItem, UpdateManagerConfig } from './types/UpdateTypes';
+import { HistoryManager } from '@xagi/design-mode-shared/HistoryManager';
+import { ObserverManager } from './managers/ObserverManager';
+import { EditManager } from './managers/EditManager';
+import { UpdateService } from '@xagi/design-mode-shared/UpdateService';
+import { bridge } from './bridge';
+import { Toast } from './ui/Toast';
+
+
+
+
+
+/**
+ * Coordinates direct edit, context menu, batching, and persistence hooks.
+ */
+export class UpdateManager {
+ private updateQueue: UpdateState[] = [];
+ private historyManager: HistoryManager;
+ private observerManager: ObserverManager;
+ private editManager: EditManager;
+ private updateService: UpdateService;
+ private callbacks: Map void>> =
+ new Map();
+ private batchTimer: NodeJS.Timeout | null = null;
+ private saveTimer: NodeJS.Timeout | null = null;
+
+ // Design mode state
+ private isDesignMode: boolean = false;
+ private selectedElement: HTMLElement | null = null;
+ private selectElementCallback: ((element: HTMLElement | null) => void) | null = null;
+
+ constructor(
+ private config: UpdateManagerConfig = {
+ enableDirectEdit: true,
+ enableBatching: true,
+ batchDebounceMs: 300,
+ maxRetries: 3,
+ autoSave: true,
+ saveDelay: 1000,
+ validation: {
+ validateSource: true,
+ validateValue: true,
+ maxLength: 10000,
+ },
+ }
+ ) {
+ this.historyManager = new HistoryManager();
+ this.observerManager = new ObserverManager(
+ (target, type) => this.editManager.handleDirectEdit(target, type),
+ (node) => this.setupElementEditHandlers(node)
+ );
+ this.editManager = new EditManager(
+ (update) => this.updateService.processUpdate(update),
+ this.config
+ );
+ this.updateService = new UpdateService(
+ this.config,
+ (update) => {
+ this.historyManager.add(update);
+ this.notifyCallbacks(update.operation, update);
+ if (this.config.autoSave) {
+ this.triggerAutoSave();
+ }
+ },
+ (update) => {
+ // onFail callback
+ if (update.error) {
+ Toast.error(`Update failed: ${update.error}`);
+ }
+ }
+ );
+
+ if (this.config.enableDirectEdit) {
+ this.observerManager.enable();
+ }
+
+ this.initializeEventListeners();
+ }
+
+ /**
+ * (Observer wired in ObserverManager)
+ */
+
+
+ /**
+ * register dblclick / contextmenu
+ */
+ private initializeEventListeners() {
+ if (!this.config.enableDirectEdit) return;
+
+ // Double-click → edit
+ document.addEventListener('dblclick', this.handleDblClick.bind(this));
+
+ // Context menu
+ document.addEventListener('contextmenu', this.handleContextMenu.bind(this));
+
+ // Shortcuts (optional)
+ // document.addEventListener('keydown', this.handleKeyDown.bind(this));
+ }
+
+
+
+ /**
+ * Double-click: only when design mode on
+ */
+ private handleDblClick(event: MouseEvent) {
+ // Require design mode
+ if (!this.isDesignMode) return;
+
+ const target = event.target as HTMLElement;
+
+ // Need source mapping
+ if (!hasSourceMapping(target)) return;
+
+ // Skip plugin UI
+ if (target.closest('#__vite_plugin_design_mode__')) return;
+
+ // Skip context menu DOM
+ if (target.closest(`[${AttributeNames.contextMenu}="true"]`)) return;
+
+ // static-content="true" only
+ // Respects configurable attribute prefix
+ const staticContentAttr = target.getAttribute(AttributeNames.staticContent);
+ if (staticContentAttr !== 'true') {
+ // Not editable without static text marker
+ return;
+ }
+
+ // preventDefault for dblclick
+ event.preventDefault();
+ event.stopPropagation();
+
+ // Check if it's pure static text - REMOVED to let EditManager handle validation
+ // if (!isPureStaticText(target)) {
+ // console.log('[UpdateManager] Ignored dblclick on non-static text element');
+ // return;
+ // }
+
+ // Delegate to EditManager
+ this.editManager.handleDirectEdit(target, 'content');
+ }
+
+ /**
+ * Context menu: add to chat / copy
+ */
+ private handleContextMenu(event: MouseEvent) {
+ const target = event.target as HTMLElement;
+
+ // Only show context menu if design mode is enabled
+ if (!this.isDesignMode) return;
+
+ // Exclude context menu
+ if (target.closest(`[${AttributeNames.contextMenu}="true"]`)) return;
+
+ // Must be mapped
+ if (!hasSourceMapping(target)) return;
+
+ // isSelected flag
+ const isSelected = !!(this.selectedElement && target === this.selectedElement);
+
+ // Preserve hover while menu opens
+ const hadHoverState = target.hasAttribute('data-design-hover');
+ if (hadHoverState) {
+ // context-menu-hover marker
+ target.setAttribute(AttributeNames.contextMenuHover, 'true');
+ if (target.hasAttribute(AttributeNames.staticClass) || target.hasAttribute(AttributeNames.staticContent)) {
+ // keep data-design-hover
+ target.setAttribute('data-design-hover', 'true');
+
+ }
+ }
+
+ event.preventDefault();
+
+ // Custom menu for mapped nodes
+ this.showContextMenu(target, event.clientX, event.clientY, isSelected);
+ }
+
+ /**
+ * Update design mode state
+ */
+ public setDesignModeState(
+ isDesignMode: boolean,
+ selectedElement: HTMLElement | null = null,
+ selectElementCallback?: (element: HTMLElement | null) => void
+ ) {
+ this.isDesignMode = isDesignMode;
+ this.selectedElement = selectedElement;
+ if (selectElementCallback) {
+ this.selectElementCallback = selectElementCallback;
+ }
+ }
+
+ /**
+ * Whether design mode is active
+ */
+ public getDesignModeState(): boolean {
+ return this.isDesignMode;
+ }
+
+
+ /**
+ * Keyboard shortcuts (currently unused hook)
+ */
+ private handleKeyDown(event: KeyboardEvent) {
+ // F2 → edit
+ if (event.key === 'F2') {
+ const selectedElement = document.activeElement as HTMLElement;
+ if (selectedElement && hasSourceMapping(selectedElement)) {
+ event.preventDefault();
+ this.editManager.handleDirectEdit(selectedElement, 'content');
+ // if (isPureStaticText(selectedElement)) {
+ // this.editManager.handleDirectEdit(selectedElement, 'content');
+ // } else {
+ // console.log('[UpdateManager] Cannot edit non-static text element');
+ // }
+ }
+ }
+
+ // Save all
+ if ((event.ctrlKey || event.metaKey) && event.key === 's') {
+ event.preventDefault();
+ this.saveAllChanges();
+ }
+
+ // Undo
+ if (
+ (event.ctrlKey || event.metaKey) &&
+ event.key === 'z' &&
+ !event.shiftKey
+ ) {
+ event.preventDefault();
+ this.undoLastUpdate();
+ }
+
+ // Redo
+ if (
+ (event.ctrlKey || event.metaKey) &&
+ (event.key === 'y' || (event.key === 'z' && event.shiftKey))
+ ) {
+ event.preventDefault();
+ this.redoLastUpdate();
+ }
+ }
+
+ /**
+ * Observer callback: mark editable nodes
+ */
+ private setupElementEditHandlers(element: HTMLElement) {
+ if (!hasSourceMapping(element)) return;
+
+ // data-edit-enabled
+ element.setAttribute('data-edit-enabled', 'true');
+
+ // dashed outline on hover
+ element.addEventListener('mouseenter', () => {
+ if (this.config.enableDirectEdit && this.isDesignMode) {
+ element.style.outline = '1px dashed #007acc';
+ }
+ });
+
+ element.addEventListener('mouseleave', () => {
+ if (!element.hasAttribute('data-selected')) {
+ element.style.outline = '';
+ }
+ });
+ }
+
+
+
+ /**
+ * Internal direct-edit path
+ */
+ private handleDirectEdit(
+ element: HTMLElement,
+ type: 'style' | 'content' | 'attribute'
+ ) {
+ if (!this.config.enableDirectEdit) return;
+
+ const sourceInfo = extractSourceInfo(element);
+ if (!sourceInfo) return;
+
+ switch (type) {
+ case 'content':
+ this.updateContent(element, element.innerText, sourceInfo);
+ break;
+ case 'style':
+ this.updateStyle(element, element.className, sourceInfo);
+ break;
+ }
+ }
+
+ /**
+ * enterEditMode
+ */
+ public enterEditMode(
+ element: HTMLElement,
+ type: 'style' | 'content' | 'attribute'
+ ) {
+ const sourceInfo = extractSourceInfo(element);
+ if (!sourceInfo) {
+ console.warn('[UpdateManager] Element has no source mapping');
+ return;
+ }
+
+ switch (type) {
+ case 'content':
+ this.editManager.handleDirectEdit(element, 'content');
+ break;
+ case 'style':
+ this.editManager.editStyle(element);
+ break;
+ case 'attribute':
+ this.editManager.editAttributes(element);
+ break;
+ }
+ }
+
+
+
+ /**
+ * (reserved)
+ */
+
+
+ /**
+ * showContextMenu
+ */
+ private showContextMenu(element: HTMLElement, x: number, y: number, isSelected: boolean) {
+ const menuItems: MenuItem[] = [];
+
+ // Built-in actions
+ menuItems.push(
+ {
+ label: 'Add to chat',
+ action: () => this.addToChat(element),
+ },
+ {
+ label: 'Copy element',
+ action: () => this.copyElement(element)
+ }
+ );
+
+ showContextMenu(element, x, y, menuItems);
+ }
+
+ /**
+ * (handled in ContextMenu)
+ */
+
+
+ /**
+ * copyElement
+ */
+ private copyElement(element: HTMLElement) {
+ const sourceInfo = extractSourceInfo(element);
+ const content = element.innerText || element.textContent || '';
+
+ // Copy element to clipboard (if possible)
+ const elementInfo = {
+ tagName: element.tagName.toLowerCase(),
+ className: element.className,
+ content: content,
+ sourceInfo: sourceInfo ?? undefined
+ };
+
+ const textToCopy = JSON.stringify(elementInfo, null, 2);
+
+ // sendCopyMessage helper
+ const sendCopyMessage = (success: boolean, error?: string) => {
+ const message: CopyElementMessage = {
+ type: 'COPY_ELEMENT',
+ payload: {
+ elementInfo: {
+ tagName: elementInfo.tagName,
+ className: elementInfo.className,
+ content: elementInfo.content,
+ sourceInfo: elementInfo.sourceInfo
+ },
+ textContent: textToCopy,
+ success,
+ error
+ },
+ timestamp: Date.now()
+ };
+
+ // iframe: bridge; top: postMessage
+ if (typeof window !== 'undefined' && window.self !== window.top) {
+
+ bridge.send(message).catch(error => {
+ console.error('[UpdateManager] Failed to send COPY_ELEMENT via bridge:', error);
+ // fallback postMessage
+ window.parent.postMessage(message, '*');
+ });
+ } else {
+
+ window.postMessage(message, '*');
+ }
+ };
+
+ // Clipboard API when available
+ if (navigator.clipboard) {
+ navigator.clipboard.writeText(textToCopy).then(() => {
+ let alertMessage = 'Copied element info to clipboard:\n\n';
+ if (sourceInfo) {
+ alertMessage += `File: ${sourceInfo.fileName}\n`;
+ alertMessage += `Line: L${sourceInfo.lineNumber}\n`;
+ alertMessage += `\n`;
+ }
+ alertMessage += `Tag: <${elementInfo.tagName}>\n`;
+ alertMessage += `className: ${elementInfo.className}\n`;
+ alertMessage += `Text: ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`;
+
+
+ sendCopyMessage(true);
+ }).catch(err => {
+ const errorMessage = err instanceof Error ? err.message : String(err);
+ console.error('[UpdateManager] Failed to copy to clipboard:', err);
+
+
+ sendCopyMessage(false, errorMessage);
+ });
+ } else {
+
+ sendCopyMessage(false, 'Clipboard API not available');
+ }
+ }
+
+ /**
+ * Add element content to chat
+ */
+ private addToChat(element: HTMLElement) {
+ const sourceInfo = extractSourceInfo(element);
+ const content = element.innerText || element.textContent || '';
+
+ // console.log('[UpdateManager] Adding to chat:', { content, sourceInfo });
+
+
+
+ const contextSourceInfo = sourceInfo ?? undefined;
+
+
+ const elementInfo = sourceInfo ? {
+ tagName: element.tagName.toLowerCase(),
+ className: element.className,
+ textContent: content,
+ sourceInfo,
+ isStaticText: isPureStaticText(element)
+ } : undefined;
+
+ const message: AddToChatMessage = {
+ type: 'ADD_TO_CHAT',
+ payload: {
+ content,
+ context: {
+ sourceInfo: contextSourceInfo,
+ elementInfo
+ }
+ },
+ timestamp: Date.now()
+ };
+
+ // iframe vs top-level postMessage
+ if (typeof window !== 'undefined' && window.self !== window.top) {
+
+ bridge.send(message).catch(error => {
+ console.error('[UpdateManager] Failed to send ADD_TO_CHAT via bridge:', error);
+
+ window.parent.postMessage(message, '*');
+ });
+ } else {
+
+ window.postMessage(message, '*');
+ }
+
+ // Format alert message with source info
+ let alertMessage = 'Added to chat:\n\n';
+ if (sourceInfo) {
+ alertMessage += `File: ${sourceInfo.fileName}\n`;
+ alertMessage += `Line: L${sourceInfo.lineNumber}\n`;
+ alertMessage += `\n`;
+ }
+ alertMessage += `Text:\n${content.substring(0, 100)}${content.length > 100 ? '...' : ''}`;
+ }
+
+ /**
+ * deleteElement (stub)
+ */
+ private deleteElement(element: HTMLElement) {
+ // Destructive edit not implemented
+ }
+
+ /**
+ * updateStyle
+ */
+ public updateStyle(
+ element: HTMLElement,
+ newClass: string,
+ sourceInfo: SourceInfo
+ ): Promise {
+ return this.editManager.updateStyle(element, newClass, sourceInfo);
+ }
+
+ /**
+ * updateContent
+ */
+ public updateContent(
+ element: HTMLElement,
+ newContent: string,
+ sourceInfo?: SourceInfo,
+ oldValue?: string
+ ): Promise {
+ return this.editManager.updateContent(element, newContent, sourceInfo, oldValue);
+ }
+
+ /**
+ * updateAttribute
+ */
+ public updateAttribute(
+ element: HTMLElement,
+ attributeName: string,
+ newValue: string,
+ sourceInfo: SourceInfo
+ ): Promise {
+ return this.editManager.updateAttribute(element, attributeName, newValue, sourceInfo);
+ }
+
+ /**
+ * batchUpdate
+ */
+ public batchUpdate(updates: BatchUpdateItem[]): Promise {
+ if (!this.config.enableBatching) {
+ // Sequential when batching off
+ return Promise.all(
+ updates.map(item => {
+ if (item.type === 'style') {
+ return this.updateStyle(
+ item.element,
+ item.newValue,
+ item.sourceInfo
+ );
+ } else if (item.type === 'content') {
+ return this.updateContent(
+ item.element,
+ item.newValue,
+ item.sourceInfo
+ );
+ } else {
+ return this.updateAttribute(
+ item.element,
+ 'data-test',
+ item.newValue,
+ item.sourceInfo
+ );
+ }
+ })
+ );
+ }
+
+ // Debounced batch
+ return new Promise(resolve => {
+
+ const updateStates: UpdateState[] = updates.map(item => ({
+ id: this.generateUpdateId(),
+ operation: item.type === 'style' ? 'style_update' : 'content_update',
+ sourceInfo: item.sourceInfo,
+ element: item.element,
+ oldValue: item.originalValue || '',
+ newValue: item.newValue,
+ status: 'pending' as const,
+ timestamp: Date.now(),
+ retryCount: 0,
+ }));
+
+
+ this.updateQueue.push(...updateStates);
+
+
+ if (this.batchTimer) {
+ clearTimeout(this.batchTimer);
+ }
+
+ this.batchTimer = setTimeout(async () => {
+ const results = await this.processBatchUpdate(updateStates);
+ resolve(results);
+ }, this.config.batchDebounceMs);
+ });
+ }
+
+ /**
+ * processUpdate
+ */
+ private async processUpdate(update: UpdateState): Promise {
+ return this.updateService.processUpdate(update);
+ }
+
+ /**
+ * processBatchUpdate
+ */
+ private async processBatchUpdate(
+ updates: UpdateState[]
+ ): Promise {
+ return this.updateService.processBatchUpdate(updates);
+ }
+
+ /**
+ * (extract in service)
+ */
+
+
+ /**
+ * generateUpdateId
+ */
+ private generateUpdateId(): string {
+ return `update_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+ }
+
+ /**
+ * triggerAutoSave
+ */
+ private triggerAutoSave() {
+ if (this.saveTimer) {
+ clearTimeout(this.saveTimer);
+ }
+
+ this.saveTimer = setTimeout(() => {
+ this.saveAllChanges();
+ }, this.config.saveDelay);
+ }
+
+ /**
+ * saveAllChanges
+ */
+ public async saveAllChanges(): Promise {
+ const pendingUpdates = this.updateQueue.filter(u => u.status === 'pending');
+ if (pendingUpdates.length === 0) return;
+
+ try {
+ const results = await this.processBatchUpdate(pendingUpdates);
+ } catch (error) {
+ console.error('[UpdateManager] Failed to save changes:', error);
+ }
+ }
+
+ /**
+ * undoLastUpdate
+ */
+ public undoLastUpdate(): boolean {
+ const lastUpdate = this.historyManager.undo();
+ if (!lastUpdate) return false;
+
+ // Restore DOM from history
+ lastUpdate.element.innerText = lastUpdate.oldValue;
+ lastUpdate.element.className = lastUpdate.oldValue;
+
+ return true;
+ }
+
+ /**
+ * redoLastUpdate
+ */
+ public redoLastUpdate(): boolean {
+ const lastReverted = this.historyManager.redo();
+ if (!lastReverted) return false;
+
+ // Re-apply from history
+ lastReverted.element.innerText = lastReverted.newValue;
+ lastReverted.element.className = lastReverted.newValue;
+
+ return true;
+ }
+
+ /**
+ * addUpdateCallback
+ */
+ public addUpdateCallback(
+ operation: UpdateOperation,
+ callback: (update: UpdateState) => void
+ ) {
+ if (!this.callbacks.has(operation)) {
+ this.callbacks.set(operation, new Set());
+ }
+ this.callbacks.get(operation)!.add(callback);
+
+
+ return () => {
+ this.callbacks.get(operation)?.delete(callback);
+ };
+ }
+
+ /**
+ * notifyCallbacks
+ */
+ private notifyCallbacks(operation: UpdateOperation, update: UpdateState) {
+ const callbacks = this.callbacks.get(operation);
+ if (callbacks) {
+ callbacks.forEach(callback => callback(update));
+ }
+ }
+
+ /**
+ * getUpdateStates
+ */
+ public getUpdateStates(): UpdateState[] {
+ return [...this.updateQueue];
+ }
+
+ /**
+ * getUpdateHistory
+ */
+ public getUpdateHistory(): UpdateState[] {
+ return this.historyManager.getHistory();
+ }
+
+ /**
+ * destroy
+ */
+ public destroy() {
+
+ this.observerManager.disable();
+
+
+ if (this.batchTimer) {
+ clearTimeout(this.batchTimer);
+ }
+
+ if (this.saveTimer) {
+ clearTimeout(this.saveTimer);
+ }
+
+
+ this.updateQueue = [];
+ this.historyManager.clear();
+ this.callbacks.clear();
+ }
+}
+
+/**
+ * UpdateManager React Hook
+ */
+export const useUpdateManager = (config?: Partial) => {
+ const updateManagerRef = useRef(null);
+ const [updateStates, setUpdateStates] = useState([]);
+ const { config: designModeConfig, isDesignMode, selectedElement } = useDesignMode();
+
+ React.useEffect(() => {
+ // Direct edit can run outside strict design mode when enabled
+ updateManagerRef.current = new UpdateManager({
+ enableDirectEdit: designModeConfig.iframeMode?.enableDirectEdit ?? true,
+ enableBatching: designModeConfig.batchUpdate?.enabled ?? true,
+ batchDebounceMs: designModeConfig.batchUpdate?.debounceMs ?? 300,
+ maxRetries: 3,
+ autoSave: true,
+ saveDelay: 1000,
+ validation: {
+ validateSource: true,
+ validateValue: true,
+ maxLength: 10000,
+ },
+ ...config,
+ });
+
+ // Track content_update events
+ const unsubscribe = updateManagerRef.current.addUpdateCallback(
+ 'content_update',
+ update => {
+ setUpdateStates(prev => [...prev, update]);
+ }
+ );
+
+ return () => {
+ unsubscribe();
+ updateManagerRef.current?.destroy();
+ updateManagerRef.current = null;
+ };
+ }, [designModeConfig]);
+
+ // Sync design mode state and selected element with UpdateManager
+ const { selectElement } = useDesignMode();
+ React.useEffect(() => {
+ if (updateManagerRef.current) {
+ updateManagerRef.current.setDesignModeState(isDesignMode, selectedElement, selectElement);
+ }
+ }, [isDesignMode, selectedElement, selectElement]);
+
+
+ return {
+ updateManager: updateManagerRef.current,
+ updateStates,
+ updateStyle: (
+ element: HTMLElement,
+ newClass: string,
+ sourceInfo: SourceInfo
+ ) => updateManagerRef.current?.updateStyle(element, newClass, sourceInfo),
+ updateContent: (
+ element: HTMLElement,
+ newContent: string,
+ sourceInfo: SourceInfo
+ ) =>
+ updateManagerRef.current?.updateContent(element, newContent, sourceInfo),
+ batchUpdate: (updates: BatchUpdateItem[]) =>
+ updateManagerRef.current?.batchUpdate(updates),
+ saveAllChanges: () => updateManagerRef.current?.saveAllChanges(),
+ undoLastUpdate: () => updateManagerRef.current?.undoLastUpdate(),
+ redoLastUpdate: () => updateManagerRef.current?.redoLastUpdate(),
+ };
+};
+
+export default UpdateManager;
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/bridge.ts b/qiming-vite-plugin-design-mode/packages/client-react/src/bridge.ts
new file mode 100644
index 00000000..8a405dee
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/bridge.ts
@@ -0,0 +1,651 @@
+import {
+ DesignModeMessage,
+ IframeToParentMessage,
+ ParentToIframeMessage,
+ RequestResponseMessage,
+ MessageValidator,
+ BridgeInterface,
+ BridgeConfig,
+ MessageUtilsInterface,
+ MessageValidationResult,
+ AcknowledgementMessage,
+ HealthCheckMessage,
+ HealthCheckResponseMessage,
+ HeartbeatMessage
+} from '@xagi/design-mode-shared/messages';
+
+/**
+ * postMessage bridge: optional request/response, validation hooks, heartbeats.
+ */
+export class EnhancedBridge implements BridgeInterface {
+ private listeners: Map> = new Map();
+ private pendingRequests: Map = new Map();
+
+ private config: BridgeConfig;
+ private _isConnected = false;
+ private lastHeartbeat = 0;
+ private heartbeatTimer?: NodeJS.Timeout;
+ private connectionCheckTimer?: NodeJS.Timeout;
+
+ constructor(config: Partial = {}) {
+ this.config = {
+ timeout: 10000,
+ retryAttempts: 3,
+ retryDelay: 1000,
+ heartbeatInterval: 30000,
+ debug: false,
+ ...config
+ };
+
+ this.initializeMessageHandling();
+ this.initializeHeartbeat();
+ this.initializeConnectionCheck();
+ }
+
+ /** Listen for `message` and mark connected after a short delay (iframe vs top). */
+ private initializeMessageHandling() {
+ if (typeof window === 'undefined') return;
+
+ window.addEventListener('message', this.handleMessage.bind(this));
+ if (this.isIframeEnvironment()) {
+ setTimeout(() => {
+ this._isConnected = true;
+ this.log('Bridge initialized and connected (iframe mode)');
+
+ this.sendReadyMessage();
+ }, 200);
+ } else {
+ setTimeout(() => {
+ this._isConnected = true;
+ this.log('Bridge initialized and connected (main window mode)');
+ }, 100);
+ }
+ }
+
+ /** Notify parent that the child iframe bridge is ready. */
+ private sendReadyMessage() {
+ try {
+ const readyMessage = {
+ type: 'BRIDGE_READY',
+ payload: {
+ timestamp: this.createTimestamp(),
+ environment: 'iframe'
+ },
+ timestamp: this.createTimestamp()
+ };
+ this.getTargetWindow().postMessage(readyMessage, '*');
+ this.log('Sent ready message to parent');
+ } catch (error) {
+ this.log('Failed to send ready message:', error);
+ }
+ }
+
+ /**
+ * Periodic HEARTBEAT posts when running inside an iframe.
+ */
+ private initializeHeartbeat() {
+ if (typeof window === 'undefined') return;
+
+ if (this.isIframeEnvironment()) {
+ this.heartbeatTimer = setInterval(() => {
+ this.sendHeartbeat();
+ }, this.config.heartbeatInterval);
+ }
+ }
+
+ /** Periodically infer disconnect from stale heartbeats. */
+ private initializeConnectionCheck() {
+ if (typeof window === 'undefined') return;
+
+ this.connectionCheckTimer = setInterval(() => {
+ this.checkConnection();
+ }, this.config.heartbeatInterval * 2);
+ }
+
+ /** True when `window.self !== window.top`. */
+ private isIframeEnvironment(): boolean {
+ return typeof window !== 'undefined' && window.self !== window.top;
+ }
+
+ /** Snapshot for diagnostics / health. */
+ public getEnvironmentInfo(): {
+ isIframe: boolean;
+ isConnected: boolean;
+ origin: string;
+ userAgent: string;
+ location: string;
+ } {
+ if (typeof window === 'undefined') {
+ return {
+ isIframe: false,
+ isConnected: false,
+ origin: '',
+ userAgent: '',
+ location: ''
+ };
+ }
+
+ return {
+ isIframe: this.isIframeEnvironment(),
+ isConnected: this._isConnected,
+ origin: window.location.origin,
+ userAgent: navigator.userAgent.substring(0, 100),
+ location: window.location.href
+ };
+ }
+
+ /** Log bridge state to the console (dev aid). */
+ public diagnose(): void {
+ // Diagnose output is explicitly gated by debug mode to avoid noisy runtime logs.
+ if (!this.config.debug) return;
+
+ const env = this.getEnvironmentInfo();
+
+ console.group('[EnhancedBridge] Bridge Diagnosis');
+ console.log('Environment:', env);
+ console.log('Connection Status:', this._isConnected);
+ console.log('Pending Requests:', this.pendingRequests.size);
+ console.log('Message Listeners:', Array.from(this.listeners.keys()));
+ console.log('Config:', this.config);
+ console.groupEnd();
+ }
+
+ /** Parent when iframe; `window` when top-level. */
+ private getTargetWindow(): Window {
+ return this.isIframeEnvironment() ? window.parent : window;
+ }
+
+ /** Route incoming postMessage payloads. */
+ private handleMessage(event: MessageEvent) {
+ // Optional origin filter (disabled by default):
+ // if (event.origin !== window.location.origin && window.location.origin !== 'null') {
+ // if (!event.origin.startsWith('http') && !event.origin.startsWith('https')) {
+ // this.log('Received message from different origin, allowing:', event.origin);
+ // } else {
+ // this.log('Skipping message from different origin:', event.origin);
+ // return;
+ // }
+ // }
+
+ const message = event.data;
+ // Drop malformed payloads
+ if (!this.isValidMessage(message)) {
+ this.log('Invalid message received:', message);
+ return;
+ }
+
+ // Child handshake
+ if (message.type === 'BRIDGE_READY') {
+ this.log('Received ready message from child');
+ this._isConnected = true;
+ return;
+ }
+
+ // 父窗口 -> iframe 的控制类消息:必须优先走业务分发,不能误判为 RPC response。
+ // 否则像 TOGGLE_DESIGN_MODE 这类同样带 requestId/timestamp 的消息会被 isResponseMessage
+ // 吞掉,导致 bridge.on('TOGGLE_DESIGN_MODE') 永远不触发,父页面一直等不到 DESIGN_MODE_CHANGED。
+ if (this.isIframeEnvironment() && this.isParentToIframeCommand(message)) {
+ this.dispatchMessage(message);
+ return;
+ }
+
+ // Completes a pending sendWithResponse(仅处理真正的响应类型,避免与父到子命令冲突)
+ if (this.isResponseMessage(message)) {
+ this.handleResponseMessage(message);
+ return;
+ }
+
+ // Fan-out to on(type) handlers
+ this.dispatchMessage(message);
+ }
+
+ /** Resolve or reject pendingRequests entry. */
+ private handleResponseMessage(message: RequestResponseMessage) {
+ const { requestId } = message;
+
+ if (this.pendingRequests.has(requestId)) {
+ const request = this.pendingRequests.get(requestId)!;
+ clearTimeout(request.timeout);
+ this.pendingRequests.delete(requestId);
+
+ if (message.type === 'ACKNOWLEDGEMENT') {
+ request.resolve({ success: true, acknowledged: true });
+ } else {
+ request.resolve(message);
+ }
+ }
+ }
+
+ /** Shape check before dispatch. */
+ private isValidMessage(message: any): message is DesignModeMessage {
+ return (
+ message &&
+ typeof message.type === 'string' &&
+ this.isSupportedMessageType(message.type)
+ );
+ }
+
+ /** Known DesignModeMessage union tags. */
+ private isSupportedMessageType(type: string): boolean {
+ const supportedTypes = [
+ // Iframe to Parent
+ 'ELEMENT_SELECTED', 'ELEMENT_DESELECTED', 'CONTENT_UPDATED', 'STYLE_UPDATED',
+ 'DESIGN_MODE_CHANGED', 'ELEMENT_STATE_RESPONSE', 'ERROR', 'ACKNOWLEDGEMENT',
+ 'HEARTBEAT', 'HEALTH_CHECK_RESPONSE', 'BRIDGE_READY', 'ADD_TO_CHAT',
+ // Parent to Iframe
+ 'TOGGLE_DESIGN_MODE', 'UPDATE_STYLE', 'UPDATE_CONTENT', 'BATCH_UPDATE',
+ 'HEALTH_CHECK'
+ ];
+ return supportedTypes.includes(type);
+ }
+
+ /**
+ * 是否为 sendWithResponse / ACK 通道的响应消息。
+ * 注意:不能仅凭 requestId + timestamp 判断,否则父页面发来的 TOGGLE_DESIGN_MODE
+ *(同样带 requestId)会被误判为 response,从而跳过 dispatchMessage。
+ */
+ private isResponseMessage(message: any): message is RequestResponseMessage {
+ if (!message || typeof message.type !== 'string') {
+ return false;
+ }
+ if (message.requestId === undefined || message.timestamp === undefined) {
+ return false;
+ }
+ return (
+ message.type === 'ACKNOWLEDGEMENT' ||
+ message.type === 'HEALTH_CHECK_RESPONSE'
+ );
+ }
+
+ /** 父窗口发往 iframe 的控制命令(在 iframe 内必须由业务 handler 处理) */
+ private isParentToIframeCommand(message: DesignModeMessage): boolean {
+ const t = (message as { type?: string }).type;
+ return (
+ t === 'TOGGLE_DESIGN_MODE' ||
+ t === 'UPDATE_STYLE' ||
+ t === 'UPDATE_CONTENT' ||
+ t === 'BATCH_UPDATE' ||
+ t === 'HEALTH_CHECK'
+ );
+ }
+
+ /**
+ * Fire-and-forget postMessage (may no-op if not connected).
+ */
+ public async send(message: T): Promise {
+ if (!this._isConnected && this.isIframeEnvironment()) {
+ this.log('Bridge not connected, attempting to reconnect...');
+
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ if (!this._isConnected) return;
+ }
+
+ if (!this._isConnected && !this.isIframeEnvironment()) {
+ this.log('Running in main window, bridge connection not applicable');
+ return;
+ }
+
+ const enhancedMessage = this.enhanceMessage(message);
+ try {
+ this.log('Sending message:', enhancedMessage);
+
+ this.getTargetWindow().postMessage(enhancedMessage, '*');
+
+ if (this.isIframeEnvironment() && enhancedMessage.requestId) {
+ this.sendAcknowledgement(enhancedMessage.requestId);
+ }
+ } catch (error) {
+ this.log('Error sending message:', error);
+
+ // Dev: surface send failures
+ if (process.env.NODE_ENV === 'development') {
+ throw error;
+ }
+ }
+ }
+
+ /** RPC-style: wait until matching response or timeout. */
+ public async sendWithResponse(
+ message: T,
+ responseType: R['type']
+ ): Promise {
+ if (!this._isConnected) {
+ throw new Error('Bridge is not connected');
+ }
+
+ const enhancedMessage = this.enhanceMessage(message);
+ const requestId = enhancedMessage.requestId!;
+
+ return new Promise((resolve, reject) => {
+ // Timeout
+ const timeout = setTimeout(() => {
+ this.pendingRequests.delete(requestId);
+ reject(new Error(`Request timeout after ${this.config.timeout}ms`));
+ }, this.config.timeout);
+
+ // Pending request map
+ this.pendingRequests.set(requestId, {
+ resolve,
+ reject,
+ timeout,
+ responseType
+ });
+
+ try {
+ this.log('Sending request with response:', enhancedMessage);
+ this.getTargetWindow().postMessage(enhancedMessage, '*');
+ } catch (error) {
+ this.pendingRequests.delete(requestId);
+ clearTimeout(timeout);
+ reject(error);
+ }
+ });
+ }
+
+ /** Ensure requestId + timestamp exist. */
+ private enhanceMessage(message: T): T & { requestId?: string; timestamp: number } {
+ const requestId = (message as any).requestId || this.generateRequestId();
+ const timestamp = (message as any).timestamp || this.createTimestamp();
+
+ return {
+ ...message,
+ requestId,
+ timestamp
+ };
+ }
+
+ /** Unique id for correlating responses. */
+ private generateRequestId(): string {
+ return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+ }
+
+ /** ms since epoch */
+ private createTimestamp(): number {
+ return Date.now();
+ }
+
+ /** Best-effort ACK (messageType not wired through yet). */
+ private sendAcknowledgement(requestId: string) {
+ const acknowledgement: AcknowledgementMessage = {
+ type: 'ACKNOWLEDGEMENT',
+ payload: {
+ messageType: 'UNKNOWN', // TODO: propagate originating type
+ },
+ requestId,
+ timestamp: this.createTimestamp()
+ };
+
+ this.getTargetWindow().postMessage(acknowledgement, '*');
+ }
+
+ /** HEARTBEAT post to parent. */
+ private sendHeartbeat() {
+ const heartbeat: HeartbeatMessage = {
+ type: 'HEARTBEAT',
+ payload: {
+ timestamp: this.createTimestamp()
+ },
+ timestamp: this.createTimestamp()
+ };
+
+ this.getTargetWindow().postMessage(heartbeat, '*');
+ this.lastHeartbeat = Date.now();
+ }
+
+ /** Mark disconnected if no heartbeats for 3× interval. */
+ private checkConnection() {
+ const now = Date.now();
+ const timeSinceLastHeartbeat = now - this.lastHeartbeat;
+
+ if (timeSinceLastHeartbeat > this.config.heartbeatInterval * 3) {
+ this.log('Connection appears to be lost');
+ this._isConnected = false;
+
+ // Optimistic reconnect
+ setTimeout(() => {
+ this._isConnected = true;
+ this.log('Connection restored');
+ }, 1000);
+ }
+ }
+
+ /** Subscribe; returns unsubscribe. */
+ public on(type: T['type'], handler: (message: T) => void): () => void {
+ if (!this.listeners.has(type)) {
+ this.listeners.set(type, new Set());
+ }
+
+ this.listeners.get(type)!.add(handler);
+
+ // Unsubscribe
+ return () => {
+ this.listeners.get(type)?.delete(handler);
+ };
+ }
+
+ /** Remove one handler for `type`. */
+ public off(type: string, handler: Function): void {
+ this.listeners.get(type)?.delete(handler);
+ }
+
+ /** Invoke all listeners for message.type */
+ private dispatchMessage(message: DesignModeMessage) {
+ const handlers = this.listeners.get(message.type);
+ if (handlers) {
+ handlers.forEach(handler => {
+ try {
+ handler(message);
+ } catch (error) {
+ this.log('Error in message handler:', error);
+ }
+ });
+ }
+ }
+
+ /** `_isConnected` flag */
+ public isConnected(): boolean {
+ return this._isConnected;
+ }
+
+ /** Same as `isConnected` (legacy name). */
+ public isConnectedToTarget(): boolean {
+ return this._isConnected;
+ }
+
+ /** Clear timers, reject pendings, remove listener. */
+ public disconnect(): void {
+ this._isConnected = false;
+
+ if (this.heartbeatTimer) {
+ clearInterval(this.heartbeatTimer);
+ }
+
+ if (this.connectionCheckTimer) {
+ clearInterval(this.connectionCheckTimer);
+ }
+
+ // Reject pending RPCs
+ this.pendingRequests.forEach(request => {
+ clearTimeout(request.timeout);
+ request.reject(new Error('Connection disconnected'));
+ });
+ this.pendingRequests.clear();
+
+ // Remove global listener
+ if (typeof window !== 'undefined') {
+ window.removeEventListener('message', this.handleMessage.bind(this));
+ }
+ this.listeners.clear();
+
+ this.log('Bridge disconnected');
+ }
+
+ /** Summarize connectivity; may issue HEALTH_CHECK RPC in iframe. */
+ public async healthCheck(): Promise<{ status: string; details: any }> {
+ try {
+ const env = this.getEnvironmentInfo();
+
+ // Top-level window
+ if (!env.isIframe && this._isConnected) {
+ return {
+ status: 'healthy',
+ details: {
+ ...env,
+ message: 'Bridge is healthy and running in main window'
+ }
+ };
+ }
+
+ if (env.isIframe && this._isConnected) {
+ // Iframe: ping parent
+ try {
+ const response = await this.sendWithResponse(
+ {
+ type: 'HEALTH_CHECK',
+ requestId: '',
+ timestamp: this.createTimestamp()
+ },
+ 'HEALTH_CHECK_RESPONSE'
+ );
+
+ return {
+ status: 'healthy',
+ details: {
+ ...env,
+ response: response.payload,
+ message: 'Bridge is healthy and responsive'
+ }
+ };
+ } catch (error) {
+ return {
+ status: 'degraded',
+ details: {
+ ...env,
+ error: error instanceof Error ? error.message : 'Unknown error',
+ message: 'Bridge is connected but not responding to health checks'
+ }
+ };
+ }
+ }
+
+ return {
+ status: env.isIframe ? 'connecting' : 'unnecessary',
+ details: {
+ ...env,
+ message: env.isIframe ? 'Bridge is initializing' : 'Bridge not needed in main window'
+ }
+ };
+ } catch (error) {
+ return {
+ status: 'unhealthy',
+ details: {
+ error: error instanceof Error ? error.message : 'Unknown error',
+ environment: this.getEnvironmentInfo()
+ }
+ };
+ }
+ }
+
+ /** Guarded by config.debug */
+ private log(...args: any[]) {
+ // Keep debug logging capability for explicit troubleshooting sessions.
+ if (this.config.debug) {
+ const timestamp = new Date().toISOString();
+ console.log(`[${timestamp}] [EnhancedBridge]`, ...args);
+ }
+ }
+
+ /** Alias for disconnect(). */
+ public destroy(): void {
+ this.disconnect();
+ }
+}
+
+/** Small helpers for request ids / timestamps. */
+export const MessageUtils: MessageUtilsInterface = {
+ generateRequestId: (): string => {
+ return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+ },
+
+ createTimestamp: (): number => {
+ return Date.now();
+ },
+
+ isValidMessage: (message: any): boolean => {
+ return message &&
+ typeof message.type === 'string' &&
+ typeof message.timestamp === 'number';
+ },
+
+ createResponse: function(
+ originalMessage: ParentToIframeMessage,
+ payload: T extends { payload: infer P } ? P : never,
+ success: boolean = true,
+ error?: string
+ ): T {
+ return {
+ payload,
+ type: (payload as any)?.type,
+ requestId: (originalMessage as any).requestId || '',
+ timestamp: this.createTimestamp()
+ } as unknown as T;
+ }
+};
+
+/** Singleton used by the design-mode client bundle. */
+export const bridge = new EnhancedBridge();
+
+/** Structural validation for a subset of message types. */
+export class MessageValidatorImpl implements MessageValidator {
+ validate(message: any): MessageValidationResult {
+ const errors: string[] = [];
+
+ if (!message) {
+ errors.push('Message is null or undefined');
+ return { isValid: false, errors };
+ }
+
+ if (typeof message.type !== 'string') {
+ errors.push('Message type must be a string');
+ }
+
+ if (typeof message.timestamp !== 'number') {
+ errors.push('Message timestamp must be a number');
+ }
+
+ // Per-type payload checks
+ switch (message.type) {
+ case 'ELEMENT_SELECTED':
+ if (!message.payload?.elementInfo) {
+ errors.push('ELEMENT_SELECTED must have elementInfo in payload');
+ }
+ break;
+
+ case 'UPDATE_STYLE':
+ case 'UPDATE_CONTENT':
+ if (!message.payload?.sourceInfo) {
+ errors.push(`${message.type} must have sourceInfo in payload`);
+ }
+ break;
+
+ case 'BATCH_UPDATE':
+ if (!Array.isArray(message.payload?.updates)) {
+ errors.push('BATCH_UPDATE must have updates array in payload');
+ }
+ break;
+ }
+
+ return {
+ isValid: errors.length === 0,
+ errors: errors.length > 0 ? errors : undefined
+ };
+ }
+}
+
+export const messageValidator = new MessageValidatorImpl();
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/index.tsx b/qiming-vite-plugin-design-mode/packages/client-react/src/index.tsx
new file mode 100644
index 00000000..77599296
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/index.tsx
@@ -0,0 +1,38 @@
+import { StrictMode } from 'react';
+import ReactDOM from 'react-dom/client';
+import { DesignModeProvider } from './DesignModeContext';
+import { DesignModeManager } from './DesignModeManager';
+
+const init = () => {
+ const containerId = '__vite_plugin_design_mode__';
+ if (document.getElementById(containerId)) return;
+
+ const container = document.createElement('div');
+ container.id = containerId;
+ document.body.appendChild(container);
+
+ // Use Shadow DOM to isolate styles
+ const shadowRoot = container.attachShadow({ mode: 'open' });
+ const rootElement = document.createElement('div');
+ shadowRoot.appendChild(rootElement);
+
+ ReactDOM.createRoot(rootElement).render(
+
+
+
+
+
+ );
+};
+
+function isInIframe() {
+ try {
+ return window.self !== window.top;
+ } catch (e) {
+ return true;
+ }
+}
+
+if (typeof window !== 'undefined') {
+ init();
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/managers/EditManager.ts b/qiming-vite-plugin-design-mode/packages/client-react/src/managers/EditManager.ts
new file mode 100644
index 00000000..10140738
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/managers/EditManager.ts
@@ -0,0 +1,364 @@
+import { UpdateState, UpdateResult, UpdateOperation, UpdateManagerConfig } from '../types/UpdateTypes';
+import { SourceInfo } from '@xagi/design-mode-shared/messages';
+import { extractSourceInfo, hasSourceMapping } from '@xagi/design-mode-shared/sourceInfo';
+import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
+import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
+import { resolveSourceInfo } from '@xagi/design-mode-shared/sourceInfoResolver';
+
+export class EditManager {
+ constructor(
+ private processUpdate: (update: UpdateState) => Promise,
+ private config: UpdateManagerConfig
+ ) { }
+
+ /**
+ * Handle direct edit (double click or mutation)
+ */
+ public handleDirectEdit(element: HTMLElement, type: 'content' | 'style') {
+ if (type === 'content') {
+ this.editTextContent(element);
+ } else {
+ this.editStyle(element);
+ }
+ }
+
+ /**
+ * Edit text content using contentEditable
+ */
+ public async editTextContent(element: HTMLElement) {
+ const sourceInfo = resolveSourceInfo(element);
+ if (!sourceInfo) return;
+
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ if (!hasStaticContent) {
+ console.warn('[EditManager] Cannot edit: element does not have static-content attribute. This might be a component definition, not a usage site.');
+ return;
+ }
+
+ const originalText = element.innerText;
+ const originalContentEditable = element.contentEditable;
+
+ element.contentEditable = 'true';
+
+ element.setAttribute('data-ignore-mutation', 'true');
+
+ const range = document.createRange();
+ range.selectNodeContents(element);
+ const selection = window.getSelection();
+ selection?.removeAllRanges();
+ selection?.addRange(range);
+
+ const handleSave = () => {
+ const newText = element.innerText.trim();
+
+ element.contentEditable = 'false';
+
+ element.removeAttribute('data-ignore-mutation');
+
+ element.removeEventListener('blur', handleSave);
+ element.removeEventListener('input', handleInput);
+ element.removeEventListener('keydown', handleKeyDown);
+ document.removeEventListener('mousedown', handleClickOutside, true);
+
+ if (newText !== originalText.trim()) {
+ element.innerText = newText;
+
+ const relatedElements = this.findAllElementsWithSameSource(element, sourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.innerText = newText;
+ }
+ });
+
+ this.notifyContentChanged(element, newText, sourceInfo, originalText);
+ }
+ };
+
+ const handleCancel = () => {
+ element.innerText = originalText;
+
+ element.contentEditable = 'false';
+
+ element.removeAttribute('data-ignore-mutation');
+
+ element.removeEventListener('blur', handleSave);
+ element.removeEventListener('input', handleInput);
+ element.removeEventListener('keydown', handleKeyDown);
+ document.removeEventListener('mousedown', handleClickOutside, true);
+ };
+
+ const handleClickOutside = (e: MouseEvent) => {
+ if (!element.contains(e.target as Node)) {
+ handleSave();
+ }
+ };
+
+ const handleInput = () => {
+ const currentText = element.innerText.trim();
+
+ const relatedElements = this.findAllElementsWithSameSource(element, sourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.innerText = currentText;
+ }
+ });
+
+ this.notifyContentChangedRealtime(element, currentText, sourceInfo, originalText);
+ };
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ if (e.ctrlKey || e.metaKey) {
+ return;
+ } else {
+ e.preventDefault();
+ element.blur();
+ }
+ } else if (e.key === 'Escape') {
+ e.preventDefault();
+ handleCancel();
+ }
+ };
+
+ element.addEventListener('blur', handleSave);
+ element.addEventListener('input', handleInput);
+ element.addEventListener('keydown', handleKeyDown);
+ document.addEventListener('mousedown', handleClickOutside, true);
+
+ element.focus();
+ }
+
+ /**
+ * Update content
+ * @param element Target DOM node
+ * @param newValue New text
+ * @param sourceInfo Optional; extracted from element when omitted
+ * @param oldValue Optional; uses current innerText when omitted
+ */
+ public async updateContent(
+ element: HTMLElement,
+ newValue: string,
+ sourceInfo?: SourceInfo,
+ oldValue?: string
+ ): Promise {
+ const finalSourceInfo = sourceInfo || extractSourceInfo(element);
+ if (!finalSourceInfo) {
+ throw new Error('Cannot update content: no source info available');
+ }
+
+ const finalOldValue = oldValue ?? element.innerText;
+
+ const update: UpdateState = {
+ id: this.generateUpdateId(),
+ operation: 'content_update',
+ sourceInfo: finalSourceInfo,
+ element,
+ oldValue: finalOldValue,
+ newValue,
+ status: 'pending',
+ timestamp: Date.now(),
+ retryCount: 0,
+ persist: false,
+ };
+
+ const relatedElements = this.findAllElementsWithSameSource(element, finalSourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.innerText = newValue;
+ }
+ });
+
+ return this.processUpdate(update);
+ }
+
+ /**
+ * Update style (class)
+ */
+ public async updateStyle(
+ element: HTMLElement,
+ newClass: string,
+ sourceInfo: SourceInfo
+ ): Promise {
+ const oldClass = element.className;
+ const finalSourceInfo = sourceInfo || extractSourceInfo(element);
+ if (!finalSourceInfo) {
+ throw new Error('Cannot update style: no source info available');
+ }
+
+ const update: UpdateState = {
+ id: this.generateUpdateId(),
+ operation: 'class_update',
+ sourceInfo,
+ element,
+ oldValue: oldClass,
+ newValue: newClass,
+ status: 'pending',
+ timestamp: Date.now(),
+ retryCount: 0,
+ persist: false,
+ };
+
+ const relatedElements = this.findAllElementsWithSameSource(element, finalSourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.className = newClass;
+ }
+ });
+
+ return this.processUpdate(update);
+ }
+
+ /**
+ * Edit style (trigger UI)
+ */
+ public editStyle(element: HTMLElement) {
+ }
+
+ /**
+ * Update attribute
+ */
+ public async updateAttribute(
+ element: HTMLElement,
+ attributeName: string,
+ newValue: string,
+ sourceInfo: SourceInfo
+ ): Promise {
+ const oldValue = element.getAttribute(attributeName) || '';
+
+ const update: UpdateState = {
+ id: this.generateUpdateId(),
+ operation: 'attribute_update',
+ sourceInfo,
+ element,
+ oldValue,
+ newValue,
+ status: 'pending',
+ timestamp: Date.now(),
+ retryCount: 0,
+ persist: false,
+ };
+
+ const relatedElements = this.findAllElementsWithSameSource(element, sourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.setAttribute(attributeName, newValue);
+ }
+ });
+
+ return this.processUpdate(update);
+ }
+
+ /**
+ * Edit attributes (trigger UI)
+ */
+ public editAttributes(element: HTMLElement) {
+ }
+
+ private generateUpdateId(): string {
+ return `update-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+ }
+
+ /**
+ * Notify parent of final text (iframe only); does not write source files.
+ */
+ private notifyContentChanged(
+ element: HTMLElement,
+ newValue: string,
+ sourceInfo?: SourceInfo,
+ oldValue?: string
+ ): void {
+ const finalSourceInfo = sourceInfo || resolveSourceInfo(element);
+ if (!finalSourceInfo) {
+ console.warn('[EditManager] Cannot notify: no source info available');
+ return;
+ }
+
+ if (window.self !== window.top) {
+ window.parent.postMessage({
+ type: 'CONTENT_UPDATED',
+ payload: {
+ sourceInfo: finalSourceInfo,
+ oldValue: oldValue || '',
+ newValue: newValue,
+ },
+ timestamp: Date.now(),
+ }, '*');
+ }
+ }
+
+ private lastRealtimeNotify: number = 0;
+ private readonly REALTIME_THROTTLE_MS = 300;
+
+ /**
+ * Throttled CONTENT_UPDATED while typing (realtime: true).
+ */
+ private notifyContentChangedRealtime(
+ element: HTMLElement,
+ newValue: string,
+ sourceInfo?: SourceInfo,
+ oldValue?: string
+ ): void {
+ const now = Date.now();
+
+ if (now - this.lastRealtimeNotify < this.REALTIME_THROTTLE_MS) {
+ return;
+ }
+
+ this.lastRealtimeNotify = now;
+
+ const finalSourceInfo = sourceInfo || resolveSourceInfo(element);
+ if (!finalSourceInfo) {
+ return;
+ }
+
+ if (window.self !== window.top) {
+ window.parent.postMessage({
+ type: 'CONTENT_UPDATED',
+ payload: {
+ sourceInfo: finalSourceInfo,
+ oldValue: oldValue || '',
+ newValue: newValue,
+ realtime: true,
+ },
+ timestamp: Date.now(),
+ }, '*');
+ }
+ }
+
+ /**
+ * List peers that share the same logical instance (lists): same `element-id`,
+ * same static-content presence, same mapped file.
+ *
+ * @param sourceInfo Deprecated; kept for signature compatibility
+ */
+ private findAllElementsWithSameSource(element: HTMLElement, sourceInfo?: SourceInfo): HTMLElement[] {
+ const elementId = element.getAttribute(AttributeNames.elementId);
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ const refSourceInfo = extractSourceInfo(element);
+ const refFileName = refSourceInfo?.fileName;
+
+ if (!elementId) {
+ console.warn('[EditManager] Element missing element-id attribute:', element);
+ return [element];
+ }
+
+ const allElementsWithId = Array.from(
+ document.querySelectorAll(`[${AttributeNames.elementId}]`)
+ ) as HTMLElement[];
+
+ return allElementsWithId.filter(el => {
+ const elId = el.getAttribute(AttributeNames.elementId);
+ const elHasStaticContent = el.hasAttribute(AttributeNames.staticContent);
+ const elSourceInfo = extractSourceInfo(el);
+ const elFileName = elSourceInfo?.fileName;
+
+ if (elId !== elementId) return false;
+
+ if (elHasStaticContent !== hasStaticContent) return false;
+
+ if (elFileName !== refFileName) return false;
+
+ return true;
+ });
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/managers/ObserverManager.ts b/qiming-vite-plugin-design-mode/packages/client-react/src/managers/ObserverManager.ts
new file mode 100644
index 00000000..3d0404d2
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/managers/ObserverManager.ts
@@ -0,0 +1,80 @@
+import { hasSourceMapping } from '@xagi/design-mode-shared/sourceInfo';
+
+export class ObserverManager {
+ private observer: MutationObserver | null = null;
+
+ constructor(
+ private onEdit: (target: HTMLElement, type: 'content' | 'style') => void,
+ private onNodeAdded: (node: HTMLElement) => void
+ ) {}
+
+ public enable() {
+ if (this.observer) return;
+
+ this.observer = new MutationObserver(mutations => {
+ mutations.forEach(mutation => {
+ // Check if the mutation should be ignored
+ const targetNode = mutation.target;
+ const targetElement = targetNode.nodeType === Node.ELEMENT_NODE
+ ? targetNode as HTMLElement
+ : targetNode.parentElement;
+
+ if (targetElement && targetElement.hasAttribute('data-ignore-mutation')) {
+ return;
+ }
+
+ if (mutation.type === 'childList') {
+ // Handle element addition
+ mutation.addedNodes.forEach(node => {
+ if (node.nodeType === Node.ELEMENT_NODE) {
+ this.onNodeAdded(node as HTMLElement);
+ }
+ });
+ } else if (mutation.type === 'characterData') {
+ // Handle text content change
+ const target = mutation.target.parentElement as HTMLElement;
+ if (target && hasSourceMapping(target)) {
+ if (target.hasAttribute('data-ignore-mutation')) {
+ return;
+ }
+ this.onEdit(target, 'content');
+ }
+ } else if (mutation.type === 'attributes') {
+ // Handle attribute change (style, class)
+ const target = mutation.target as HTMLElement;
+ if (target && hasSourceMapping(target)) {
+ const attributeName = mutation.attributeName;
+ const newValue = target.getAttribute(attributeName!);
+ const oldValue = mutation.oldValue;
+
+ if (newValue === oldValue) {
+ return;
+ }
+
+ if (attributeName === 'class') {
+ this.onEdit(target, 'style');
+ } else if (attributeName?.startsWith('style')) {
+ this.onEdit(target, 'style');
+ }
+ }
+ }
+ });
+ });
+
+ this.observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ characterData: true,
+ attributes: true,
+ attributeOldValue: true,
+ attributeFilter: ['class', 'style'],
+ });
+ }
+
+ public disable() {
+ if (this.observer) {
+ this.observer.disconnect();
+ this.observer = null;
+ }
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/types/UpdateTypes.ts b/qiming-vite-plugin-design-mode/packages/client-react/src/types/UpdateTypes.ts
new file mode 100644
index 00000000..a691a2ba
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/types/UpdateTypes.ts
@@ -0,0 +1,58 @@
+import { SourceInfo } from '@xagi/design-mode-shared/messages';
+
+/** High-level update kind tracked by `UpdateManager`. */
+export type UpdateOperation =
+ | 'style_update'
+ | 'content_update'
+ | 'attribute_update'
+ | 'class_update'
+ | 'batch_update';
+
+/** One in-flight or historical mutation. */
+export interface UpdateState {
+ id: string;
+ operation: UpdateOperation;
+ sourceInfo: SourceInfo;
+ element: HTMLElement;
+ oldValue: string;
+ newValue: string;
+ status: 'pending' | 'processing' | 'completed' | 'failed' | 'reverted';
+ timestamp: number;
+ error?: string;
+ retryCount: number;
+ persist?: boolean;
+}
+
+/** Result of persisting / previewing an update. */
+export interface UpdateResult {
+ success: boolean;
+ element: HTMLElement;
+ updateId: string;
+ error?: string;
+ serverResponse?: any;
+}
+
+/** One row in a batch POST body. */
+export interface BatchUpdateItem {
+ element: HTMLElement;
+ type: 'style' | 'content' | 'attribute';
+ sourceInfo: SourceInfo;
+ newValue: string;
+ originalValue?: string;
+ selector?: string;
+}
+
+/** `UpdateManager` runtime options. */
+export interface UpdateManagerConfig {
+ enableDirectEdit: boolean;
+ enableBatching: boolean;
+ batchDebounceMs: number;
+ maxRetries: number;
+ autoSave: boolean;
+ saveDelay: number;
+ validation: {
+ validateSource: boolean;
+ validateValue: boolean;
+ maxLength: number;
+ };
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/ui/ContextMenu.ts b/qiming-vite-plugin-design-mode/packages/client-react/src/ui/ContextMenu.ts
new file mode 100644
index 00000000..23c626c1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/ui/ContextMenu.ts
@@ -0,0 +1,231 @@
+import { extractSourceInfo } from '@xagi/design-mode-shared/sourceInfo';
+import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
+import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
+
+export interface MenuItem {
+ label: string;
+ action: () => void;
+ disabled?: boolean;
+}
+
+/**
+ * Create and show a context menu at the specified position
+ */
+export function showContextMenu(
+ element: HTMLElement,
+ x: number,
+ y: number,
+ menuItems: MenuItem[]
+): HTMLElement {
+ // Remove existing menu
+ const existingMenu = document.querySelector(`[${AttributeNames.contextMenu}="true"]`) as HTMLElement;
+ if (existingMenu) {
+ document.body.removeChild(existingMenu);
+ }
+
+ // Preserve hover when context menu opened from a hovered static node
+ const hadHoverState = element.hasAttribute(AttributeNames.contextMenuHover);
+
+ // Create context menu
+ const menu = document.createElement('div');
+ menu.setAttribute(AttributeNames.contextMenu, 'true');
+ menu.style.position = 'fixed';
+ menu.style.left = x + 'px';
+ menu.style.top = y + 'px';
+ menu.style.background = 'white';
+ menu.style.border = '0.5px solid #ccc';
+ menu.style.borderRadius = '6px';
+ menu.style.padding = '0';
+ menu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
+ menu.style.zIndex = '10000';
+ menu.style.minWidth = '150px';
+ menu.style.fontSize = '14px';
+
+ // Create menu items
+ menuItems.forEach(item => {
+ // Separator row
+ if (item.label === '---' || item.disabled) {
+ const separator = document.createElement('div');
+ separator.style.height = '1px';
+ separator.style.background = '#e5e7eb';
+ separator.style.margin = '0';
+ separator.style.padding = '0';
+ menu.appendChild(separator);
+ return;
+ }
+
+ const menuItem = document.createElement('div');
+ menuItem.textContent = item.label;
+ menuItem.style.padding = '8px 16px';
+ menuItem.style.borderRadius = '4px';
+ menuItem.style.margin = '0';
+
+ if (item.disabled) {
+ menuItem.style.color = '#999';
+ menuItem.style.cursor = 'not-allowed';
+ } else {
+ menuItem.style.cursor = 'pointer';
+ menuItem.style.color = '#333';
+ }
+
+ menuItem.style.background = 'transparent';
+
+ menuItem.addEventListener('mouseenter', () => {
+ if (!item.disabled) {
+ menuItem.style.background = '#f0f0f0';
+ }
+ });
+
+ menuItem.addEventListener('mouseleave', () => {
+ menuItem.style.background = 'transparent';
+ });
+
+ menuItem.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (item.disabled) return;
+ item.action();
+ closeContextMenu(menu);
+ });
+
+ menu.appendChild(menuItem);
+ });
+
+ // Add to page
+ document.body.appendChild(menu);
+
+ // Ensure menu stays within viewport
+ const rect = menu.getBoundingClientRect();
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
+
+ if (rect.right > viewportWidth) {
+ menu.style.left = (viewportWidth - rect.width - 10) + 'px';
+ }
+ if (rect.bottom > viewportHeight) {
+ menu.style.top = (viewportHeight - rect.height - 10) + 'px';
+ }
+
+ // Setup close handlers
+ setupContextMenuCloseHandlers(menu, element, hadHoverState);
+
+ return menu;
+}
+
+/**
+ * Close the context menu
+ */
+export function closeContextMenu(menu: HTMLElement) {
+ // Execute cleanup function
+ if ((menu as any).__cleanupHandlers) {
+ (menu as any).__cleanupHandlers();
+ delete (menu as any).__cleanupHandlers;
+ }
+
+ // Restore hover outline after menu closes
+ const targetElement = (menu as any).__targetElement as HTMLElement | null;
+ const hadHoverState = (menu as any).__hadHoverState as boolean;
+
+ // Remove menu element
+ if (menu.parentNode) {
+ menu.parentNode.removeChild(menu);
+ }
+
+ if (targetElement && hadHoverState) {
+ targetElement.removeAttribute(AttributeNames.contextMenuHover);
+
+ setTimeout(() => {
+ const mouseX = (window as any).__lastMouseX || 0;
+ const mouseY = (window as any).__lastMouseY || 0;
+ const rect = targetElement.getBoundingClientRect();
+ const isMouseOver =
+ mouseX >= rect.left &&
+ mouseX <= rect.right &&
+ mouseY >= rect.top &&
+ mouseY <= rect.bottom;
+
+ if (!isMouseOver) {
+ targetElement.removeAttribute('data-design-hover');
+ } else {
+ const mouseEnterEvent = new MouseEvent('mouseenter', {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ clientX: mouseX,
+ clientY: mouseY
+ });
+ targetElement.dispatchEvent(mouseEnterEvent);
+ }
+ }, 10);
+ }
+}
+
+/**
+ * Setup context menu close handlers (clickoutside and ESC key)
+ */
+function setupContextMenuCloseHandlers(
+ menu: HTMLElement,
+ targetElement: HTMLElement,
+ hadHoverState: boolean
+) {
+ (menu as any).__targetElement = targetElement;
+ (menu as any).__hadHoverState = hadHoverState;
+
+ const trackMouse = (e: MouseEvent) => {
+ (window as any).__lastMouseX = e.clientX;
+ (window as any).__lastMouseY = e.clientY;
+ };
+ document.addEventListener('mousemove', trackMouse);
+
+ const closeMenu = () => {
+ document.removeEventListener('mousemove', trackMouse);
+ closeContextMenu(menu);
+ };
+
+ // Click outside to close
+ const handleClickOutside = (e: MouseEvent) => {
+ if (!menu.contains(e.target as Node)) {
+ // Prevent event from bubbling to avoid deselecting element
+ e.preventDefault();
+ e.stopPropagation();
+ closeMenu();
+ }
+ };
+
+ // Right-click outside to close
+ const handleContextMenu = (e: MouseEvent) => {
+ if (!menu.contains(e.target as Node)) {
+ closeMenu();
+ }
+ };
+
+ // ESC key to close
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ closeMenu();
+ }
+ };
+
+ // Scroll to close
+ const handleScroll = () => {
+ closeMenu();
+ };
+
+ // Add listeners with slight delay to avoid immediate triggering
+ setTimeout(() => {
+ document.addEventListener('click', handleClickOutside, true);
+ document.addEventListener('contextmenu', handleContextMenu, true);
+ document.addEventListener('keydown', handleKeyDown, true);
+ window.addEventListener('scroll', handleScroll, true);
+ window.addEventListener('resize', handleScroll, true);
+ }, 0);
+
+ // Store cleanup function
+ (menu as any).__cleanupHandlers = () => {
+ document.removeEventListener('click', handleClickOutside, true);
+ document.removeEventListener('contextmenu', handleContextMenu, true);
+ document.removeEventListener('keydown', handleKeyDown, true);
+ window.removeEventListener('scroll', handleScroll, true);
+ window.removeEventListener('resize', handleScroll, true);
+ };
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/ui/EditModeUI.ts b/qiming-vite-plugin-design-mode/packages/client-react/src/ui/EditModeUI.ts
new file mode 100644
index 00000000..9410f623
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/ui/EditModeUI.ts
@@ -0,0 +1,76 @@
+export interface EditOptions {
+ element: HTMLElement;
+ initialValue: string;
+ onSave: (newValue: string) => void;
+ onCancel: () => void;
+}
+
+/**
+ * Enter edit mode for the given element
+ */
+export function enterEditMode(options: EditOptions): HTMLTextAreaElement {
+ const { element, initialValue, onSave, onCancel } = options;
+
+ const textArea = document.createElement('textarea');
+
+ // Set textarea styles to match element
+ textArea.value = initialValue;
+ textArea.style.position = 'absolute';
+ textArea.style.zIndex = '9999';
+ textArea.style.width = element.offsetWidth + 'px';
+ textArea.style.height = element.offsetHeight + 'px';
+
+ const computedStyle = window.getComputedStyle(element);
+ textArea.style.fontSize = computedStyle.fontSize;
+ textArea.style.fontFamily = computedStyle.fontFamily;
+ textArea.style.color = computedStyle.color;
+ textArea.style.lineHeight = computedStyle.lineHeight;
+ textArea.style.letterSpacing = computedStyle.letterSpacing;
+ textArea.style.textAlign = computedStyle.textAlign;
+
+ textArea.style.background = 'rgba(255, 255, 255, 0.9)';
+ textArea.style.border = '2px solid #007acc';
+ textArea.style.padding = '4px';
+ textArea.style.margin = '0';
+ textArea.style.resize = 'none';
+ textArea.style.outline = 'none';
+
+ // Get element bounding rect
+ const rect = element.getBoundingClientRect();
+ textArea.style.left = rect.left + 'px';
+ textArea.style.top = rect.top + 'px';
+
+ // Add to page
+ document.body.appendChild(textArea);
+ textArea.focus();
+
+ // Handle save and cancel
+ const handleSave = () => {
+ onSave(textArea.value);
+ };
+
+ const handleCancel = () => {
+ onCancel();
+ };
+
+ // Event listeners
+ textArea.addEventListener('blur', handleSave);
+ textArea.addEventListener('keydown', e => {
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
+ handleSave();
+ } else if (e.key === 'Escape') {
+ handleCancel();
+ }
+ });
+
+ return textArea;
+}
+
+/**
+ * Exit edit mode
+ */
+export function exitEditMode(editor: HTMLElement) {
+ if (editor.parentNode) {
+ editor.parentNode.removeChild(editor);
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/src/ui/Toast.ts b/qiming-vite-plugin-design-mode/packages/client-react/src/ui/Toast.ts
new file mode 100644
index 00000000..cbe63892
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/src/ui/Toast.ts
@@ -0,0 +1,98 @@
+/**
+ * Simple Toast Notification Utility
+ */
+export class Toast {
+ private static container: HTMLElement | null = null;
+ private static styleId = 'appdev-design-mode-toast-styles';
+
+ private static ensureContainer() {
+ if (this.container && document.body.contains(this.container)) return;
+
+ // Inject styles
+ if (!document.getElementById(this.styleId)) {
+ const style = document.createElement('style');
+ style.id = this.styleId;
+ style.textContent = `
+ .design-mode-toast-container {
+ position: fixed;
+ top: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 10000;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ pointer-events: none;
+ }
+ .design-mode-toast {
+ background: white;
+ color: #333;
+ padding: 10px 20px;
+ border-radius: 6px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ font-family: system-ui, -apple-system, sans-serif;
+ font-size: 14px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ animation: design-mode-toast-in 0.3s ease-out forwards;
+ border: 1px solid #eee;
+ min-width: 200px;
+ max-width: 400px;
+ }
+ .design-mode-toast.error {
+ border-left: 4px solid #ef4444;
+ }
+ .design-mode-toast.success {
+ border-left: 4px solid #22c55e;
+ }
+ .design-mode-toast.info {
+ border-left: 4px solid #3b82f6;
+ }
+ @keyframes design-mode-toast-in {
+ from { opacity: 0; transform: translateY(-20px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+ @keyframes design-mode-toast-out {
+ from { opacity: 1; transform: translateY(0); }
+ to { opacity: 0; transform: translateY(-20px); }
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ // Create container
+ this.container = document.createElement('div');
+ this.container.className = 'design-mode-toast-container';
+ document.body.appendChild(this.container);
+ }
+
+ public static show(message: string, type: 'success' | 'error' | 'info' = 'info', duration = 3000) {
+ this.ensureContainer();
+
+ const toast = document.createElement('div');
+ toast.className = `design-mode-toast ${type}`;
+ toast.textContent = message;
+
+ this.container!.appendChild(toast);
+
+ setTimeout(() => {
+ toast.style.animation = 'design-mode-toast-out 0.3s ease-in forwards';
+ toast.addEventListener('animationend', () => {
+ toast.remove();
+ });
+ }, duration);
+ }
+
+ public static error(message: string, duration = 4000) {
+ this.show(message, 'error', duration);
+ }
+
+ public static success(message: string, duration = 3000) {
+ this.show(message, 'success', duration);
+ }
+
+ public static info(message: string, duration = 3000) {
+ this.show(message, 'info', duration);
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-react/tsconfig.json b/qiming-vite-plugin-design-mode/packages/client-react/tsconfig.json
new file mode 100644
index 00000000..466391bb
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-react/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "jsx": "react-jsx",
+ "declaration": true,
+ "declarationMap": true,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/package.json b/qiming-vite-plugin-design-mode/packages/client-shared/package.json
new file mode 100644
index 00000000..5da5cb17
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "@xagi/design-mode-shared",
+ "version": "1.2.0",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ },
+ "./bridge": "./dist/bridge.js",
+ "./messages": "./dist/messages.js",
+ "./types": "./dist/types.js",
+ "./attributeNames": "./dist/attributeNames.js",
+ "./elementUtils": "./dist/elementUtils.js",
+ "./sourceInfo": "./dist/sourceInfo.js",
+ "./sourceInfoResolver": "./dist/sourceInfoResolver.js",
+ "./UpdateService": "./dist/UpdateService.js",
+ "./HistoryManager": "./dist/HistoryManager.js"
+ },
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsc"
+ },
+ "devDependencies": {
+ "typescript": "^5.0.0"
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/HistoryManager.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/HistoryManager.ts
new file mode 100644
index 00000000..47d6875e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/HistoryManager.ts
@@ -0,0 +1,72 @@
+import { UpdateState } from './types';
+
+/**
+ * History Manager
+ * Manages the history of updates for undo/redo functionality
+ */
+export class HistoryManager {
+ private history: UpdateState[] = [];
+
+ /**
+ * Add an update to history
+ */
+ public add(update: UpdateState) {
+ this.history.push(update);
+ }
+
+ /**
+ * Get all history
+ */
+ public getHistory(): UpdateState[] {
+ return [...this.history];
+ }
+
+ /**
+ * Clear history
+ */
+ public clear() {
+ this.history = [];
+ }
+
+ /**
+ * Undo the last update
+ * Moves the update from history to redo stack
+ */
+ public undo(): UpdateState | null {
+ const update = this.history.pop();
+ if (update) {
+ update.status = 'reverted';
+ this.redoStack.push(update);
+ }
+ return update || null;
+ }
+
+ /**
+ * Get the last reverted update for redo
+ * Note: Since we are changing how undo works (popping), we need to handle redo differently.
+ * If we want to support redo, we should store the undone updates.
+ */
+ private redoStack: UpdateState[] = [];
+
+ /**
+ * Redo the last undone update
+ * Moves the update from redo stack to history
+ */
+ public redo(): UpdateState | null {
+ const update = this.redoStack.pop();
+ if (update) {
+ update.status = 'completed';
+ this.history.push(update);
+ }
+ return update || null;
+ }
+
+ // To maintain backward compatibility with the "broken" logic (if it was indeed broken),
+ // or to fix it. The user asked to refactor, which implies improving maintainability.
+ // Fixing a bug is also good.
+ // However, I should be careful not to change behavior if the user relies on it (even if buggy).
+ // But "redo not working" is hardly a feature.
+
+ // I'll implement `undo` to return the update, and let the caller handle the DOM restoration.
+ // The manager just manages the state.
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/UpdateService.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/UpdateService.ts
new file mode 100644
index 00000000..5b0ad720
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/UpdateService.ts
@@ -0,0 +1,221 @@
+import { UpdateState, UpdateResult, UpdateManagerConfig } from './types';
+
+/**
+ * UpdateService
+ * Handles update processing, validation, and server synchronization
+ */
+export class UpdateService {
+ private processingUpdates = new Set();
+
+ constructor(
+ private config: UpdateManagerConfig,
+ private onComplete: (update: UpdateState) => void,
+ private onFail: (update: UpdateState) => void
+ ) { }
+
+ /**
+ * Process a single update
+ */
+ public async processUpdate(update: UpdateState): Promise {
+ // Validate update
+ if (!this.validateUpdate(update)) {
+ return {
+ success: false,
+ element: update.element,
+ updateId: update.id,
+ error: 'Update validation failed',
+ };
+ }
+
+ // Mark as processing
+ update.status = 'processing';
+ this.processingUpdates.add(update.id);
+
+ try {
+ // Apply DOM update
+ this.applyUpdateToDOM(update);
+
+ let serverResponse;
+ // Save to source via API (only if persist is true)
+ if (update.persist) {
+ serverResponse = await this.saveToSource(update);
+ } else {
+ serverResponse = { success: true, preview: true };
+ }
+
+ // Mark as completed
+ update.status = 'completed';
+ this.onComplete(update);
+
+ return {
+ success: true,
+ element: update.element,
+ updateId: update.id,
+ serverResponse,
+ };
+ } catch (error) {
+ update.status = 'failed';
+ update.error = error instanceof Error ? error.message : 'Unknown error';
+
+ // Retry mechanism
+ if (update.retryCount < this.config.maxRetries) {
+ update.retryCount++;
+ return this.processUpdate(update);
+ }
+
+ this.onFail(update);
+
+ return {
+ success: false,
+ element: update.element,
+ updateId: update.id,
+ error: update.error,
+ };
+ } finally {
+ this.processingUpdates.delete(update.id);
+ }
+ }
+
+ /**
+ * Process batch update
+ */
+ public async processBatchUpdate(
+ updates: UpdateState[]
+ ): Promise {
+ try {
+ // Build batch API request
+ const batchRequest = {
+ updates: updates.map(update => ({
+ filePath: update.sourceInfo.fileName,
+ line: update.sourceInfo.lineNumber,
+ column: update.sourceInfo.columnNumber,
+ type: (update.operation === 'style_update' || update.operation === 'class_update') ? 'style' : 'content',
+ newValue: update.newValue,
+ originalValue: update.oldValue,
+ })),
+ };
+
+ // Call batch API
+ const response = await fetch('/__appdev_design_mode/batch-update', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(batchRequest),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Batch update failed: ${response.statusText}`);
+ }
+
+ const results = await response.json();
+
+ // Mark all updates as completed
+ updates.forEach(update => {
+ update.status = 'completed';
+ this.onComplete(update);
+ });
+
+ return results.map((result: any, index: number) => ({
+ success: result.success,
+ element: updates[index].element,
+ updateId: updates[index].id,
+ serverResponse: result,
+ }));
+ } catch (error) {
+ // Mark all updates as failed
+ updates.forEach(update => {
+ update.status = 'failed';
+ update.error = error instanceof Error ? error.message : 'Unknown error';
+ this.onFail(update);
+ });
+
+ return updates.map(update => ({
+ success: false,
+ element: update.element,
+ updateId: update.id,
+ error: update.error,
+ }));
+ }
+ }
+
+ /**
+ * Apply update to DOM
+ */
+ private applyUpdateToDOM(update: UpdateState) {
+ const { element, operation, newValue } = update;
+
+ switch (operation) {
+ case 'style_update':
+ case 'class_update':
+ element.className = newValue;
+ break;
+ case 'content_update':
+ element.innerText = newValue;
+ break;
+ case 'attribute_update':
+ // This should get attribute name from sourceInfo
+ // Currently simplified to set data-attribute
+ element.setAttribute('data-updated-value', newValue);
+ break;
+ }
+ }
+
+ /**
+ * Save to source file via API
+ */
+ private async saveToSource(update: UpdateState): Promise {
+ const response = await fetch('/__appdev_design_mode/update', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ filePath: update.sourceInfo.fileName,
+ line: update.sourceInfo.lineNumber,
+ column: update.sourceInfo.columnNumber,
+ newValue: update.newValue,
+ originalValue: update.oldValue,
+ type: (update.operation === 'style_update' || update.operation === 'class_update') ? 'style' : 'content',
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to save source: ${response.statusText}`);
+ }
+
+ return await response.json();
+ }
+
+ /**
+ * Validate update before processing
+ */
+ private validateUpdate(update: UpdateState): boolean {
+ // Validate source mapping
+ if (this.config.validation.validateSource) {
+ if (
+ !update.sourceInfo.fileName ||
+ update.sourceInfo.lineNumber === null ||
+ update.sourceInfo.columnNumber === null
+ ) {
+ return false;
+ }
+ }
+
+ // Validate value
+ if (this.config.validation.validateValue) {
+ if (update.newValue.length > this.config.validation.maxLength) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if update is processing
+ */
+ public isProcessing(updateId: string): boolean {
+ return this.processingUpdates.has(updateId);
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.d.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.d.ts
new file mode 100644
index 00000000..ae904779
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.d.ts
@@ -0,0 +1,37 @@
+/**
+ * Resolved `data-*` names for source mapping (prefix from global config or meta tag).
+ */
+/** e.g. `data-xagi` */
+export declare function getPrefix(): string;
+/** Call after changing `window.__APPDEV_DESIGN_MODE_CONFIG__` in tests. */
+export declare function resetPrefixCache(): void;
+/** `{prefix}-{suffix}` */
+export declare function getAttributeName(suffix: string): string;
+/** Lazy getters so prefix changes propagate */
+export declare const AttributeNames: {
+ readonly file: string;
+ readonly line: string;
+ readonly column: string;
+ readonly info: string;
+ readonly elementId: string;
+ readonly component: string;
+ readonly function: string;
+ readonly position: string;
+ readonly staticContent: string;
+ /**
+ * Get the children source attribute name (for tracking where static children come from)
+ * Example: data-xagi-children-source
+ */
+ readonly childrenSource: string;
+ readonly contextMenu: string;
+ readonly contextMenuHover: string;
+ readonly import: string;
+ /**
+ * Get the static-class attribute name (for tracking if className is a pure static string)
+ * Example: data-xagi-static-class
+ */
+ readonly staticClass: string;
+};
+/** True for any `{prefix}-*` */
+export declare function isSourceMappingAttribute(attributeName: string): boolean;
+//# sourceMappingURL=attributeNames.d.ts.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.d.ts.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.d.ts.map
new file mode 100644
index 00000000..77884a91
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"attributeNames.d.ts","sourceRoot":"","sources":["attributeNames.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0BH,uBAAuB;AACvB,wBAAgB,SAAS,IAAI,MAAM,CAKlC;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,0BAA0B;AAC1B,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,+CAA+C;AAC/C,eAAO,MAAM,cAAc;;;;;;;;;;IA4BzB;;;OAGG;;;;;IAaH;;;OAGG;;CAIK,CAAC;AAEX,gCAAgC;AAChC,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAGvE"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.js b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.js
new file mode 100644
index 00000000..fa080428
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.js
@@ -0,0 +1,97 @@
+/**
+ * Resolved `data-*` names for source mapping (prefix from global config or meta tag).
+ */
+const DEFAULT_PREFIX = 'data-xagi';
+function getAttributePrefix() {
+ if (typeof window !== 'undefined') {
+ const globalConfig = window.__APPDEV_DESIGN_MODE_CONFIG__;
+ if (globalConfig?.attributePrefix) {
+ return globalConfig.attributePrefix;
+ }
+ const metaTag = document.querySelector('meta[name="appdev-design-mode:attribute-prefix"]');
+ if (metaTag) {
+ const prefix = metaTag.getAttribute('content');
+ if (prefix) {
+ return prefix;
+ }
+ }
+ }
+ return DEFAULT_PREFIX;
+}
+/** Cached prefix */
+let cachedPrefix = null;
+/** e.g. `data-xagi` */
+export function getPrefix() {
+ if (cachedPrefix === null) {
+ cachedPrefix = getAttributePrefix();
+ }
+ return cachedPrefix;
+}
+/** Call after changing `window.__APPDEV_DESIGN_MODE_CONFIG__` in tests. */
+export function resetPrefixCache() {
+ cachedPrefix = null;
+}
+/** `{prefix}-{suffix}` */
+export function getAttributeName(suffix) {
+ const prefix = getPrefix();
+ return `${prefix}-${suffix}`;
+}
+/** Lazy getters so prefix changes propagate */
+export const AttributeNames = {
+ get file() {
+ return getAttributeName('file');
+ },
+ get line() {
+ return getAttributeName('line');
+ },
+ get column() {
+ return getAttributeName('column');
+ },
+ get info() {
+ return getAttributeName('info');
+ },
+ get elementId() {
+ return getAttributeName('element-id');
+ },
+ get component() {
+ return getAttributeName('component');
+ },
+ get function() {
+ return getAttributeName('function');
+ },
+ get position() {
+ return getAttributeName('position');
+ },
+ get staticContent() {
+ return getAttributeName('static-content');
+ },
+ /**
+ * Get the children source attribute name (for tracking where static children come from)
+ * Example: data-xagi-children-source
+ */
+ get childrenSource() {
+ return getAttributeName('children-source');
+ },
+ get contextMenu() {
+ return getAttributeName('context-menu');
+ },
+ get contextMenuHover() {
+ return getAttributeName('context-menu-hover');
+ },
+ get import() {
+ return getAttributeName('import');
+ },
+ /**
+ * Get the static-class attribute name (for tracking if className is a pure static string)
+ * Example: data-xagi-static-class
+ */
+ get staticClass() {
+ return getAttributeName('static-class');
+ },
+};
+/** True for any `{prefix}-*` */
+export function isSourceMappingAttribute(attributeName) {
+ const prefix = getPrefix();
+ return attributeName.startsWith(`${prefix}-`);
+}
+//# sourceMappingURL=attributeNames.js.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.js.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.js.map
new file mode 100644
index 00000000..4c78d4fd
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"attributeNames.js","sourceRoot":"","sources":["attributeNames.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,cAAc,GAAG,WAAW,CAAC;AAEnC,SAAS,kBAAkB;IACzB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,MAAM,YAAY,GAAI,MAAc,CAAC,6BAA6B,CAAC;QACnE,IAAI,YAAY,EAAE,eAAe,EAAE,CAAC;YAClC,OAAO,YAAY,CAAC,eAAe,CAAC;QACtC,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,kDAAkD,CAAC,CAAC;QAC3F,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;YAC/C,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,oBAAoB;AACpB,IAAI,YAAY,GAAkB,IAAI,CAAC;AAEvC,uBAAuB;AACvB,MAAM,UAAU,SAAS;IACvB,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,YAAY,GAAG,kBAAkB,EAAE,CAAC;IACtC,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB;IAC9B,YAAY,GAAG,IAAI,CAAC;AACtB,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED,+CAA+C;AAC/C,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,IAAI;QACN,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,IAAI;QACN,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,MAAM;QACR,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,IAAI;QACN,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,SAAS;QACX,OAAO,gBAAgB,CAAC,YAAY,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,SAAS;QACX,OAAO,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,aAAa;QACf,OAAO,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IAC5C,CAAC;IACD;;;OAGG;IACH,IAAI,cAAc;QAChB,OAAO,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,WAAW;QACb,OAAO,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,gBAAgB;QAClB,OAAO,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,MAAM;QACR,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IACD;;;OAGG;IACH,IAAI,WAAW;QACb,OAAO,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAC1C,CAAC;CACO,CAAC;AAEX,gCAAgC;AAChC,MAAM,UAAU,wBAAwB,CAAC,aAAqB;IAC5D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,aAAa,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;AAChD,CAAC"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.ts
new file mode 100644
index 00000000..5af864c0
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/attributeNames.ts
@@ -0,0 +1,107 @@
+/**
+ * Resolved `data-*` names for source mapping (prefix from global config or meta tag).
+ */
+
+const DEFAULT_PREFIX = 'data-xagi';
+
+function getAttributePrefix(): string {
+ if (typeof window !== 'undefined') {
+ const globalConfig = (window as any).__APPDEV_DESIGN_MODE_CONFIG__;
+ if (globalConfig?.attributePrefix) {
+ return globalConfig.attributePrefix;
+ }
+
+ const metaTag = document.querySelector('meta[name="appdev-design-mode:attribute-prefix"]');
+ if (metaTag) {
+ const prefix = metaTag.getAttribute('content');
+ if (prefix) {
+ return prefix;
+ }
+ }
+ }
+
+ return DEFAULT_PREFIX;
+}
+
+/** Cached prefix */
+let cachedPrefix: string | null = null;
+
+/** e.g. `data-xagi` */
+export function getPrefix(): string {
+ if (cachedPrefix === null) {
+ cachedPrefix = getAttributePrefix();
+ }
+ return cachedPrefix;
+}
+
+/** Call after changing `window.__APPDEV_DESIGN_MODE_CONFIG__` in tests. */
+export function resetPrefixCache(): void {
+ cachedPrefix = null;
+}
+
+/** `{prefix}-{suffix}` */
+export function getAttributeName(suffix: string): string {
+ const prefix = getPrefix();
+ return `${prefix}-${suffix}`;
+}
+
+/** Lazy getters so prefix changes propagate */
+export const AttributeNames = {
+ get file() {
+ return getAttributeName('file');
+ },
+ get line() {
+ return getAttributeName('line');
+ },
+ get column() {
+ return getAttributeName('column');
+ },
+ get info() {
+ return getAttributeName('info');
+ },
+ get elementId() {
+ return getAttributeName('element-id');
+ },
+ get component() {
+ return getAttributeName('component');
+ },
+ get function() {
+ return getAttributeName('function');
+ },
+ get position() {
+ return getAttributeName('position');
+ },
+ get staticContent() {
+ return getAttributeName('static-content');
+ },
+ /**
+ * Get the children source attribute name (for tracking where static children come from)
+ * Example: data-xagi-children-source
+ */
+ get childrenSource() {
+ return getAttributeName('children-source');
+ },
+ get contextMenu() {
+ return getAttributeName('context-menu');
+ },
+ get contextMenuHover() {
+ return getAttributeName('context-menu-hover');
+ },
+ get import() {
+ return getAttributeName('import');
+ },
+ /**
+ * Get the static-class attribute name (for tracking if className is a pure static string)
+ * Example: data-xagi-static-class
+ */
+ get staticClass() {
+ return getAttributeName('static-class');
+ },
+} as const;
+
+/** True for any `{prefix}-*` */
+export function isSourceMappingAttribute(attributeName: string): boolean {
+ const prefix = getPrefix();
+ return attributeName.startsWith(`${prefix}-`);
+}
+
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.d.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.d.ts
new file mode 100644
index 00000000..d3ca0450
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.d.ts
@@ -0,0 +1,97 @@
+import { DesignModeMessage, MessageValidator, BridgeInterface, BridgeConfig, MessageUtilsInterface, MessageValidationResult } from './messages';
+/**
+ * postMessage bridge: optional request/response, validation hooks, heartbeats.
+ */
+export declare class EnhancedBridge implements BridgeInterface {
+ private listeners;
+ private pendingRequests;
+ private config;
+ private _isConnected;
+ private lastHeartbeat;
+ private heartbeatTimer?;
+ private connectionCheckTimer?;
+ constructor(config?: Partial);
+ /** Listen for `message` and mark connected after a short delay (iframe vs top). */
+ private initializeMessageHandling;
+ /** Notify parent that the child iframe bridge is ready. */
+ private sendReadyMessage;
+ /**
+ * Periodic HEARTBEAT posts when running inside an iframe.
+ */
+ private initializeHeartbeat;
+ /** Periodically infer disconnect from stale heartbeats. */
+ private initializeConnectionCheck;
+ /** True when `window.self !== window.top`. */
+ private isIframeEnvironment;
+ /** Snapshot for diagnostics / health. */
+ getEnvironmentInfo(): {
+ isIframe: boolean;
+ isConnected: boolean;
+ origin: string;
+ userAgent: string;
+ location: string;
+ };
+ /** Log bridge state to the console (dev aid). */
+ diagnose(): void;
+ /** Parent when iframe; `window` when top-level. */
+ private getTargetWindow;
+ /** Route incoming postMessage payloads. */
+ private handleMessage;
+ /** Resolve or reject pendingRequests entry. */
+ private handleResponseMessage;
+ /** Shape check before dispatch. */
+ private isValidMessage;
+ /** Known DesignModeMessage union tags. */
+ private isSupportedMessageType;
+ /** ACK or carries requestId. */
+ private isResponseMessage;
+ /**
+ * Fire-and-forget postMessage (may no-op if not connected).
+ */
+ send(message: T): Promise;
+ /** RPC-style: wait until matching response or timeout. */
+ sendWithResponse(message: T, responseType: R['type']): Promise;
+ /** Ensure requestId + timestamp exist. */
+ private enhanceMessage;
+ /** Unique id for correlating responses. */
+ private generateRequestId;
+ /** ms since epoch */
+ private createTimestamp;
+ /** Best-effort ACK (messageType not wired through yet). */
+ private sendAcknowledgement;
+ /** HEARTBEAT post to parent. */
+ private sendHeartbeat;
+ /** Mark disconnected if no heartbeats for 3× interval. */
+ private checkConnection;
+ /** Subscribe; returns unsubscribe. */
+ on(type: T['type'], handler: (message: T) => void): () => void;
+ /** Remove one handler for `type`. */
+ off(type: string, handler: Function): void;
+ /** Invoke all listeners for message.type */
+ private dispatchMessage;
+ /** `_isConnected` flag */
+ isConnected(): boolean;
+ /** Same as `isConnected` (legacy name). */
+ isConnectedToTarget(): boolean;
+ /** Clear timers, reject pendings, remove listener. */
+ disconnect(): void;
+ /** Summarize connectivity; may issue HEALTH_CHECK RPC in iframe. */
+ healthCheck(): Promise<{
+ status: string;
+ details: any;
+ }>;
+ /** Guarded by config.debug */
+ private log;
+ /** Alias for disconnect(). */
+ destroy(): void;
+}
+/** Small helpers for request ids / timestamps. */
+export declare const MessageUtils: MessageUtilsInterface;
+/** Singleton used by the design-mode client bundle. */
+export declare const bridge: EnhancedBridge;
+/** Structural validation for a subset of message types. */
+export declare class MessageValidatorImpl implements MessageValidator {
+ validate(message: any): MessageValidationResult;
+}
+export declare const messageValidator: MessageValidatorImpl;
+//# sourceMappingURL=bridge.d.ts.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.d.ts.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.d.ts.map
new file mode 100644
index 00000000..5240a054
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EAIjB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,qBAAqB,EACrB,uBAAuB,EAKxB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,qBAAa,cAAe,YAAW,eAAe;IACpD,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,eAAe,CAKR;IAEf,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,oBAAoB,CAAC,CAAiB;gBAElC,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM;IAe9C,mFAAmF;IACnF,OAAO,CAAC,yBAAyB;IAoBjC,2DAA2D;IAC3D,OAAO,CAAC,gBAAgB;IAkBxB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B,2DAA2D;IAC3D,OAAO,CAAC,yBAAyB;IAQjC,8CAA8C;IAC9C,OAAO,CAAC,mBAAmB;IAI3B,yCAAyC;IAClC,kBAAkB,IAAI;QAC3B,QAAQ,EAAE,OAAO,CAAC;QAClB,WAAW,EAAE,OAAO,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;KAClB;IAoBD,iDAAiD;IAC1C,QAAQ,IAAI,IAAI;IAYvB,mDAAmD;IACnD,OAAO,CAAC,eAAe;IAIvB,2CAA2C;IAC3C,OAAO,CAAC,aAAa;IAoCrB,+CAA+C;IAC/C,OAAO,CAAC,qBAAqB;IAgB7B,mCAAmC;IACnC,OAAO,CAAC,cAAc;IAQtB,0CAA0C;IAC1C,OAAO,CAAC,sBAAsB;IAa9B,gCAAgC;IAChC,OAAO,CAAC,iBAAiB;IAOzB;;OAEG;IACU,IAAI,CAAC,CAAC,SAAS,iBAAiB,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCzE,0DAA0D;IAC7C,gBAAgB,CAAC,CAAC,SAAS,iBAAiB,EAAE,CAAC,SAAS,iBAAiB,EACpF,OAAO,EAAE,CAAC,EACV,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,GACtB,OAAO,CAAC,CAAC,CAAC;IAkCb,0CAA0C;IAC1C,OAAO,CAAC,cAAc;IAWtB,2CAA2C;IAC3C,OAAO,CAAC,iBAAiB;IAIzB,qBAAqB;IACrB,OAAO,CAAC,eAAe;IAIvB,2DAA2D;IAC3D,OAAO,CAAC,mBAAmB;IAa3B,gCAAgC;IAChC,OAAO,CAAC,aAAa;IAarB,0DAA0D;IACzD,OAAO,CAAC,eAAe;IAgBxB,sCAAsC;IAC/B,EAAE,CAAC,CAAC,SAAS,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAalG,qCAAqC;IAC9B,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI;IAIjD,4CAA4C;IAC5C,OAAO,CAAC,eAAe;IAavB,0BAA0B;IACnB,WAAW,IAAI,OAAO;IAI7B,2CAA2C;IACpC,mBAAmB,IAAI,OAAO;IAIrC,sDAAsD;IAC/C,UAAU,IAAI,IAAI;IA2BzB,oEAAoE;IACvD,WAAW,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC;IAiErE,8BAA8B;IAC9B,OAAO,CAAC,GAAG;IAOX,8BAA8B;IACvB,OAAO,IAAI,IAAI;CAGvB;AAED,kDAAkD;AAClD,eAAO,MAAM,YAAY,EAAE,qBA4B1B,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,MAAM,gBAAuB,CAAC;AAE3C,2DAA2D;AAC3D,qBAAa,oBAAqB,YAAW,gBAAgB;IAC3D,QAAQ,CAAC,OAAO,EAAE,GAAG,GAAG,uBAAuB;CA2ChD;AAED,eAAO,MAAM,gBAAgB,sBAA6B,CAAC"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.js b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.js
new file mode 100644
index 00000000..3cdcb946
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.js
@@ -0,0 +1,503 @@
+/**
+ * postMessage bridge: optional request/response, validation hooks, heartbeats.
+ */
+export class EnhancedBridge {
+ constructor(config = {}) {
+ this.listeners = new Map();
+ this.pendingRequests = new Map();
+ this._isConnected = false;
+ this.lastHeartbeat = 0;
+ this.config = {
+ timeout: 10000,
+ retryAttempts: 3,
+ retryDelay: 1000,
+ heartbeatInterval: 30000,
+ debug: false,
+ ...config
+ };
+ this.initializeMessageHandling();
+ this.initializeHeartbeat();
+ this.initializeConnectionCheck();
+ }
+ /** Listen for `message` and mark connected after a short delay (iframe vs top). */
+ initializeMessageHandling() {
+ if (typeof window === 'undefined')
+ return;
+ window.addEventListener('message', this.handleMessage.bind(this));
+ if (this.isIframeEnvironment()) {
+ setTimeout(() => {
+ this._isConnected = true;
+ this.log('Bridge initialized and connected (iframe mode)');
+ this.sendReadyMessage();
+ }, 200);
+ }
+ else {
+ setTimeout(() => {
+ this._isConnected = true;
+ this.log('Bridge initialized and connected (main window mode)');
+ }, 100);
+ }
+ }
+ /** Notify parent that the child iframe bridge is ready. */
+ sendReadyMessage() {
+ try {
+ const readyMessage = {
+ type: 'BRIDGE_READY',
+ payload: {
+ timestamp: this.createTimestamp(),
+ environment: 'iframe'
+ },
+ timestamp: this.createTimestamp()
+ };
+ this.getTargetWindow().postMessage(readyMessage, '*');
+ this.log('Sent ready message to parent');
+ }
+ catch (error) {
+ this.log('Failed to send ready message:', error);
+ }
+ }
+ /**
+ * Periodic HEARTBEAT posts when running inside an iframe.
+ */
+ initializeHeartbeat() {
+ if (typeof window === 'undefined')
+ return;
+ if (this.isIframeEnvironment()) {
+ this.heartbeatTimer = setInterval(() => {
+ this.sendHeartbeat();
+ }, this.config.heartbeatInterval);
+ }
+ }
+ /** Periodically infer disconnect from stale heartbeats. */
+ initializeConnectionCheck() {
+ if (typeof window === 'undefined')
+ return;
+ this.connectionCheckTimer = setInterval(() => {
+ this.checkConnection();
+ }, this.config.heartbeatInterval * 2);
+ }
+ /** True when `window.self !== window.top`. */
+ isIframeEnvironment() {
+ return typeof window !== 'undefined' && window.self !== window.top;
+ }
+ /** Snapshot for diagnostics / health. */
+ getEnvironmentInfo() {
+ if (typeof window === 'undefined') {
+ return {
+ isIframe: false,
+ isConnected: false,
+ origin: '',
+ userAgent: '',
+ location: ''
+ };
+ }
+ return {
+ isIframe: this.isIframeEnvironment(),
+ isConnected: this._isConnected,
+ origin: window.location.origin,
+ userAgent: navigator.userAgent.substring(0, 100),
+ location: window.location.href
+ };
+ }
+ /** Log bridge state to the console (dev aid). */
+ diagnose() {
+ const env = this.getEnvironmentInfo();
+ console.group('[EnhancedBridge] Bridge Diagnosis');
+ console.log('Environment:', env);
+ console.log('Connection Status:', this._isConnected);
+ console.log('Pending Requests:', this.pendingRequests.size);
+ console.log('Message Listeners:', Array.from(this.listeners.keys()));
+ console.log('Config:', this.config);
+ console.groupEnd();
+ }
+ /** Parent when iframe; `window` when top-level. */
+ getTargetWindow() {
+ return this.isIframeEnvironment() ? window.parent : window;
+ }
+ /** Route incoming postMessage payloads. */
+ handleMessage(event) {
+ // Optional origin filter (disabled by default):
+ // if (event.origin !== window.location.origin && window.location.origin !== 'null') {
+ // if (!event.origin.startsWith('http') && !event.origin.startsWith('https')) {
+ // this.log('Received message from different origin, allowing:', event.origin);
+ // } else {
+ // this.log('Skipping message from different origin:', event.origin);
+ // return;
+ // }
+ // }
+ const message = event.data;
+ // Drop malformed payloads
+ if (!this.isValidMessage(message)) {
+ this.log('Invalid message received:', message);
+ return;
+ }
+ // Child handshake
+ if (message.type === 'BRIDGE_READY') {
+ this.log('Received ready message from child');
+ this._isConnected = true;
+ return;
+ }
+ // Completes a pending sendWithResponse
+ if (this.isResponseMessage(message)) {
+ this.handleResponseMessage(message);
+ return;
+ }
+ // Fan-out to on(type) handlers
+ this.dispatchMessage(message);
+ }
+ /** Resolve or reject pendingRequests entry. */
+ handleResponseMessage(message) {
+ const { requestId } = message;
+ if (this.pendingRequests.has(requestId)) {
+ const request = this.pendingRequests.get(requestId);
+ clearTimeout(request.timeout);
+ this.pendingRequests.delete(requestId);
+ if (message.type === 'ACKNOWLEDGEMENT') {
+ request.resolve({ success: true, acknowledged: true });
+ }
+ else {
+ request.resolve(message);
+ }
+ }
+ }
+ /** Shape check before dispatch. */
+ isValidMessage(message) {
+ return (message &&
+ typeof message.type === 'string' &&
+ this.isSupportedMessageType(message.type));
+ }
+ /** Known DesignModeMessage union tags. */
+ isSupportedMessageType(type) {
+ const supportedTypes = [
+ // Iframe to Parent
+ 'ELEMENT_SELECTED', 'ELEMENT_DESELECTED', 'CONTENT_UPDATED', 'STYLE_UPDATED',
+ 'DESIGN_MODE_CHANGED', 'ELEMENT_STATE_RESPONSE', 'ERROR', 'ACKNOWLEDGEMENT',
+ 'HEARTBEAT', 'HEALTH_CHECK_RESPONSE', 'BRIDGE_READY', 'ADD_TO_CHAT',
+ // Parent to Iframe
+ 'TOGGLE_DESIGN_MODE', 'UPDATE_STYLE', 'UPDATE_CONTENT', 'BATCH_UPDATE',
+ 'HEALTH_CHECK'
+ ];
+ return supportedTypes.includes(type);
+ }
+ /** ACK or carries requestId. */
+ isResponseMessage(message) {
+ return message &&
+ (message.type === 'ACKNOWLEDGEMENT' ||
+ message.requestId !== undefined) &&
+ (message.timestamp !== undefined);
+ }
+ /**
+ * Fire-and-forget postMessage (may no-op if not connected).
+ */
+ async send(message) {
+ if (!this._isConnected && this.isIframeEnvironment()) {
+ this.log('Bridge not connected, attempting to reconnect...');
+ await new Promise(resolve => setTimeout(resolve, 100));
+ if (!this._isConnected) {
+ console.warn('[EnhancedBridge] Bridge still not connected, message will be queued');
+ return;
+ }
+ }
+ if (!this._isConnected && !this.isIframeEnvironment()) {
+ this.log('Running in main window, bridge connection not applicable');
+ return;
+ }
+ const enhancedMessage = this.enhanceMessage(message);
+ try {
+ this.log('Sending message:', enhancedMessage);
+ this.getTargetWindow().postMessage(enhancedMessage, '*');
+ if (this.isIframeEnvironment() && enhancedMessage.requestId) {
+ this.sendAcknowledgement(enhancedMessage.requestId);
+ }
+ }
+ catch (error) {
+ this.log('Error sending message:', error);
+ // Dev: surface send failures
+ if (process.env.NODE_ENV === 'development') {
+ throw error;
+ }
+ }
+ }
+ /** RPC-style: wait until matching response or timeout. */
+ async sendWithResponse(message, responseType) {
+ if (!this._isConnected) {
+ throw new Error('Bridge is not connected');
+ }
+ const enhancedMessage = this.enhanceMessage(message);
+ const requestId = enhancedMessage.requestId;
+ return new Promise((resolve, reject) => {
+ // Timeout
+ const timeout = setTimeout(() => {
+ this.pendingRequests.delete(requestId);
+ reject(new Error(`Request timeout after ${this.config.timeout}ms`));
+ }, this.config.timeout);
+ // Pending request map
+ this.pendingRequests.set(requestId, {
+ resolve,
+ reject,
+ timeout,
+ responseType
+ });
+ try {
+ this.log('Sending request with response:', enhancedMessage);
+ this.getTargetWindow().postMessage(enhancedMessage, '*');
+ }
+ catch (error) {
+ this.pendingRequests.delete(requestId);
+ clearTimeout(timeout);
+ reject(error);
+ }
+ });
+ }
+ /** Ensure requestId + timestamp exist. */
+ enhanceMessage(message) {
+ const requestId = message.requestId || this.generateRequestId();
+ const timestamp = message.timestamp || this.createTimestamp();
+ return {
+ ...message,
+ requestId,
+ timestamp
+ };
+ }
+ /** Unique id for correlating responses. */
+ generateRequestId() {
+ return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+ }
+ /** ms since epoch */
+ createTimestamp() {
+ return Date.now();
+ }
+ /** Best-effort ACK (messageType not wired through yet). */
+ sendAcknowledgement(requestId) {
+ const acknowledgement = {
+ type: 'ACKNOWLEDGEMENT',
+ payload: {
+ messageType: 'UNKNOWN', // TODO: propagate originating type
+ },
+ requestId,
+ timestamp: this.createTimestamp()
+ };
+ this.getTargetWindow().postMessage(acknowledgement, '*');
+ }
+ /** HEARTBEAT post to parent. */
+ sendHeartbeat() {
+ const heartbeat = {
+ type: 'HEARTBEAT',
+ payload: {
+ timestamp: this.createTimestamp()
+ },
+ timestamp: this.createTimestamp()
+ };
+ this.getTargetWindow().postMessage(heartbeat, '*');
+ this.lastHeartbeat = Date.now();
+ }
+ /** Mark disconnected if no heartbeats for 3× interval. */
+ checkConnection() {
+ const now = Date.now();
+ const timeSinceLastHeartbeat = now - this.lastHeartbeat;
+ if (timeSinceLastHeartbeat > this.config.heartbeatInterval * 3) {
+ this.log('Connection appears to be lost');
+ this._isConnected = false;
+ // Optimistic reconnect
+ setTimeout(() => {
+ this._isConnected = true;
+ this.log('Connection restored');
+ }, 1000);
+ }
+ }
+ /** Subscribe; returns unsubscribe. */
+ on(type, handler) {
+ if (!this.listeners.has(type)) {
+ this.listeners.set(type, new Set());
+ }
+ this.listeners.get(type).add(handler);
+ // Unsubscribe
+ return () => {
+ this.listeners.get(type)?.delete(handler);
+ };
+ }
+ /** Remove one handler for `type`. */
+ off(type, handler) {
+ this.listeners.get(type)?.delete(handler);
+ }
+ /** Invoke all listeners for message.type */
+ dispatchMessage(message) {
+ const handlers = this.listeners.get(message.type);
+ if (handlers) {
+ handlers.forEach(handler => {
+ try {
+ handler(message);
+ }
+ catch (error) {
+ this.log('Error in message handler:', error);
+ }
+ });
+ }
+ }
+ /** `_isConnected` flag */
+ isConnected() {
+ return this._isConnected;
+ }
+ /** Same as `isConnected` (legacy name). */
+ isConnectedToTarget() {
+ return this._isConnected;
+ }
+ /** Clear timers, reject pendings, remove listener. */
+ disconnect() {
+ this._isConnected = false;
+ if (this.heartbeatTimer) {
+ clearInterval(this.heartbeatTimer);
+ }
+ if (this.connectionCheckTimer) {
+ clearInterval(this.connectionCheckTimer);
+ }
+ // Reject pending RPCs
+ this.pendingRequests.forEach(request => {
+ clearTimeout(request.timeout);
+ request.reject(new Error('Connection disconnected'));
+ });
+ this.pendingRequests.clear();
+ // Remove global listener
+ if (typeof window !== 'undefined') {
+ window.removeEventListener('message', this.handleMessage.bind(this));
+ }
+ this.listeners.clear();
+ this.log('Bridge disconnected');
+ }
+ /** Summarize connectivity; may issue HEALTH_CHECK RPC in iframe. */
+ async healthCheck() {
+ try {
+ const env = this.getEnvironmentInfo();
+ // Top-level window
+ if (!env.isIframe && this._isConnected) {
+ return {
+ status: 'healthy',
+ details: {
+ ...env,
+ message: 'Bridge is healthy and running in main window'
+ }
+ };
+ }
+ if (env.isIframe && this._isConnected) {
+ // Iframe: ping parent
+ try {
+ const response = await this.sendWithResponse({
+ type: 'HEALTH_CHECK',
+ requestId: '',
+ timestamp: this.createTimestamp()
+ }, 'HEALTH_CHECK_RESPONSE');
+ return {
+ status: 'healthy',
+ details: {
+ ...env,
+ response: response.payload,
+ message: 'Bridge is healthy and responsive'
+ }
+ };
+ }
+ catch (error) {
+ return {
+ status: 'degraded',
+ details: {
+ ...env,
+ error: error instanceof Error ? error.message : 'Unknown error',
+ message: 'Bridge is connected but not responding to health checks'
+ }
+ };
+ }
+ }
+ return {
+ status: env.isIframe ? 'connecting' : 'unnecessary',
+ details: {
+ ...env,
+ message: env.isIframe ? 'Bridge is initializing' : 'Bridge not needed in main window'
+ }
+ };
+ }
+ catch (error) {
+ return {
+ status: 'unhealthy',
+ details: {
+ error: error instanceof Error ? error.message : 'Unknown error',
+ environment: this.getEnvironmentInfo()
+ }
+ };
+ }
+ }
+ /** Guarded by config.debug */
+ log(...args) {
+ if (this.config.debug) {
+ const timestamp = new Date().toISOString();
+ console.log(`[${timestamp}] [EnhancedBridge]`, ...args);
+ }
+ }
+ /** Alias for disconnect(). */
+ destroy() {
+ this.disconnect();
+ }
+}
+/** Small helpers for request ids / timestamps. */
+export const MessageUtils = {
+ generateRequestId: () => {
+ return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+ },
+ createTimestamp: () => {
+ return Date.now();
+ },
+ isValidMessage: (message) => {
+ return message &&
+ typeof message.type === 'string' &&
+ typeof message.timestamp === 'number';
+ },
+ createResponse: function (originalMessage, payload, success = true, error) {
+ return {
+ payload,
+ type: payload?.type,
+ requestId: originalMessage.requestId || '',
+ timestamp: this.createTimestamp()
+ };
+ }
+};
+/** Singleton used by the design-mode client bundle. */
+export const bridge = new EnhancedBridge();
+/** Structural validation for a subset of message types. */
+export class MessageValidatorImpl {
+ validate(message) {
+ const errors = [];
+ if (!message) {
+ errors.push('Message is null or undefined');
+ return { isValid: false, errors };
+ }
+ if (typeof message.type !== 'string') {
+ errors.push('Message type must be a string');
+ }
+ if (typeof message.timestamp !== 'number') {
+ errors.push('Message timestamp must be a number');
+ }
+ // Per-type payload checks
+ switch (message.type) {
+ case 'ELEMENT_SELECTED':
+ if (!message.payload?.elementInfo) {
+ errors.push('ELEMENT_SELECTED must have elementInfo in payload');
+ }
+ break;
+ case 'UPDATE_STYLE':
+ case 'UPDATE_CONTENT':
+ if (!message.payload?.sourceInfo) {
+ errors.push(`${message.type} must have sourceInfo in payload`);
+ }
+ break;
+ case 'BATCH_UPDATE':
+ if (!Array.isArray(message.payload?.updates)) {
+ errors.push('BATCH_UPDATE must have updates array in payload');
+ }
+ break;
+ }
+ return {
+ isValid: errors.length === 0,
+ errors: errors.length > 0 ? errors : undefined
+ };
+ }
+}
+export const messageValidator = new MessageValidatorImpl();
+//# sourceMappingURL=bridge.js.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.js.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.js.map
new file mode 100644
index 00000000..1a657fb9
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"bridge.js","sourceRoot":"","sources":["bridge.ts"],"names":[],"mappings":"AAgBA;;GAEG;AACH,MAAM,OAAO,cAAc;IAezB,YAAY,SAAgC,EAAE;QAdtC,cAAS,GAA+B,IAAI,GAAG,EAAE,CAAC;QAClD,oBAAe,GAKlB,IAAI,GAAG,EAAE,CAAC;QAGP,iBAAY,GAAG,KAAK,CAAC;QACrB,kBAAa,GAAG,CAAC,CAAC;QAKxB,IAAI,CAAC,MAAM,GAAG;YACZ,OAAO,EAAE,KAAK;YACd,aAAa,EAAE,CAAC;YAChB,UAAU,EAAE,IAAI;YAChB,iBAAiB,EAAE,KAAK;YACxB,KAAK,EAAE,KAAK;YACZ,GAAG,MAAM;SACV,CAAC;QAEF,IAAI,CAAC,yBAAyB,EAAE,CAAC;QACjC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,yBAAyB,EAAE,CAAC;IACnC,CAAC;IAED,mFAAmF;IAC3E,yBAAyB;QAC/B,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAE1C,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAElE,IAAI,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;YAC/B,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;gBACzB,IAAI,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;gBAE3D,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,CAAC,EAAE,GAAG,CAAC,CAAC;QACV,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;gBACzB,IAAI,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;YAClE,CAAC,EAAE,GAAG,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,2DAA2D;IACnD,gBAAgB;QACtB,IAAI,CAAC;YACH,MAAM,YAAY,GAAG;gBACnB,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE;oBACP,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE;oBACjC,WAAW,EAAE,QAAQ;iBACtB;gBACD,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE;aAClC,CAAC;YAEF,IAAI,CAAC,eAAe,EAAE,CAAC,WAAW,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;YACtD,IAAI,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED;;OAEG;IACK,mBAAmB;QACzB,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAE1C,IAAI,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;YAC/B,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;gBACrC,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,2DAA2D;IACnD,yBAAyB;QAC/B,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAE1C,IAAI,CAAC,oBAAoB,GAAG,WAAW,CAAC,GAAG,EAAE;YAC3C,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,8CAA8C;IACtC,mBAAmB;QACzB,OAAO,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,CAAC;IACrE,CAAC;IAED,yCAAyC;IAClC,kBAAkB;QAOvB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,KAAK;gBAClB,MAAM,EAAE,EAAE;gBACV,SAAS,EAAE,EAAE;gBACb,QAAQ,EAAE,EAAE;aACb,CAAC;QACJ,CAAC;QAED,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE;YACpC,WAAW,EAAE,IAAI,CAAC,YAAY;YAC9B,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;YAC9B,SAAS,EAAE,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC;YAChD,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI;SAC/B,CAAC;IACJ,CAAC;IAED,iDAAiD;IAC1C,QAAQ;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAEtC,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC5D,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO,CAAC,QAAQ,EAAE,CAAC;IACrB,CAAC;IAED,mDAAmD;IAC3C,eAAe;QACrB,OAAO,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7D,CAAC;IAED,2CAA2C;IACnC,aAAa,CAAC,KAAmB;QACvC,gDAAgD;QAChD,sFAAsF;QACtF,iFAAiF;QACjF,mFAAmF;QACnF,aAAa;QACb,yEAAyE;QACzE,cAAc;QACd,MAAM;QACN,IAAI;QAEJ,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;QAE3B,0BAA0B;QAC1B,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,GAAG,CAAC,2BAA2B,EAAE,OAAO,CAAC,CAAC;YAC/C,OAAO;QACT,CAAC;QAED,kBAAkB;QAClB,IAAI,OAAO,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACpC,IAAI,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;YAC9C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,OAAO;QACT,CAAC;QAED,uCAAuC;QACvC,IAAI,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;YACpC,OAAO;QACT,CAAC;QAED,+BAA+B;QAC/B,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,+CAA+C;IACvC,qBAAqB,CAAC,OAA+B;QAC3D,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;QAE9B,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACxC,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC;YACrD,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC9B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAEvC,IAAI,OAAO,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBACvC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,mCAAmC;IAC3B,cAAc,CAAC,OAAY;QACjC,OAAO,CACL,OAAO;YACP,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;YAChC,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,IAAI,CAAC,CAC1C,CAAC;IACJ,CAAC;IAED,0CAA0C;IAClC,sBAAsB,CAAC,IAAY;QACzC,MAAM,cAAc,GAAG;YACrB,mBAAmB;YACnB,kBAAkB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,eAAe;YAC5E,qBAAqB,EAAE,wBAAwB,EAAE,OAAO,EAAE,iBAAiB;YAC3E,WAAW,EAAE,uBAAuB,EAAE,cAAc,EAAE,aAAa;YACnE,mBAAmB;YACnB,oBAAoB,EAAE,cAAc,EAAE,gBAAgB,EAAE,cAAc;YACtE,cAAc;SACf,CAAC;QACF,OAAO,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,gCAAgC;IACxB,iBAAiB,CAAC,OAAY;QACpC,OAAO,OAAO;YACZ,CAAC,OAAO,CAAC,IAAI,KAAK,iBAAiB;gBAClC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;YACjC,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;IACtC,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,IAAI,CAA8B,OAAU;QACvD,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;YACrD,IAAI,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;YAE7D,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAEvD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;gBACvB,OAAO,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;gBACpF,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;YACtD,IAAI,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;YACrE,OAAO;QACT,CAAC;QAED,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAErD,IAAI,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,eAAe,CAAC,CAAC;YAE9C,IAAI,CAAC,eAAe,EAAE,CAAC,WAAW,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;YAEzD,IAAI,IAAI,CAAC,mBAAmB,EAAE,IAAI,eAAe,CAAC,SAAS,EAAE,CAAC;gBAC5D,IAAI,CAAC,mBAAmB,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;YAE1C,6BAA6B;YAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;gBAC3C,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,0DAA0D;IACnD,KAAK,CAAC,gBAAgB,CAC3B,OAAU,EACV,YAAuB;QAEvB,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,eAAe,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,eAAe,CAAC,SAAU,CAAC;QAE7C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,UAAU;YACV,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC9B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACvC,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;YACtE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAExB,sBAAsB;YACtB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,EAAE;gBAClC,OAAO;gBACP,MAAM;gBACN,OAAO;gBACP,YAAY;aACb,CAAC,CAAC;YAEH,IAAI,CAAC;gBACH,IAAI,CAAC,GAAG,CAAC,gCAAgC,EAAE,eAAe,CAAC,CAAC;gBAC5D,IAAI,CAAC,eAAe,EAAE,CAAC,WAAW,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;YAC3D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACvC,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,0CAA0C;IAClC,cAAc,CAA8B,OAAU;QAC5D,MAAM,SAAS,GAAI,OAAe,CAAC,SAAS,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzE,MAAM,SAAS,GAAI,OAAe,CAAC,SAAS,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;QAEvE,OAAO;YACL,GAAG,OAAO;YACV,SAAS;YACT,SAAS;SACV,CAAC;IACJ,CAAC;IAED,2CAA2C;IACnC,iBAAiB;QACvB,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACxE,CAAC;IAED,qBAAqB;IACb,eAAe;QACrB,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IAED,2DAA2D;IACnD,mBAAmB,CAAC,SAAiB;QAC3C,MAAM,eAAe,GAA2B;YAC9C,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE;gBACP,WAAW,EAAE,SAAS,EAAE,mCAAmC;aAC5D;YACD,SAAS;YACT,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE;SAClC,CAAC;QAEF,IAAI,CAAC,eAAe,EAAE,CAAC,WAAW,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IAC3D,CAAC;IAED,gCAAgC;IACxB,aAAa;QACnB,MAAM,SAAS,GAAqB;YAClC,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE;gBACP,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE;aAClC;YACD,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE;SAClC,CAAC;QAEF,IAAI,CAAC,eAAe,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACnD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAClC,CAAC;IAED,0DAA0D;IACjD,eAAe;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,sBAAsB,GAAG,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC;QAExD,IAAI,sBAAsB,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,GAAG,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;YAC1C,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;YAE1B,uBAAuB;YACvB,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;gBACzB,IAAI,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;YAClC,CAAC,EAAE,IAAI,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAED,sCAAsC;IAC/B,EAAE,CAA8B,IAAe,EAAE,OAA6B;QACnF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QACtC,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAEvC,cAAc;QACd,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC,CAAC;IACJ,CAAC;IAED,qCAAqC;IAC9B,GAAG,CAAC,IAAY,EAAE,OAAiB;QACxC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,4CAA4C;IACpC,eAAe,CAAC,OAA0B;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;gBACzB,IAAI,CAAC;oBACH,OAAO,CAAC,OAAO,CAAC,CAAC;gBACnB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,CAAC,GAAG,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,0BAA0B;IACnB,WAAW;QAChB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,2CAA2C;IACpC,mBAAmB;QACxB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED,sDAAsD;IAC/C,UAAU;QACf,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAE1B,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACrC,CAAC;QAED,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC9B,aAAa,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC3C,CAAC;QAED,sBAAsB;QACtB,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;YACrC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAE7B,yBAAyB;QACzB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QAEvB,IAAI,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAClC,CAAC;IAED,oEAAoE;IAC7D,KAAK,CAAC,WAAW;QACtB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAEtC,mBAAmB;YACnB,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACvC,OAAO;oBACL,MAAM,EAAE,SAAS;oBACjB,OAAO,EAAE;wBACP,GAAG,GAAG;wBACN,OAAO,EAAE,8CAA8C;qBACxD;iBACF,CAAC;YACJ,CAAC;YAED,IAAI,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACtC,sBAAsB;gBACtB,IAAI,CAAC;oBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAC1C;wBACE,IAAI,EAAE,cAAc;wBACpB,SAAS,EAAE,EAAE;wBACb,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE;qBAClC,EACD,uBAAuB,CACxB,CAAC;oBAEF,OAAO;wBACL,MAAM,EAAE,SAAS;wBACjB,OAAO,EAAE;4BACP,GAAG,GAAG;4BACN,QAAQ,EAAE,QAAQ,CAAC,OAAO;4BAC1B,OAAO,EAAE,kCAAkC;yBAC5C;qBACF,CAAC;gBACJ,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO;wBACL,MAAM,EAAE,UAAU;wBAClB,OAAO,EAAE;4BACP,GAAG,GAAG;4BACN,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;4BAC/D,OAAO,EAAE,yDAAyD;yBACnE;qBACF,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,OAAO;gBACL,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa;gBACnD,OAAO,EAAE;oBACP,GAAG,GAAG;oBACN,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,kCAAkC;iBACtF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE;oBACP,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;oBAC/D,WAAW,EAAE,IAAI,CAAC,kBAAkB,EAAE;iBACvC;aACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,8BAA8B;IACtB,GAAG,CAAC,GAAG,IAAW;QACxB,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACtB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,IAAI,SAAS,oBAAoB,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,8BAA8B;IACvB,OAAO;QACZ,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;CACF;AAED,kDAAkD;AAClD,MAAM,CAAC,MAAM,YAAY,GAA0B;IACjD,iBAAiB,EAAE,GAAW,EAAE;QAC9B,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACxE,CAAC;IAED,eAAe,EAAE,GAAW,EAAE;QAC5B,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IAED,cAAc,EAAE,CAAC,OAAY,EAAW,EAAE;QACxC,OAAO,OAAO;YACP,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;YAChC,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ,CAAC;IAC/C,CAAC;IAED,cAAc,EAAE,UACd,eAAsC,EACtC,OAAmD,EACnD,UAAmB,IAAI,EACvB,KAAc;QAEd,OAAO;YACL,OAAO;YACP,IAAI,EAAG,OAAe,EAAE,IAAI;YAC5B,SAAS,EAAG,eAAuB,CAAC,SAAS,IAAI,EAAE;YACnD,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE;SAClB,CAAC;IACpB,CAAC;CACF,CAAC;AAEF,uDAAuD;AACvD,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;AAE3C,2DAA2D;AAC3D,MAAM,OAAO,oBAAoB;IAC/B,QAAQ,CAAC,OAAY;QACnB,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;YAC5C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QACpC,CAAC;QAED,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QACpD,CAAC;QAED,0BAA0B;QAC1B,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,KAAK,kBAAkB;gBACrB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC;oBAClC,MAAM,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;gBACnE,CAAC;gBACD,MAAM;YAER,KAAK,cAAc,CAAC;YACpB,KAAK,gBAAgB;gBACnB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;oBACjC,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,kCAAkC,CAAC,CAAC;gBACjE,CAAC;gBACD,MAAM;YAER,KAAK,cAAc;gBACjB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;oBAC7C,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;gBACjE,CAAC;gBACD,MAAM;QACV,CAAC;QAED,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;YAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;SAC/C,CAAC;IACJ,CAAC;CACF;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,oBAAoB,EAAE,CAAC"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.ts
new file mode 100644
index 00000000..b18e7961
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/bridge.ts
@@ -0,0 +1,651 @@
+import {
+ DesignModeMessage,
+ IframeToParentMessage,
+ ParentToIframeMessage,
+ RequestResponseMessage,
+ MessageValidator,
+ BridgeInterface,
+ BridgeConfig,
+ MessageUtilsInterface,
+ MessageValidationResult,
+ AcknowledgementMessage,
+ HealthCheckMessage,
+ HealthCheckResponseMessage,
+ HeartbeatMessage
+} from './messages';
+
+/**
+ * postMessage bridge: optional request/response, validation hooks, heartbeats.
+ */
+export class EnhancedBridge implements BridgeInterface {
+ private listeners: Map> = new Map();
+ private pendingRequests: Map = new Map();
+
+ private config: BridgeConfig;
+ private _isConnected = false;
+ private lastHeartbeat = 0;
+ private heartbeatTimer?: NodeJS.Timeout;
+ private connectionCheckTimer?: NodeJS.Timeout;
+
+ constructor(config: Partial = {}) {
+ this.config = {
+ timeout: 10000,
+ retryAttempts: 3,
+ retryDelay: 1000,
+ heartbeatInterval: 30000,
+ debug: false,
+ ...config
+ };
+
+ this.initializeMessageHandling();
+ this.initializeHeartbeat();
+ this.initializeConnectionCheck();
+ }
+
+ /** Listen for `message` and mark connected after a short delay (iframe vs top). */
+ private initializeMessageHandling() {
+ if (typeof window === 'undefined') return;
+
+ window.addEventListener('message', this.handleMessage.bind(this));
+ if (this.isIframeEnvironment()) {
+ setTimeout(() => {
+ this._isConnected = true;
+ this.log('Bridge initialized and connected (iframe mode)');
+
+ this.sendReadyMessage();
+ }, 200);
+ } else {
+ setTimeout(() => {
+ this._isConnected = true;
+ this.log('Bridge initialized and connected (main window mode)');
+ }, 100);
+ }
+ }
+
+ /** Notify parent that the child iframe bridge is ready. */
+ private sendReadyMessage() {
+ try {
+ const readyMessage = {
+ type: 'BRIDGE_READY',
+ payload: {
+ timestamp: this.createTimestamp(),
+ environment: 'iframe'
+ },
+ timestamp: this.createTimestamp()
+ };
+ this.getTargetWindow().postMessage(readyMessage, '*');
+ this.log('Sent ready message to parent');
+ } catch (error) {
+ this.log('Failed to send ready message:', error);
+ }
+ }
+
+ /**
+ * Periodic HEARTBEAT posts when running inside an iframe.
+ */
+ private initializeHeartbeat() {
+ if (typeof window === 'undefined') return;
+
+ if (this.isIframeEnvironment()) {
+ this.heartbeatTimer = setInterval(() => {
+ this.sendHeartbeat();
+ }, this.config.heartbeatInterval);
+ }
+ }
+
+ /** Periodically infer disconnect from stale heartbeats. */
+ private initializeConnectionCheck() {
+ if (typeof window === 'undefined') return;
+
+ this.connectionCheckTimer = setInterval(() => {
+ this.checkConnection();
+ }, this.config.heartbeatInterval * 2);
+ }
+
+ /** True when `window.self !== window.top`. */
+ private isIframeEnvironment(): boolean {
+ return typeof window !== 'undefined' && window.self !== window.top;
+ }
+
+ /** Snapshot for diagnostics / health. */
+ public getEnvironmentInfo(): {
+ isIframe: boolean;
+ isConnected: boolean;
+ origin: string;
+ userAgent: string;
+ location: string;
+ } {
+ if (typeof window === 'undefined') {
+ return {
+ isIframe: false,
+ isConnected: false,
+ origin: '',
+ userAgent: '',
+ location: ''
+ };
+ }
+
+ return {
+ isIframe: this.isIframeEnvironment(),
+ isConnected: this._isConnected,
+ origin: window.location.origin,
+ userAgent: navigator.userAgent.substring(0, 100),
+ location: window.location.href
+ };
+ }
+
+ /** Log bridge state to the console (dev aid). */
+ public diagnose(): void {
+ // Diagnose output is explicitly gated by debug mode to avoid noisy runtime logs.
+ if (!this.config.debug) return;
+
+ const env = this.getEnvironmentInfo();
+
+ console.group('[EnhancedBridge] Bridge Diagnosis');
+ console.log('Environment:', env);
+ console.log('Connection Status:', this._isConnected);
+ console.log('Pending Requests:', this.pendingRequests.size);
+ console.log('Message Listeners:', Array.from(this.listeners.keys()));
+ console.log('Config:', this.config);
+ console.groupEnd();
+ }
+
+ /** Parent when iframe; `window` when top-level. */
+ private getTargetWindow(): Window {
+ return this.isIframeEnvironment() ? window.parent : window;
+ }
+
+ /** Route incoming postMessage payloads. */
+ private handleMessage(event: MessageEvent) {
+ // Optional origin filter (disabled by default):
+ // if (event.origin !== window.location.origin && window.location.origin !== 'null') {
+ // if (!event.origin.startsWith('http') && !event.origin.startsWith('https')) {
+ // this.log('Received message from different origin, allowing:', event.origin);
+ // } else {
+ // this.log('Skipping message from different origin:', event.origin);
+ // return;
+ // }
+ // }
+
+ const message = event.data;
+ // Drop malformed payloads
+ if (!this.isValidMessage(message)) {
+ this.log('Invalid message received:', message);
+ return;
+ }
+
+ // Child handshake
+ if (message.type === 'BRIDGE_READY') {
+ this.log('Received ready message from child');
+ this._isConnected = true;
+ return;
+ }
+
+ // 父窗口 -> iframe 的控制类消息:必须优先走业务分发,不能误判为 RPC response。
+ // 否则像 TOGGLE_DESIGN_MODE 这类同样带 requestId/timestamp 的消息会被 isResponseMessage
+ // 吞掉,导致 bridge.on('TOGGLE_DESIGN_MODE') 永远不触发,父页面一直等不到 DESIGN_MODE_CHANGED。
+ if (this.isIframeEnvironment() && this.isParentToIframeCommand(message)) {
+ this.dispatchMessage(message);
+ return;
+ }
+
+ // Completes a pending sendWithResponse(仅处理真正的响应类型,避免与父到子命令冲突)
+ if (this.isResponseMessage(message)) {
+ this.handleResponseMessage(message);
+ return;
+ }
+
+ // Fan-out to on(type) handlers
+ this.dispatchMessage(message);
+ }
+
+ /** Resolve or reject pendingRequests entry. */
+ private handleResponseMessage(message: RequestResponseMessage) {
+ const { requestId } = message;
+
+ if (this.pendingRequests.has(requestId)) {
+ const request = this.pendingRequests.get(requestId)!;
+ clearTimeout(request.timeout);
+ this.pendingRequests.delete(requestId);
+
+ if (message.type === 'ACKNOWLEDGEMENT') {
+ request.resolve({ success: true, acknowledged: true });
+ } else {
+ request.resolve(message);
+ }
+ }
+ }
+
+ /** Shape check before dispatch. */
+ private isValidMessage(message: any): message is DesignModeMessage {
+ return (
+ message &&
+ typeof message.type === 'string' &&
+ this.isSupportedMessageType(message.type)
+ );
+ }
+
+ /** Known DesignModeMessage union tags. */
+ private isSupportedMessageType(type: string): boolean {
+ const supportedTypes = [
+ // Iframe to Parent
+ 'ELEMENT_SELECTED', 'ELEMENT_DESELECTED', 'CONTENT_UPDATED', 'STYLE_UPDATED',
+ 'DESIGN_MODE_CHANGED', 'ELEMENT_STATE_RESPONSE', 'ERROR', 'ACKNOWLEDGEMENT',
+ 'HEARTBEAT', 'HEALTH_CHECK_RESPONSE', 'BRIDGE_READY', 'ADD_TO_CHAT',
+ // Parent to Iframe
+ 'TOGGLE_DESIGN_MODE', 'UPDATE_STYLE', 'UPDATE_CONTENT', 'BATCH_UPDATE',
+ 'HEALTH_CHECK'
+ ];
+ return supportedTypes.includes(type);
+ }
+
+ /**
+ * 是否为 sendWithResponse / ACK 通道的响应消息。
+ * 注意:不能仅凭 requestId + timestamp 判断,否则父页面发来的 TOGGLE_DESIGN_MODE
+ *(同样带 requestId)会被误判为 response,从而跳过 dispatchMessage。
+ */
+ private isResponseMessage(message: any): message is RequestResponseMessage {
+ if (!message || typeof message.type !== 'string') {
+ return false;
+ }
+ if (message.requestId === undefined || message.timestamp === undefined) {
+ return false;
+ }
+ return (
+ message.type === 'ACKNOWLEDGEMENT' ||
+ message.type === 'HEALTH_CHECK_RESPONSE'
+ );
+ }
+
+ /** 父窗口发往 iframe 的控制命令(在 iframe 内必须由业务 handler 处理) */
+ private isParentToIframeCommand(message: DesignModeMessage): boolean {
+ const t = (message as { type?: string }).type;
+ return (
+ t === 'TOGGLE_DESIGN_MODE' ||
+ t === 'UPDATE_STYLE' ||
+ t === 'UPDATE_CONTENT' ||
+ t === 'BATCH_UPDATE' ||
+ t === 'HEALTH_CHECK'
+ );
+ }
+
+ /**
+ * Fire-and-forget postMessage (may no-op if not connected).
+ */
+ public async send(message: T): Promise {
+ if (!this._isConnected && this.isIframeEnvironment()) {
+ this.log('Bridge not connected, attempting to reconnect...');
+
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ if (!this._isConnected) return;
+ }
+
+ if (!this._isConnected && !this.isIframeEnvironment()) {
+ this.log('Running in main window, bridge connection not applicable');
+ return;
+ }
+
+ const enhancedMessage = this.enhanceMessage(message);
+ try {
+ this.log('Sending message:', enhancedMessage);
+
+ this.getTargetWindow().postMessage(enhancedMessage, '*');
+
+ if (this.isIframeEnvironment() && enhancedMessage.requestId) {
+ this.sendAcknowledgement(enhancedMessage.requestId);
+ }
+ } catch (error) {
+ this.log('Error sending message:', error);
+
+ // Dev: surface send failures
+ if (process.env.NODE_ENV === 'development') {
+ throw error;
+ }
+ }
+ }
+
+ /** RPC-style: wait until matching response or timeout. */
+ public async sendWithResponse(
+ message: T,
+ responseType: R['type']
+ ): Promise {
+ if (!this._isConnected) {
+ throw new Error('Bridge is not connected');
+ }
+
+ const enhancedMessage = this.enhanceMessage(message);
+ const requestId = enhancedMessage.requestId!;
+
+ return new Promise((resolve, reject) => {
+ // Timeout
+ const timeout = setTimeout(() => {
+ this.pendingRequests.delete(requestId);
+ reject(new Error(`Request timeout after ${this.config.timeout}ms`));
+ }, this.config.timeout);
+
+ // Pending request map
+ this.pendingRequests.set(requestId, {
+ resolve,
+ reject,
+ timeout,
+ responseType
+ });
+
+ try {
+ this.log('Sending request with response:', enhancedMessage);
+ this.getTargetWindow().postMessage(enhancedMessage, '*');
+ } catch (error) {
+ this.pendingRequests.delete(requestId);
+ clearTimeout(timeout);
+ reject(error);
+ }
+ });
+ }
+
+ /** Ensure requestId + timestamp exist. */
+ private enhanceMessage(message: T): T & { requestId?: string; timestamp: number } {
+ const requestId = (message as any).requestId || this.generateRequestId();
+ const timestamp = (message as any).timestamp || this.createTimestamp();
+
+ return {
+ ...message,
+ requestId,
+ timestamp
+ };
+ }
+
+ /** Unique id for correlating responses. */
+ private generateRequestId(): string {
+ return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+ }
+
+ /** ms since epoch */
+ private createTimestamp(): number {
+ return Date.now();
+ }
+
+ /** Best-effort ACK (messageType not wired through yet). */
+ private sendAcknowledgement(requestId: string) {
+ const acknowledgement: AcknowledgementMessage = {
+ type: 'ACKNOWLEDGEMENT',
+ payload: {
+ messageType: 'UNKNOWN', // TODO: propagate originating type
+ },
+ requestId,
+ timestamp: this.createTimestamp()
+ };
+
+ this.getTargetWindow().postMessage(acknowledgement, '*');
+ }
+
+ /** HEARTBEAT post to parent. */
+ private sendHeartbeat() {
+ const heartbeat: HeartbeatMessage = {
+ type: 'HEARTBEAT',
+ payload: {
+ timestamp: this.createTimestamp()
+ },
+ timestamp: this.createTimestamp()
+ };
+
+ this.getTargetWindow().postMessage(heartbeat, '*');
+ this.lastHeartbeat = Date.now();
+ }
+
+ /** Mark disconnected if no heartbeats for 3× interval. */
+ private checkConnection() {
+ const now = Date.now();
+ const timeSinceLastHeartbeat = now - this.lastHeartbeat;
+
+ if (timeSinceLastHeartbeat > this.config.heartbeatInterval * 3) {
+ this.log('Connection appears to be lost');
+ this._isConnected = false;
+
+ // Optimistic reconnect
+ setTimeout(() => {
+ this._isConnected = true;
+ this.log('Connection restored');
+ }, 1000);
+ }
+ }
+
+ /** Subscribe; returns unsubscribe. */
+ public on(type: T['type'], handler: (message: T) => void): () => void {
+ if (!this.listeners.has(type)) {
+ this.listeners.set(type, new Set());
+ }
+
+ this.listeners.get(type)!.add(handler);
+
+ // Unsubscribe
+ return () => {
+ this.listeners.get(type)?.delete(handler);
+ };
+ }
+
+ /** Remove one handler for `type`. */
+ public off(type: string, handler: Function): void {
+ this.listeners.get(type)?.delete(handler);
+ }
+
+ /** Invoke all listeners for message.type */
+ private dispatchMessage(message: DesignModeMessage) {
+ const handlers = this.listeners.get(message.type);
+ if (handlers) {
+ handlers.forEach(handler => {
+ try {
+ handler(message);
+ } catch (error) {
+ this.log('Error in message handler:', error);
+ }
+ });
+ }
+ }
+
+ /** `_isConnected` flag */
+ public isConnected(): boolean {
+ return this._isConnected;
+ }
+
+ /** Same as `isConnected` (legacy name). */
+ public isConnectedToTarget(): boolean {
+ return this._isConnected;
+ }
+
+ /** Clear timers, reject pendings, remove listener. */
+ public disconnect(): void {
+ this._isConnected = false;
+
+ if (this.heartbeatTimer) {
+ clearInterval(this.heartbeatTimer);
+ }
+
+ if (this.connectionCheckTimer) {
+ clearInterval(this.connectionCheckTimer);
+ }
+
+ // Reject pending RPCs
+ this.pendingRequests.forEach(request => {
+ clearTimeout(request.timeout);
+ request.reject(new Error('Connection disconnected'));
+ });
+ this.pendingRequests.clear();
+
+ // Remove global listener
+ if (typeof window !== 'undefined') {
+ window.removeEventListener('message', this.handleMessage.bind(this));
+ }
+ this.listeners.clear();
+
+ this.log('Bridge disconnected');
+ }
+
+ /** Summarize connectivity; may issue HEALTH_CHECK RPC in iframe. */
+ public async healthCheck(): Promise<{ status: string; details: any }> {
+ try {
+ const env = this.getEnvironmentInfo();
+
+ // Top-level window
+ if (!env.isIframe && this._isConnected) {
+ return {
+ status: 'healthy',
+ details: {
+ ...env,
+ message: 'Bridge is healthy and running in main window'
+ }
+ };
+ }
+
+ if (env.isIframe && this._isConnected) {
+ // Iframe: ping parent
+ try {
+ const response = await this.sendWithResponse(
+ {
+ type: 'HEALTH_CHECK',
+ requestId: '',
+ timestamp: this.createTimestamp()
+ },
+ 'HEALTH_CHECK_RESPONSE'
+ );
+
+ return {
+ status: 'healthy',
+ details: {
+ ...env,
+ response: response.payload,
+ message: 'Bridge is healthy and responsive'
+ }
+ };
+ } catch (error) {
+ return {
+ status: 'degraded',
+ details: {
+ ...env,
+ error: error instanceof Error ? error.message : 'Unknown error',
+ message: 'Bridge is connected but not responding to health checks'
+ }
+ };
+ }
+ }
+
+ return {
+ status: env.isIframe ? 'connecting' : 'unnecessary',
+ details: {
+ ...env,
+ message: env.isIframe ? 'Bridge is initializing' : 'Bridge not needed in main window'
+ }
+ };
+ } catch (error) {
+ return {
+ status: 'unhealthy',
+ details: {
+ error: error instanceof Error ? error.message : 'Unknown error',
+ environment: this.getEnvironmentInfo()
+ }
+ };
+ }
+ }
+
+ /** Guarded by config.debug */
+ private log(...args: any[]) {
+ // Keep debug logging capability for explicit troubleshooting sessions.
+ if (this.config.debug) {
+ const timestamp = new Date().toISOString();
+ console.log(`[${timestamp}] [EnhancedBridge]`, ...args);
+ }
+ }
+
+ /** Alias for disconnect(). */
+ public destroy(): void {
+ this.disconnect();
+ }
+}
+
+/** Small helpers for request ids / timestamps. */
+export const MessageUtils: MessageUtilsInterface = {
+ generateRequestId: (): string => {
+ return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+ },
+
+ createTimestamp: (): number => {
+ return Date.now();
+ },
+
+ isValidMessage: (message: any): boolean => {
+ return message &&
+ typeof message.type === 'string' &&
+ typeof message.timestamp === 'number';
+ },
+
+ createResponse: function(
+ originalMessage: ParentToIframeMessage,
+ payload: T extends { payload: infer P } ? P : never,
+ success: boolean = true,
+ error?: string
+ ): T {
+ return {
+ payload,
+ type: (payload as any)?.type,
+ requestId: (originalMessage as any).requestId || '',
+ timestamp: this.createTimestamp()
+ } as unknown as T;
+ }
+};
+
+/** Singleton used by the design-mode client bundle. */
+export const bridge = new EnhancedBridge();
+
+/** Structural validation for a subset of message types. */
+export class MessageValidatorImpl implements MessageValidator {
+ validate(message: any): MessageValidationResult {
+ const errors: string[] = [];
+
+ if (!message) {
+ errors.push('Message is null or undefined');
+ return { isValid: false, errors };
+ }
+
+ if (typeof message.type !== 'string') {
+ errors.push('Message type must be a string');
+ }
+
+ if (typeof message.timestamp !== 'number') {
+ errors.push('Message timestamp must be a number');
+ }
+
+ // Per-type payload checks
+ switch (message.type) {
+ case 'ELEMENT_SELECTED':
+ if (!message.payload?.elementInfo) {
+ errors.push('ELEMENT_SELECTED must have elementInfo in payload');
+ }
+ break;
+
+ case 'UPDATE_STYLE':
+ case 'UPDATE_CONTENT':
+ if (!message.payload?.sourceInfo) {
+ errors.push(`${message.type} must have sourceInfo in payload`);
+ }
+ break;
+
+ case 'BATCH_UPDATE':
+ if (!Array.isArray(message.payload?.updates)) {
+ errors.push('BATCH_UPDATE must have updates array in payload');
+ }
+ break;
+ }
+
+ return {
+ isValid: errors.length === 0,
+ errors: errors.length > 0 ? errors : undefined
+ };
+ }
+}
+
+export const messageValidator = new MessageValidatorImpl();
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.d.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.d.ts
new file mode 100644
index 00000000..17fb28e8
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.d.ts
@@ -0,0 +1,5 @@
+/**
+ * True when the element has only text child nodes (no element children).
+ */
+export declare function isPureStaticText(element: HTMLElement): boolean;
+//# sourceMappingURL=elementUtils.d.ts.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.d.ts.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.d.ts.map
new file mode 100644
index 00000000..049c0b0c
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"elementUtils.d.ts","sourceRoot":"","sources":["elementUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAa9D"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.js b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.js
new file mode 100644
index 00000000..c1116b0d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.js
@@ -0,0 +1,16 @@
+/**
+ * True when the element has only text child nodes (no element children).
+ */
+export function isPureStaticText(element) {
+ if (element.children.length > 0) {
+ return false;
+ }
+ for (let i = 0; i < element.childNodes.length; i++) {
+ const node = element.childNodes[i];
+ if (node.nodeType !== Node.TEXT_NODE) {
+ return false;
+ }
+ }
+ return true;
+}
+//# sourceMappingURL=elementUtils.js.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.js.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.js.map
new file mode 100644
index 00000000..5073d174
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"elementUtils.js","sourceRoot":"","sources":["elementUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAoB;IACnD,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;YACrC,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.ts
new file mode 100644
index 00000000..24390d90
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/elementUtils.ts
@@ -0,0 +1,17 @@
+/**
+ * True when the element has only text child nodes (no element children).
+ */
+export function isPureStaticText(element: HTMLElement): boolean {
+ if (element.children.length > 0) {
+ return false;
+ }
+
+ for (let i = 0; i < element.childNodes.length; i++) {
+ const node = element.childNodes[i];
+ if (node.nodeType !== Node.TEXT_NODE) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/index.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/index.ts
new file mode 100644
index 00000000..772a6ac4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/index.ts
@@ -0,0 +1,20 @@
+// Export all types
+export * from './types';
+export * from './messages';
+
+// Export attribute names utilities
+export * from './attributeNames';
+
+// Export bridge
+export * from './bridge';
+
+// Export source info utilities
+export * from './sourceInfo';
+export * from './sourceInfoResolver';
+
+// Export element utilities
+export * from './elementUtils';
+
+// Export services
+export * from './UpdateService';
+export * from './HistoryManager';
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.d.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.d.ts
new file mode 100644
index 00000000..139abaed
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.d.ts
@@ -0,0 +1,265 @@
+export interface SourceInfo {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+ elementType?: string;
+ componentName?: string;
+ functionName?: string;
+ elementId?: string;
+ importPath?: string;
+ isUIComponent?: boolean;
+}
+export interface ElementInfo {
+ tagName: string;
+ className: string;
+ textContent: string;
+ sourceInfo: SourceInfo;
+ isStaticText: boolean;
+ isStaticClass?: boolean;
+ componentName?: string;
+ componentPath?: string;
+ props?: Record;
+ hierarchy?: {
+ tagName: string;
+ componentName?: string;
+ fileName?: string;
+ }[];
+}
+export interface MessageValidationResult {
+ isValid: boolean;
+ errors?: string[];
+}
+export interface MessageValidator {
+ validate: (message: any) => MessageValidationResult;
+}
+export interface BridgeReadyMessage {
+ type: 'BRIDGE_READY';
+ payload: {
+ timestamp: number;
+ environment: 'iframe' | 'main';
+ };
+ timestamp?: number;
+ requestId?: string;
+}
+export interface ElementSelectedMessage {
+ type: 'ELEMENT_SELECTED';
+ payload: {
+ elementInfo: ElementInfo;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+export interface ElementDeselectedMessage {
+ type: 'ELEMENT_DESELECTED';
+ requestId?: string;
+ timestamp?: number;
+ payload?: null;
+}
+export interface ContentUpdatedMessage {
+ type: 'CONTENT_UPDATED';
+ payload: {
+ sourceInfo: SourceInfo;
+ oldValue: string;
+ newValue: string;
+ realtime?: boolean;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+export interface ContentUpdatedCallbackMessage {
+ type: 'CONTENT_UPDATED_CALLBACK';
+ payload: {
+ sourceInfo: SourceInfo;
+ oldValue: string;
+ newValue: string;
+ realtime?: boolean;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+export interface StyleUpdatedMessage {
+ type: 'STYLE_UPDATED';
+ payload: {
+ sourceInfo: SourceInfo;
+ oldClass: string;
+ newClass: string;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+export interface DesignModeChangedMessage {
+ type: 'DESIGN_MODE_CHANGED';
+ enabled: boolean;
+ requestId?: string;
+ timestamp?: number;
+}
+export interface ToggleDesignModeMessage {
+ type: 'TOGGLE_DESIGN_MODE';
+ enabled: boolean;
+ requestId?: string;
+ timestamp?: number;
+}
+export interface UpdateStyleMessage {
+ type: 'UPDATE_STYLE';
+ payload: {
+ sourceInfo: SourceInfo;
+ newClass: string;
+ persist?: boolean;
+ };
+ requestId?: string;
+ timestamp?: number;
+ enabled?: boolean;
+}
+export interface UpdateContentMessage {
+ type: 'UPDATE_CONTENT';
+ payload: {
+ sourceInfo: SourceInfo;
+ newContent: string;
+ persist?: boolean;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+export interface BatchUpdateItem {
+ type: 'style' | 'content';
+ sourceInfo: SourceInfo;
+ newValue: string;
+ originalValue?: string;
+}
+export interface BatchUpdateMessage {
+ type: 'BATCH_UPDATE';
+ payload: {
+ updates: BatchUpdateItem[];
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+export interface AddToChatMessage {
+ type: 'ADD_TO_CHAT';
+ payload: {
+ content: string;
+ context?: {
+ elementInfo?: ElementInfo;
+ sourceInfo?: SourceInfo;
+ };
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+export interface CopyElementMessage {
+ type: 'COPY_ELEMENT';
+ payload: {
+ elementInfo: {
+ tagName: string;
+ className: string;
+ content: string;
+ sourceInfo?: SourceInfo;
+ };
+ textContent: string;
+ success: boolean;
+ error?: string;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+export interface ErrorMessage {
+ type: 'ERROR';
+ payload: {
+ code: string;
+ message: string;
+ details?: any;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+export interface AcknowledgementMessage {
+ type: 'ACKNOWLEDGEMENT';
+ payload: {
+ messageType: string;
+ };
+ requestId: string;
+ timestamp?: number;
+}
+export interface HeartbeatMessage {
+ type: 'HEARTBEAT';
+ payload?: {
+ timestamp: number;
+ };
+ timestamp?: number;
+}
+export interface HealthCheckMessage {
+ type: 'HEALTH_CHECK';
+ requestId?: string;
+ timestamp?: number;
+}
+export interface HealthCheckResponseMessage {
+ type: 'HEALTH_CHECK_RESPONSE';
+ payload: {
+ status: 'healthy' | 'unhealthy' | 'degraded' | 'connecting' | 'unnecessary';
+ version?: string;
+ uptime?: number;
+ message?: string;
+ response?: any;
+ error?: string;
+ isIframe?: boolean;
+ isConnected?: boolean;
+ origin?: string;
+ userAgent?: string;
+ location?: string;
+ };
+ requestId: string;
+ timestamp?: number;
+}
+export type IframeToParentMessage = ElementSelectedMessage | ElementDeselectedMessage | ContentUpdatedMessage | StyleUpdatedMessage | DesignModeChangedMessage | ErrorMessage | AcknowledgementMessage | HeartbeatMessage | HealthCheckResponseMessage | BridgeReadyMessage | AddToChatMessage | CopyElementMessage | ContentUpdatedCallbackMessage;
+export type ParentToIframeMessage = ToggleDesignModeMessage | UpdateStyleMessage | UpdateContentMessage | BatchUpdateMessage | HealthCheckMessage | HeartbeatMessage;
+export type DesignModeMessage = IframeToParentMessage | ParentToIframeMessage;
+export type RequestMessage = ParentToIframeMessage & {
+ requestId: string;
+ timestamp: number;
+};
+export type ResponseMessage = IframeToParentMessage & {
+ requestId: string;
+ success?: boolean;
+ error?: string;
+ timestamp: number;
+};
+export type RequestResponseMessage = RequestMessage | ResponseMessage | AcknowledgementMessage;
+export interface MessageHandler {
+ type: T['type'];
+ handler: (message: T) => Promise | any;
+ validator?: MessageValidator;
+}
+export interface IframeModeConfig {
+ enabled: boolean;
+ hideUI: boolean;
+ enableSelection: boolean;
+ enableDirectEdit: boolean;
+}
+export interface BatchUpdateConfig {
+ enabled: boolean;
+ debounceMs: number;
+}
+export interface BridgeConfig {
+ timeout: number;
+ retryAttempts: number;
+ retryDelay: number;
+ heartbeatInterval: number;
+ debug: boolean;
+}
+export interface MessageUtilsInterface {
+ generateRequestId: () => string;
+ createTimestamp: () => number;
+ isValidMessage: (message: any) => boolean;
+ createResponse: (originalMessage: ParentToIframeMessage, payload: T extends {
+ payload: infer P;
+ } ? P : never, success?: boolean, error?: string) => T;
+}
+export interface BridgeInterface {
+ send: (message: T) => Promise;
+ sendWithResponse: (message: T, responseType: R['type']) => Promise;
+ on: (type: T['type'], handler: (message: T) => void) => void;
+ off: (type: string, handler: Function) => void;
+ isConnected: () => boolean;
+ disconnect: () => void;
+}
+//# sourceMappingURL=messages.d.ts.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.d.ts.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.d.ts.map
new file mode 100644
index 00000000..da8ebb31
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["messages.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,SAAS,CAAC,EAAE;QACV,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,EAAE,CAAC;CACL;AAGD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,uBAAuB,CAAC;CACrD;AAGD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,QAAQ,GAAG,MAAM,CAAC;KAChC,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE;QACP,WAAW,EAAE,WAAW,CAAC;KAC1B,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,iBAAiB,CAAC;IACxB,OAAO,EAAE;QACP,UAAU,EAAE,UAAU,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,0BAA0B,CAAC;IACjC,OAAO,EAAE;QACP,UAAU,EAAE,UAAU,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE;QACP,UAAU,EAAE,UAAU,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE;QACP,UAAU,EAAE,UAAU,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE;QACP,UAAU,EAAE,UAAU,CAAC;QACvB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE;QACP,OAAO,EAAE,eAAe,EAAE,CAAC;KAC5B,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE;YACR,WAAW,CAAC,EAAE,WAAW,CAAC;YAC1B,UAAU,CAAC,EAAE,UAAU,CAAC;SACzB,CAAC;KACH,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,MAAM,CAAC;YAChB,SAAS,EAAE,MAAM,CAAC;YAClB,OAAO,EAAE,MAAM,CAAC;YAChB,UAAU,CAAC,EAAE,UAAU,CAAC;SACzB,CAAC;QACF,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,GAAG,CAAC;KACf,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,iBAAiB,CAAC;IACxB,OAAO,EAAE;QACP,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,CAAC,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,OAAO,EAAE;QACP,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,aAAa,CAAC;QAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,GAAG,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,MAAM,qBAAqB,GAC7B,sBAAsB,GACtB,wBAAwB,GACxB,qBAAqB,GACrB,mBAAmB,GACnB,wBAAwB,GACxB,YAAY,GACZ,sBAAsB,GACtB,gBAAgB,GAChB,0BAA0B,GAC1B,kBAAkB,GAClB,gBAAgB,GAChB,kBAAkB,GAClB,6BAA6B,CAAC;AAElC,MAAM,MAAM,qBAAqB,GAC7B,uBAAuB,GACvB,kBAAkB,GAClB,oBAAoB,GACpB,kBAAkB,GAClB,kBAAkB,GAClB,gBAAgB,CAAC;AAErB,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,qBAAqB,CAAC;AAG9E,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,qBAAqB,GAAG;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,cAAc,GAAG,eAAe,GAAG,sBAAsB,CAAC;AAG/F,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,iBAAiB,GAAG,iBAAiB;IAC7E,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IAChB,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IAC5C,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAGD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,OAAO,CAAC;CAChB;AAGD,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,cAAc,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IAC1C,cAAc,EAAE,CAAC,CAAC,SAAS,qBAAqB,EAC9C,eAAe,EAAE,qBAAqB,EACtC,OAAO,EAAE,CAAC,SAAS;QAAE,OAAO,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,KAAK,EACnD,OAAO,CAAC,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,KACX,CAAC,CAAC;CACR;AAGD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,CAAC,CAAC,SAAS,iBAAiB,EAAE,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,gBAAgB,EAAE,CAAC,CAAC,SAAS,iBAAiB,EAAE,CAAC,SAAS,iBAAiB,EACzE,OAAO,EAAE,CAAC,EACV,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,KACpB,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,EAAE,EAAE,CAAC,CAAC,SAAS,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC;IAC1F,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC/C,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.js b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.js
new file mode 100644
index 00000000..aac40a39
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.js
@@ -0,0 +1,3 @@
+// Message types for iframe ↔ parent window communication
+export {};
+//# sourceMappingURL=messages.js.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.js.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.js.map
new file mode 100644
index 00000000..cded8c96
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"messages.js","sourceRoot":"","sources":["messages.ts"],"names":[],"mappings":"AAAA,yDAAyD"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.ts
new file mode 100644
index 00000000..87c41c0d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/messages.ts
@@ -0,0 +1,340 @@
+// Message types for iframe ↔ parent window communication
+
+export interface SourceInfo {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+ elementType?: string;
+ componentName?: string;
+ functionName?: string;
+ elementId?: string;
+ importPath?: string;
+ isUIComponent?: boolean; // Whether this is a UI-kit component (e.g. under components/ui)
+}
+
+export interface ElementInfo {
+ tagName: string;
+ className: string;
+ textContent: string;
+ sourceInfo: SourceInfo;
+ isStaticText: boolean;
+ isStaticClass?: boolean; // Whether className is a pure static string (editable)
+ componentName?: string;
+ componentPath?: string;
+ props?: Record;
+ hierarchy?: {
+ tagName: string;
+ componentName?: string;
+ fileName?: string;
+ }[];
+}
+
+// Message validation types
+export interface MessageValidationResult {
+ isValid: boolean;
+ errors?: string[];
+}
+
+export interface MessageValidator {
+ validate: (message: any) => MessageValidationResult;
+}
+
+// Bridge Ready Message
+export interface BridgeReadyMessage {
+ type: 'BRIDGE_READY';
+ payload: {
+ timestamp: number;
+ environment: 'iframe' | 'main';
+ };
+ timestamp?: number;
+ requestId?: string;
+}
+
+// iframe → Parent messages
+export interface ElementSelectedMessage {
+ type: 'ELEMENT_SELECTED';
+ payload: {
+ elementInfo: ElementInfo;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface ElementDeselectedMessage {
+ type: 'ELEMENT_DESELECTED';
+ requestId?: string;
+ timestamp?: number;
+ payload?: null; // Added optional payload to match usage
+}
+
+export interface ContentUpdatedMessage {
+ type: 'CONTENT_UPDATED';
+ payload: {
+ sourceInfo: SourceInfo;
+ oldValue: string;
+ newValue: string;
+ realtime?: boolean; // Whether this is a realtime (throttled) update
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface ContentUpdatedCallbackMessage {
+ type: 'CONTENT_UPDATED_CALLBACK';
+ payload: {
+ sourceInfo: SourceInfo;
+ oldValue: string;
+ newValue: string;
+ realtime?: boolean; // Whether this is a realtime (throttled) update
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface StyleUpdatedMessage {
+ type: 'STYLE_UPDATED';
+ payload: {
+ sourceInfo: SourceInfo;
+ oldClass: string;
+ newClass: string;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface DesignModeChangedMessage {
+ type: 'DESIGN_MODE_CHANGED';
+ enabled: boolean;
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Parent → iframe messages
+export interface ToggleDesignModeMessage {
+ type: 'TOGGLE_DESIGN_MODE';
+ enabled: boolean;
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface UpdateStyleMessage {
+ type: 'UPDATE_STYLE';
+ payload: {
+ sourceInfo: SourceInfo;
+ newClass: string;
+ persist?: boolean;
+ };
+ requestId?: string;
+ timestamp?: number;
+ enabled?: boolean; // Added to fix TS error where enabled was accessed on this type (though likely a bug in usage, adding optional property avoids strict error if discriminated union fails)
+}
+
+export interface UpdateContentMessage {
+ type: 'UPDATE_CONTENT';
+ payload: {
+ sourceInfo: SourceInfo;
+ newContent: string;
+ persist?: boolean;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface BatchUpdateItem {
+ type: 'style' | 'content';
+ sourceInfo: SourceInfo;
+ newValue: string;
+ originalValue?: string;
+}
+
+export interface BatchUpdateMessage {
+ type: 'BATCH_UPDATE';
+ payload: {
+ updates: BatchUpdateItem[];
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Add to Chat Message
+export interface AddToChatMessage {
+ type: 'ADD_TO_CHAT';
+ payload: {
+ content: string;
+ context?: {
+ elementInfo?: ElementInfo;
+ sourceInfo?: SourceInfo;
+ };
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Copy Element Message
+export interface CopyElementMessage {
+ type: 'COPY_ELEMENT';
+ payload: {
+ elementInfo: {
+ tagName: string;
+ className: string;
+ content: string;
+ sourceInfo?: SourceInfo;
+ };
+ textContent: string; // JSON string for clipboard
+ success: boolean; // Whether copy succeeded
+ error?: string; // Error message when copy failed
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Error message
+export interface ErrorMessage {
+ type: 'ERROR';
+ payload: {
+ code: string;
+ message: string;
+ details?: any;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Acknowledgement message
+export interface AcknowledgementMessage {
+ type: 'ACKNOWLEDGEMENT';
+ payload: {
+ messageType: string;
+ };
+ requestId: string; // Response MUST have requestId
+ timestamp?: number;
+}
+
+// Heartbeat and health-check messages
+export interface HeartbeatMessage {
+ type: 'HEARTBEAT';
+ payload?: {
+ timestamp: number;
+ };
+ timestamp?: number;
+}
+
+export interface HealthCheckMessage {
+ type: 'HEALTH_CHECK';
+ requestId?: string; // Made optional for sender
+ timestamp?: number; // Made optional for sender
+}
+
+export interface HealthCheckResponseMessage {
+ type: 'HEALTH_CHECK_RESPONSE';
+ payload: {
+ status: 'healthy' | 'unhealthy' | 'degraded' | 'connecting' | 'unnecessary';
+ version?: string;
+ uptime?: number;
+ message?: string;
+ response?: any;
+ error?: string;
+ isIframe?: boolean;
+ isConnected?: boolean;
+ origin?: string;
+ userAgent?: string;
+ location?: string;
+ };
+ requestId: string; // Response MUST have requestId
+ timestamp?: number;
+}
+
+// Union types
+export type IframeToParentMessage =
+ | ElementSelectedMessage
+ | ElementDeselectedMessage
+ | ContentUpdatedMessage
+ | StyleUpdatedMessage
+ | DesignModeChangedMessage
+ | ErrorMessage
+ | AcknowledgementMessage
+ | HeartbeatMessage
+ | HealthCheckResponseMessage
+ | BridgeReadyMessage
+ | AddToChatMessage
+ | CopyElementMessage
+ | ContentUpdatedCallbackMessage;
+
+export type ParentToIframeMessage =
+ | ToggleDesignModeMessage
+ | UpdateStyleMessage
+ | UpdateContentMessage
+ | BatchUpdateMessage
+ | HealthCheckMessage
+ | HeartbeatMessage; // Added HeartbeatMessage here too for bidirectional support
+
+export type DesignModeMessage = IframeToParentMessage | ParentToIframeMessage;
+
+// Promise-based request/response types
+export type RequestMessage = ParentToIframeMessage & {
+ requestId: string;
+ timestamp: number;
+};
+
+export type ResponseMessage = IframeToParentMessage & {
+ requestId: string;
+ success?: boolean;
+ error?: string;
+ timestamp: number;
+};
+
+export type RequestResponseMessage = RequestMessage | ResponseMessage | AcknowledgementMessage;
+
+// Message handler type
+export interface MessageHandler {
+ type: T['type'];
+ handler: (message: T) => Promise | any;
+ validator?: MessageValidator;
+}
+
+// Config-related types
+export interface IframeModeConfig {
+ enabled: boolean;
+ hideUI: boolean;
+ enableSelection: boolean;
+ enableDirectEdit: boolean;
+}
+
+export interface BatchUpdateConfig {
+ enabled: boolean;
+ debounceMs: number;
+}
+
+export interface BridgeConfig {
+ timeout: number;
+ retryAttempts: number;
+ retryDelay: number;
+ heartbeatInterval: number;
+ debug: boolean;
+}
+
+// Message utility helpers
+export interface MessageUtilsInterface {
+ generateRequestId: () => string;
+ createTimestamp: () => number;
+ isValidMessage: (message: any) => boolean;
+ createResponse: (
+ originalMessage: ParentToIframeMessage,
+ payload: T extends { payload: infer P } ? P : never,
+ success?: boolean,
+ error?: string
+ ) => T;
+}
+
+// Bridge interface
+export interface BridgeInterface {
+ send: (message: T) => Promise;
+ sendWithResponse: (
+ message: T,
+ responseType: R['type']
+ ) => Promise;
+ on: (type: T['type'], handler: (message: T) => void) => void;
+ off: (type: string, handler: Function) => void;
+ isConnected: () => boolean;
+ disconnect: () => void;
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.d.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.d.ts
new file mode 100644
index 00000000..969b7c36
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.d.ts
@@ -0,0 +1,10 @@
+import { SourceInfo } from './messages';
+/**
+ * Whether the element carries the compact JSON `{prefix}-info` attribute.
+ */
+export declare function hasSourceMapping(element: HTMLElement): boolean;
+/**
+ * Parse `{prefix}-info` JSON into `SourceInfo`, or null when missing/invalid.
+ */
+export declare function extractSourceInfo(element: HTMLElement): SourceInfo | null;
+//# sourceMappingURL=sourceInfo.d.ts.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.d.ts.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.d.ts.map
new file mode 100644
index 00000000..4d5aff8c
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"sourceInfo.d.ts","sourceRoot":"","sources":["sourceInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAE9D;AAqCD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,CA4BzE"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.js b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.js
new file mode 100644
index 00000000..0b2e70cc
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.js
@@ -0,0 +1,69 @@
+import { AttributeNames } from './attributeNames';
+/**
+ * Whether the element carries the compact JSON `{prefix}-info` attribute.
+ */
+export function hasSourceMapping(element) {
+ return element.hasAttribute(AttributeNames.info);
+}
+function toNumber(value) {
+ if (typeof value === 'number' && Number.isFinite(value)) {
+ return value;
+ }
+ if (typeof value === 'string' && value.trim() !== '') {
+ const parsed = Number.parseInt(value, 10);
+ if (Number.isFinite(parsed)) {
+ return parsed;
+ }
+ }
+ return null;
+}
+function normalizeSourceInfo(raw) {
+ const fileName = typeof raw.fileName === 'string' ? raw.fileName : null;
+ const lineNumber = toNumber(raw.lineNumber ?? raw.line);
+ const columnNumber = toNumber(raw.columnNumber ?? raw.column);
+ if (!fileName || lineNumber === null || columnNumber === null) {
+ return null;
+ }
+ return {
+ fileName,
+ lineNumber,
+ columnNumber,
+ elementType: typeof raw.elementType === 'string' ? raw.elementType : undefined,
+ componentName: typeof raw.componentName === 'string' ? raw.componentName : undefined,
+ functionName: typeof raw.functionName === 'string' ? raw.functionName : undefined,
+ elementId: typeof raw.elementId === 'string' ? raw.elementId : undefined,
+ importPath: typeof raw.importPath === 'string' ? raw.importPath : undefined,
+ isUIComponent: typeof raw.isUIComponent === 'boolean' ? raw.isUIComponent : undefined,
+ };
+}
+/**
+ * Parse `{prefix}-info` JSON into `SourceInfo`, or null when missing/invalid.
+ */
+export function extractSourceInfo(element) {
+ const sourceInfoStr = element.getAttribute(AttributeNames.info);
+ if (sourceInfoStr) {
+ try {
+ const parsed = JSON.parse(sourceInfoStr);
+ const normalized = normalizeSourceInfo(parsed);
+ if (normalized) {
+ return normalized;
+ }
+ }
+ catch (e) {
+ console.warn(`[sourceInfo] Failed to parse ${AttributeNames.info}:`, e);
+ }
+ }
+ // Legacy per-field attrs.
+ const fileName = element.getAttribute(AttributeNames.file);
+ const line = toNumber(element.getAttribute(AttributeNames.line));
+ const column = toNumber(element.getAttribute(AttributeNames.column));
+ if (fileName && line !== null && column !== null) {
+ return {
+ fileName,
+ lineNumber: line,
+ columnNumber: column,
+ };
+ }
+ return null;
+}
+//# sourceMappingURL=sourceInfo.js.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.js.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.js.map
new file mode 100644
index 00000000..1a50de6a
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"sourceInfo.js","sourceRoot":"","sources":["sourceInfo.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAoB;IACnD,OAAO,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC1C,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,mBAAmB,CAAC,GAA4B;IACvD,MAAM,QAAQ,GAAG,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAE9D,IAAI,CAAC,QAAQ,IAAI,UAAU,KAAK,IAAI,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC9D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,QAAQ;QACR,UAAU;QACV,YAAY;QACZ,WAAW,EAAE,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;QAC9E,aAAa,EAAE,OAAO,GAAG,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;QACpF,YAAY,EAAE,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;QACjF,SAAS,EAAE,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;QACxE,UAAU,EAAE,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;QAC3E,aAAa,EAAE,OAAO,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;KACtF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAoB;IACpD,MAAM,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAChE,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAA4B,CAAC;YACpE,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;YAC/C,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO,UAAU,CAAC;YACpB,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,gCAAgC,cAAc,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAErE,IAAI,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACjD,OAAO;YACL,QAAQ;YACR,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,MAAM;SACrB,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.ts
new file mode 100644
index 00000000..bff85bb5
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfo.ts
@@ -0,0 +1,77 @@
+import { SourceInfo } from './messages';
+import { AttributeNames } from './attributeNames';
+
+/**
+ * Whether the element carries the compact JSON `{prefix}-info` attribute.
+ */
+export function hasSourceMapping(element: HTMLElement): boolean {
+ return element.hasAttribute(AttributeNames.info);
+}
+
+function toNumber(value: unknown): number | null {
+ if (typeof value === 'number' && Number.isFinite(value)) {
+ return value;
+ }
+ if (typeof value === 'string' && value.trim() !== '') {
+ const parsed = Number.parseInt(value, 10);
+ if (Number.isFinite(parsed)) {
+ return parsed;
+ }
+ }
+ return null;
+}
+
+function normalizeSourceInfo(raw: Record): SourceInfo | null {
+ const fileName = typeof raw.fileName === 'string' ? raw.fileName : null;
+ const lineNumber = toNumber(raw.lineNumber ?? raw.line);
+ const columnNumber = toNumber(raw.columnNumber ?? raw.column);
+
+ if (!fileName || lineNumber === null || columnNumber === null) {
+ return null;
+ }
+
+ return {
+ fileName,
+ lineNumber,
+ columnNumber,
+ elementType: typeof raw.elementType === 'string' ? raw.elementType : undefined,
+ componentName: typeof raw.componentName === 'string' ? raw.componentName : undefined,
+ functionName: typeof raw.functionName === 'string' ? raw.functionName : undefined,
+ elementId: typeof raw.elementId === 'string' ? raw.elementId : undefined,
+ importPath: typeof raw.importPath === 'string' ? raw.importPath : undefined,
+ isUIComponent: typeof raw.isUIComponent === 'boolean' ? raw.isUIComponent : undefined,
+ };
+}
+
+/**
+ * Parse `{prefix}-info` JSON into `SourceInfo`, or null when missing/invalid.
+ */
+export function extractSourceInfo(element: HTMLElement): SourceInfo | null {
+ const sourceInfoStr = element.getAttribute(AttributeNames.info);
+ if (sourceInfoStr) {
+ try {
+ const parsed = JSON.parse(sourceInfoStr) as Record;
+ const normalized = normalizeSourceInfo(parsed);
+ if (normalized) {
+ return normalized;
+ }
+ } catch (e) {
+ console.warn(`[sourceInfo] Failed to parse ${AttributeNames.info}:`, e);
+ }
+ }
+
+ // Legacy per-field attrs.
+ const fileName = element.getAttribute(AttributeNames.file);
+ const line = toNumber(element.getAttribute(AttributeNames.line));
+ const column = toNumber(element.getAttribute(AttributeNames.column));
+
+ if (fileName && line !== null && column !== null) {
+ return {
+ fileName,
+ lineNumber: line,
+ columnNumber: column,
+ };
+ }
+
+ return null;
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.d.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.d.ts
new file mode 100644
index 00000000..8d871f50
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.d.ts
@@ -0,0 +1,13 @@
+import { SourceInfo } from './types';
+/**
+ * Resolve the **usage-site** `SourceInfo` for an element.
+ *
+ * Pass-through/static-content nodes may be compiled inside a UI-kit file; this walks
+ * parents / `children-source` / cross-file children so selections map to the caller file.
+ */
+export declare function resolveSourceInfo(element: HTMLElement): SourceInfo | null;
+/**
+ * Heuristic: node lives in a component **definition** (both parent/child static, same file).
+ */
+export declare function isInComponentDefinition(element: HTMLElement): boolean;
+//# sourceMappingURL=sourceInfoResolver.d.ts.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.d.ts.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.d.ts.map
new file mode 100644
index 00000000..7e2a49ff
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"sourceInfoResolver.d.ts","sourceRoot":"","sources":["sourceInfoResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAkDrC;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,CAuGzE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAmBrE"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.js b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.js
new file mode 100644
index 00000000..b54b4b0b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.js
@@ -0,0 +1,157 @@
+import { AttributeNames } from './attributeNames';
+import { extractSourceInfo } from './sourceInfo';
+/**
+ * BFS for the first descendant whose `{prefix}-info` points at a different file than `currentFile`.
+ * Used to detect wrapper components (e.g. `` inside a design-system `Button`).
+ *
+ * @param element Root to search under
+ * @param currentFile File path of the current element’s mapping
+ */
+function findChildFromDifferentFile(element, currentFile) {
+ const queue = [];
+ for (let i = 0; i < element.children.length; i++) {
+ const child = element.children[i];
+ if (child instanceof HTMLElement) {
+ queue.push(child);
+ }
+ }
+ let depth = 0;
+ const maxDepth = 5;
+ while (queue.length > 0 && depth < maxDepth) {
+ const levelSize = queue.length;
+ for (let i = 0; i < levelSize; i++) {
+ const child = queue.shift();
+ if (!child)
+ continue;
+ const childInfo = extractSourceInfo(child);
+ if (childInfo && childInfo.fileName !== currentFile) {
+ return child;
+ }
+ for (let j = 0; j < child.children.length; j++) {
+ const grandChild = child.children[j];
+ if (grandChild instanceof HTMLElement) {
+ queue.push(grandChild);
+ }
+ }
+ }
+ depth++;
+ }
+ return null;
+}
+/**
+ * Resolve the **usage-site** `SourceInfo` for an element.
+ *
+ * Pass-through/static-content nodes may be compiled inside a UI-kit file; this walks
+ * parents / `children-source` / cross-file children so selections map to the caller file.
+ */
+export function resolveSourceInfo(element) {
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ const currentInfo = extractSourceInfo(element);
+ const currentFile = currentInfo?.fileName;
+ if (currentInfo?.isUIComponent) {
+ let parent = element.parentElement;
+ let depth = 0;
+ while (parent && depth < 20) {
+ const parentInfo = extractSourceInfo(parent);
+ if (parentInfo?.fileName && !parentInfo.isUIComponent) {
+ return {
+ fileName: parentInfo.fileName,
+ lineNumber: parentInfo.lineNumber,
+ columnNumber: parentInfo.columnNumber,
+ elementType: element.tagName?.toLowerCase() || 'unknown',
+ componentName: currentInfo?.componentName,
+ functionName: currentInfo?.functionName
+ };
+ }
+ parent = parent.parentElement;
+ depth++;
+ }
+ }
+ const candidates = [
+ 'data-xagi-children-source',
+ 'data-source-children-source',
+ AttributeNames.childrenSource
+ ];
+ let childrenSource = null;
+ for (const attr of candidates) {
+ const val = element.getAttribute(attr);
+ if (val) {
+ childrenSource = val;
+ break;
+ }
+ }
+ if (childrenSource) {
+ const parts = childrenSource.split(':');
+ if (parts.length >= 3) {
+ const fileName = parts.slice(0, -2).join(':'); // Paths may contain `:`
+ const lineNumber = parseInt(parts[parts.length - 2], 10);
+ const columnNumber = parseInt(parts[parts.length - 1], 10);
+ if (!isNaN(lineNumber) && !isNaN(columnNumber)) {
+ return {
+ fileName,
+ lineNumber,
+ columnNumber,
+ elementType: element.tagName?.toLowerCase() || 'unknown',
+ componentName: currentInfo?.componentName,
+ functionName: currentInfo?.functionName
+ };
+ }
+ }
+ }
+ if (currentFile) {
+ const childWithDifferentSource = findChildFromDifferentFile(element, currentFile);
+ if (childWithDifferentSource) {
+ const childInfo = extractSourceInfo(childWithDifferentSource);
+ if (childInfo && childInfo.fileName !== currentFile) {
+ return {
+ fileName: childInfo.fileName,
+ lineNumber: childInfo.lineNumber,
+ columnNumber: childInfo.columnNumber,
+ elementType: element.tagName?.toLowerCase() || 'unknown',
+ componentName: currentInfo?.componentName,
+ functionName: currentInfo?.functionName
+ };
+ }
+ }
+ }
+ if (!hasStaticContent) {
+ return extractSourceInfo(element);
+ }
+ let currentElement = element;
+ let depth = 0;
+ while (currentElement && depth < 20) {
+ const parent = currentElement.parentElement;
+ if (!parent) {
+ break;
+ }
+ const parentInfo = extractSourceInfo(parent);
+ const parentFile = parentInfo?.fileName;
+ const parentHasStaticContent = parent.hasAttribute(AttributeNames.staticContent);
+ if (parentFile && (!parentHasStaticContent || parentFile !== currentFile)) {
+ return parentInfo;
+ }
+ currentElement = parent;
+ depth++;
+ }
+ return extractSourceInfo(element);
+}
+/**
+ * Heuristic: node lives in a component **definition** (both parent/child static, same file).
+ */
+export function isInComponentDefinition(element) {
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ const sourceInfo = extractSourceInfo(element);
+ const sourceFile = sourceInfo?.fileName;
+ if (!hasStaticContent || !sourceFile) {
+ return false;
+ }
+ const parent = element.parentElement;
+ if (!parent) {
+ return false;
+ }
+ const parentInfo = extractSourceInfo(parent);
+ const parentFile = parentInfo?.fileName;
+ const parentHasStaticContent = parent.hasAttribute(AttributeNames.staticContent);
+ return parentHasStaticContent && parentFile === sourceFile;
+}
+//# sourceMappingURL=sourceInfoResolver.js.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.js.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.js.map
new file mode 100644
index 00000000..0a5aeae9
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"sourceInfoResolver.js","sourceRoot":"","sources":["sourceInfoResolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;;GAMG;AACH,SAAS,0BAA0B,CAAC,OAAoB,EAAE,WAAmB;IACzE,MAAM,KAAK,GAAkB,EAAE,CAAC;IAEhC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACL,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,QAAQ,GAAG,CAAC,CAAC;IAEnB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,GAAG,QAAQ,EAAE,CAAC;QAC1C,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;QAE/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK;gBAAE,SAAS;YAErB,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,SAAS,IAAI,SAAS,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;gBAClD,OAAO,KAAK,CAAC;YACjB,CAAC;YAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC7C,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACrC,IAAI,UAAU,YAAY,WAAW,EAAE,CAAC;oBACpC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBAC3B,CAAC;YACL,CAAC;QACL,CAAC;QAED,KAAK,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAoB;IAClD,MAAM,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAC5E,MAAM,WAAW,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,WAAW,EAAE,QAAQ,CAAC;IAE1C,IAAI,WAAW,EAAE,aAAa,EAAE,CAAC;QAC7B,IAAI,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;QACnC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,OAAO,MAAM,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;YAC1B,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;YAC7C,IAAI,UAAU,EAAE,QAAQ,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;gBACpD,OAAO;oBACH,QAAQ,EAAE,UAAU,CAAC,QAAQ;oBAC7B,UAAU,EAAE,UAAU,CAAC,UAAU;oBACjC,YAAY,EAAE,UAAU,CAAC,YAAY;oBACrC,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,SAAS;oBACxD,aAAa,EAAE,WAAW,EAAE,aAAa;oBACzC,YAAY,EAAE,WAAW,EAAE,YAAY;iBAC1C,CAAC;YACN,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC;YAC9B,KAAK,EAAE,CAAC;QACZ,CAAC;IACL,CAAC;IAED,MAAM,UAAU,GAAG;QACf,2BAA2B;QAC3B,6BAA6B;QAC7B,cAAc,CAAC,cAAc;KAChC,CAAC;IAEF,IAAI,cAAc,GAAkB,IAAI,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,GAAG,EAAE,CAAC;YACN,cAAc,GAAG,GAAG,CAAC;YACrB,MAAM;QACV,CAAC;IACL,CAAC;IAED,IAAI,cAAc,EAAE,CAAC;QACjB,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACpB,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,wBAAwB;YACvE,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzD,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAE3D,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC7C,OAAO;oBACH,QAAQ;oBACR,UAAU;oBACV,YAAY;oBACZ,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,SAAS;oBACxD,aAAa,EAAE,WAAW,EAAE,aAAa;oBACzC,YAAY,EAAE,WAAW,EAAE,YAAY;iBAC1C,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED,IAAI,WAAW,EAAE,CAAC;QACd,MAAM,wBAAwB,GAAG,0BAA0B,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAClF,IAAI,wBAAwB,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,iBAAiB,CAAC,wBAAwB,CAAC,CAAC;YAC9D,IAAI,SAAS,IAAI,SAAS,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;gBAClD,OAAO;oBACH,QAAQ,EAAE,SAAS,CAAC,QAAQ;oBAC5B,UAAU,EAAE,SAAS,CAAC,UAAU;oBAChC,YAAY,EAAE,SAAS,CAAC,YAAY;oBACpC,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,SAAS;oBACxD,aAAa,EAAE,WAAW,EAAE,aAAa;oBACzC,YAAY,EAAE,WAAW,EAAE,YAAY;iBAC1C,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACpB,OAAO,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,cAAc,GAAuB,OAAO,CAAC;IACjD,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAO,cAAc,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QAClC,MAAM,MAAM,GAAuB,cAAc,CAAC,aAAa,CAAC;QAChE,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM;QACV,CAAC;QAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,UAAU,GAAG,UAAU,EAAE,QAAQ,CAAC;QACxC,MAAM,sBAAsB,GAAG,MAAM,CAAC,YAAY,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAEjF,IAAI,UAAU,IAAI,CAAC,CAAC,sBAAsB,IAAI,UAAU,KAAK,WAAW,CAAC,EAAE,CAAC;YACxE,OAAO,UAAU,CAAC;QACtB,CAAC;QAED,cAAc,GAAG,MAAM,CAAC;QACxB,KAAK,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,iBAAiB,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAoB;IACxD,MAAM,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAC5E,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,UAAU,EAAE,QAAQ,CAAC;IAExC,IAAI,CAAC,gBAAgB,IAAI,CAAC,UAAU,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IACrC,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,UAAU,EAAE,QAAQ,CAAC;IACxC,MAAM,sBAAsB,GAAG,MAAM,CAAC,YAAY,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAEjF,OAAO,sBAAsB,IAAI,UAAU,KAAK,UAAU,CAAC;AAC/D,CAAC"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.ts
new file mode 100644
index 00000000..55f6cfbf
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/sourceInfoResolver.ts
@@ -0,0 +1,190 @@
+import { SourceInfo } from './types';
+import { AttributeNames } from './attributeNames';
+import { extractSourceInfo } from './sourceInfo';
+
+/**
+ * BFS for the first descendant whose `{prefix}-info` points at a different file than `currentFile`.
+ * Used to detect wrapper components (e.g. `` inside a design-system `Button`).
+ *
+ * @param element Root to search under
+ * @param currentFile File path of the current element’s mapping
+ */
+function findChildFromDifferentFile(element: HTMLElement, currentFile: string): HTMLElement | null {
+ const queue: HTMLElement[] = [];
+
+ for (let i = 0; i < element.children.length; i++) {
+ const child = element.children[i];
+ if (child instanceof HTMLElement) {
+ queue.push(child);
+ }
+ }
+
+ let depth = 0;
+ const maxDepth = 5;
+
+ while (queue.length > 0 && depth < maxDepth) {
+ const levelSize = queue.length;
+
+ for (let i = 0; i < levelSize; i++) {
+ const child = queue.shift();
+ if (!child) continue;
+
+ const childInfo = extractSourceInfo(child);
+ if (childInfo && childInfo.fileName !== currentFile) {
+ return child;
+ }
+
+ for (let j = 0; j < child.children.length; j++) {
+ const grandChild = child.children[j];
+ if (grandChild instanceof HTMLElement) {
+ queue.push(grandChild);
+ }
+ }
+ }
+
+ depth++;
+ }
+
+ return null;
+}
+
+/**
+ * Resolve the **usage-site** `SourceInfo` for an element.
+ *
+ * Pass-through/static-content nodes may be compiled inside a UI-kit file; this walks
+ * parents / `children-source` / cross-file children so selections map to the caller file.
+ */
+export function resolveSourceInfo(element: HTMLElement): SourceInfo | null {
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ const currentInfo = extractSourceInfo(element);
+ const currentFile = currentInfo?.fileName;
+
+ if (currentInfo?.isUIComponent) {
+ let parent = element.parentElement;
+ let depth = 0;
+ while (parent && depth < 20) {
+ const parentInfo = extractSourceInfo(parent);
+ if (parentInfo?.fileName && !parentInfo.isUIComponent) {
+ return {
+ fileName: parentInfo.fileName,
+ lineNumber: parentInfo.lineNumber,
+ columnNumber: parentInfo.columnNumber,
+ elementType: element.tagName?.toLowerCase() || 'unknown',
+ componentName: currentInfo?.componentName,
+ functionName: currentInfo?.functionName
+ };
+ }
+ parent = parent.parentElement;
+ depth++;
+ }
+ }
+
+ const candidates = [
+ 'data-xagi-children-source',
+ 'data-source-children-source',
+ AttributeNames.childrenSource
+ ];
+
+ let childrenSource: string | null = null;
+ for (const attr of candidates) {
+ const val = element.getAttribute(attr);
+ if (val) {
+ childrenSource = val;
+ break;
+ }
+ }
+
+ if (childrenSource) {
+ const parts = childrenSource.split(':');
+ if (parts.length >= 3) {
+ const fileName = parts.slice(0, -2).join(':'); // Paths may contain `:`
+ const lineNumber = parseInt(parts[parts.length - 2], 10);
+ const columnNumber = parseInt(parts[parts.length - 1], 10);
+
+ if (!isNaN(lineNumber) && !isNaN(columnNumber)) {
+ return {
+ fileName,
+ lineNumber,
+ columnNumber,
+ elementType: element.tagName?.toLowerCase() || 'unknown',
+ componentName: currentInfo?.componentName,
+ functionName: currentInfo?.functionName
+ };
+ }
+ }
+ }
+
+ if (currentFile) {
+ const childWithDifferentSource = findChildFromDifferentFile(element, currentFile);
+ if (childWithDifferentSource) {
+ const childInfo = extractSourceInfo(childWithDifferentSource);
+ if (childInfo && childInfo.fileName !== currentFile) {
+ return {
+ fileName: childInfo.fileName,
+ lineNumber: childInfo.lineNumber,
+ columnNumber: childInfo.columnNumber,
+ elementType: element.tagName?.toLowerCase() || 'unknown',
+ componentName: currentInfo?.componentName,
+ functionName: currentInfo?.functionName
+ };
+ }
+ }
+ }
+
+ if (!hasStaticContent) {
+ return extractSourceInfo(element);
+ }
+
+ // Only walk up for elements inside component definitions (both parent and child
+ // have static-content in the same file). Regular nested HTML should use its own sourceInfo.
+ if (!isInComponentDefinition(element)) {
+ return extractSourceInfo(element);
+ }
+
+ let currentElement: HTMLElement | null = element;
+ let depth = 0;
+
+ while (currentElement && depth < 20) {
+ const parent: HTMLElement | null = currentElement.parentElement;
+ if (!parent) {
+ break;
+ }
+
+ const parentInfo = extractSourceInfo(parent);
+ const parentFile = parentInfo?.fileName;
+ const parentHasStaticContent = parent.hasAttribute(AttributeNames.staticContent);
+
+ if (parentFile && (!parentHasStaticContent || parentFile !== currentFile)) {
+ return parentInfo;
+ }
+
+ currentElement = parent;
+ depth++;
+ }
+
+ return extractSourceInfo(element);
+}
+
+/**
+ * Heuristic: node lives in a component **definition** (both parent/child static, same file).
+ */
+export function isInComponentDefinition(element: HTMLElement): boolean {
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ const sourceInfo = extractSourceInfo(element);
+ const sourceFile = sourceInfo?.fileName;
+
+ if (!hasStaticContent || !sourceFile) {
+ return false;
+ }
+
+ const parent = element.parentElement;
+ if (!parent) {
+ return false;
+ }
+
+ const parentInfo = extractSourceInfo(parent);
+ const parentFile = parentInfo?.fileName;
+ const parentHasStaticContent = parent.hasAttribute(AttributeNames.staticContent);
+
+ return parentHasStaticContent && parentFile === sourceFile;
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/types.d.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.d.ts
new file mode 100644
index 00000000..d83e0905
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.d.ts
@@ -0,0 +1,50 @@
+import type { SourceInfo, ElementInfo } from './messages';
+export type { SourceInfo, ElementInfo };
+/** High-level update kind tracked by `UpdateManager`. */
+export type UpdateOperation = 'style_update' | 'content_update' | 'attribute_update' | 'class_update' | 'batch_update';
+/** One in-flight or historical mutation. */
+export interface UpdateState {
+ id: string;
+ operation: UpdateOperation;
+ sourceInfo: SourceInfo;
+ element: HTMLElement;
+ oldValue: string;
+ newValue: string;
+ status: 'pending' | 'processing' | 'completed' | 'failed' | 'reverted';
+ timestamp: number;
+ error?: string;
+ retryCount: number;
+ persist?: boolean;
+}
+/** Result of persisting / previewing an update. */
+export interface UpdateResult {
+ success: boolean;
+ element: HTMLElement;
+ updateId: string;
+ error?: string;
+ serverResponse?: any;
+}
+/** One row in a batch POST body for UpdateManager. */
+export interface UpdateManagerBatchItem {
+ element: HTMLElement;
+ type: 'style' | 'content' | 'attribute';
+ sourceInfo: SourceInfo;
+ newValue: string;
+ originalValue?: string;
+ selector?: string;
+}
+/** `UpdateManager` runtime options. */
+export interface UpdateManagerConfig {
+ enableDirectEdit: boolean;
+ enableBatching: boolean;
+ batchDebounceMs: number;
+ maxRetries: number;
+ autoSave: boolean;
+ saveDelay: number;
+ validation: {
+ validateSource: boolean;
+ validateValue: boolean;
+ maxLength: number;
+ };
+}
+//# sourceMappingURL=types.d.ts.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/types.d.ts.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.d.ts.map
new file mode 100644
index 00000000..672efc76
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG1D,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;AAExC,yDAAyD;AACzD,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,gBAAgB,GAChB,kBAAkB,GAClB,cAAc,GACd,cAAc,CAAC;AAEnB,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,eAAe,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,mDAAmD;AACnD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,GAAG,CAAC;CACtB;AAED,sDAAsD;AACtD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;IACxC,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,uCAAuC;AACvC,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE;QACV,cAAc,EAAE,OAAO,CAAC;QACxB,aAAa,EAAE,OAAO,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH"}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/types.js b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.js
new file mode 100644
index 00000000..718fd38a
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.js
@@ -0,0 +1,2 @@
+export {};
+//# sourceMappingURL=types.js.map
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/types.js.map b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.js.map
new file mode 100644
index 00000000..8da0887a
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":""}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/src/types.ts b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.ts
new file mode 100644
index 00000000..560b03b4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/src/types.ts
@@ -0,0 +1,62 @@
+// Import shared types from messages
+import type { SourceInfo, ElementInfo } from './messages';
+
+// Re-export for convenience
+export type { SourceInfo, ElementInfo };
+
+/** High-level update kind tracked by `UpdateManager`. */
+export type UpdateOperation =
+ | 'style_update'
+ | 'content_update'
+ | 'attribute_update'
+ | 'class_update'
+ | 'batch_update';
+
+/** One in-flight or historical mutation. */
+export interface UpdateState {
+ id: string;
+ operation: UpdateOperation;
+ sourceInfo: SourceInfo;
+ element: HTMLElement;
+ oldValue: string;
+ newValue: string;
+ status: 'pending' | 'processing' | 'completed' | 'failed' | 'reverted';
+ timestamp: number;
+ error?: string;
+ retryCount: number;
+ persist?: boolean;
+}
+
+/** Result of persisting / previewing an update. */
+export interface UpdateResult {
+ success: boolean;
+ element: HTMLElement;
+ updateId: string;
+ error?: string;
+ serverResponse?: any;
+}
+
+/** One row in a batch POST body for UpdateManager. */
+export interface UpdateManagerBatchItem {
+ element: HTMLElement;
+ type: 'style' | 'content' | 'attribute';
+ sourceInfo: SourceInfo;
+ newValue: string;
+ originalValue?: string;
+ selector?: string;
+}
+
+/** `UpdateManager` runtime options. */
+export interface UpdateManagerConfig {
+ enableDirectEdit: boolean;
+ enableBatching: boolean;
+ batchDebounceMs: number;
+ maxRetries: number;
+ autoSave: boolean;
+ saveDelay: number;
+ validation: {
+ validateSource: boolean;
+ validateValue: boolean;
+ maxLength: number;
+ };
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-shared/tsconfig.json b/qiming-vite-plugin-design-mode/packages/client-shared/tsconfig.json
new file mode 100644
index 00000000..28b3de18
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-shared/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "declaration": true,
+ "declarationMap": true
+ },
+ "include": ["src/**/*"]
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/package.json b/qiming-vite-plugin-design-mode/packages/client-vue/package.json
new file mode 100644
index 00000000..00ad397c
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@xagi/design-mode-client-vue",
+ "version": "1.2.0",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsc && node ./scripts/copy-vue-to-dist.mjs"
+ },
+ "dependencies": {
+ "@xagi/design-mode-shared": "1.2.0",
+ "tailwind-merge": "^3.4.0"
+ },
+ "peerDependencies": {
+ "vue": "^3.3.0"
+ },
+ "devDependencies": {
+ "@vue/compiler-sfc": "^3.3.0",
+ "typescript": "^5.0.0"
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/scripts/copy-vue-to-dist.mjs b/qiming-vite-plugin-design-mode/packages/client-vue/scripts/copy-vue-to-dist.mjs
new file mode 100644
index 00000000..413282b9
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/scripts/copy-vue-to-dist.mjs
@@ -0,0 +1,47 @@
+// 构建后把 src 下所有 .vue 复制到 dist,与 tsc 输出目录一致。
+//
+// 背景:tsc 只编译 .ts 为 dist 下的 .js,不会复制 .vue;但 dist/index.js 仍 import './DesignModeApp.vue'。
+// Vite 虚拟模块会把相对路径改成绝对路径,若 dist 里没有对应 .vue 会报 Failed to resolve import。
+//
+// 用法:package.json 的 build 在 tsc 之后执行本脚本。
+import { copyFileSync, mkdirSync, readdirSync, statSync } from 'node:fs';
+import { dirname, join, relative } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const pkgRoot = join(__dirname, '..');
+const srcDir = join(pkgRoot, 'src');
+const distDir = join(pkgRoot, 'dist');
+
+/**
+ * 递归遍历目录,将每个 `.vue` 复制到 dist 中相同相对路径。
+ * @param {string} dir 当前正在扫描的目录(绝对路径,位于 src 下)
+ */
+function copyVueFiles(dir) {
+ for (const ent of readdirSync(dir, { withFileTypes: true })) {
+ const full = join(dir, ent.name);
+ if (ent.isDirectory()) {
+ copyVueFiles(full);
+ continue;
+ }
+ if (!ent.name.endsWith('.vue')) {
+ continue;
+ }
+ const rel = relative(srcDir, full);
+ const outPath = join(distDir, rel);
+ mkdirSync(dirname(outPath), { recursive: true });
+ copyFileSync(full, outPath);
+ }
+}
+
+if (!statSync(srcDir, { throwIfNoEntry: false })?.isDirectory()) {
+ console.error('[copy-vue-to-dist] src directory not found:', srcDir);
+ process.exit(1);
+}
+if (!statSync(distDir, { throwIfNoEntry: false })?.isDirectory()) {
+ console.error('[copy-vue-to-dist] dist directory not found; run tsc first:', distDir);
+ process.exit(1);
+}
+
+copyVueFiles(srcDir);
+console.log('[copy-vue-to-dist] Copied all .vue files from src to dist.');
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/DesignModeApp.vue b/qiming-vite-plugin-design-mode/packages/client-vue/src/DesignModeApp.vue
new file mode 100644
index 00000000..4bc539a5
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/DesignModeApp.vue
@@ -0,0 +1,170 @@
+
+
+
+
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/README.md b/qiming-vite-plugin-design-mode/packages/client-vue/src/README.md
new file mode 100644
index 00000000..b5b4ee4f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/README.md
@@ -0,0 +1,296 @@
+# Vue3 Design Mode Client
+
+Vue3 Single File Component implementation of the design mode UI, ported from React.
+
+## Structure
+
+```
+src/client-vue/
+├── DesignModeApp.vue # Root component (160 lines)
+├── components/
+│ ├── ContextMenu.vue # Right-click context menu (175 lines)
+│ ├── DesignModeUI.vue # Main UI with toggle & edit panel (333 lines)
+│ ├── Toast.vue # Toast notification component (101 lines)
+│ └── ToastContainer.vue # Toast container manager (31 lines)
+├── composables/
+│ ├── useDesignMode.ts # Design mode state & actions (210 lines)
+│ ├── useEditManager.ts # Direct editing logic (367 lines)
+│ ├── useObserverManager.ts # MutationObserver management (99 lines)
+│ ├── useSelectionManager.ts # Selection & hover logic (540 lines)
+│ └── useToast.ts # Toast notification system (50 lines)
+├── index.ts # Entry point with Shadow DOM (32 lines)
+└── exports.ts # Public API exports (7 lines)
+```
+
+**Total: ~2,103 lines**
+
+## Components
+
+### 1. DesignModeApp.vue
+Root component that:
+- Creates and provides design mode context via `createDesignMode()`
+- Sets up global event listeners (click, hover, mouseout)
+- Injects global CSS for hover/selected states
+- Manages lifecycle and cleanup
+
+### 2. DesignModeUI.vue
+Main UI component featuring:
+- **Toggle Button**: Fixed bottom-right position
+- **Edit Panel**: 320px wide panel when element selected
+- **Tailwind Presets**:
+ - Background colors (7 options)
+ - Text colors (5 options)
+ - Padding (6 options)
+ - Border radius (5 options)
+- **Reset Button**: Reload page to reset all modifications
+
+### 3. ContextMenu.vue
+Right-click context menu with:
+- Dynamic positioning with viewport boundary checks
+- Menu items with hover states
+- Close handlers: click outside, ESC, scroll, resize
+- Hover state restoration after close
+
+### 4. Toast.vue & ToastContainer.vue
+Toast notification system:
+- Three types: success, error, info
+- Slide-in/out animations
+- Auto-dismiss (3-4s configurable)
+- Stacked display at top-center
+
+## Composables
+
+### useDesignMode.ts
+Core design mode state management:
+- `isDesignMode`: Boolean toggle state
+- `selectedElement`: Currently selected element
+- `hoveredElement`: Currently hovered element
+- `toggleDesignMode()`: Toggle design mode on/off
+- `selectElement()`: Select element and notify parent
+- `modifyElementClass()`: Update element classes
+- `updateElementContent()`: Update element text content
+- `resetModifications()`: Reload page
+
+Uses Vue3 `provide/inject` pattern for global state.
+
+### useToast.ts
+Toast notification management:
+- `show()`: Display toast with type and duration
+- `error()`: Show error toast (4s)
+- `success()`: Show success toast (3s)
+- `info()`: Show info toast (3s)
+- `remove()`: Remove toast by ID
+
+### useEditManager.ts
+Direct editing functionality:
+- Double-click to edit text content
+- ContentEditable mode with save/cancel
+- Real-time sync across list items
+- Keyboard shortcuts (Enter to save, ESC to cancel)
+
+### useObserverManager.ts
+MutationObserver for DOM changes:
+- Watches content and style changes
+- Filters ignored mutations (`data-ignore-mutation`)
+- Triggers callbacks for edits
+
+### useSelectionManager.ts
+Advanced selection management:
+- Click/hover selection
+- Element validation (static-content/static-class)
+- Highlight/unhighlight elements
+- Keyboard shortcuts (ESC to clear)
+- Component root detection
+
+## Usage
+
+### Entry Point (index.ts)
+```typescript
+import { createApp } from 'vue';
+import DesignModeApp from './DesignModeApp.vue';
+
+// Creates Shadow DOM container
+// Mounts Vue app
+// Detects iframe environment
+```
+
+### In Components
+```vue
+
+```
+
+### Toast Notifications
+```typescript
+import { useToast } from './composables/useToast';
+
+const toast = useToast();
+
+toast.success('Changes saved!');
+toast.error('Failed to update');
+toast.info('Element selected');
+```
+
+## Key Features
+
+### 1. Shadow DOM Isolation
+- Styles isolated from host page
+- No CSS conflicts
+- Clean encapsulation
+
+### 2. Iframe Communication
+- PostMessage API for parent ↔ iframe
+- Message types: ELEMENT_SELECTED, STYLE_UPDATED, CONTENT_UPDATED
+- Automatic source info resolution
+
+### 3. List Synchronization
+- Elements with same `element-id` update together
+- Real-time preview across all instances
+- Prevents desync in mapped lists
+
+### 4. Tailwind Integration
+- Preset buttons for common utilities
+- Visual color pickers
+- Class merging (simple Set-based)
+
+### 5. Keyboard Shortcuts
+- **Enter**: Save content edit
+- **ESC**: Cancel edit or clear selection
+- **Ctrl/Cmd+A**: Select first element
+- **Ctrl/Cmd+D**: Clear selection
+
+## Differences from React Version
+
+### Architecture
+- **React**: Context API + hooks
+- **Vue3**: Provide/Inject + composables
+
+### State Management
+- **React**: `useState`, `useEffect`
+- **Vue3**: `ref`, `watch`, `onMounted`
+
+### Event Handling
+- **React**: Inline event handlers
+- **Vue3**: `@click`, `@mouseenter` directives
+
+### Styling
+- **React**: Inline style objects
+- **Vue3**: `:style` bindings + scoped CSS
+
+### Lifecycle
+- **React**: `useEffect` with cleanup
+- **Vue3**: `onMounted`, `onUnmounted`
+
+## Shared Utilities
+
+Both React and Vue3 clients use shared utilities from `client-shared/`:
+
+- `attributeNames.ts`: Data attribute name resolution
+- `sourceInfo.ts`: Source mapping extraction
+- `sourceInfoResolver.ts`: Advanced source resolution
+- `elementUtils.ts`: Element validation helpers
+- `bridge.ts`: Iframe bridge communication
+- `UpdateService.ts`: Update API client
+- `HistoryManager.ts`: Undo/redo functionality
+
+## Integration
+
+### Vite Plugin Configuration
+```typescript
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import appdevDesignMode from 'vite-plugin-appdev-design-mode';
+
+export default defineConfig({
+ plugins: [
+ vue(),
+ appdevDesignMode({
+ framework: 'vue3', // Use Vue3 client
+ enabled: true,
+ }),
+ ],
+});
+```
+
+### Build Output
+The plugin will:
+1. Inject Vue3 client code into your app
+2. Create Shadow DOM container
+3. Mount DesignModeApp component
+4. Enable design mode features
+
+## API Reference
+
+### createDesignMode()
+Creates design mode context. Call once in root component.
+
+**Returns**: `DesignModeContext`
+
+### useDesignMode()
+Access design mode context. Must be called within a component that has `createDesignMode()` in its ancestor tree.
+
+**Returns**: `DesignModeContext`
+
+### DesignModeContext
+```typescript
+interface DesignModeContext {
+ // State
+ isDesignMode: Ref;
+ selectedElement: Ref;
+ hoveredElement: Ref;
+
+ // Actions
+ toggleDesignMode: () => void;
+ selectElement: (element: HTMLElement | null) => void;
+ setHoveredElement: (element: HTMLElement | null) => void;
+ modifyElementClass: (element: HTMLElement, newClass: string) => Promise;
+ updateElementContent: (element: HTMLElement, newContent: string) => Promise;
+ resetModifications: () => void;
+}
+```
+
+## Development
+
+### Prerequisites
+- Vue 3.x
+- TypeScript 5.x
+- Vite 5.x
+
+### Testing
+```bash
+# Run in development mode
+npm run dev
+
+# Build for production
+npm run build
+
+# Type check
+npm run type-check
+```
+
+## Future Enhancements
+
+- [ ] Undo/redo functionality
+- [ ] Attribute editing UI
+- [ ] Style inspector panel
+- [ ] Component hierarchy viewer
+- [ ] Keyboard shortcut customization
+- [ ] Multi-select support
+- [ ] Drag-to-reorder elements
+- [ ] Copy/paste element styles
+- [ ] Export changes as patch file
+
+## License
+
+MIT
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/components/ContextMenu.vue b/qiming-vite-plugin-design-mode/packages/client-vue/src/components/ContextMenu.vue
new file mode 100644
index 00000000..91f63b30
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/components/ContextMenu.vue
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+ (e.currentTarget as HTMLElement).style.background = '#f0f0f0'"
+ @mouseleave="(e) => (e.currentTarget as HTMLElement).style.background = 'transparent'"
+ :style="{
+ padding: '8px 16px',
+ borderRadius: '4px',
+ margin: '0',
+ cursor: item.disabled ? 'not-allowed' : 'pointer',
+ color: item.disabled ? '#999' : '#333',
+ background: 'transparent',
+ }"
+ >
+ {{ item.label }}
+
+
+
+
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/components/DesignModeUI.vue b/qiming-vite-plugin-design-mode/packages/client-vue/src/components/DesignModeUI.vue
new file mode 100644
index 00000000..39614e1f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/components/DesignModeUI.vue
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit Element
+
+
+ {{ getElementTag }}
+
+
+
+
+
+
+
+
+
+
+
+
+ A
+
+
+
+
+
+
+
+
+
+ {{ preset.label }}
+
+
+
+
+
+
+
+
+
+ {{ preset.label }}
+
+
+
+
+
+
+ Reset All Modifications
+
+
+
+
+
+
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/components/Toast.vue b/qiming-vite-plugin-design-mode/packages/client-vue/src/components/Toast.vue
new file mode 100644
index 00000000..4617c6b4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/components/Toast.vue
@@ -0,0 +1,101 @@
+
+
+
+
+ {{ message }}
+
+
+
+
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/components/ToastContainer.vue b/qiming-vite-plugin-design-mode/packages/client-vue/src/components/ToastContainer.vue
new file mode 100644
index 00000000..643c9e01
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/components/ToastContainer.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/README.md b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/README.md
new file mode 100644
index 00000000..ffdcf041
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/README.md
@@ -0,0 +1,238 @@
+# Vue3 Design Mode Composables
+
+Vue3 Composition API equivalents of the React design mode hooks.
+
+## Overview
+
+This directory contains Vue3 composables that provide the same functionality as the React hooks in `/src/client/`. These composables enable design mode functionality in Vue3 applications.
+
+## Composables
+
+### 1. `useDesignMode.ts` (912 lines)
+
+**Purpose**: Global design mode state management and bridge communication.
+
+**Ported from**: `src/client/DesignModeContext.tsx` (1,079 lines)
+
+**Key Features**:
+- Global state management using `reactive()` and `provide/inject`
+- Bridge communication with parent window via postMessage
+- Message handling for style/content updates
+- Batch update support with debouncing
+- Health check and connection monitoring
+
+**Usage**:
+```typescript
+import { createDesignMode, useDesignMode } from './composables';
+
+// In root component
+const designMode = createDesignMode({
+ enabled: true,
+ iframeMode: {
+ enabled: true,
+ enableSelection: true,
+ enableDirectEdit: true,
+ },
+ batchUpdate: {
+ enabled: true,
+ debounceMs: 300,
+ },
+});
+
+// In child components
+const {
+ isDesignMode,
+ selectedElement,
+ toggleDesignMode,
+ selectElement,
+ modifyElementClass,
+ updateElementContent
+} = useDesignMode();
+```
+
+**Key Methods**:
+- `toggleDesignMode()` - Toggle design mode on/off
+- `selectElement(element)` - Select an element for editing
+- `modifyElementClass(element, newClass)` - Update element classes
+- `updateElementContent(element, newContent)` - Update element text content
+- `batchUpdateElements(updates)` - Batch multiple updates
+- `resetModifications()` - Reload page to reset changes
+
+### 2. `useSelectionManager.ts` (539 lines)
+
+**Purpose**: Element selection and interaction handling.
+
+**Ported from**: `src/client/SelectionManager.tsx` (597 lines)
+
+**Key Features**:
+- Click and hover event handling
+- Element validation (must have source mapping + static-content/static-class)
+- Visual highlighting with outline and shadow
+- Keyboard shortcuts (Esc to clear, Ctrl/Cmd+A to select)
+- Element info extraction with hierarchy
+- Component root detection for library components
+
+**Usage**:
+```typescript
+import { useSelectionManager } from './composables';
+
+const {
+ selectedElement,
+ hoverElement,
+ selectElement,
+ clearSelection,
+ extractElementInfo,
+ addSelectionListener,
+} = useSelectionManager(document.body, {
+ enableSelection: true,
+ enableHover: true,
+ selectionDelay: 0,
+ excludeSelectors: ['script', 'style', 'meta'],
+ includeOnlyElements: false,
+});
+
+// Listen to selection changes
+const unsubscribe = addSelectionListener((element) => {
+ if (element) {
+ const info = extractElementInfo(element);
+ console.log('Selected:', info);
+ }
+});
+```
+
+**Key Methods**:
+- `selectElement(element)` - Select and highlight element
+- `clearSelection()` - Clear current selection
+- `extractElementInfo(element)` - Get detailed element information
+- `addSelectionListener(callback)` - Subscribe to selection changes
+
+### 3. `useEditManager.ts` (367 lines)
+
+**Purpose**: Content editing with contentEditable mode.
+
+**Ported from**: `src/client/managers/EditManager.ts` (365 lines)
+
+**Key Features**:
+- ContentEditable mode for inline text editing
+- Real-time sync to related elements (list items with same element-id)
+- Throttled notifications (300ms) during typing
+- Enter saves, Escape cancels
+- Click outside to save
+- Automatic peer element synchronization
+
+**Usage**:
+```typescript
+import { useEditManager } from './composables';
+
+const processUpdate = async (update: UpdateState) => {
+ // Handle update logic
+ return { success: true };
+};
+
+const {
+ handleDirectEdit,
+ editTextContent,
+ updateContent,
+ updateStyle,
+ updateAttribute,
+} = useEditManager(processUpdate, config);
+
+// Double-click to edit
+element.addEventListener('dblclick', () => {
+ handleDirectEdit(element, 'content');
+});
+```
+
+**Key Methods**:
+- `handleDirectEdit(element, type)` - Trigger edit mode
+- `editTextContent(element)` - Enable contentEditable editing
+- `updateContent(element, newValue)` - Programmatic content update
+- `updateStyle(element, newClass)` - Programmatic style update
+- `updateAttribute(element, name, value)` - Update element attribute
+
+### 4. `useObserverManager.ts` (98 lines)
+
+**Purpose**: DOM mutation observation.
+
+**Ported from**: `src/client/managers/ObserverManager.ts` (81 lines)
+
+**Key Features**:
+- MutationObserver setup for DOM changes
+- Watches: childList, characterData, attributes (class, style)
+- Skips elements with `data-ignore-mutation` attribute
+- Callbacks for content/style edits and node additions
+
+**Usage**:
+```typescript
+import { useObserverManager } from './composables';
+
+const { enable, disable, isEnabled } = useObserverManager(
+ (target, type) => {
+ console.log(`Edit detected: ${type} on`, target);
+ },
+ (node) => {
+ console.log('Node added:', node);
+ }
+);
+
+// Start observing
+enable();
+
+// Stop observing
+disable();
+```
+
+**Key Methods**:
+- `enable()` - Start observing DOM mutations
+- `disable()` - Stop observing and disconnect
+
+## Key Differences from React Version
+
+### State Management
+- **React**: `useState`, `useCallback`, `useEffect`
+- **Vue3**: `ref`, `reactive`, `computed`, `watch`, `onMounted`, `onBeforeUnmount`
+
+### Context/Injection
+- **React**: `createContext`, `useContext`, `Provider`
+- **Vue3**: `provide`, `inject`, `InjectionKey`
+
+### Lifecycle
+- **React**: `useEffect` with cleanup
+- **Vue3**: `onMounted`, `onBeforeUnmount`
+
+### Reactivity
+- **React**: Explicit state setters
+- **Vue3**: Direct mutation of `ref.value` or `reactive` properties
+
+## Integration with Shared Code
+
+All composables import shared utilities from `/src/client-shared/`:
+- `bridge.ts` - PostMessage communication
+- `attributeNames.ts` - Data attribute name resolution
+- `sourceInfo.ts` - Source mapping extraction
+- `sourceInfoResolver.ts` - Usage-site resolution
+- `elementUtils.ts` - Element validation helpers
+
+## Type Safety
+
+All composables are fully typed with TypeScript, importing types from:
+- `/src/types/messages.ts` - Message type definitions
+- `/src/types/UpdateTypes.ts` - Update operation types
+
+## Testing
+
+To test these composables:
+
+1. Import in a Vue3 component
+2. Call `createDesignMode()` in the root component
+3. Use `useDesignMode()` in child components
+4. Verify bridge communication with parent window
+5. Test element selection and editing
+
+## Future Enhancements
+
+- [ ] Add Vue3-specific optimizations (e.g., `shallowRef` for large objects)
+- [ ] Create Vue3 component wrappers for easier integration
+- [ ] Add Pinia store integration option
+- [ ] Implement Vue DevTools integration
+- [ ] Add unit tests with Vitest
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/index.ts b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/index.ts
new file mode 100644
index 00000000..68490efa
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/index.ts
@@ -0,0 +1,16 @@
+/**
+ * Vue3 Composables for Design Mode
+ *
+ * These composables provide Vue3 equivalents to the React hooks
+ * for managing design mode functionality.
+ */
+
+export { createDesignMode, useDesignMode } from './useDesignMode';
+export type { DesignModeConfig, Modification } from './useDesignMode';
+
+export { useSelectionManager } from './useSelectionManager';
+export type { SelectionManagerConfig } from './useSelectionManager';
+
+export { useEditManager } from './useEditManager';
+
+export { useObserverManager } from './useObserverManager';
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useDesignMode.ts b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useDesignMode.ts
new file mode 100644
index 00000000..e46be01f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useDesignMode.ts
@@ -0,0 +1,915 @@
+import { ref, reactive, computed, provide, inject, onMounted, onBeforeUnmount } from 'vue';
+import { twMerge } from 'tailwind-merge';
+import type {
+ DesignModeMessage,
+ ParentToIframeMessage,
+ IframeToParentMessage,
+ BridgeConfig,
+ ElementInfo,
+ SourceInfo,
+ ToggleDesignModeMessage,
+ UpdateStyleMessage,
+ UpdateContentMessage,
+ BatchUpdateMessage,
+ HeartbeatMessage,
+ HealthCheckMessage,
+ HealthCheckResponseMessage,
+} from '@xagi/design-mode-shared/messages';
+import { bridge, messageValidator } from '@xagi/design-mode-shared/bridge';
+import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
+import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
+import { extractSourceInfo as extractSourceInfoFromAttributes } from '@xagi/design-mode-shared/sourceInfo';
+import { resolveSourceInfo } from '@xagi/design-mode-shared/sourceInfoResolver';
+
+export interface Modification {
+ id: string;
+ element: string;
+ type: 'class' | 'style';
+ oldValue: string;
+ newValue: string;
+ timestamp: number;
+}
+
+export interface DesignModeConfig {
+ enabled?: boolean;
+ iframeMode?: {
+ enabled: boolean;
+ hideUI: boolean;
+ enableSelection: boolean;
+ enableDirectEdit: boolean;
+ };
+ batchUpdate?: {
+ enabled: boolean;
+ debounceMs: number;
+ };
+ bridge?: Partial;
+}
+
+interface DesignModeState {
+ isDesignMode: boolean;
+ selectedElement: HTMLElement | null;
+ modifications: Modification[];
+ isConnected: boolean;
+ bridgeStatus: 'connected' | 'disconnected' | 'connecting' | 'error';
+ config: DesignModeConfig;
+}
+
+const DESIGN_MODE_KEY = Symbol('design-mode');
+
+/**
+ * Vue3 composable for design mode management
+ * Provides global state and actions for design mode functionality
+ */
+export function createDesignMode(userConfig: DesignModeConfig = {}) {
+ // Defaults
+ const defaultConfig: DesignModeConfig = {
+ enabled: true,
+ iframeMode: {
+ enabled: true,
+ hideUI: false,
+ enableSelection: true,
+ enableDirectEdit: true,
+ },
+ batchUpdate: {
+ enabled: true,
+ debounceMs: 300,
+ },
+ bridge: {
+ timeout: 10000,
+ retryAttempts: 3,
+ heartbeatInterval: 30000,
+ debug: process.env.NODE_ENV === 'development',
+ },
+ ...userConfig,
+ };
+
+ // State
+ const state = reactive({
+ isDesignMode: false,
+ selectedElement: null,
+ modifications: [],
+ isConnected: false,
+ bridgeStatus: 'connecting',
+ config: defaultConfig,
+ });
+
+ // Batch debounce state
+ const batchUpdateTimer = ref(null);
+ const pendingBatchUpdates = ref<
+ Array<{
+ element: HTMLElement;
+ type: 'style' | 'content';
+ newValue: string;
+ originalValue?: string;
+ }>
+ >([]);
+
+ // Unsubscribe handlers
+ const unsubscribeHandlers = ref<(() => void)[]>([]);
+
+ /**
+ * postMessage when iframe + connected
+ */
+ const sendToParent = (message: IframeToParentMessage) => {
+ if (!state.config.iframeMode?.enabled) {
+ return;
+ }
+
+ // Use bridge if connected, fallback to direct postMessage
+ if (bridge.isConnected()) {
+ bridge.send(message).catch(error => {
+ if (window.self !== window.top) {
+ window.parent.postMessage(message, '*');
+ }
+ });
+ return;
+ }
+
+ if (window.self !== window.top) {
+ window.parent.postMessage(message, '*');
+ }
+ };
+
+ /**
+ * findElementBySourceInfo
+ */
+ const findElementBySourceInfo = (sourceInfo: SourceInfo): HTMLElement | null => {
+ // 1) element-id
+ if (sourceInfo.elementId) {
+ const element = document.querySelector(`[${AttributeNames.elementId}="${sourceInfo.elementId}"]`);
+ if (element) return element as HTMLElement;
+ }
+
+ // 2) legacy file/line/column attrs
+ const selector = `[${AttributeNames.file}="${sourceInfo.fileName}"][${AttributeNames.line}="${sourceInfo.lineNumber}"][${AttributeNames.column}="${sourceInfo.columnNumber}"]`;
+ const element = document.querySelector(selector);
+ if (element) return element as HTMLElement;
+
+ // 3) children-source
+ const childrenSourceValue = `${sourceInfo.fileName}:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`;
+ const elementByChildrenSource = document.querySelector(`[${AttributeNames.childrenSource}="${childrenSourceValue}"]`);
+ if (elementByChildrenSource) return elementByChildrenSource as HTMLElement;
+
+ // 4) scan all -info
+ const allElements = document.querySelectorAll(`[${AttributeNames.info}]`);
+ for (let i = 0; i < allElements.length; i++) {
+ const el = allElements[i] as HTMLElement;
+ try {
+ const infoStr = el.getAttribute(AttributeNames.info);
+ if (infoStr) {
+ const info = JSON.parse(infoStr);
+ if (
+ info.fileName === sourceInfo.fileName &&
+ info.lineNumber === sourceInfo.lineNumber &&
+ info.columnNumber === sourceInfo.columnNumber
+ ) {
+ return el;
+ }
+ }
+ } catch (e) {
+ // ignore parse error
+ }
+ }
+
+ return null;
+ };
+
+ /**
+ * Parent-driven style patch
+ */
+ const handleExternalStyleUpdate = async (message: UpdateStyleMessage) => {
+ if (!state.config.iframeMode?.enabled) return;
+
+ const updateMessage = message;
+ const { sourceInfo, newClass } = updateMessage.payload;
+
+ try {
+ // validate
+ const validation = messageValidator.validate(updateMessage);
+ if (!validation.isValid) {
+ console.error(
+ '[DesignMode] Invalid style update message:',
+ validation.errors
+ );
+ return;
+ }
+
+ // resolve element
+ const element = findElementBySourceInfo(sourceInfo);
+ if (!element) {
+ console.error(
+ '[DesignMode] Element not found for sourceInfo:',
+ sourceInfo
+ );
+
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'ELEMENT_NOT_FOUND',
+ message: `Element not found: ${sourceInfo.fileName}:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`,
+ details: { sourceInfo },
+ },
+ timestamp: Date.now(),
+ });
+ return;
+ }
+
+ const oldClass = element.className;
+
+ // Same element-id → list sync
+ const elementId = element.getAttribute(AttributeNames.elementId);
+ let relatedElements: HTMLElement[] = [element];
+
+ if (elementId) {
+ // query all [element-id]
+ const allElementsWithId = Array.from(
+ document.querySelectorAll(`[${AttributeNames.elementId}]`)
+ ) as HTMLElement[];
+
+ relatedElements = allElementsWithId.filter(el => {
+ const elId = el.getAttribute(AttributeNames.elementId);
+ return elId === elementId;
+ });
+ } else {
+ console.warn('[DesignMode] Element missing element-id attribute, only updating current element');
+ }
+
+ // Apply class to all peers
+ relatedElements.forEach(el => {
+ el.setAttribute('data-ignore-mutation', 'true');
+ el.className = newClass;
+ // Use setTimeout to ensure MutationObserver sees the attribute
+ setTimeout(() => {
+ el.removeAttribute('data-ignore-mutation');
+ }, 0);
+ });
+
+ // Refresh selection ref
+ if (state.selectedElement === element) {
+ state.selectedElement = element;
+ }
+
+ // STYLE_UPDATED
+ sendToParent({
+ type: 'STYLE_UPDATED',
+ payload: {
+ sourceInfo,
+ oldClass,
+ newClass,
+ },
+ timestamp: Date.now(),
+ });
+
+ } catch (error) {
+ console.error(
+ '[DesignMode] Error handling external style update:',
+ error
+ );
+
+ // ERROR
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'STYLE_UPDATE_FAILED',
+ message: error instanceof Error ? error.message : 'Unknown error',
+ details: { sourceInfo: updateMessage.payload.sourceInfo },
+ },
+ timestamp: Date.now(),
+ });
+ }
+ };
+
+ /**
+ * Parent-driven content patch
+ */
+ const handleExternalContentUpdate = async (message: UpdateContentMessage) => {
+ if (!state.config.iframeMode?.enabled) return;
+
+ const updateMessage = message;
+ const { sourceInfo, newContent } = updateMessage.payload;
+
+ try {
+ // validate
+ const validation = messageValidator.validate(updateMessage);
+ if (!validation.isValid) {
+ console.error(
+ '[DesignMode] Invalid content update message:',
+ validation.errors
+ );
+ return;
+ }
+
+ // resolve element
+ const element = findElementBySourceInfo(sourceInfo);
+ if (!element) {
+ console.error(
+ '[DesignMode] Element not found for sourceInfo:',
+ sourceInfo
+ );
+
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'ELEMENT_NOT_FOUND',
+ message: `Element not found: ${sourceInfo.fileName}:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`,
+ details: { sourceInfo },
+ },
+ timestamp: Date.now(),
+ });
+ return;
+ }
+
+ const originalContent = element.innerText || element.textContent || '';
+
+ // Same element-id → list sync
+ const elementId = element.getAttribute(AttributeNames.elementId);
+ let relatedElements: HTMLElement[] = [element];
+
+ if (elementId) {
+ // query all [element-id]
+ const allElementsWithId = Array.from(
+ document.querySelectorAll(`[${AttributeNames.elementId}]`)
+ ) as HTMLElement[];
+
+ relatedElements = allElementsWithId.filter(el => {
+ const elId = el.getAttribute(AttributeNames.elementId);
+ return elId === elementId;
+ });
+ } else {
+ console.warn('[DesignMode] Element missing element-id attribute, only updating current element');
+ }
+
+ // Apply text to all peers (list sync)
+ relatedElements.forEach(el => {
+ el.setAttribute('data-ignore-mutation', 'true');
+ el.innerText = newContent;
+ // Use setTimeout to ensure MutationObserver sees the attribute
+ setTimeout(() => {
+ el.removeAttribute('data-ignore-mutation');
+ }, 0);
+ });
+
+ // Refresh selection ref
+ if (state.selectedElement === element) {
+ state.selectedElement = element;
+ }
+
+ // CONTENT_UPDATED_CALLBACK
+ sendToParent({
+ type: 'CONTENT_UPDATED_CALLBACK',
+ payload: {
+ sourceInfo,
+ oldValue: originalContent,
+ newValue: newContent,
+ },
+ timestamp: Date.now(),
+ });
+
+ } catch (error) {
+ console.error(
+ '[DesignMode] Error handling external content update:',
+ error
+ );
+
+ // ERROR
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'CONTENT_UPDATE_FAILED',
+ message: error instanceof Error ? error.message : 'Unknown error',
+ details: { sourceInfo: updateMessage.payload.sourceInfo },
+ },
+ timestamp: Date.now(),
+ });
+ }
+ };
+
+ /**
+ * updateSource (HTTP)
+ */
+ const updateSource = async (
+ element: HTMLElement,
+ newValue: string,
+ type: 'style' | 'content',
+ originalValue?: string
+ ) => {
+ const sourceInfo = extractSourceInfo(element);
+ if (!sourceInfo) {
+ throw new Error('Element does not have source mapping data');
+ }
+
+ try {
+ const response = await fetch('/__appdev_design_mode/update', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ filePath: sourceInfo.fileName,
+ line: sourceInfo.lineNumber,
+ column: sourceInfo.columnNumber,
+ newValue,
+ type,
+ originalValue,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to update source');
+ }
+
+ } catch (error) {
+ console.error('[DesignMode] Error updating source:', error);
+ throw error;
+ }
+ };
+
+ /**
+ * Parent BATCH_UPDATE
+ */
+ const handleExternalBatchUpdate = async (message: BatchUpdateMessage) => {
+ const updateMessage = message;
+ const { updates } = updateMessage.payload;
+
+ try {
+ // validate
+ const validation = messageValidator.validate(updateMessage);
+ if (!validation.isValid) {
+ console.error(
+ '[DesignMode] Invalid batch update message:',
+ validation.errors
+ );
+ return;
+ }
+
+ // Promise.allSettled items
+ const results = await Promise.allSettled(
+ updates.map(async (update: any) => {
+ const element = findElementBySourceInfo(update.sourceInfo);
+ if (!element) {
+ throw new Error(
+ `Element not found: ${update.sourceInfo.fileName}:${update.sourceInfo.lineNumber}`
+ );
+ }
+
+ if (update.type === 'style') {
+ element.setAttribute('data-ignore-mutation', 'true');
+ element.className = update.newValue;
+ setTimeout(() => element.removeAttribute('data-ignore-mutation'), 0);
+ } else if (update.type === 'content') {
+ element.setAttribute('data-ignore-mutation', 'true');
+ element.innerText = update.newValue;
+ setTimeout(() => element.removeAttribute('data-ignore-mutation'), 0);
+ }
+
+ await updateSource(
+ element,
+ update.newValue,
+ update.type,
+ update.originalValue
+ );
+
+ return { success: true, sourceInfo: update.sourceInfo };
+ })
+ );
+
+ } catch (error) {
+ console.error('[DesignMode] Error handling batch update:', error);
+
+ // ERROR
+ sendToParent({
+ type: 'ERROR',
+ payload: {
+ code: 'BATCH_UPDATE_FAILED',
+ message: error instanceof Error ? error.message : 'Unknown error',
+ details: { updatesCount: updates?.length || 0 },
+ },
+ timestamp: Date.now(),
+ });
+ }
+ };
+
+ /**
+ * 悬停高亮由 DesignModeApp 通过 data-design-hover 等在 DOM 上处理;
+ * 保留空实现供根组件调用,避免运行时 TypeError,后续可接入状态或埋点。
+ */
+ const setHoveredElement = (_element: HTMLElement | null) => {};
+
+ /**
+ * selectElement
+ */
+ const selectElement = async (element: HTMLElement | null) => {
+ if (element && (element.hasAttribute(AttributeNames.staticContent)
+ || element.hasAttribute(AttributeNames.staticClass))) {
+ state.selectedElement = element;
+ } else {
+ state.selectedElement = null;
+ }
+
+ // iframe: ELEMENT_SELECTED
+ if (element && state.config.iframeMode?.enabled) {
+ const sourceInfo = resolveSourceInfo(element);
+
+ if (sourceInfo) {
+ const hasStaticContentAttr = element.hasAttribute(AttributeNames.staticContent);
+ const isActuallyPureText = isPureStaticText(element);
+ const isStaticText = hasStaticContentAttr && isActuallyPureText;
+
+ const isStaticClass = element.hasAttribute(AttributeNames.staticClass);
+
+ let textContent = '';
+ if (isStaticText) {
+ textContent = element.textContent || element.innerText || '';
+ } else {
+ textContent = element.innerText || element.textContent || '';
+ }
+
+ const elementInfo: ElementInfo = {
+ tagName: element.tagName.toLowerCase(),
+ className: element.className,
+ textContent: textContent,
+ sourceInfo,
+ isStaticText: isStaticText || false,
+ isStaticClass: isStaticClass,
+ };
+
+ sendToParent({
+ type: 'ELEMENT_SELECTED',
+ payload: { elementInfo },
+ timestamp: Date.now(),
+ });
+
+ } else {
+ console.warn(
+ `[DesignMode] Element selected but could not resolve source info:`,
+ element
+ );
+ }
+ } else if (!element && state.config.iframeMode?.enabled) {
+ sendToParent({
+ type: 'ELEMENT_DESELECTED',
+ timestamp: Date.now(),
+ });
+ }
+ };
+
+ /**
+ * toggleDesignMode
+ */
+ const toggleDesignMode = () => {
+ state.isDesignMode = !state.isDesignMode;
+ if (!state.isDesignMode) {
+ state.selectedElement = null;
+ }
+ };
+
+ /**
+ * Local extractSourceInfo helper
+ */
+ const extractSourceInfo = (element: HTMLElement): SourceInfo | null => {
+ return extractSourceInfoFromAttributes(element);
+ };
+
+ /**
+ * modifyElementClass
+ */
+ const modifyElementClass = async (element: HTMLElement, newClass: string) => {
+ const oldClasses = element.className;
+ const mergedClasses = twMerge(oldClasses, newClass);
+
+ // twMerge + DOM
+ element.className = mergedClasses;
+
+ // PATCH /update
+ await updateSource(element, mergedClasses, 'style', oldClasses);
+
+ // modifications list
+ const modification: Modification = {
+ id: Date.now().toString(),
+ element: element.id || 'unknown',
+ type: 'class',
+ oldValue: oldClasses,
+ newValue: mergedClasses,
+ timestamp: Date.now(),
+ };
+
+ state.modifications = [modification, ...state.modifications];
+
+ // STYLE_UPDATED to parent
+ if (state.config.iframeMode?.enabled) {
+ const sourceInfo = extractSourceInfo(element);
+ if (sourceInfo) {
+ sendToParent({
+ type: 'STYLE_UPDATED',
+ payload: {
+ sourceInfo,
+ oldClass: oldClasses,
+ newClass: mergedClasses,
+ },
+ timestamp: Date.now(),
+ });
+ }
+ }
+ };
+
+ /**
+ * updateElementContent
+ */
+ const updateElementContent = async (element: HTMLElement, newContent: string) => {
+ const sourceInfo = extractSourceInfo(element);
+ const originalContent = element.innerText;
+
+ // Update DOM
+ element.innerText = newContent;
+
+ // PATCH /update
+ await updateSource(element, newContent, 'content', originalContent);
+
+ // CONTENT_UPDATED to parent
+ if (state.config.iframeMode?.enabled) {
+ const sourceInfo = extractSourceInfo(element);
+ if (sourceInfo) {
+ sendToParent({
+ type: 'CONTENT_UPDATED',
+ payload: {
+ sourceInfo,
+ oldValue: originalContent,
+ newValue: newContent,
+ },
+ timestamp: Date.now(),
+ });
+ }
+ }
+ };
+
+ /**
+ * batchUpdateElements
+ */
+ const batchUpdateElements = async (
+ updates: Array<{
+ element: HTMLElement;
+ type: 'style' | 'content';
+ newValue: string;
+ originalValue?: string;
+ }>
+ ) => {
+ if (!state.config.batchUpdate?.enabled) {
+ // Sequential fallback
+ await Promise.all(
+ updates.map(update => {
+ if (update.type === 'style') {
+ return modifyElementClass(update.element, update.newValue);
+ } else {
+ return updateElementContent(update.element, update.newValue);
+ }
+ })
+ );
+ return;
+ }
+
+ // Debounced queue
+ const newUpdates = [...pendingBatchUpdates.value, ...updates];
+ pendingBatchUpdates.value = newUpdates;
+
+ // Reset debounce timer
+ if (batchUpdateTimer.value) {
+ clearTimeout(batchUpdateTimer.value);
+ }
+
+ // Schedule flush
+ const timer = setTimeout(async () => {
+ try {
+ // Build payload
+ const batchUpdateItems = newUpdates.map(update => {
+ const sourceInfo = extractSourceInfo(update.element);
+ if (!sourceInfo) {
+ throw new Error('Element missing source mapping');
+ }
+
+ return {
+ type: update.type,
+ sourceInfo,
+ newValue: update.newValue,
+ originalValue: update.originalValue,
+ };
+ });
+
+ // iframe: bridge BATCH_UPDATE
+ if (state.config.iframeMode?.enabled) {
+ await bridge.send({
+ type: 'BATCH_UPDATE',
+ payload: { updates: batchUpdateItems },
+ timestamp: Date.now(),
+ });
+ } else {
+ // top: fetch batch endpoint
+ await fetch('/__appdev_design_mode/batch-update', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ updates: batchUpdateItems,
+ }),
+ });
+ }
+
+ // Clear queue
+ pendingBatchUpdates.value = [];
+ } catch (error) {
+ console.error('[DesignMode] Batch update failed:', error);
+ pendingBatchUpdates.value = [];
+ throw error;
+ }
+ }, state.config.batchUpdate.debounceMs);
+
+ batchUpdateTimer.value = timer;
+ };
+
+ /**
+ * resetModifications
+ */
+ const resetModifications = () => {
+ window.location.reload();
+ };
+
+ /**
+ * sendMessage
+ */
+ const sendMessage = async (message: T) => {
+ if (state.config.iframeMode?.enabled) {
+ await bridge.send(message);
+ }
+ };
+
+ /**
+ * sendMessageWithResponse
+ */
+ const sendMessageWithResponse = async (
+ message: T,
+ responseType: R['type']
+ ): Promise => {
+ if (state.config.iframeMode?.enabled) {
+ return await bridge.sendWithResponse(message, responseType);
+ }
+ throw new Error('Iframe mode is not enabled');
+ };
+
+ /**
+ * healthCheck
+ */
+ const healthCheck = async () => {
+ if (state.config.iframeMode?.enabled) {
+ return await bridge.healthCheck();
+ }
+ return { status: 'healthy', details: { mode: 'standalone' } };
+ };
+
+ /**
+ * Wire iframe bridge listeners
+ */
+ const setupBridge = () => {
+ if (state.config.iframeMode?.enabled) {
+ state.bridgeStatus = 'connecting';
+
+ // Poll isConnected
+ const connectionCheck = setInterval(() => {
+ const connected = bridge.isConnected();
+ state.isConnected = connected;
+ state.bridgeStatus = connected ? 'connected' : 'disconnected';
+ }, 1000);
+
+ // TOGGLE_DESIGN_MODE
+ unsubscribeHandlers.value.push(
+ bridge.on('TOGGLE_DESIGN_MODE', message => {
+ const newState = message.enabled;
+ state.isDesignMode = newState;
+
+ // Echo DESIGN_MODE_CHANGED
+ if (window.self !== window.top) {
+ sendToParent({
+ type: 'DESIGN_MODE_CHANGED',
+ enabled: newState,
+ requestId: message.requestId,
+ timestamp: Date.now(),
+ });
+ }
+ })
+ );
+
+ // UPDATE_STYLE
+ unsubscribeHandlers.value.push(
+ bridge.on('UPDATE_STYLE', async message => {
+ await handleExternalStyleUpdate(message);
+ })
+ );
+
+ // UPDATE_CONTENT
+ unsubscribeHandlers.value.push(
+ bridge.on('UPDATE_CONTENT', async message => {
+ await handleExternalContentUpdate(message);
+ })
+ );
+
+ // BATCH_UPDATE
+ unsubscribeHandlers.value.push(
+ bridge.on('BATCH_UPDATE', async message => {
+ await handleExternalBatchUpdate(message);
+ })
+ );
+
+ // HEARTBEAT
+ unsubscribeHandlers.value.push(
+ bridge.on('HEARTBEAT', _ => {
+ // Echo HEARTBEAT
+ bridge.send({
+ type: 'HEARTBEAT',
+ payload: { timestamp: Date.now() },
+ timestamp: Date.now(),
+ });
+ })
+ );
+
+ // HEALTH_CHECK
+ unsubscribeHandlers.value.push(
+ bridge.on('HEALTH_CHECK', async message => {
+ const healthStatus = await bridge.healthCheck();
+ const response: HealthCheckResponseMessage = {
+ type: 'HEALTH_CHECK_RESPONSE',
+ payload: {
+ status: healthStatus.status === 'healthy' ? 'healthy' : 'unhealthy',
+ version: '2.0.0',
+ uptime: Date.now() - ((window as any).__startTime || 0),
+ },
+ requestId: message.requestId || '',
+ timestamp: Date.now(),
+ };
+ bridge.send(response);
+ })
+ );
+
+ // Initial health probe
+ const initialHealthCheck = setTimeout(async () => {
+ try {
+ const health = await bridge.healthCheck();
+ state.bridgeStatus = health.status === 'healthy' ? 'connected' : 'error';
+ } catch (error) {
+ state.bridgeStatus = 'error';
+ }
+ }, 1000);
+
+ // Cleanup function
+ return () => {
+ clearInterval(connectionCheck);
+ clearTimeout(initialHealthCheck);
+ unsubscribeHandlers.value.forEach(unsubscribe => unsubscribe());
+ };
+ }
+ };
+
+ onMounted(() => {
+ const cleanup = setupBridge();
+ if (cleanup) {
+ onBeforeUnmount(cleanup);
+ }
+ });
+
+ const api = {
+ // State
+ state,
+ isDesignMode: computed(() => state.isDesignMode),
+ selectedElement: computed(() => state.selectedElement),
+ modifications: computed(() => state.modifications),
+ isConnected: computed(() => state.isConnected),
+ bridgeStatus: computed(() => state.bridgeStatus),
+ config: computed(() => state.config),
+
+ // Actions
+ toggleDesignMode,
+ setHoveredElement,
+ selectElement,
+ modifyElementClass,
+ updateElementContent,
+ batchUpdateElements,
+ resetModifications,
+
+ // Bridge helpers
+ sendMessage,
+ sendMessageWithResponse,
+ healthCheck,
+ };
+
+ // Provide for injection
+ provide(DESIGN_MODE_KEY, api);
+
+ return api;
+}
+
+/**
+ * Inject design mode context
+ */
+export function useDesignMode() {
+ const context = inject>(DESIGN_MODE_KEY);
+ if (!context) {
+ throw new Error('useDesignMode must be used within a component that has called createDesignMode');
+ }
+ return context;
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useEditManager.ts b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useEditManager.ts
new file mode 100644
index 00000000..c4f354fd
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useEditManager.ts
@@ -0,0 +1,367 @@
+import { ref } from 'vue';
+import type { UpdateState, UpdateResult, UpdateManagerConfig } from '@xagi/design-mode-shared/types';
+import type { SourceInfo } from '@xagi/design-mode-shared/messages';
+import { extractSourceInfo, hasSourceMapping } from '@xagi/design-mode-shared/sourceInfo';
+import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
+import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
+import { resolveSourceInfo } from '@xagi/design-mode-shared/sourceInfoResolver';
+
+/**
+ * Vue3 composable for edit management
+ * Handles contentEditable mode, real-time sync, and update operations
+ */
+export function useEditManager(
+ processUpdate: (update: UpdateState) => Promise,
+ config: UpdateManagerConfig
+) {
+ const lastRealtimeNotify = ref(0);
+ const REALTIME_THROTTLE_MS = 300;
+
+ /**
+ * Handle direct edit (double click or mutation)
+ */
+ const handleDirectEdit = (element: HTMLElement, type: 'content' | 'style') => {
+ if (type === 'content') {
+ editTextContent(element);
+ } else {
+ editStyle(element);
+ }
+ };
+
+ /**
+ * Edit text content using contentEditable
+ */
+ const editTextContent = async (element: HTMLElement) => {
+ const sourceInfo = resolveSourceInfo(element);
+ if (!sourceInfo) return;
+
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ if (!hasStaticContent) {
+ console.warn('[EditManager] Cannot edit: element does not have static-content attribute. This might be a component definition, not a usage site.');
+ return;
+ }
+
+ const originalText = element.innerText;
+ const originalContentEditable = element.contentEditable;
+
+ element.contentEditable = 'true';
+ element.setAttribute('data-ignore-mutation', 'true');
+
+ const range = document.createRange();
+ range.selectNodeContents(element);
+ const selection = window.getSelection();
+ selection?.removeAllRanges();
+ selection?.addRange(range);
+
+ const handleSave = () => {
+ const newText = element.innerText.trim();
+
+ element.contentEditable = 'false';
+ element.removeAttribute('data-ignore-mutation');
+
+ element.removeEventListener('blur', handleSave);
+ element.removeEventListener('input', handleInput);
+ element.removeEventListener('keydown', handleKeyDown);
+ document.removeEventListener('mousedown', handleClickOutside, true);
+
+ if (newText !== originalText.trim()) {
+ element.innerText = newText;
+
+ const relatedElements = findAllElementsWithSameSource(element, sourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.innerText = newText;
+ }
+ });
+
+ notifyContentChanged(element, newText, sourceInfo, originalText);
+ }
+ };
+
+ const handleCancel = () => {
+ element.innerText = originalText;
+ element.contentEditable = 'false';
+ element.removeAttribute('data-ignore-mutation');
+
+ element.removeEventListener('blur', handleSave);
+ element.removeEventListener('input', handleInput);
+ element.removeEventListener('keydown', handleKeyDown);
+ document.removeEventListener('mousedown', handleClickOutside, true);
+ };
+
+ const handleClickOutside = (e: MouseEvent) => {
+ if (!element.contains(e.target as Node)) {
+ handleSave();
+ }
+ };
+
+ const handleInput = () => {
+ const currentText = element.innerText.trim();
+
+ const relatedElements = findAllElementsWithSameSource(element, sourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.innerText = currentText;
+ }
+ });
+
+ notifyContentChangedRealtime(element, currentText, sourceInfo, originalText);
+ };
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ if (e.ctrlKey || e.metaKey) {
+ return;
+ } else {
+ e.preventDefault();
+ element.blur();
+ }
+ } else if (e.key === 'Escape') {
+ e.preventDefault();
+ handleCancel();
+ }
+ };
+
+ element.addEventListener('blur', handleSave);
+ element.addEventListener('input', handleInput);
+ element.addEventListener('keydown', handleKeyDown);
+ document.addEventListener('mousedown', handleClickOutside, true);
+
+ element.focus();
+ };
+
+ /**
+ * Update content
+ */
+ const updateContent = async (
+ element: HTMLElement,
+ newValue: string,
+ sourceInfo?: SourceInfo,
+ oldValue?: string
+ ): Promise => {
+ const finalSourceInfo = sourceInfo || extractSourceInfo(element);
+ if (!finalSourceInfo) {
+ throw new Error('Cannot update content: no source info available');
+ }
+
+ const finalOldValue = oldValue ?? element.innerText;
+
+ const update: UpdateState = {
+ id: generateUpdateId(),
+ operation: 'content_update',
+ sourceInfo: finalSourceInfo,
+ element,
+ oldValue: finalOldValue,
+ newValue,
+ status: 'pending',
+ timestamp: Date.now(),
+ retryCount: 0,
+ persist: false,
+ };
+
+ const relatedElements = findAllElementsWithSameSource(element, finalSourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.innerText = newValue;
+ }
+ });
+
+ return processUpdate(update);
+ };
+
+ /**
+ * Update style (class)
+ */
+ const updateStyle = async (
+ element: HTMLElement,
+ newClass: string,
+ sourceInfo: SourceInfo
+ ): Promise => {
+ const oldClass = element.className;
+ const finalSourceInfo = sourceInfo || extractSourceInfo(element);
+ if (!finalSourceInfo) {
+ throw new Error('Cannot update style: no source info available');
+ }
+
+ const update: UpdateState = {
+ id: generateUpdateId(),
+ operation: 'class_update',
+ sourceInfo,
+ element,
+ oldValue: oldClass,
+ newValue: newClass,
+ status: 'pending',
+ timestamp: Date.now(),
+ retryCount: 0,
+ persist: false,
+ };
+
+ const relatedElements = findAllElementsWithSameSource(element, finalSourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.className = newClass;
+ }
+ });
+
+ return processUpdate(update);
+ };
+
+ /**
+ * Edit style (trigger UI)
+ */
+ const editStyle = (element: HTMLElement) => {
+ // Placeholder for style editing UI
+ };
+
+ /**
+ * Update attribute
+ */
+ const updateAttribute = async (
+ element: HTMLElement,
+ attributeName: string,
+ newValue: string,
+ sourceInfo: SourceInfo
+ ): Promise => {
+ const oldValue = element.getAttribute(attributeName) || '';
+
+ const update: UpdateState = {
+ id: generateUpdateId(),
+ operation: 'attribute_update',
+ sourceInfo,
+ element,
+ oldValue,
+ newValue,
+ status: 'pending',
+ timestamp: Date.now(),
+ retryCount: 0,
+ persist: false,
+ };
+
+ const relatedElements = findAllElementsWithSameSource(element, sourceInfo);
+ relatedElements.forEach(el => {
+ if (el !== element) {
+ el.setAttribute(attributeName, newValue);
+ }
+ });
+
+ return processUpdate(update);
+ };
+
+ /**
+ * Edit attributes (trigger UI)
+ */
+ const editAttributes = (element: HTMLElement) => {
+ // Placeholder for attribute editing UI
+ };
+
+ const generateUpdateId = (): string => {
+ return `update-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+ };
+
+ /**
+ * Notify parent of final text (iframe only); does not write source files.
+ */
+ const notifyContentChanged = (
+ element: HTMLElement,
+ newValue: string,
+ sourceInfo?: SourceInfo,
+ oldValue?: string
+ ): void => {
+ const finalSourceInfo = sourceInfo || resolveSourceInfo(element);
+ if (!finalSourceInfo) {
+ console.warn('[EditManager] Cannot notify: no source info available');
+ return;
+ }
+
+ if (window.self !== window.top) {
+ window.parent.postMessage({
+ type: 'CONTENT_UPDATED',
+ payload: {
+ sourceInfo: finalSourceInfo,
+ oldValue: oldValue || '',
+ newValue: newValue,
+ },
+ timestamp: Date.now(),
+ }, '*');
+ }
+ };
+
+ /**
+ * Throttled CONTENT_UPDATED while typing (realtime: true).
+ */
+ const notifyContentChangedRealtime = (
+ element: HTMLElement,
+ newValue: string,
+ sourceInfo?: SourceInfo,
+ oldValue?: string
+ ): void => {
+ const now = Date.now();
+
+ if (now - lastRealtimeNotify.value < REALTIME_THROTTLE_MS) {
+ return;
+ }
+
+ lastRealtimeNotify.value = now;
+
+ const finalSourceInfo = sourceInfo || resolveSourceInfo(element);
+ if (!finalSourceInfo) {
+ return;
+ }
+
+ if (window.self !== window.top) {
+ window.parent.postMessage({
+ type: 'CONTENT_UPDATED',
+ payload: {
+ sourceInfo: finalSourceInfo,
+ oldValue: oldValue || '',
+ newValue: newValue,
+ realtime: true,
+ },
+ timestamp: Date.now(),
+ }, '*');
+ }
+ };
+
+ /**
+ * List peers that share the same logical instance (lists): same `element-id`,
+ * same static-content presence, same mapped file.
+ */
+ const findAllElementsWithSameSource = (element: HTMLElement, sourceInfo?: SourceInfo): HTMLElement[] => {
+ const elementId = element.getAttribute(AttributeNames.elementId);
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ const refSourceInfo = extractSourceInfo(element);
+ const refFileName = refSourceInfo?.fileName;
+
+ if (!elementId) {
+ console.warn('[EditManager] Element missing element-id attribute:', element);
+ return [element];
+ }
+
+ const allElementsWithId = Array.from(
+ document.querySelectorAll(`[${AttributeNames.elementId}]`)
+ ) as HTMLElement[];
+
+ return allElementsWithId.filter(el => {
+ const elId = el.getAttribute(AttributeNames.elementId);
+ const elHasStaticContent = el.hasAttribute(AttributeNames.staticContent);
+ const elSourceInfo = extractSourceInfo(el);
+ const elFileName = elSourceInfo?.fileName;
+
+ if (elId !== elementId) return false;
+ if (elHasStaticContent !== hasStaticContent) return false;
+ if (elFileName !== refFileName) return false;
+
+ return true;
+ });
+ };
+
+ return {
+ handleDirectEdit,
+ editTextContent,
+ updateContent,
+ updateStyle,
+ editStyle,
+ updateAttribute,
+ editAttributes,
+ };
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useObserverManager.ts b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useObserverManager.ts
new file mode 100644
index 00000000..f569b9ad
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useObserverManager.ts
@@ -0,0 +1,98 @@
+import { ref, onMounted, onBeforeUnmount } from 'vue';
+import { hasSourceMapping } from '@xagi/design-mode-shared/sourceInfo';
+
+/**
+ * Vue3 composable for MutationObserver management
+ * Watches DOM changes and triggers callbacks for content/style edits
+ */
+export function useObserverManager(
+ onEdit: (target: HTMLElement, type: 'content' | 'style') => void,
+ onNodeAdded: (node: HTMLElement) => void
+) {
+ const observer = ref(null);
+ const isEnabled = ref(false);
+
+ const enable = () => {
+ if (observer.value) return;
+
+ observer.value = new MutationObserver(mutations => {
+ mutations.forEach(mutation => {
+ // Check if the mutation should be ignored
+ const targetNode = mutation.target;
+ const targetElement = targetNode.nodeType === Node.ELEMENT_NODE
+ ? targetNode as HTMLElement
+ : targetNode.parentElement;
+
+ if (targetElement && targetElement.hasAttribute('data-ignore-mutation')) {
+ return;
+ }
+
+ if (mutation.type === 'childList') {
+ // Handle element addition
+ mutation.addedNodes.forEach(node => {
+ if (node.nodeType === Node.ELEMENT_NODE) {
+ onNodeAdded(node as HTMLElement);
+ }
+ });
+ } else if (mutation.type === 'characterData') {
+ // Handle text content change
+ const target = mutation.target.parentElement as HTMLElement;
+ if (target && hasSourceMapping(target)) {
+ if (target.hasAttribute('data-ignore-mutation')) {
+ return;
+ }
+ onEdit(target, 'content');
+ }
+ } else if (mutation.type === 'attributes') {
+ // Handle attribute change (style, class)
+ const target = mutation.target as HTMLElement;
+ if (target && hasSourceMapping(target)) {
+ const attributeName = mutation.attributeName;
+ const newValue = target.getAttribute(attributeName!);
+ const oldValue = mutation.oldValue;
+
+ if (newValue === oldValue) {
+ return;
+ }
+
+ if (attributeName === 'class') {
+ onEdit(target, 'style');
+ } else if (attributeName?.startsWith('style')) {
+ onEdit(target, 'style');
+ }
+ }
+ }
+ });
+ });
+
+ observer.value.observe(document.body, {
+ childList: true,
+ subtree: true,
+ characterData: true,
+ attributes: true,
+ attributeOldValue: true,
+ attributeFilter: ['class', 'style'],
+ });
+
+ isEnabled.value = true;
+ };
+
+ const disable = () => {
+ if (observer.value) {
+ observer.value.disconnect();
+ observer.value = null;
+ isEnabled.value = false;
+ }
+ };
+
+ onBeforeUnmount(() => {
+ disable();
+ });
+
+ return {
+ observer,
+ isEnabled,
+ enable,
+ disable,
+ };
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useSelectionManager.ts b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useSelectionManager.ts
new file mode 100644
index 00000000..0d509022
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useSelectionManager.ts
@@ -0,0 +1,539 @@
+import { ref, onMounted, onBeforeUnmount } from 'vue';
+import type { ElementInfo, SourceInfo } from '@xagi/design-mode-shared/messages';
+import { AttributeNames, isSourceMappingAttribute } from '@xagi/design-mode-shared/attributeNames';
+import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
+import { extractSourceInfo as extractSourceInfoFromAttributes } from '@xagi/design-mode-shared/sourceInfo';
+
+export interface SelectionManagerConfig {
+ enableSelection: boolean;
+ enableHover: boolean;
+ selectionDelay: number;
+ excludeSelectors: string[];
+ includeOnlyElements: boolean;
+}
+
+/**
+ * Vue3 composable for selection management
+ * Handles click/hover selection for mapped elements
+ */
+export function useSelectionManager(
+ container: HTMLElement,
+ config: SelectionManagerConfig = {
+ enableSelection: true,
+ enableHover: true,
+ selectionDelay: 0,
+ excludeSelectors: [
+ 'script',
+ 'style',
+ 'meta',
+ 'link',
+ 'head',
+ 'title',
+ 'html',
+ 'body',
+ '[data-selection-exclude="true"]',
+ '.no-selection'
+ ],
+ includeOnlyElements: false
+ }
+) {
+ const selectedElement = ref(null);
+ const hoverElement = ref(null);
+ const isSelecting = ref(false);
+ const selectionStartTime = ref(0);
+ const preventNextClick = ref(false);
+ const selectionListeners = new Set<(element: HTMLElement | null) => void>();
+
+ /**
+ * Whether element may be selected (mapped + static-content or static-class)
+ */
+ const isValidElement = (element: HTMLElement): boolean => {
+ if (!element || !element.tagName) return false;
+
+ // Exclude context menu
+ if (element.closest(`[${AttributeNames.contextMenu}="true"]`)) return false;
+
+ // Exclusion list
+ for (const selector of config.excludeSelectors) {
+ if (element.matches(selector) || element.closest(selector)) {
+ return false;
+ }
+ }
+
+ // Optional tag whitelist
+ if (config.includeOnlyElements) {
+ const validElements = ['DIV', 'SPAN', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BUTTON', 'A'];
+ if (!validElements.includes(element.tagName)) {
+ return false;
+ }
+ }
+
+ // Must have -info JSON
+ if (!element.hasAttribute(AttributeNames.info)) {
+ return false;
+ }
+
+ // Need static-content or static-class
+ const hasStaticContent = element.hasAttribute(AttributeNames.staticContent);
+ const hasStaticClass = element.hasAttribute(AttributeNames.staticClass);
+
+ if (!hasStaticContent && !hasStaticClass) {
+ return false;
+ }
+
+ return true;
+ };
+
+ /**
+ * Apply selection highlight
+ */
+ const selectElement = (element: HTMLElement) => {
+ if (selectedElement.value === element) return;
+
+ // Clear previous outline
+ if (selectedElement.value) {
+ clearElementHighlighting(selectedElement.value);
+ }
+
+ selectedElement.value = element;
+
+ // Highlight new node
+ highlightElement(element);
+
+ // Notify subscribers
+ selectionListeners.forEach(listener => listener(element));
+ };
+
+ /**
+ * clearSelection
+ */
+ const clearSelection = () => {
+ if (selectedElement.value) {
+ clearElementHighlighting(selectedElement.value);
+ selectedElement.value = null;
+ selectionListeners.forEach(listener => listener(null));
+ }
+ };
+
+ /**
+ * highlightElement
+ */
+ const highlightElement = (element: HTMLElement) => {
+ // Restore prior inline styles if any
+ const existingHighlight = element.getAttribute('data-selection-highlight');
+ if (existingHighlight) {
+ const existingStyles = JSON.parse(existingHighlight);
+ Object.entries(existingStyles).forEach(([property, value]) => {
+ (element.style as any)[property] = value;
+ });
+ }
+
+ // Snapshot pre-highlight
+ const originalStyles = {
+ outline: element.style.outline,
+ boxShadow: element.style.boxShadow,
+ backgroundColor: element.style.backgroundColor,
+ cursor: element.style.cursor
+ };
+
+ // Selection chrome
+ element.style.outline = '2px solid #007acc';
+ element.style.boxShadow = '0 0 0 2px rgba(0, 122, 204, 0.3)';
+ element.style.backgroundColor = 'rgba(0, 122, 204, 0.1)';
+ element.style.cursor = 'pointer';
+
+ // Persist snapshot on data-selection-highlight
+ element.setAttribute('data-selection-highlight', JSON.stringify(originalStyles));
+ };
+
+ /**
+ * clearElementHighlighting
+ */
+ const clearElementHighlighting = (element: HTMLElement) => {
+ const highlightData = element.getAttribute('data-selection-highlight');
+ if (highlightData) {
+ try {
+ const originalStyles = JSON.parse(highlightData);
+ Object.entries(originalStyles).forEach(([property, value]) => {
+ (element.style as any)[property] = value;
+ });
+ element.removeAttribute('data-selection-highlight');
+ } catch (e) {
+ console.warn('[SelectionManager] Failed to restore original styles:', e);
+ }
+ }
+ };
+
+ /**
+ * onHoverElement
+ */
+ const onHoverElement = (element: HTMLElement | null) => {
+ // Hook for hover UX
+ };
+
+ /**
+ * selectAll (first candidate only)
+ */
+ const selectAll = () => {
+ const selectableElements = Array.from(
+ container.querySelectorAll('*')
+ ).filter(el => isValidElement(el as HTMLElement));
+
+ if (selectableElements.length > 0) {
+ selectElement(selectableElements[0] as HTMLElement);
+ }
+ };
+
+ /**
+ * Click handler
+ */
+ const handleClick = (event: MouseEvent) => {
+ if (!config.enableSelection) return;
+ if (preventNextClick.value) {
+ event.preventDefault();
+ event.stopPropagation();
+ preventNextClick.value = false;
+ return;
+ }
+
+ const target = event.target as HTMLElement;
+ if (!isValidElement(target)) return;
+
+ // Optional selectionDelay
+ if (config.selectionDelay > 0) {
+ setTimeout(() => {
+ selectElement(target);
+ }, config.selectionDelay);
+ } else {
+ selectElement(target);
+ }
+ };
+
+ /**
+ * mousedown
+ */
+ const handleMouseDown = (event: MouseEvent) => {
+ const target = event.target as HTMLElement;
+ if (!isValidElement(target)) return;
+
+ isSelecting.value = true;
+ selectionStartTime.value = Date.now();
+ preventNextClick.value = false;
+ };
+
+ /**
+ * mouseup
+ */
+ const handleMouseUp = (event: MouseEvent) => {
+ if (!isSelecting.value) return;
+
+ const target = event.target as HTMLElement;
+ const duration = Date.now() - selectionStartTime.value;
+
+ // Long-press (>500ms) suppresses click selection
+ if (duration > 500) {
+ preventNextClick.value = true;
+ }
+
+ isSelecting.value = false;
+ };
+
+ /**
+ * mouseenter
+ */
+ const handleMouseEnter = (event: MouseEvent) => {
+ if (!config.enableHover) return;
+
+ const target = event.target as HTMLElement;
+ if (!isValidElement(target)) return;
+
+ hoverElement.value = target;
+ onHoverElement(target);
+ };
+
+ /**
+ * mouseleave
+ */
+ const handleMouseLeave = (event: MouseEvent) => {
+ if (!config.enableHover) return;
+
+ const target = event.target as HTMLElement;
+ if (hoverElement.value === target) {
+ hoverElement.value = null;
+ onHoverElement(null);
+ }
+ };
+
+ /**
+ * keydown
+ */
+ const handleKeyDown = (event: KeyboardEvent) => {
+ // Esc clears selection
+ if (event.key === 'Escape') {
+ clearSelection();
+ }
+
+ // Ctrl/Cmd+A: select first match
+ if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
+ event.preventDefault();
+ selectAll();
+ }
+
+ // Ctrl/Cmd+D: clear
+ if ((event.ctrlKey || event.metaKey) && event.key === 'd') {
+ event.preventDefault();
+ clearSelection();
+ }
+ };
+
+ /**
+ * keyup
+ */
+ const handleKeyUp = (event: KeyboardEvent) => {
+ // noop
+ };
+
+ /**
+ * extractSourceInfo
+ */
+ const extractSourceInfo = (element: HTMLElement): SourceInfo | null => {
+ return extractSourceInfoFromAttributes(element);
+ };
+
+ /**
+ * Walk up for library component root
+ */
+ const findComponentRoot = (element: HTMLElement): HTMLElement => {
+ const sourceInfo = extractSourceInfo(element);
+ if (!sourceInfo || !sourceInfo.fileName) return element;
+
+ // Heuristic: components/ui or common
+ const isLibraryComponent = sourceInfo.fileName.includes('/components/ui/') ||
+ sourceInfo.fileName.includes('/components/common/');
+
+ if (!isLibraryComponent) return element;
+
+ // Stop at file boundary
+ let current = element;
+ let componentRoot = element;
+
+ while (current.parentElement) {
+ const parent = current.parentElement;
+ const parentSourceInfo = extractSourceInfo(parent);
+
+ // Boundary: different file or no mapping
+ if (!parentSourceInfo || parentSourceInfo.fileName !== sourceInfo.fileName) {
+ componentRoot = current;
+ break;
+ }
+
+ current = parent;
+ }
+
+ return componentRoot;
+ };
+
+ /**
+ * extractElementInfo
+ */
+ const extractElementInfo = (element: HTMLElement): ElementInfo | null => {
+ if (!element) return null;
+
+ // Prefer library root
+ const targetElement = findComponentRoot(element);
+ const sourceInfo = extractSourceInfo(targetElement);
+
+ if (!sourceInfo) {
+ console.warn('[SelectionManager] Element has no source mapping:', targetElement);
+ return null;
+ }
+
+ // Layout (rect unused below — kept for future)
+ const rect = targetElement.getBoundingClientRect();
+ const computedStyle = window.getComputedStyle(targetElement);
+
+ // Static text flags
+ const hasStaticContentAttr = targetElement.hasAttribute(AttributeNames.staticContent);
+ const isActuallyPureText = isPureStaticText(targetElement);
+ const isStaticText = hasStaticContentAttr && isActuallyPureText;
+
+ const isStaticClass = targetElement.hasAttribute(AttributeNames.staticClass);
+
+ // Text snapshot
+ let textContent = '';
+ if (isStaticText) {
+ textContent = getElementTextContent(targetElement);
+ } else {
+ textContent = targetElement.innerText || targetElement.textContent || '';
+ }
+
+ // Ancestor chain with mappings
+ const hierarchy: { tagName: string; componentName?: string; fileName?: string }[] = [];
+ let current: HTMLElement | null = targetElement;
+ while (current && current !== document.body) {
+ const info = extractSourceInfo(current);
+ if (info) {
+ hierarchy.push({
+ tagName: current.tagName.toLowerCase(),
+ componentName: info.componentName,
+ fileName: info.fileName
+ });
+ }
+ current = current.parentElement;
+ }
+
+ // DOM attrs as pseudo-props
+ const props = getElementAttributes(targetElement);
+
+ return {
+ tagName: targetElement.tagName.toLowerCase(),
+ className: targetElement.className || '',
+ textContent: textContent,
+ sourceInfo,
+ isStaticText: isStaticText || false,
+ isStaticClass: isStaticClass,
+ componentName: sourceInfo.componentName,
+ componentPath: sourceInfo.fileName,
+ props,
+ hierarchy
+ };
+ };
+
+ /**
+ * getElementTextContent
+ */
+ const getElementTextContent = (element: HTMLElement): string => {
+ let textContent = element.textContent || '';
+
+ // Truncate preview
+ if (textContent.length > 100) {
+ textContent = textContent.substring(0, 100) + '...';
+ }
+
+ return textContent.trim();
+ };
+
+ /**
+ * getElementAttributes
+ */
+ const getElementAttributes = (element: HTMLElement): Record => {
+ const attributes: Record = {};
+ const elementAttributes = Array.from(element.attributes);
+
+ elementAttributes.forEach(attr => {
+ // Strip mapping + selection attrs
+ if (!isSourceMappingAttribute(attr.name) &&
+ !attr.name.startsWith('data-selection-')) {
+ attributes[attr.name] = attr.value;
+ }
+ });
+
+ return attributes;
+ };
+
+ /**
+ * getElementDomPath
+ */
+ const getElementDomPath = (element: HTMLElement): string => {
+ const path: string[] = [];
+ let current: HTMLElement | null = element;
+
+ while (current && current !== container) {
+ let selector = current.tagName.toLowerCase();
+
+ if (current.id) {
+ selector += `#${current.id}`;
+ path.unshift(selector);
+ break;
+ }
+
+ if (current.className) {
+ const classes = Array.from(current.classList).slice(0, 3);
+ selector += `.${classes.join('.')}`;
+ }
+
+ // nth-of-type disambiguation
+ const siblings = Array.from(current.parentNode?.children || []);
+ const sameTagSiblings = siblings.filter(sibling =>
+ sibling.tagName === current!.tagName
+ );
+
+ if (sameTagSiblings.length > 1) {
+ const index = sameTagSiblings.indexOf(current);
+ selector += `:nth-of-type(${index + 1})`;
+ }
+
+ path.unshift(selector);
+ current = current.parentElement;
+ }
+
+ return path.join(' > ');
+ };
+
+ /**
+ * addSelectionListener
+ */
+ const addSelectionListener = (listener: (element: HTMLElement | null) => void) => {
+ selectionListeners.add(listener);
+ return () => selectionListeners.delete(listener);
+ };
+
+ /**
+ * Initialize event listeners
+ */
+ const initializeEventListeners = () => {
+ if (!config.enableSelection) return;
+
+ container.addEventListener('click', handleClick, true);
+ container.addEventListener('mousedown', handleMouseDown, true);
+ container.addEventListener('mouseup', handleMouseUp, true);
+
+ if (config.enableHover) {
+ container.addEventListener('mouseenter', handleMouseEnter, true);
+ container.addEventListener('mouseleave', handleMouseLeave, true);
+ }
+
+ // Keyboard shortcuts when selection enabled
+ if (config.enableSelection) {
+ document.addEventListener('keydown', handleKeyDown);
+ document.addEventListener('keyup', handleKeyUp);
+ }
+ };
+
+ /**
+ * destroy
+ */
+ const destroy = () => {
+ clearSelection();
+
+ container.removeEventListener('click', handleClick, true);
+ container.removeEventListener('mousedown', handleMouseDown, true);
+ container.removeEventListener('mouseup', handleMouseUp, true);
+ container.removeEventListener('mouseenter', handleMouseEnter, true);
+ container.removeEventListener('mouseleave', handleMouseLeave, true);
+ document.removeEventListener('keydown', handleKeyDown);
+ document.removeEventListener('keyup', handleKeyUp);
+
+ selectionListeners.clear();
+ };
+
+ onMounted(() => {
+ initializeEventListeners();
+ });
+
+ onBeforeUnmount(() => {
+ destroy();
+ });
+
+ return {
+ selectedElement,
+ hoverElement,
+ isSelecting,
+ selectElement,
+ clearSelection,
+ extractElementInfo,
+ addSelectionListener,
+ getSelectedElement: () => selectedElement.value,
+ getHoverElement: () => hoverElement.value,
+ };
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useToast.ts b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useToast.ts
new file mode 100644
index 00000000..194ce63f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/composables/useToast.ts
@@ -0,0 +1,50 @@
+import { ref } from 'vue';
+
+export interface ToastItem {
+ id: string;
+ message: string;
+ type: 'success' | 'error' | 'info';
+ duration: number;
+}
+
+const toasts = ref([]);
+
+export function useToast() {
+ const show = (message: string, type: 'success' | 'error' | 'info' = 'info', duration = 3000) => {
+ const id = Date.now().toString() + Math.random().toString(36).substr(2, 9);
+ const toast: ToastItem = { id, message, type, duration };
+ toasts.value.push(toast);
+
+ setTimeout(() => {
+ remove(id);
+ }, duration + 300); // Add animation time
+ };
+
+ const remove = (id: string) => {
+ const index = toasts.value.findIndex(t => t.id === id);
+ if (index > -1) {
+ toasts.value.splice(index, 1);
+ }
+ };
+
+ const error = (message: string, duration = 4000) => {
+ show(message, 'error', duration);
+ };
+
+ const success = (message: string, duration = 3000) => {
+ show(message, 'success', duration);
+ };
+
+ const info = (message: string, duration = 3000) => {
+ show(message, 'info', duration);
+ };
+
+ return {
+ toasts,
+ show,
+ remove,
+ error,
+ success,
+ info,
+ };
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/exports.ts b/qiming-vite-plugin-design-mode/packages/client-vue/src/exports.ts
new file mode 100644
index 00000000..eed9ab8b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/exports.ts
@@ -0,0 +1,7 @@
+export { useDesignMode, createDesignMode } from './composables/useDesignMode';
+export { useToast } from './composables/useToast';
+export { default as DesignModeApp } from './DesignModeApp.vue';
+export { default as DesignModeUI } from './components/DesignModeUI.vue';
+export { default as ContextMenu } from './components/ContextMenu.vue';
+export { default as Toast } from './components/Toast.vue';
+export { default as ToastContainer } from './components/ToastContainer.vue';
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/index.ts b/qiming-vite-plugin-design-mode/packages/client-vue/src/index.ts
new file mode 100644
index 00000000..77c0eec6
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/index.ts
@@ -0,0 +1,32 @@
+import { createApp } from 'vue';
+import DesignModeApp from './DesignModeApp.vue';
+
+const init = () => {
+ const containerId = '__vite_plugin_design_mode__';
+ if (document.getElementById(containerId)) return;
+
+ const container = document.createElement('div');
+ container.id = containerId;
+ document.body.appendChild(container);
+
+ // Use Shadow DOM to isolate styles
+ const shadowRoot = container.attachShadow({ mode: 'open' });
+ const rootElement = document.createElement('div');
+ shadowRoot.appendChild(rootElement);
+
+ // Create Vue app
+ const app = createApp(DesignModeApp);
+ app.mount(rootElement);
+};
+
+function isInIframe() {
+ try {
+ return window.self !== window.top;
+ } catch (e) {
+ return true;
+ }
+}
+
+if (typeof window !== 'undefined') {
+ init();
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/src/shims-vue.d.ts b/qiming-vite-plugin-design-mode/packages/client-vue/src/shims-vue.d.ts
new file mode 100644
index 00000000..40556b9d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/src/shims-vue.d.ts
@@ -0,0 +1,5 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue';
+ const component: DefineComponent, Record, unknown>;
+ export default component;
+}
diff --git a/qiming-vite-plugin-design-mode/packages/client-vue/tsconfig.json b/qiming-vite-plugin-design-mode/packages/client-vue/tsconfig.json
new file mode 100644
index 00000000..0ebbd7d4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/client-vue/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "baseUrl": ".",
+ "paths": {},
+ "declaration": true,
+ "declarationMap": true,
+ "composite": true,
+ "jsx": "preserve"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/package.json b/qiming-vite-plugin-design-mode/packages/plugin/package.json
new file mode 100644
index 00000000..74f8c04c
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@xagi/vite-plugin-design-mode",
+ "version": "1.2.0",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ }
+ },
+ "files": [
+ "dist"
+ ],
+ "bin": {
+ "vite-plugin-design-mode": "dist/cli/index.js"
+ },
+ "scripts": {
+ "build": "tsc"
+ },
+ "dependencies": {
+ "@xagi/design-mode-client-react": "1.2.0",
+ "@xagi/design-mode-client-vue": "1.2.0",
+ "@xagi/design-mode-shared": "1.2.0",
+ "@babel/standalone": "^7.23.0",
+ "@babel/traverse": "^7.24.0",
+ "@babel/types": "7.29.0",
+ "@vue/compiler-dom": "^3.5.32",
+ "@vue/compiler-sfc": "^3.5.32"
+ },
+ "peerDependencies": {
+ "vite": "^4.0.0 || ^5.0.0"
+ },
+ "devDependencies": {
+ "@types/babel__core": "^7.20.0",
+ "@types/babel__standalone": "^7.1.9",
+ "@types/babel__traverse": "^7.20.0",
+ "@types/node": "^20.0.0",
+ "typescript": "^5.0.0",
+ "vite": "^5.0.0"
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/cli/index.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/cli/index.ts
new file mode 100644
index 00000000..e9adcf07
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/cli/index.ts
@@ -0,0 +1,51 @@
+#!/usr/bin/env node
+
+/**
+ * CLI entry: parse argv and dispatch install / uninstall.
+ */
+
+import { main as installMain } from './install.js';
+import { main as uninstallMain } from './uninstall.js';
+
+const command = process.argv[2];
+
+switch (command) {
+ case 'install':
+ installMain();
+ break;
+ case 'uninstall':
+ uninstallMain();
+ break;
+ case undefined:
+ case '--help':
+ case '-h':
+ console.log(`
+@xagi/vite-plugin-design-mode CLI
+
+Usage:
+ npx @xagi/vite-plugin-design-mode
+ pnpm dlx @xagi/vite-plugin-design-mode
+
+Commands:
+ install Add the plugin to package.json and vite.config
+ uninstall Remove the plugin from package.json and vite.config
+
+Notes:
+ - Only edits config files; it does not run your package manager.
+ - After install/uninstall, run install/remove yourself to sync node_modules.
+
+Examples:
+ pnpm dlx @xagi/vite-plugin-design-mode install
+ npx @xagi/vite-plugin-design-mode install
+
+ pnpm dlx @xagi/vite-plugin-design-mode uninstall
+ npx @xagi/vite-plugin-design-mode uninstall
+
+More info: https://www.npmjs.com/package/@xagi/vite-plugin-design-mode
+`);
+ break;
+ default:
+ console.error(`✗ Unknown command: ${command}`);
+ console.error('Run with --help to see available commands.');
+ process.exit(1);
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/cli/install.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/cli/install.ts
new file mode 100644
index 00000000..8f77aacc
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/cli/install.ts
@@ -0,0 +1,336 @@
+#!/usr/bin/env node
+
+/**
+ * Install flow: add devDependency + wire `appdevDesignMode()` in Vite config.
+ */
+
+import { existsSync, readFileSync, writeFileSync } from 'fs';
+import { join, resolve, dirname } from 'path';
+import { fileURLToPath } from 'url';
+
+const PLUGIN_NAME = '@xagi/vite-plugin-design-mode';
+const VITE_CONFIG_FILES = ['vite.config.ts', 'vite.config.js', 'vite.config.mjs'];
+
+/**
+ * Read plugin version by walking up from this file until the package named PLUGIN_NAME is found.
+ */
+function getPluginVersion(): string {
+ try {
+ const __filename = fileURLToPath(import.meta.url);
+ const __dirname = dirname(__filename);
+ let currentDir = resolve(__dirname);
+ const root = resolve('/');
+ let depth = 0;
+ const maxDepth = 5;
+
+ while (currentDir !== root && depth < maxDepth) {
+ const packageJsonPath = join(currentDir, 'package.json');
+ if (existsSync(packageJsonPath)) {
+ try {
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
+ if (packageJson.name === PLUGIN_NAME && packageJson.version) {
+ return packageJson.version;
+ }
+ } catch (_error) {
+ // Invalid JSON — keep walking
+ }
+ }
+ currentDir = dirname(currentDir);
+ depth++;
+ }
+ } catch (_error) {
+ // Fallback to latest below
+ }
+
+ return 'latest';
+}
+
+/** Walk up from startDir until package.json exists. */
+function findProjectRoot(startDir: string = process.cwd()): string {
+ let currentDir = resolve(startDir);
+ const root = resolve('/');
+
+ while (currentDir !== root) {
+ const packageJsonPath = join(currentDir, 'package.json');
+ if (existsSync(packageJsonPath)) {
+ return currentDir;
+ }
+ currentDir = dirname(currentDir);
+ }
+
+ return startDir;
+}
+
+interface PackageJson {
+ dependencies?: Record;
+ devDependencies?: Record;
+ packageManager?: string;
+}
+
+function getDependencyVersion(packageJson: PackageJson, depName: string): string | undefined {
+ return packageJson.dependencies?.[depName] || packageJson.devDependencies?.[depName];
+}
+
+/** package.json lists vite */
+function hasVite(packageJson: PackageJson): boolean {
+ return !!packageJson.dependencies?.vite || !!packageJson.devDependencies?.vite;
+}
+
+/** package.json lists react */
+function hasReact(packageJson: PackageJson): boolean {
+ return !!packageJson.dependencies?.react || !!packageJson.devDependencies?.react;
+}
+
+/** package.json lists vue */
+function hasVue(packageJson: PackageJson): boolean {
+ return !!getDependencyVersion(packageJson, 'vue');
+}
+
+/**
+ * Returns false only when we can confidently identify a Vue 2 range.
+ * Unknown or non-semver formats (workspace:, github:, file:) are treated as supported.
+ */
+function isVue3Version(version: string): boolean {
+ const normalized = version.trim();
+ if (!normalized) return true;
+
+ if (/(^|[^\d])2\./.test(normalized) || /(^|[^\d])\^?~?2(\D|$)/.test(normalized)) {
+ return false;
+ }
+
+ if (/(^|[^\d])3\./.test(normalized) || /(^|[^\d])\^?~?3(\D|$)/.test(normalized)) {
+ return true;
+ }
+
+ return true;
+}
+
+/** PLUGIN_NAME present in deps */
+function isPluginInstalled(packageJson: PackageJson): boolean {
+ return !!packageJson.dependencies?.[PLUGIN_NAME] || !!packageJson.devDependencies?.[PLUGIN_NAME];
+}
+
+function addPluginToPackageJson(packageJson: PackageJson, version: string): PackageJson {
+ if (!packageJson.devDependencies) {
+ packageJson.devDependencies = {};
+ }
+ packageJson.devDependencies[PLUGIN_NAME] = `^${version}`;
+ return packageJson;
+}
+
+/** First existing vite.config.{ts,js,mjs} */
+function findViteConfig(projectRoot: string): string | null {
+ for (const file of VITE_CONFIG_FILES) {
+ const configPath = join(projectRoot, file);
+ if (existsSync(configPath)) {
+ return configPath;
+ }
+ }
+ return null;
+}
+
+function hasImport(content: string): boolean {
+ const importPatterns = [
+ /import\s+appdevDesignMode\s+from\s+['"]@xagi\/vite-plugin-design-mode['"]/,
+ /import\s+\{\s*default\s+as\s+appdevDesignMode\s*\}\s+from\s+['"]@xagi\/vite-plugin-design-mode['"]/,
+ ];
+ return importPatterns.some(pattern => pattern.test(content));
+}
+
+function hasPluginConfig(content: string): boolean {
+ const pluginPatterns = [/appdevDesignMode\s*\(/, /appdevDesignMode\s*\(\s*\{/];
+ return pluginPatterns.some(pattern => pattern.test(content));
+}
+
+function addImport(content: string): string {
+ if (hasImport(content)) {
+ return content;
+ }
+
+ const importRegex = /^import\s+.*?from\s+['"].*?['"];?$/gm;
+ const imports = content.match(importRegex);
+
+ if (imports && imports.length > 0) {
+ const lastImport = imports[imports.length - 1];
+ const lastImportIndex = content.lastIndexOf(lastImport);
+ const insertIndex = lastImportIndex + lastImport.length;
+ const useSingleQuote = lastImport.includes("'");
+ const quote = useSingleQuote ? "'" : '"';
+
+ const newImport = `\nimport appdevDesignMode from ${quote}@xagi/vite-plugin-design-mode${quote};`;
+ return content.slice(0, insertIndex) + newImport + content.slice(insertIndex);
+ }
+
+ const useSingleQuote = content.includes("'");
+ const quote = useSingleQuote ? "'" : '"';
+ return `import appdevDesignMode from ${quote}@xagi/vite-plugin-design-mode${quote};\n${content}`;
+}
+
+function addPluginConfig(content: string): string {
+ if (hasPluginConfig(content)) {
+ return content.replace(/appdevDesignMode\s*\([^)]*\)/g, 'appdevDesignMode()');
+ }
+
+ const pluginsArrayRegex = /plugins\s*:\s*\[/;
+ const match = content.match(pluginsArrayRegex);
+
+ if (match) {
+ const startIndex = match.index! + match[0].length;
+ let depth = 1;
+ let i = startIndex;
+ let inString = false;
+ let stringChar = '';
+ let inTemplateString = false;
+
+ while (i < content.length && depth > 0) {
+ const char = content[i];
+ const prevChar = i > 0 ? content[i - 1] : '';
+
+ if ((char === '"' || char === "'") && prevChar !== '\\') {
+ if (!inString && !inTemplateString) {
+ inString = true;
+ stringChar = char;
+ } else if (inString && char === stringChar) {
+ inString = false;
+ stringChar = '';
+ }
+ }
+
+ if (char === '`' && prevChar !== '\\') {
+ inTemplateString = !inTemplateString;
+ }
+
+ if (!inString && !inTemplateString) {
+ if (char === '[') {
+ depth++;
+ } else if (char === ']') {
+ depth--;
+ if (depth === 0) {
+ const beforeClosing = content.substring(startIndex, i);
+ const afterClosing = content.substring(i);
+
+ const cleanedBefore = beforeClosing
+ .replace(/\/\/.*$/gm, '')
+ .replace(/\/\*[\s\S]*?\*\//g, '')
+ .trim();
+
+ const hasOtherPlugins = cleanedBefore.length > 0;
+ const beforePlugins = content.substring(0, match.index!);
+ const lastNewlineIndex = beforePlugins.lastIndexOf('\n');
+ const pluginsLine = beforePlugins.substring(lastNewlineIndex + 1);
+ const indent = pluginsLine.match(/^(\s*)/)?.[1] || ' ';
+
+ if (hasOtherPlugins) {
+ let lastNonWhitespace = beforeClosing.length - 1;
+ while (lastNonWhitespace >= 0 && /\s/.test(beforeClosing[lastNonWhitespace])) {
+ lastNonWhitespace--;
+ }
+
+ const lastChar = lastNonWhitespace >= 0 ? beforeClosing[lastNonWhitespace] : '';
+ const needsComma = lastChar !== ',';
+ const insertText = (needsComma ? ',' : '') + '\n' + indent + ' appdevDesignMode()';
+
+ return (
+ content.substring(0, startIndex + lastNonWhitespace + 1) +
+ insertText +
+ '\n' +
+ indent +
+ afterClosing
+ );
+ }
+
+ return (
+ content.substring(0, startIndex) +
+ '\n' +
+ indent +
+ ' appdevDesignMode()\n' +
+ indent +
+ afterClosing
+ );
+ }
+ }
+ }
+
+ i++;
+ }
+ }
+
+ const defineConfigRegex = /defineConfig\s*\(\s*\{([\s\S]*?)\}\s*\)/;
+ const configMatch = content.match(defineConfigRegex);
+
+ if (configMatch) {
+ const configObject = configMatch[0];
+ const pluginsConfig = `\n plugins: [\n appdevDesignMode()\n ],`;
+ const newConfigObject = configObject.replace(/\}\s*\)$/, `${pluginsConfig}\n}`);
+ return content.replace(defineConfigRegex, newConfigObject);
+ }
+
+ return `${content}\n\n// appdevDesignMode plugin\nplugins: [appdevDesignMode()],`;
+}
+
+function main() {
+ console.log('Installing @xagi/vite-plugin-design-mode...\n');
+ const installerVersion = getPluginVersion();
+ console.log(`[vite-plugin-design-mode] installer version: ${installerVersion}`);
+ const projectRoot = findProjectRoot();
+ console.log(`Project root: ${projectRoot}\n`);
+
+ const packageJsonPath = join(projectRoot, 'package.json');
+ if (!existsSync(packageJsonPath)) {
+ console.error('Error: package.json not found');
+ console.error(`Directory: ${projectRoot}`);
+ console.error('Run this command from your project root.');
+ process.exit(1);
+ }
+
+ const packageJson: PackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
+
+ const hasViteDep = hasVite(packageJson);
+ const hasReactDep = hasReact(packageJson);
+ const hasVueDep = hasVue(packageJson);
+ const vueVersion = getDependencyVersion(packageJson, 'vue');
+ const hasVue3Dep = hasVueDep && isVue3Version(vueVersion || '');
+
+ if (!hasViteDep) {
+ console.error('Error: Vite not found in package.json');
+ process.exit(1);
+ }
+ if (!hasReactDep && !hasVueDep) {
+ console.error('Error: neither React nor Vue found in package.json');
+ process.exit(1);
+ }
+ if (!hasReactDep && hasVueDep && !hasVue3Dep) {
+ console.error(`Error: unsupported Vue version: ${vueVersion}`);
+ process.exit(1);
+ }
+
+ const pluginVersion = installerVersion;
+ const isInstalled = isPluginInstalled(packageJson);
+ const updatedPackageJson = addPluginToPackageJson(packageJson, pluginVersion);
+ const versionString = `^${pluginVersion}`;
+ const currentVersion =
+ packageJson.devDependencies?.[PLUGIN_NAME] || packageJson.dependencies?.[PLUGIN_NAME];
+
+ if (!isInstalled || currentVersion !== versionString) {
+ writeFileSync(packageJsonPath, JSON.stringify(updatedPackageJson, null, 2) + '\n', 'utf-8');
+ }
+
+ const viteConfigPath = findViteConfig(projectRoot);
+ if (!viteConfigPath) {
+ console.log('Done. Run your package manager install command.');
+ return;
+ }
+
+ let configContent = readFileSync(viteConfigPath, 'utf-8');
+ const originalContent = configContent;
+ configContent = addImport(configContent);
+ configContent = addPluginConfig(configContent);
+
+ if (configContent !== originalContent) {
+ writeFileSync(viteConfigPath, configContent, 'utf-8');
+ }
+
+ console.log('Done.');
+}
+
+export { main };
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/cli/uninstall.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/cli/uninstall.ts
new file mode 100644
index 00000000..097da8d1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/cli/uninstall.ts
@@ -0,0 +1,138 @@
+#!/usr/bin/env node
+
+/**
+ * Uninstall: remove devDependency and strip plugin from vite.config.
+ */
+
+import { existsSync, readFileSync, writeFileSync } from 'fs';
+import { join, resolve, dirname } from 'path';
+
+const PLUGIN_NAME = '@xagi/vite-plugin-design-mode';
+const VITE_CONFIG_FILES = ['vite.config.ts', 'vite.config.js', 'vite.config.mjs'];
+
+/** Walk up from startDir until package.json exists. */
+function findProjectRoot(startDir: string = process.cwd()): string {
+ let currentDir = resolve(startDir);
+ const root = resolve('/');
+
+ while (currentDir !== root) {
+ const packageJsonPath = join(currentDir, 'package.json');
+ if (existsSync(packageJsonPath)) {
+ return currentDir;
+ }
+ currentDir = dirname(currentDir);
+ }
+
+ return startDir;
+}
+
+interface PackageJson {
+ dependencies?: Record;
+ devDependencies?: Record;
+ packageManager?: string;
+}
+
+function isPluginInstalled(packageJson: PackageJson): boolean {
+ return !!packageJson.dependencies?.[PLUGIN_NAME] || !!packageJson.devDependencies?.[PLUGIN_NAME];
+}
+
+function removePluginFromPackageJson(packageJson: PackageJson): PackageJson {
+ if (!isPluginInstalled(packageJson)) {
+ return packageJson;
+ }
+
+ if (packageJson.dependencies?.[PLUGIN_NAME]) {
+ delete packageJson.dependencies[PLUGIN_NAME];
+ if (Object.keys(packageJson.dependencies).length === 0) {
+ delete packageJson.dependencies;
+ }
+ }
+
+ if (packageJson.devDependencies?.[PLUGIN_NAME]) {
+ delete packageJson.devDependencies[PLUGIN_NAME];
+ if (Object.keys(packageJson.devDependencies).length === 0) {
+ delete packageJson.devDependencies;
+ }
+ }
+
+ return packageJson;
+}
+
+function findViteConfig(projectRoot: string): string | null {
+ for (const file of VITE_CONFIG_FILES) {
+ const configPath = join(projectRoot, file);
+ if (existsSync(configPath)) {
+ return configPath;
+ }
+ }
+ return null;
+}
+
+function removeImport(content: string): string {
+ const importPatterns = [
+ /import\s+appdevDesignMode\s+from\s+['"]@xagi\/vite-plugin-design-mode['"];?\s*\n?/g,
+ /import\s+\{\s*default\s+as\s+appdevDesignMode\s*\}\s+from\s+['"]@xagi\/vite-plugin-design-mode['"];?\s*\n?/g,
+ ];
+
+ let result = content;
+ for (const pattern of importPatterns) {
+ result = result.replace(pattern, '');
+ }
+ return result;
+}
+
+function removePluginConfig(content: string): string {
+ const pluginCallPattern = /appdevDesignMode\s*\([^)]*\)/g;
+ let result = content.replace(pluginCallPattern, '');
+ result = result.replace(/,\s*,/g, ',');
+ result = result.replace(/,\s*\]/g, ']');
+ result = result.replace(/\[\s*,/g, '[');
+ result = result.replace(/,\s*}/g, '}');
+ result = result.replace(/\/\/\s*appdevDesignMode.*?\n/g, '');
+ result = result.replace(/\/\*\s*appdevDesignMode.*?\*\//g, '');
+ result = result.replace(/,\s*plugins\s*:\s*\[\s*\]/g, '');
+ result = result.replace(/plugins\s*:\s*\[\s*\]\s*,?/g, '');
+ result = result.replace(/\n{3,}/g, '\n\n');
+ result = result.replace(/[ \t]+$/gm, '');
+ return result;
+}
+
+function main() {
+ console.log('Removing @xagi/vite-plugin-design-mode...\n');
+ const projectRoot = findProjectRoot();
+ const packageJsonPath = join(projectRoot, 'package.json');
+ if (!existsSync(packageJsonPath)) {
+ console.error('Error: package.json not found');
+ process.exit(1);
+ }
+
+ const packageJson: PackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
+ if (!isPluginInstalled(packageJson)) {
+ console.log('Plugin not listed in package.json, nothing to remove.');
+ return;
+ }
+
+ const updatedPackageJson = removePluginFromPackageJson(packageJson);
+ if (updatedPackageJson !== packageJson) {
+ writeFileSync(packageJsonPath, JSON.stringify(updatedPackageJson, null, 2) + '\n', 'utf-8');
+ }
+
+ const viteConfigPath = findViteConfig(projectRoot);
+ if (!viteConfigPath) {
+ console.log('Done.');
+ return;
+ }
+
+ let configContent = readFileSync(viteConfigPath, 'utf-8');
+ const originalContent = configContent;
+ configContent = removeImport(configContent);
+ configContent = removePluginConfig(configContent);
+
+ if (configContent !== originalContent) {
+ writeFileSync(viteConfigPath, configContent, 'utf-8');
+ }
+
+ console.log('Done.');
+}
+
+export { main };
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/core/astTransformer.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/core/astTransformer.ts
new file mode 100644
index 00000000..1d0ca8f8
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/core/astTransformer.ts
@@ -0,0 +1,47 @@
+import * as babel from '@babel/standalone';
+import { createSourceMappingPlugin } from './sourceMapper.js';
+import type { DesignModeOptions } from '../types';
+
+export function transformSourceCode(
+ code: string,
+ id: string,
+ options: Required
+): string {
+ try {
+ // Handle ESM/CJS interop
+ const babelApi = (babel as any).default || babel;
+
+
+
+ // Parse source into AST (via Babel transform)
+ // Use a single transform call to ensure our plugin runs before presets compile JSX
+ const result = babelApi.transform(code, {
+ ast: false, // We don't need the AST output
+ code: true,
+ sourceType: 'module',
+ filename: id,
+ presets: [
+ 'typescript',
+ ['react', {
+ runtime: 'automatic', // Use automatic JSX runtime (React 17+)
+ development: false, // Use production runtime for better performance
+ }]
+ ],
+ plugins: [
+ createSourceMappingPlugin(id, options)
+ ],
+ parserOpts: {
+ allowImportExportEverywhere: true,
+ allowReturnOutsideFunction: true,
+ createParenthesizedExpressions: true
+ }
+ });
+
+ return (result && result.code) || code;
+ } catch (error) {
+ if (options.verbose) {
+ console.warn(`[appdev-design-mode] AST transformation failed for ${id}:`, error);
+ }
+ return code;
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/core/batchUpdater.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/core/batchUpdater.ts
new file mode 100644
index 00000000..c7abbd03
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/core/batchUpdater.ts
@@ -0,0 +1,101 @@
+import { IncomingMessage, ServerResponse } from 'http';
+import { performUpdate, UpdateRequest } from './codeUpdater.js';
+
+export interface BatchUpdateRequest {
+ updates: UpdateRequest[];
+}
+
+export interface BatchUpdateResult {
+ results: Array<{
+ success: boolean;
+ message?: string;
+ filePath: string;
+ type: 'style' | 'content';
+ }>;
+ summary: {
+ total: number;
+ success: number;
+ failed: number;
+ };
+}
+
+export async function handleBatchUpdate(req: IncomingMessage, res: ServerResponse, root: string) {
+ if (req.method !== 'POST') {
+ res.statusCode = 405;
+ res.end('Method Not Allowed');
+ return;
+ }
+
+ const body = await readBody(req);
+ try {
+ const data = JSON.parse(body) as BatchUpdateRequest;
+ const { updates } = data;
+
+ if (!Array.isArray(updates)) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'Invalid request: updates must be an array' }));
+ return;
+ }
+
+
+ const results = [];
+ let successCount = 0;
+ let failedCount = 0;
+
+ for (const update of updates) {
+ try {
+ const result = performUpdate(root, update);
+ if (result.success) {
+ successCount++;
+ } else {
+ failedCount++;
+ }
+ results.push({
+ success: result.success,
+ message: result.message,
+ filePath: update.filePath,
+ type: update.type
+ });
+ } catch (error) {
+ failedCount++;
+ results.push({
+ success: false,
+ message: String(error),
+ filePath: update.filePath,
+ type: update.type
+ });
+ }
+ }
+
+ const response: BatchUpdateResult = {
+ results,
+ summary: {
+ total: updates.length,
+ success: successCount,
+ failed: failedCount
+ }
+ };
+
+ res.statusCode = 200;
+ res.setHeader('Content-Type', 'application/json');
+ res.end(JSON.stringify(response));
+
+ } catch (error) {
+ console.error('[appdev-design-mode] Error handling batch update:', error);
+ res.statusCode = 500;
+ res.end(JSON.stringify({ error: String(error) }));
+ }
+}
+
+function readBody(req: IncomingMessage): Promise {
+ return new Promise((resolve, reject) => {
+ let body = '';
+ req.on('data', chunk => {
+ body += chunk.toString();
+ });
+ req.on('end', () => {
+ resolve(body);
+ });
+ req.on('error', reject);
+ });
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/core/codeUpdater.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/core/codeUpdater.ts
new file mode 100644
index 00000000..37e8a993
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/core/codeUpdater.ts
@@ -0,0 +1,160 @@
+import * as fs from 'fs';
+import path from 'path';
+import { IncomingMessage, ServerResponse } from 'http';
+
+export interface UpdateRequest {
+ filePath: string;
+ line: number;
+ column: number;
+ newValue: string;
+ type: 'style' | 'content';
+ originalValue?: string; // Original value for matching/replacement
+}
+
+export function performUpdate(root: string, data: UpdateRequest): { success: boolean, message: string } {
+ const { filePath, newValue, type, originalValue } = data;
+
+ // Resolve absolute path
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(root, filePath);
+
+ // Security: Validate path is within project root (prevent path traversal)
+ if (!isPathWithinRoot(root, absolutePath)) {
+ console.error('[appdev-design-mode] Security: Path traversal attempt blocked:', absolutePath);
+ return { success: false, message: 'Access denied: path outside project root' };
+ }
+
+ if (!fs.existsSync(absolutePath)) {
+ console.error('[appdev-design-mode] File not found:', absolutePath);
+ return { success: false, message: 'File not found' };
+ }
+
+ // Security: Check file size limit (10MB)
+ const stats = fs.statSync(absolutePath);
+ const maxFileSize = 10 * 1024 * 1024; // 10MB
+ if (stats.size > maxFileSize) {
+ console.error('[appdev-design-mode] File too large:', absolutePath, stats.size);
+ return { success: false, message: 'File too large (max 10MB)' };
+ }
+
+ // Security: Validate file extension (only allow source files)
+ const allowedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.vue'];
+ const ext = path.extname(absolutePath).toLowerCase();
+ if (!allowedExtensions.includes(ext)) {
+ console.error('[appdev-design-mode] Invalid file type:', absolutePath);
+ return { success: false, message: 'Invalid file type' };
+ }
+
+ // Security: Validate newValue length (prevent DoS)
+ const maxValueLength = 100000; // 100KB
+ if (newValue.length > maxValueLength) {
+ console.error('[appdev-design-mode] Value too large:', newValue.length);
+ return { success: false, message: 'Value too large (max 100KB)' };
+ }
+
+ let sourceCode = fs.readFileSync(absolutePath, 'utf-8');
+ let updated = false;
+
+ if (type === 'content') {
+ if (originalValue && originalValue !== newValue) {
+ // Try to match with original value first
+ const escapedOriginal = originalValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const regex = new RegExp(`(>\\s*)${escapedOriginal}(\\s*<)`, 'g');
+
+ const newSourceCode = sourceCode.replace(regex, `$1${newValue}$2`);
+
+ if (newSourceCode !== sourceCode) {
+ fs.writeFileSync(absolutePath, newSourceCode, 'utf-8');
+ updated = true;
+ } else {
+ // Fallback: If original value not found, try to find any similar content
+ // This handles cases where the file was already updated by HMR
+ console.warn('[appdev-design-mode] Original value not found, trying fallback match');
+
+ // Try to find the new value in the file (maybe it's already there)
+ if (sourceCode.includes(newValue)) {
+ updated = true; // Consider it successful since the desired state is already there
+ }
+ }
+ } else if (originalValue === newValue) {
+ // No change needed
+ updated = true;
+ } else {
+ console.warn('[appdev-design-mode] Missing originalValue for content update');
+ }
+ } else if (type === 'style') {
+ // TODO: Full style update pipeline
+ // Simple implementation: find className="..." and replace (AST would be more robust)
+ if (originalValue) {
+ // Try className="originalValue"
+ const escapedOriginal = originalValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ // Matches className="...", class="...", className={...}, etc.; simplified to className="value"
+ const regex = new RegExp(`(className=["'])${escapedOriginal}(["'])`, 'g');
+ const newSourceCode = sourceCode.replace(regex, `$1${newValue}$2`);
+
+ if (newSourceCode !== sourceCode) {
+ fs.writeFileSync(absolutePath, newSourceCode, 'utf-8');
+ updated = true;
+ }
+ } else {
+ // Style update requires originalValue (current implementation)
+ }
+ }
+
+ if (updated) {
+ return { success: true, message: 'File updated successfully' };
+ } else {
+ console.warn('[appdev-design-mode] Could not update file - no match found');
+ return { success: false, message: 'Could not locate content to update' };
+ }
+}
+
+export async function handleUpdate(req: IncomingMessage, res: ServerResponse, root: string) {
+ if (req.method !== 'POST') {
+ res.statusCode = 405;
+ res.end('Method Not Allowed');
+ return;
+ }
+
+ const body = await readBody(req);
+ try {
+ const data = JSON.parse(body) as UpdateRequest;
+ const result = performUpdate(root, data);
+
+ if (result.success) {
+ res.statusCode = 200;
+ res.setHeader('Content-Type', 'application/json');
+ res.end(JSON.stringify(result));
+ } else {
+ res.statusCode = 400;
+ res.end(JSON.stringify(result));
+ }
+
+ } catch (error) {
+ console.error('[appdev-design-mode] Error handling update:', error);
+ res.statusCode = 500;
+ res.end(JSON.stringify({ success: false, error: String(error) }));
+ }
+}
+
+function readBody(req: IncomingMessage): Promise {
+ return new Promise((resolve, reject) => {
+ let body = '';
+ req.on('data', chunk => {
+ body += chunk.toString();
+ });
+ req.on('end', () => {
+ resolve(body);
+ });
+ req.on('error', reject);
+ });
+}
+
+function isPathWithinRoot(rootDir: string, targetPath: string): boolean {
+ const resolvedRoot = path.resolve(rootDir);
+ const resolvedTarget = path.resolve(targetPath);
+ if (resolvedRoot === resolvedTarget) {
+ return true;
+ }
+ const relativePath = path.relative(resolvedRoot, resolvedTarget);
+ return relativePath !== '' && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/core/serverMiddleware.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/core/serverMiddleware.ts
new file mode 100644
index 00000000..b3cfc8a1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/core/serverMiddleware.ts
@@ -0,0 +1,974 @@
+import type { ViteDevServer } from 'vite';
+import type { DesignModeOptions } from '../types';
+import * as fs from 'fs';
+import * as path from 'path';
+import { applyVueSfcTemplateUpdate } from './vueSfcUpdater.js';
+
+export function createServerMiddleware(
+ options: Required,
+ rootDir: string
+) {
+ const enableBackup = options.enableBackup === true;
+ const enableHistory = options.enableHistory === true;
+
+ return async (req: any, res: any) => {
+ const url = new URL(req.url, 'http://localhost');
+
+ try {
+ switch (url.pathname) {
+ // Core endpoints
+ case '/get-source':
+ await handleGetSource(url, res, rootDir);
+ break;
+ case '/modify-source':
+ await handleModifySource(req, res, rootDir);
+ break;
+ case '/health':
+ await handleHealthCheck(res);
+ break;
+
+ // Extended endpoints
+ case '/update':
+ await handleUpdate(req, res, rootDir, enableBackup);
+ break;
+ case '/batch-update':
+ await handleBatchUpdate(req, res, rootDir, enableBackup, enableHistory);
+ break;
+ case '/batch-update-status':
+ await handleBatchUpdateStatus(req, res, rootDir, enableHistory);
+ break;
+ case '/undo':
+ await handleUndo(req, res, rootDir, enableBackup);
+ break;
+ case '/redo':
+ await handleRedo(req, res, rootDir);
+ break;
+ case '/get-history':
+ await handleGetHistory(req, res, rootDir, enableHistory);
+ break;
+ case '/validate-update':
+ await handleValidateUpdate(req, res, rootDir);
+ break;
+
+ default:
+ res.statusCode = 404;
+ res.end(JSON.stringify({
+ error: 'Not found',
+ requestedPath: url.pathname,
+ availableEndpoints: [
+ '/get-source',
+ '/modify-source',
+ '/update',
+ '/batch-update',
+ '/batch-update-status',
+ '/undo',
+ '/redo',
+ '/get-history',
+ '/validate-update',
+ '/health'
+ ]
+ }));
+ }
+ } catch (error) {
+ console.error('[DesignMode] Server middleware error:', error);
+ res.statusCode = 500;
+ res.end(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error',
+ stack: process.env.NODE_ENV === 'development' ? (error instanceof Error ? error.stack : undefined) : undefined
+ })
+ );
+ }
+ };
+}
+
+/**
+ * GET /get-source — enriched source snapshot for an element id.
+ */
+async function handleGetSource(url: URL, res: any, rootDir: string) {
+ const elementId = url.searchParams.get('elementId');
+
+ if (!elementId) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'Missing elementId parameter' }));
+ return;
+ }
+
+ try {
+ // Parse elementId → file position
+ const sourceInfo = parseElementId(elementId);
+
+ if (!sourceInfo) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'Invalid elementId format' }));
+ return;
+ }
+
+ const filePath = path.resolve(rootDir, sourceInfo.fileName);
+
+ // Security: Validate path is within project root (prevent path traversal)
+ if (!isPathWithinRoot(rootDir, filePath)) {
+ res.statusCode = 403;
+ res.end(JSON.stringify({ error: 'Access denied: path outside project root' }));
+ return;
+ }
+
+ try {
+ await fs.promises.access(filePath, fs.constants.F_OK);
+ } catch {
+ res.statusCode = 404;
+ res.end(JSON.stringify({ error: 'Source file not found', filePath }));
+ return;
+ }
+
+ const fileContent = await fs.promises.readFile(filePath, 'utf-8');
+
+ // Target line + context window
+ const lines = fileContent.split('\n');
+ const targetLine = Math.max(0, sourceInfo.lineNumber - 1);
+ const contextLines = lines.slice(
+ Math.max(0, targetLine - 5),
+ Math.min(lines.length, targetLine + 5)
+ );
+
+ // Response payload
+ const response = {
+ sourceInfo: {
+ ...sourceInfo,
+ fileContent: fileContent,
+ contextLines: contextLines,
+ targetLineContent: lines[targetLine] || '',
+ totalLines: lines.length,
+ fileExists: true
+ },
+ elementMetadata: {
+ tagName: extractTagNameFromLine(lines[targetLine]),
+ estimatedClassName: extractClassNameFromLine(lines[targetLine]),
+ lineContext: getLineContext(lines, targetLine)
+ },
+ fileStats: {
+ size: fileContent.length,
+ lastModified: (await fs.promises.stat(filePath)).mtime.getTime()
+ }
+ };
+
+ res.statusCode = 200;
+ res.end(JSON.stringify(response));
+ } catch (error) {
+ res.statusCode = 500;
+ res.end(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error'
+ })
+ );
+ }
+}
+
+/**
+ * POST /modify-source — legacy style update.
+ */
+async function handleModifySource(req: any, res: any, rootDir: string) {
+ if (req.method !== 'POST') {
+ res.statusCode = 405;
+ res.end(JSON.stringify({ error: 'Method not allowed' }));
+ return;
+ }
+
+ let body = '';
+ req.on('data', (chunk: any) => {
+ body += chunk.toString();
+ });
+
+ req.on('end', async () => {
+ try {
+ const { elementId, newStyles, oldStyles } = JSON.parse(body);
+
+ if (!elementId || newStyles === undefined) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'Missing required parameters' }));
+ return;
+ }
+
+ // Parse elementId
+ const sourceInfo = parseElementId(elementId);
+ if (!sourceInfo) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'Invalid elementId format' }));
+ return;
+ }
+
+ const filePath = path.resolve(rootDir, sourceInfo.fileName);
+
+ // Security: Validate path is within project root (prevent path traversal)
+ if (!isPathWithinRoot(rootDir, filePath)) {
+ res.statusCode = 403;
+ res.end(JSON.stringify({ error: 'Access denied: path outside project root' }));
+ return;
+ }
+
+ const fileContent = await fs.promises.readFile(filePath, 'utf-8');
+
+ const updatedContent = await smartReplaceInSource(
+ fileContent,
+ {
+ lineNumber: sourceInfo.lineNumber,
+ columnNumber: sourceInfo.columnNumber,
+ newValue: newStyles,
+ originalValue: oldStyles || '',
+ type: 'style',
+ },
+ rootDir,
+ filePath
+ );
+
+ // Persist
+ await fs.promises.writeFile(filePath, updatedContent, 'utf-8');
+
+ res.statusCode = 200;
+ res.end(
+ JSON.stringify({
+ success: true,
+ message: 'Source modified successfully',
+ sourceInfo,
+ })
+ );
+ } catch (error) {
+ res.statusCode = 500;
+ res.end(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error',
+ })
+ );
+ }
+ });
+}
+
+/**
+ * GET /health
+ */
+async function handleHealthCheck(res: any) {
+ res.statusCode = 200;
+ res.end(
+ JSON.stringify({
+ status: 'ok',
+ timestamp: Date.now(),
+ plugin: '@xagi/vite-plugin-design-mode',
+ })
+ );
+}
+
+/**
+ * POST /update — style, content, or attribute.
+ */
+async function handleUpdate(req: any, res: any, rootDir: string, enableBackup: boolean = false) {
+ if (req.method !== 'POST') {
+ res.statusCode = 405;
+ res.end(JSON.stringify({ error: 'Method not allowed' }));
+ return;
+ }
+
+ let body = '';
+ req.on('data', (chunk: any) => {
+ body += chunk.toString();
+ });
+
+ req.on('end', async () => {
+ try {
+ const updateData = JSON.parse(body);
+ const { filePath, line, column, newValue, originalValue, type } = updateData;
+
+ // Validate body
+ if (!filePath || line === undefined || column === undefined || newValue === undefined || !type) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'Missing required parameters' }));
+ return;
+ }
+
+ if (!['style', 'content', 'attribute'].includes(type)) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'Invalid update type' }));
+ return;
+ }
+
+ const fullFilePath = path.resolve(rootDir, filePath);
+
+ try {
+ await fs.promises.access(fullFilePath, fs.constants.F_OK);
+ } catch {
+ res.statusCode = 404;
+ res.end(JSON.stringify({ error: 'Source file not found', filePath }));
+ return;
+ }
+
+ const fileContent = await fs.promises.readFile(fullFilePath, 'utf-8');
+ const lines = fileContent.split('\n');
+ const targetLine = Math.max(0, line - 1);
+
+ if (targetLine >= lines.length) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: `Line ${line} exceeds file length (${lines.length} lines)` }));
+ return;
+ }
+
+ // Line-aware replace
+ const updatedContent = await smartReplaceInSource(
+ fileContent,
+ {
+ lineNumber: line,
+ columnNumber: column,
+ newValue,
+ originalValue,
+ type
+ },
+ rootDir,
+ fullFilePath
+ );
+
+ // Optional .backup copy
+ let backupPath: string | undefined;
+ if (enableBackup) {
+ backupPath = `${fullFilePath}.backup.${Date.now()}`;
+ await fs.promises.writeFile(backupPath, fileContent, 'utf-8');
+ }
+
+ // Persist
+ await fs.promises.writeFile(fullFilePath, updatedContent, 'utf-8');
+
+ // Audit record (in-memory response)
+ const updateRecord = {
+ id: `update_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ filePath,
+ line,
+ column,
+ type,
+ newValue,
+ originalValue,
+ timestamp: Date.now(),
+ backupPath: backupPath || null
+ };
+
+ res.statusCode = 200;
+ res.end(
+ JSON.stringify({
+ success: true,
+ message: 'Update completed successfully',
+ updateRecord,
+ affectedLines: {
+ before: lines[targetLine],
+ after: updatedContent.split('\n')[targetLine]
+ }
+ })
+ );
+ } catch (error) {
+ res.statusCode = 500;
+ res.end(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error',
+ details: error instanceof Error ? error.stack : undefined
+ })
+ );
+ }
+ });
+}
+
+/**
+ * POST /batch-update
+ */
+async function handleBatchUpdate(req: any, res: any, rootDir: string, enableBackup: boolean = false, enableHistory: boolean = false) {
+ if (req.method !== 'POST') {
+ res.statusCode = 405;
+ res.end(JSON.stringify({ error: 'Method not allowed' }));
+ return;
+ }
+
+ let body = '';
+ req.on('data', (chunk: any) => {
+ body += chunk.toString();
+ });
+
+ req.on('end', async () => {
+ try {
+ const { updates } = JSON.parse(body);
+
+ if (!Array.isArray(updates) || updates.length === 0) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'No updates provided or invalid format' }));
+ return;
+ }
+
+ // Validate each item
+ const validationResults = await Promise.allSettled(
+ updates.map(update => validateUpdateRequest(update, rootDir))
+ );
+
+ // Batch session id
+ const batchId = `batch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+ const batchSession: {
+ id: string;
+ timestamp: number;
+ totalUpdates: number;
+ successfulUpdates: number;
+ failedUpdates: number;
+ updates: any[];
+ } = {
+ id: batchId,
+ timestamp: Date.now(),
+ totalUpdates: updates.length,
+ successfulUpdates: 0,
+ failedUpdates: 0,
+ updates: []
+ };
+
+ // Apply updates sequentially
+ const results = [];
+ for (let i = 0; i < updates.length; i++) {
+ const update = updates[i];
+ const validation = validationResults[i];
+
+ if (validation.status === 'rejected') {
+ results.push({
+ success: false,
+ error: validation.reason,
+ update
+ });
+ batchSession.failedUpdates++;
+ continue;
+ }
+
+ try {
+ const result = await processSingleUpdate(update, rootDir, batchId, enableBackup);
+ results.push(result);
+ if (result.success) {
+ batchSession.successfulUpdates++;
+ } else {
+ batchSession.failedUpdates++;
+ }
+ batchSession.updates.push(result);
+ } catch (error) {
+ results.push({
+ success: false,
+ error: error instanceof Error ? error.message : 'Unknown error',
+ update
+ });
+ batchSession.failedUpdates++;
+ batchSession.updates.push(results[results.length - 1]);
+ }
+ }
+
+ // Persist session JSON when history enabled
+ if (enableHistory) {
+ const sessionFile = path.join(rootDir, '.appdev_batch_sessions.json');
+ let sessions = {};
+ try {
+ const existingSessionsContent = await fs.promises.readFile(sessionFile, 'utf-8');
+ sessions = JSON.parse(existingSessionsContent);
+ } catch {
+ // Missing or unreadable → start empty
+ }
+
+ (sessions as any)[batchId] = batchSession;
+ await fs.promises.writeFile(sessionFile, JSON.stringify(sessions, null, 2), 'utf-8');
+ }
+
+ res.statusCode = 200;
+ res.end(
+ JSON.stringify({
+ batchId,
+ success: batchSession.failedUpdates === 0,
+ summary: {
+ total: updates.length,
+ successful: batchSession.successfulUpdates,
+ failed: batchSession.failedUpdates
+ },
+ results
+ })
+ );
+ } catch (error) {
+ res.statusCode = 500;
+ res.end(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error'
+ })
+ );
+ }
+ });
+}
+
+/**
+ * GET /batch-update-status
+ */
+async function handleBatchUpdateStatus(req: any, res: any, rootDir: string, enableHistory: boolean = false) {
+ const batchId = req.url.split('?')[1]?.split('=')[1];
+
+ if (!batchId) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'Missing batchId' }));
+ return;
+ }
+
+ if (!enableHistory) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({
+ error: 'History is disabled. Cannot query batch status without history enabled.',
+ enableHistory: false
+ }));
+ return;
+ }
+
+ try {
+ const sessionFile = path.join(rootDir, '.appdev_batch_sessions.json');
+ let sessions = {};
+
+ try {
+ const existingSessionsContent = await fs.promises.readFile(sessionFile, 'utf-8');
+ sessions = JSON.parse(existingSessionsContent);
+ } catch {
+ // Session file missing
+ res.statusCode = 404;
+ res.end(JSON.stringify({ error: 'Batch session not found' }));
+ return;
+ }
+
+ const session = (sessions as any)[batchId];
+ if (!session) {
+ res.statusCode = 404;
+ res.end(JSON.stringify({ error: 'Batch session not found' }));
+ return;
+ }
+
+ res.statusCode = 200;
+ res.end(JSON.stringify({
+ success: true,
+ session
+ }));
+ } catch (error) {
+ res.statusCode = 500;
+ res.end(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error'
+ })
+ );
+ }
+}
+
+/**
+ * POST /undo — restore from backup when backups enabled.
+ */
+async function handleUndo(req: any, res: any, rootDir: string, enableBackup: boolean = false) {
+ if (req.method !== 'POST') {
+ res.statusCode = 405;
+ res.end(JSON.stringify({ error: 'Method not allowed' }));
+ return;
+ }
+
+ let body = '';
+ req.on('data', (chunk: any) => {
+ body += chunk.toString();
+ });
+
+ req.on('end', async () => {
+ try {
+ const { batchId } = JSON.parse(body);
+
+ if (!batchId) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({ error: 'Missing batchId' }));
+ return;
+ }
+
+ if (!enableBackup) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({
+ error: 'Backup is disabled. Cannot undo without backup files.',
+ enableBackup: false
+ }));
+ return;
+ }
+
+ // Find backup sidecar files
+ const backupFiles = await fs.promises.readdir(rootDir);
+ const matchingBackups = backupFiles
+ .filter(file => file.startsWith('.') && file.includes('.backup.') && file.includes(batchId));
+
+ if (matchingBackups.length === 0) {
+ res.statusCode = 404;
+ res.end(JSON.stringify({ error: 'No backup files found for this batch' }));
+ return;
+ }
+
+ // Pick lexicographically latest (timestamp suffix)
+ const latestBackup = matchingBackups.sort().pop()!;
+ const backupPath = path.join(rootDir, latestBackup);
+ const originalFile = backupPath.replace(/\.backup\.\d+$/, '');
+
+ // Restore from backup
+ const backupContent = await fs.promises.readFile(backupPath, 'utf-8');
+ await fs.promises.writeFile(originalFile, backupContent, 'utf-8');
+
+ await fs.promises.unlink(backupPath);
+
+ res.statusCode = 200;
+ res.end(
+ JSON.stringify({
+ success: true,
+ message: 'Undo completed successfully',
+ restoredFile: originalFile,
+ backupFile: backupPath
+ })
+ );
+ } catch (error) {
+ res.statusCode = 500;
+ res.end(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error'
+ })
+ );
+ }
+ });
+}
+
+/**
+ * POST /redo — not implemented.
+ */
+async function handleRedo(req: any, res: any, rootDir: string) {
+ // Could re-apply updates from `.appdev_batch_sessions.json`
+ res.statusCode = 501;
+ res.end(JSON.stringify({ error: 'Redo operation not yet implemented' }));
+}
+
+/**
+ * GET /get-history
+ */
+async function handleGetHistory(req: any, res: any, rootDir: string, enableHistory: boolean = false) {
+ if (!enableHistory) {
+ res.statusCode = 400;
+ res.end(JSON.stringify({
+ error: 'History is disabled. Cannot get history without history enabled.',
+ enableHistory: false
+ }));
+ return;
+ }
+
+ try {
+ const sessionFile = path.join(rootDir, '.appdev_batch_sessions.json');
+ let sessions = {};
+
+ try {
+ const existingSessionsContent = await fs.promises.readFile(sessionFile, 'utf-8');
+ sessions = JSON.parse(existingSessionsContent);
+ } catch {
+ // No session file → empty history
+ }
+
+ res.statusCode = 200;
+ res.end(JSON.stringify({
+ success: true,
+ history: sessions
+ }));
+ } catch (error) {
+ res.statusCode = 500;
+ res.end(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error'
+ })
+ );
+ }
+}
+
+/**
+ * POST /validate-update
+ */
+async function handleValidateUpdate(req: any, res: any, rootDir: string) {
+ if (req.method !== 'POST') {
+ res.statusCode = 405;
+ res.end(JSON.stringify({ error: 'Method not allowed' }));
+ return;
+ }
+
+ let body = '';
+ req.on('data', (chunk: any) => {
+ body += chunk.toString();
+ });
+
+ req.on('end', async () => {
+ try {
+ const updateData = JSON.parse(body);
+ const validation = await validateUpdateRequest(updateData, rootDir);
+
+ res.statusCode = 200;
+ res.end(JSON.stringify({
+ success: true,
+ validation
+ }));
+ } catch (error) {
+ res.statusCode = 500;
+ res.end(
+ JSON.stringify({
+ error: error instanceof Error ? error.message : 'Unknown error'
+ })
+ );
+ }
+ });
+}
+
+/**
+ * Helpers
+ */
+
+// Parse `elementId` → file + line + column
+function parseElementId(
+ elementId: string
+): { fileName: string; lineNumber: number; columnNumber: number } | null {
+ // Support IDs shaped like "::_[_]".
+ const match = elementId.match(/^(.*):(\d+):(\d+)_/);
+ if (!match) return null;
+
+ const fileName = match[1];
+ const lineNumber = parseInt(match[2], 10);
+ const columnNumber = parseInt(match[3], 10);
+
+ if (!fileName || isNaN(lineNumber) || isNaN(columnNumber)) return null;
+
+ return {
+ fileName,
+ lineNumber,
+ columnNumber,
+ };
+}
+
+// Line-oriented smart replace
+async function smartReplaceInSource(
+ content: string,
+ options: {
+ lineNumber: number;
+ columnNumber: number;
+ newValue: string;
+ originalValue?: string;
+ type: 'style' | 'content' | 'attribute';
+ },
+ rootDir: string,
+ filePath?: string
+): Promise {
+ const lines = content.split('\n');
+ const targetLine = Math.max(0, options.lineNumber - 1);
+
+ if (targetLine >= lines.length) {
+ throw new Error(`Line ${options.lineNumber} exceeds file length`);
+ }
+
+ const line = lines[targetLine];
+ let newLine = line;
+ const isVueFile = Boolean(filePath && filePath.endsWith('.vue'));
+
+ if (isVueFile) {
+ return applyVueSfcTemplateUpdate(content, {
+ lineNumber: options.lineNumber,
+ columnNumber: options.columnNumber,
+ newValue: options.newValue,
+ originalValue: options.originalValue,
+ type: options.type,
+ });
+ }
+
+ try {
+ switch (options.type) {
+ case 'style':
+ newLine = await smartReplaceStyle(line, options);
+ break;
+ case 'content':
+ newLine = await smartReplaceContent(line, options);
+ break;
+ case 'attribute':
+ newLine = await smartReplaceAttribute(line, options);
+ break;
+ }
+
+ lines[targetLine] = newLine;
+ return lines.join('\n');
+ } catch (error) {
+ console.error('[DesignMode] Smart replace failed:', error);
+ return content; // Fallback to original content
+ }
+}
+
+async function smartReplaceStyle(line: string, options: any): Promise {
+ const { newValue } = options;
+
+ // 1) className="..." or className='...'
+ const classNameRegex = /className=(["'])(.*?)\1/;
+ if (classNameRegex.test(line)) {
+ return line.replace(classNameRegex, `className=$1${newValue}$1`);
+ }
+
+ // 2) className={...} → coerce to static string (design mode output)
+ const classNameExpressionRegex = /className=\{([^}]*)\}/;
+ if (classNameExpressionRegex.test(line)) {
+ return line.replace(classNameExpressionRegex, `className="${newValue}"`);
+ }
+
+ // 3) No className: insert after opening tag name ( {
+ if (options.originalValue && line.includes(options.originalValue)) {
+ return line.replace(
+ new RegExp(escapeRegExp(options.originalValue), 'g'),
+ options.newValue
+ );
+ }
+
+ // Between `>` and `<` when it matches original
+ const contentMatch = line.match(/>([^<]*));
+ if (contentMatch && contentMatch[1] === options.originalValue) {
+ return line.replace(contentMatch[0], `>${options.newValue}<`);
+ }
+
+ return options.newValue;
+}
+
+async function smartReplaceAttribute(line: string, options: any): Promise {
+ return line.replace(
+ new RegExp(`${options.attributeName}="[^"]*"`),
+ `${options.attributeName}="${options.newValue}"`
+ );
+}
+
+async function validateUpdateRequest(update: any, rootDir: string): Promise<{ valid: boolean; errors: string[] }> {
+ const errors: string[] = [];
+
+ if (!update.filePath) errors.push('Missing filePath');
+ if (update.line === undefined) errors.push('Missing line');
+ if (update.column === undefined) errors.push('Missing column');
+ if (update.newValue === undefined) errors.push('Missing newValue');
+ if (!update.type) errors.push('Missing type');
+
+ // File must exist under root
+ if (update.filePath) {
+ const fullPath = path.resolve(rootDir, update.filePath);
+ try {
+ await fs.promises.access(fullPath, fs.constants.F_OK);
+ } catch {
+ errors.push(`File not found: ${update.filePath}`);
+ }
+ }
+
+ // type ∈ style | content | attribute
+ if (update.type && !['style', 'content', 'attribute'].includes(update.type)) {
+ errors.push(`Invalid update type: ${update.type}`);
+ }
+
+ return {
+ valid: errors.length === 0,
+ errors
+ };
+}
+
+// Single item inside batch
+async function processSingleUpdate(update: any, rootDir: string, batchId: string, enableBackup: boolean = false): Promise {
+ try {
+ const validation = await validateUpdateRequest(update, rootDir);
+ if (!validation.valid) {
+ return {
+ success: false,
+ error: validation.errors.join(', '),
+ update
+ };
+ }
+
+ const fullFilePath = path.resolve(rootDir, update.filePath);
+ const fileContent = await fs.promises.readFile(fullFilePath, 'utf-8');
+
+ const updatedContent = await smartReplaceInSource(
+ fileContent,
+ {
+ lineNumber: update.line,
+ columnNumber: update.column,
+ newValue: update.newValue,
+ originalValue: update.originalValue,
+ type: update.type
+ },
+ rootDir,
+ fullFilePath
+ );
+
+ if (enableBackup) {
+ const backupPath = `${fullFilePath}.backup.${Date.now()}`;
+ await fs.promises.writeFile(backupPath, fileContent, 'utf-8');
+ }
+
+ await fs.promises.writeFile(fullFilePath, updatedContent, 'utf-8');
+
+ return {
+ success: true,
+ update,
+ affectedLines: {
+ before: fileContent.split('\n')[update.line - 1],
+ after: updatedContent.split('\n')[update.line - 1]
+ }
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : 'Unknown error',
+ update
+ };
+ }
+}
+
+// Heuristic: file touched in last 5 minutes
+async function checkForModifications(filePath: string): Promise {
+ try {
+ const stat = await fs.promises.stat(filePath);
+ const lastModified = stat.mtime.getTime();
+ const now = Date.now();
+
+ return (now - lastModified) < 5 * 60 * 1000;
+ } catch {
+ return false;
+ }
+}
+
+// Best-effort tag from a source line
+function extractTagNameFromLine(line: string): string {
+ const tagMatch = line.match(/<(\w+)/);
+ return tagMatch ? tagMatch[1] : 'unknown';
+}
+
+// className="..." substring
+function extractClassNameFromLine(line: string): string {
+ const classMatch = line.match(/className\s*=\s*["']([^"']+)["']/);
+ return classMatch ? classMatch[1] : '';
+}
+
+// Neighbor lines around index
+function getLineContext(lines: string[], targetLine: number): { before: string; current: string; after: string } {
+ return {
+ before: lines[Math.max(0, targetLine - 1)] || '',
+ current: lines[targetLine] || '',
+ after: lines[Math.min(lines.length - 1, targetLine + 1)] || ''
+ };
+}
+
+function escapeRegExp(string: string): string {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
+function isPathWithinRoot(rootDir: string, targetPath: string): boolean {
+ const resolvedRoot = path.resolve(rootDir);
+ const resolvedTarget = path.resolve(targetPath);
+ const relativePath = path.relative(resolvedRoot, resolvedTarget);
+ if (resolvedRoot === resolvedTarget) {
+ return true;
+ }
+ return relativePath !== '' && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
+}
+
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/core/sourceMapper.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/core/sourceMapper.ts
new file mode 100644
index 00000000..69daec8e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/core/sourceMapper.ts
@@ -0,0 +1,444 @@
+import * as babel from '@babel/standalone';
+import traverse from '@babel/traverse';
+import * as t from '@babel/types';
+import type { DesignModeOptions } from '../types';
+
+export interface SourceMappingInfo {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+ elementType: string;
+ componentName?: string;
+ functionName?: string;
+ elementId: string;
+ attributePrefix: string;
+ importPath?: string; // Resolved import path for the component
+ isUIComponent?: boolean; // True when file is under components/ui (UI kit)
+}
+
+
+export interface JSXElementWithLoc extends t.JSXOpeningElement {
+ loc: t.SourceLocation;
+}
+
+// PluginItem type from Babel
+type PluginItem = any;
+type NodePath = any;
+
+/**
+ * Whether the source file lives under a UI-kit folder (components/ui).
+ * Runtime selection prefers usage site over definition for these.
+ */
+function isUIComponentFile(filePath: string): boolean {
+ // Match /components/ui/ or \components\ui\
+ return /[/\\]components[/\\]ui[/\\]/.test(filePath);
+}
+
+/**
+ * Babel plugin factory: inject source-mapping data attributes.
+ */
+export function createSourceMappingPlugin(
+ fileName: string,
+ options: Required
+): PluginItem {
+ const { attributePrefix } = options;
+ const imports: Record = {}; // local name -> module specifier
+
+ return {
+ visitor: {
+ // 1) Collect import bindings
+ ImportDeclaration(path: NodePath) {
+ const { node } = path;
+ const sourceValue = node.source.value; // e.g. "@/components/ui/button"
+
+ node.specifiers.forEach((specifier: any) => {
+ if (t.isImportSpecifier(specifier)) {
+ // import { Button } from ...
+ imports[specifier.local.name] = sourceValue;
+ // console.log(`[DesignMode] Found import: ${specifier.local.name} from ${sourceValue}`);
+ } else if (t.isImportDefaultSpecifier(specifier)) {
+ // import Button from ...
+ imports[specifier.local.name] = sourceValue;
+ // console.log(`[DesignMode] Found default import: ${specifier.local.name} from ${sourceValue}`);
+ } else if (t.isImportNamespaceSpecifier(specifier)) {
+ // import * as UI from ...
+ imports[specifier.local.name] = sourceValue;
+ }
+ });
+ },
+
+ JSXOpeningElement(path: NodePath) {
+ const { node } = path;
+
+ const location = node.loc;
+ if (!location) return;
+
+ const componentInfo = extractComponentInfo(path);
+ const elementType = getJSXElementName(node.name);
+
+ // Resolve import path for JSX name (member expr uses object part only)
+ let importPath: string | undefined;
+ if (t.isJSXIdentifier(node.name)) {
+ importPath = imports[node.name.name];
+ } else if (t.isJSXMemberExpression(node.name) && t.isJSXIdentifier(node.name.object)) {
+ importPath = imports[node.name.object.name];
+ }
+
+ // if (importPath) {
+ // console.log(`[DesignMode] Injecting importPath for ${elementType}: ${importPath}`);
+ // }
+
+ const isUIComponent = isUIComponentFile(fileName);
+
+ const sourceInfo: SourceMappingInfo = {
+ fileName: fileName,
+ lineNumber: location.start.line,
+ columnNumber: location.start.column,
+ elementType: elementType,
+ componentName: componentInfo.componentName,
+ functionName: componentInfo.functionName,
+ elementId: generateElementId(node, fileName, location),
+ attributePrefix,
+ importPath,
+ isUIComponent
+ };
+
+
+ addSourceInfoAttribute(node, sourceInfo, options);
+
+ addPositionAttribute(node, location, options);
+
+ addElementIdAttribute(node, sourceInfo, options);
+
+ // Optional: extra per-field attrs (disabled to keep DOM small; info JSON holds all)
+ // addIndividualAttributes(node, sourceInfo, options);
+
+ // Check if content is static and add attribute
+ if (isStaticContent(path)) {
+ addStaticContentAttribute(node, path, options);
+ }
+
+ // Check if className is static (pure string literal) and add attribute
+ if (isStaticClassName(node)) {
+ addStaticClassAttribute(node, options);
+ }
+ },
+
+ JSXElement(path: NodePath) {
+ const { node } = path;
+ const { openingElement, children } = node;
+
+ if (!children || children.length === 0) return;
+
+ const hasStaticText = children.some((child: any) => t.isJSXText(child) && child.value.trim() !== '');
+
+ if (hasStaticText) {
+ const textChild = children.find((child: any) => t.isJSXText(child) && child.value.trim() !== '');
+ if (textChild && textChild.loc) {
+ // children-source: where static text came from (for pass-through components)
+ const childrenSourceValue = `${fileName}:${textChild.loc.start.line}:${textChild.loc.start.column}`;
+ const attributeName = `${attributePrefix}-children-source`;
+
+ openingElement.attributes.push(
+ t.jsxAttribute(
+ t.jsxIdentifier(attributeName),
+ t.stringLiteral(childrenSourceValue)
+ )
+ );
+ }
+ }
+ }
+ }
+ };
+}
+
+/**
+ * Best-effort enclosing React component / function name.
+ */
+function extractComponentInfo(path: NodePath): { componentName?: string; functionName?: string } {
+ const componentInfo: { componentName?: string; functionName?: string } = {};
+
+ // Nearest function or class
+ const functionParent = path.findParent((p: NodePath) =>
+ t.isFunctionDeclaration(p.node) ||
+ t.isArrowFunctionExpression(p.node) ||
+ t.isClassDeclaration(p.node)
+ );
+
+ if (functionParent) {
+ const node = functionParent.node;
+
+ if (t.isFunctionDeclaration(node) && node.id?.name) {
+ componentInfo.functionName = node.id.name;
+ // React: components are PascalCase
+ if (/^[A-Z]/.test(node.id.name)) {
+ componentInfo.componentName = node.id.name;
+ }
+ } else if (t.isArrowFunctionExpression(node)) {
+ componentInfo.functionName = 'anonymous-arrow-function';
+ } else if (t.isClassDeclaration(node) && node.id?.name) {
+ componentInfo.functionName = node.id.name;
+ componentInfo.componentName = node.id.name;
+ }
+ }
+
+ // Fallback: const Foo = ...
+ const variableParent = path.findParent((p: NodePath) =>
+ t.isVariableDeclarator(p.node)
+ );
+
+ if (variableParent && t.isVariableDeclarator(variableParent.node)) {
+ const id = variableParent.node.id;
+ if (t.isIdentifier(id)) {
+ componentInfo.functionName = id.name;
+ if (/^[A-Z]/.test(id.name)) {
+ componentInfo.componentName = id.name;
+ }
+ }
+ }
+
+ return componentInfo;
+}
+
+/**
+ * JSX tag name as string (identifier or member root).
+ */
+function getJSXElementName(name: any): string {
+ if (t.isJSXIdentifier(name)) {
+ return name.name;
+ } else if (t.isJSXMemberExpression(name)) {
+ return `${getJSXElementName(name.object)}`;
+ }
+ return 'unknown';
+}
+
+/**
+ * Stable id: file:line:col_tag#id
+ */
+function generateElementId(node: t.JSXOpeningElement, fileName: string, location: t.SourceLocation): string {
+ const tagName = getJSXElementName(node.name);
+
+ // const className = extractStringAttribute(node, 'className'); // omitted to shorten id
+ const id = extractStringAttribute(node, 'id');
+
+ const baseId = `${fileName}:${location.start.line}:${location.start.column}`;
+ const tag = tagName.toLowerCase();
+ // const cls = className ? className.replace(/\s+/g, '-') : '';
+ const elementId = id ? `#${id}` : '';
+
+ // return `${baseId}_${tag}${cls ? '_' + cls : ''}${elementId}`;
+ return `${baseId}_${tag}${elementId}`;
+}
+
+/**
+ * String literal JSX attribute value, if any.
+ */
+function extractStringAttribute(node: t.JSXOpeningElement, attributeName: string): string | null {
+ const attr = node.attributes.find(a =>
+ t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === attributeName
+ ) as t.JSXAttribute;
+
+ if (attr && t.isStringLiteral(attr.value)) {
+ return attr.value.value;
+ }
+
+ return null;
+}
+
+/**
+ * Inject compact JSON on `{prefix}-info`.
+ */
+function addSourceInfoAttribute(node: t.JSXOpeningElement, sourceInfo: SourceMappingInfo, options: Required) {
+ const { attributePrefix } = options;
+
+ // Remove previous info attr
+ node.attributes = node.attributes.filter(a =>
+ !(t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === `${attributePrefix}-info`)
+ );
+
+ // Push new JSON attr
+ const attr = t.jSXAttribute(
+ t.jSXIdentifier(`${attributePrefix}-info`),
+ t.stringLiteral(JSON.stringify({
+ fileName: sourceInfo.fileName,
+ lineNumber: sourceInfo.lineNumber,
+ columnNumber: sourceInfo.columnNumber,
+ elementType: sourceInfo.elementType,
+ componentName: sourceInfo.componentName,
+ functionName: sourceInfo.functionName,
+ elementId: sourceInfo.elementId,
+ importPath: sourceInfo.importPath,
+ isUIComponent: sourceInfo.isUIComponent
+ }))
+ );
+
+ node.attributes.unshift(attr);
+}
+
+/**
+ * `{prefix}-position` shorthand.
+ */
+function addPositionAttribute(node: t.JSXOpeningElement, location: t.SourceLocation, options: Required) {
+ const { attributePrefix } = options;
+
+ node.attributes = node.attributes.filter(a =>
+ !(t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === `${attributePrefix}-position`)
+ );
+
+ const attr = t.jSXAttribute(
+ t.jSXIdentifier(`${attributePrefix}-position`),
+ t.stringLiteral(`${location.start.line}:${location.start.column}`)
+ );
+
+ node.attributes.unshift(attr);
+}
+
+/**
+ * `{prefix}-element-id`.
+ */
+function addElementIdAttribute(node: t.JSXOpeningElement, sourceInfo: SourceMappingInfo, options: Required) {
+ const { attributePrefix } = options;
+
+ node.attributes = node.attributes.filter(a =>
+ !(t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === `${attributePrefix}-element-id`)
+ );
+
+ const attr = t.jSXAttribute(
+ t.jSXIdentifier(`${attributePrefix}-element-id`),
+ t.stringLiteral(sourceInfo.elementId)
+ );
+
+ node.attributes.unshift(attr);
+}
+
+/**
+ * Legacy: one attribute per field (debug / queries).
+ */
+function addIndividualAttributes(node: t.JSXOpeningElement, sourceInfo: SourceMappingInfo, options: Required) {
+ const { attributePrefix } = options;
+
+ // Helper to add attribute if it doesn't exist
+ const addAttr = (name: string, value: string | number | undefined) => {
+ if (value === undefined) return;
+
+ node.attributes = node.attributes.filter(a =>
+ !(t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === name)
+ );
+
+ node.attributes.unshift(t.jSXAttribute(
+ t.jSXIdentifier(name),
+ t.stringLiteral(String(value))
+ ));
+ };
+
+ addAttr(`${attributePrefix}-file`, sourceInfo.fileName);
+ addAttr(`${attributePrefix}-line`, sourceInfo.lineNumber);
+ addAttr(`${attributePrefix}-column`, sourceInfo.columnNumber);
+ addAttr(`${attributePrefix}-component`, sourceInfo.componentName);
+ addAttr(`${attributePrefix}-function`, sourceInfo.functionName);
+ addAttr(`${attributePrefix}-import`, sourceInfo.importPath);
+}
+
+/**
+ * True only when children are exclusively JSXText (no expr containers, nested tags, etc.).
+ */
+function isStaticContent(path: NodePath): boolean {
+ const { node } = path;
+ const element = path.parent; // JSXElement
+
+ if (!t.isJSXElement(element)) return false;
+
+ if (element.children.length === 0) return false;
+
+ return element.children.every(child => {
+ if (t.isJSXText(child)) {
+ return true;
+ }
+
+ return false;
+ });
+}
+
+/**
+ * `{prefix}-static-content="true"` when children are pure JSXText.
+ */
+function addStaticContentAttribute(node: t.JSXOpeningElement, path: NodePath, options: Required) {
+ const { attributePrefix } = options;
+ const attributeName = `${attributePrefix}-static-content`;
+
+ if (!isStaticContent(path)) {
+ return;
+ }
+
+ // Check if attribute already exists
+ const hasAttr = node.attributes.some(a =>
+ t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === attributeName
+ );
+
+ if (!hasAttr) {
+ node.attributes.unshift(t.jSXAttribute(
+ t.jSXIdentifier(attributeName),
+ t.stringLiteral('true')
+ ));
+ }
+}
+
+/**
+ * Static className: absent, string literal, or expr with only static string/template without expressions.
+ * Dynamic: variables, cn(), conditional expr, template with ${}, etc.
+ */
+function isStaticClassName(node: t.JSXOpeningElement): boolean {
+ const classNameAttr = node.attributes.find(attr =>
+ t.isJSXAttribute(attr) &&
+ t.isJSXIdentifier(attr.name) &&
+ attr.name.name === 'className'
+ ) as t.JSXAttribute | undefined;
+
+ if (!classNameAttr) {
+ return true;
+ }
+
+ const value = classNameAttr.value;
+
+ if (t.isStringLiteral(value)) {
+ return true;
+ }
+
+ if (t.isJSXExpressionContainer(value)) {
+ const expression = value.expression;
+
+ if (t.isStringLiteral(expression)) {
+ return true;
+ }
+
+ if (t.isTemplateLiteral(expression)) {
+ if (expression.expressions.length === 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * `{prefix}-static-class="true"` when className is static per `isStaticClassName`.
+ */
+function addStaticClassAttribute(node: t.JSXOpeningElement, options: Required) {
+ const { attributePrefix } = options;
+ const attributeName = `${attributePrefix}-static-class`;
+
+ // Check if attribute already exists
+ const hasAttr = node.attributes.some(a =>
+ t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === attributeName
+ );
+
+ if (!hasAttr) {
+ node.attributes.unshift(t.jSXAttribute(
+ t.jSXIdentifier(attributeName),
+ t.stringLiteral('true')
+ ));
+ }
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/core/vueSfcTransformer.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/core/vueSfcTransformer.ts
new file mode 100644
index 00000000..fdd556ff
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/core/vueSfcTransformer.ts
@@ -0,0 +1,184 @@
+import { NodeTypes, parse as parseTemplate } from '@vue/compiler-dom';
+import { parse as parseSfc } from '@vue/compiler-sfc';
+import type { DesignModeOptions } from '../types';
+
+function escapeHtmlAttr(value: string): string {
+ return value
+ .replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(//g, '>');
+}
+
+function getLineAndColumnFromIndex(source: string, index: number): { line: number; column: number } {
+ const safeIndex = Math.max(0, Math.min(index, source.length));
+ const prefix = source.slice(0, safeIndex);
+ const lines = prefix.split('\n');
+ return {
+ line: lines.length,
+ column: (lines[lines.length - 1] ?? '').length + 1,
+ };
+}
+
+export function transformVueSfcTemplate(
+ code: string,
+ id: string,
+ options: Required
+): string {
+ const sfc = parseSfc(code, { filename: id });
+ const templateBlock = sfc.descriptor.template;
+ if (!templateBlock) {
+ return code;
+ }
+
+ const templateContent = templateBlock.content;
+ const attrPrefix = options.attributePrefix;
+ const existingAttrPattern = new RegExp(
+ `\\s${attrPrefix}-[\\w-]+=(?:"[^"]*"|'[^']*')`,
+ 'g'
+ );
+ const cleanedTemplate = templateContent.replace(existingAttrPattern, '');
+ const templateBaseOffset = templateBlock.loc.start.offset;
+ const ast = parseTemplate(cleanedTemplate, { comments: true });
+
+ const insertions: Array<{ offset: number; text: string }> = [];
+ let elementIndex = 0;
+
+ const visit = (node: any) => {
+ if (!node || node.type !== NodeTypes.ELEMENT) {
+ if (Array.isArray(node?.children)) {
+ node.children.forEach(visit);
+ }
+ return;
+ }
+
+ const tagName = String(node.tag);
+ const startTagEndInTemplate = findStartTagEndOffset(cleanedTemplate, node.loc.start.offset);
+ if (startTagEndInTemplate < 0) {
+ if (Array.isArray(node.children)) {
+ node.children.forEach(visit);
+ }
+ return;
+ }
+
+ const absoluteStartOffset = templateBaseOffset + node.loc.start.offset;
+ const { line, column } = getLineAndColumnFromIndex(code, absoluteStartOffset);
+ const elementType = tagName.toLowerCase();
+ const elementId = `${id}:${line}:${column}_${elementType}_${elementIndex++}`;
+ const sourceInfo = {
+ fileName: id,
+ lineNumber: line,
+ columnNumber: column,
+ // Backward compatibility for old clients that still read `line`/`column`.
+ line,
+ column,
+ elementId,
+ elementType,
+ };
+ const infoAttr = escapeHtmlAttr(JSON.stringify(sourceInfo));
+ let attrsToInject =
+ ` ${attrPrefix}-info="${infoAttr}"` +
+ ` ${attrPrefix}-position="${line}:${column}"` +
+ ` ${attrPrefix}-element-id="${elementId}"`;
+
+ // Add static-content attribute if children are pure text
+ if (isStaticContent(node)) {
+ attrsToInject += ` ${attrPrefix}-static-content="true"`;
+ }
+
+ // Add static-class attribute if class is static
+ if (isStaticClass(node)) {
+ attrsToInject += ` ${attrPrefix}-static-class="true"`;
+ }
+
+ insertions.push({
+ offset: templateBaseOffset + startTagEndInTemplate,
+ text: attrsToInject,
+ });
+
+ if (Array.isArray(node.children)) {
+ node.children.forEach(visit);
+ }
+ };
+
+ ast.children.forEach(visit);
+
+ if (insertions.length === 0 && cleanedTemplate === templateContent) {
+ return code;
+ }
+
+ let rebuilt = code.slice(0, templateBaseOffset) + cleanedTemplate + code.slice(templateBaseOffset + templateContent.length);
+ insertions.sort((a, b) => b.offset - a.offset).forEach((entry) => {
+ rebuilt = rebuilt.slice(0, entry.offset) + entry.text + rebuilt.slice(entry.offset);
+ });
+
+ return rebuilt;
+}
+
+function findStartTagEndOffset(source: string, startOffset: number): number {
+ let index = startOffset;
+ let quote: '"' | "'" | null = null;
+ while (index < source.length) {
+ const char = source[index];
+ if (!quote && (char === '"' || char === "'")) {
+ quote = char;
+ index += 1;
+ continue;
+ }
+ if (quote && char === quote) {
+ quote = null;
+ index += 1;
+ continue;
+ }
+ if (!quote && char === '>') {
+ if (index > startOffset && source[index - 1] === '/') {
+ return index - 1;
+ }
+ return index;
+ }
+ index += 1;
+ }
+ return -1;
+}
+
+/**
+ * Check if element has only static text content (no interpolations, no nested elements)
+ */
+function isStaticContent(node: any): boolean {
+ if (!node.children || node.children.length === 0) {
+ return false;
+ }
+
+ // All children must be TEXT nodes (type 2)
+ return node.children.every((child: any) => {
+ return child.type === NodeTypes.TEXT;
+ });
+}
+
+/**
+ * Check if element has static class attribute (no v-bind:class, no :class)
+ * Static means: absent, or a plain string attribute
+ */
+function isStaticClass(node: any): boolean {
+ if (!node.props || node.props.length === 0) {
+ return true; // No class at all = static
+ }
+
+ // Look for class-related props
+ for (const prop of node.props) {
+ // v-bind:class or :class (type 7 = DIRECTIVE)
+ if (prop.type === NodeTypes.DIRECTIVE) {
+ if (prop.name === 'bind' && prop.arg?.content === 'class') {
+ return false; // Dynamic binding
+ }
+ }
+
+ // Plain class attribute (type 6 = ATTRIBUTE)
+ if (prop.type === NodeTypes.ATTRIBUTE && prop.name === 'class') {
+ // Static string class
+ return true;
+ }
+ }
+
+ return true; // No class attribute found = static
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/core/vueSfcUpdater.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/core/vueSfcUpdater.ts
new file mode 100644
index 00000000..b8550645
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/core/vueSfcUpdater.ts
@@ -0,0 +1,176 @@
+import { parse as parseSfc } from '@vue/compiler-sfc';
+import { NodeTypes, parse as parseTemplate } from '@vue/compiler-dom';
+
+type UpdateKind = 'style' | 'content' | 'attribute';
+
+export interface VueSfcUpdateOptions {
+ lineNumber: number;
+ columnNumber: number;
+ newValue: string;
+ originalValue?: string;
+ type: UpdateKind;
+}
+
+interface SourceSpan {
+ start: number;
+ end: number;
+}
+
+export function applyVueSfcTemplateUpdate(
+ source: string,
+ options: VueSfcUpdateOptions
+): string {
+ const sfc = parseSfc(source, { filename: 'component.vue' });
+ const templateBlock = sfc.descriptor.template;
+ if (!templateBlock) {
+ throw new Error('Vue SFC missing block.');
+ }
+
+ const templateStartOffset = templateBlock.loc.start.offset;
+ const templateSource = templateBlock.content;
+ const targetOffsetInFile = getOffsetFromLineColumn(source, options.lineNumber, options.columnNumber);
+ const targetOffsetInTemplate = targetOffsetInFile - templateStartOffset;
+ if (targetOffsetInTemplate < 0 || targetOffsetInTemplate > templateSource.length) {
+ throw new Error('Target position is outside block.');
+ }
+
+ const ast = parseTemplate(templateSource, { comments: true });
+ const targetElement = findElementByPosition(ast, targetOffsetInTemplate);
+ if (!targetElement) {
+ throw new Error('Unable to locate target element in Vue template AST.');
+ }
+
+ switch (options.type) {
+ case 'style':
+ return updateElementClass(source, templateStartOffset, targetElement, options.newValue);
+ case 'content':
+ return updateElementTextContent(source, templateStartOffset, targetElement, options.newValue, options.originalValue);
+ case 'attribute':
+ throw new Error('Vue attribute update is not supported in AST mode yet.');
+ default:
+ return source;
+ }
+}
+
+function updateElementClass(
+ fullSource: string,
+ templateStartOffset: number,
+ elementNode: any,
+ newClassValue: string
+): string {
+ const classAttr = elementNode.props.find(
+ (prop: any) => prop.type === NodeTypes.ATTRIBUTE && prop.name === 'class'
+ );
+ if (classAttr?.value?.loc) {
+ const valueLoc = toFullFileSpan(templateStartOffset, classAttr.value.loc.start.offset, classAttr.value.loc.end.offset);
+ const originalValueSource = fullSource.slice(valueLoc.start, valueLoc.end);
+ const quoteChar = originalValueSource.startsWith("'") ? "'" : '"';
+ const escaped = escapeAttributeValue(newClassValue, quoteChar);
+ return replaceSpan(fullSource, valueLoc, `${quoteChar}${escaped}${quoteChar}`);
+ }
+
+ const classBind = elementNode.props.find(
+ (prop: any) => prop.type === NodeTypes.DIRECTIVE && prop.name === 'bind' && prop.arg?.type === NodeTypes.SIMPLE_EXPRESSION && prop.arg.content === 'class'
+ );
+ if (classBind?.exp?.type === NodeTypes.SIMPLE_EXPRESSION && classBind.exp.isStatic) {
+ const bindLoc = toFullFileSpan(templateStartOffset, classBind.loc.start.offset, classBind.loc.end.offset);
+ return replaceSpan(fullSource, bindLoc, `class="${escapeAttributeValue(newClassValue)}"`);
+ }
+ if (classBind) {
+ throw new Error('Dynamic :class expression is not safely editable in Vue AST mode.');
+ }
+
+ const insertOffset = templateStartOffset + elementNode.loc.start.offset + `<${elementNode.tag}`.length;
+ return `${fullSource.slice(0, insertOffset)} class="${escapeAttributeValue(newClassValue)}"${fullSource.slice(insertOffset)}`;
+}
+
+function updateElementTextContent(
+ fullSource: string,
+ templateStartOffset: number,
+ elementNode: any,
+ newText: string,
+ originalValue?: string
+): string {
+ const textChild = elementNode.children.find(
+ (child: any) => child.type === NodeTypes.TEXT && child.content.trim().length > 0
+ );
+ if (!textChild?.loc) {
+ throw new Error('Target Vue element has no editable static text node.');
+ }
+
+ const currentText = textChild.content;
+ if (originalValue && !currentText.includes(originalValue)) {
+ throw new Error('Original text does not match current Vue template text.');
+ }
+
+ const replacement = originalValue
+ ? currentText.replace(new RegExp(escapeRegExp(originalValue), 'g'), newText)
+ : newText;
+ const textLoc = toFullFileSpan(templateStartOffset, textChild.loc.start.offset, textChild.loc.end.offset);
+ return replaceSpan(fullSource, textLoc, replacement);
+}
+
+function findElementByPosition(ast: any, targetOffset: number): any | null {
+ let matched: any | null = null;
+
+ const visit = (node: any) => {
+ if (!node?.loc?.start || !node?.loc?.end) {
+ return;
+ }
+ if (targetOffset < node.loc.start.offset || targetOffset > node.loc.end.offset) {
+ return;
+ }
+ if (node.type === NodeTypes.ELEMENT) {
+ matched = node;
+ }
+ if (Array.isArray(node.children)) {
+ for (const child of node.children) {
+ visit(child);
+ }
+ }
+ };
+
+ for (const child of ast.children ?? []) {
+ visit(child);
+ }
+
+ return matched;
+}
+
+function getOffsetFromLineColumn(source: string, line: number, column: number): number {
+ const lines = source.split('\n');
+ if (line < 1 || line > lines.length) {
+ throw new Error(`Line ${line} exceeds source length.`);
+ }
+ const targetLine = lines[line - 1] ?? '';
+ if (column < 1 || column > targetLine.length + 1) {
+ throw new Error(`Column ${column} is invalid for line ${line}.`);
+ }
+ let offset = 0;
+ for (let i = 0; i < line - 1; i++) {
+ offset += (lines[i] ?? '').length + 1;
+ }
+ return offset + (column - 1);
+}
+
+function toFullFileSpan(templateStart: number, start: number, end: number): SourceSpan {
+ return {
+ start: templateStart + start,
+ end: templateStart + end,
+ };
+}
+
+function replaceSpan(source: string, span: SourceSpan, replacement: string): string {
+ return `${source.slice(0, span.start)}${replacement}${source.slice(span.end)}`;
+}
+
+function escapeAttributeValue(value: string, quoteChar: '"' | "'" = '"'): string {
+ if (quoteChar === "'") {
+ return value.replace(/'/g, ''');
+ }
+ return value.replace(/"/g, '"');
+}
+
+function escapeRegExp(value: string): string {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/index.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/index.ts
new file mode 100644
index 00000000..1c408086
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/index.ts
@@ -0,0 +1,569 @@
+import type { Plugin } from 'vite';
+import { createServerMiddleware } from './core/serverMiddleware.js';
+import { transformSourceCode } from './core/astTransformer.js';
+import { transformVueSfcTemplate } from './core/vueSfcTransformer.js';
+
+
+export interface DesignModeOptions {
+ /**
+ * Whether design mode is enabled.
+ * @default true
+ */
+ enabled?: boolean;
+
+ /**
+ * Whether to enable in production builds (usually false).
+ * @default false
+ */
+ enableInProduction?: boolean;
+
+ /**
+ * Prefix for injected source-mapping attributes.
+ * @default 'data-source'
+ */
+ attributePrefix?: string;
+
+ /**
+ * Verbose logging.
+ * @default false
+ */
+ verbose?: boolean;
+
+ /**
+ * Glob/path patterns to exclude from transformation.
+ */
+ exclude?: string[];
+
+ /**
+ * Glob/path patterns to include.
+ */
+ include?: string[];
+
+ /**
+ * Enable backup copies before writes.
+ * @default false
+ */
+ enableBackup?: boolean;
+
+ /**
+ * Enable update history / batch session tracking.
+ * @default false
+ */
+ enableHistory?: boolean;
+
+ /**
+ * Target framework mode.
+ * - auto: detect from project dependencies
+ * - react: force React client injection
+ * - vue: disable React client injection for Vue projects
+ * @default 'auto'
+ */
+ framework?: 'auto' | 'react' | 'vue';
+}
+
+const DEFAULT_OPTIONS: Required = {
+ enabled: true,
+ enableInProduction: false,
+ attributePrefix: 'data-xagi',
+ verbose: true,
+ exclude: ['node_modules', 'dist'],
+ include: ['src/**/*.{ts,js,tsx,jsx,vue}'],
+ enableBackup: false,
+ enableHistory: false,
+ framework: 'auto',
+};
+
+import { fileURLToPath } from 'url';
+import { dirname, resolve } from 'path';
+import { existsSync, readFileSync } from 'fs';
+import { createRequire } from 'module';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const require = createRequire(import.meta.url);
+
+// Virtual module id for loading client code in Vite
+const VIRTUAL_CLIENT_MODULE_ID = 'virtual:appdev-design-mode-client';
+const RESOLVED_VIRTUAL_CLIENT_MODULE_ID = '\0' + VIRTUAL_CLIENT_MODULE_ID + '.tsx';
+
+type ClientRuntimeMode = 'react' | 'vue';
+
+function appdevDesignModePlugin(userOptions: DesignModeOptions = {}): Plugin {
+ const options = { ...DEFAULT_OPTIONS, ...userOptions };
+ // Resolved base URL for client script injection
+ let basePath = '/';
+ // Current Vite command (serve vs build)
+ let currentCommand: 'serve' | 'build' = 'serve';
+ // Framework runtime resolution
+ let resolvedFramework: 'react' | 'vue' = 'react';
+ let hasReactRuntime = true;
+ let clientRuntimeMode: ClientRuntimeMode = 'react';
+ let hasWarnedVueLiteRuntime = false;
+
+ // Whether the plugin should run (dev by default; prod only if allowed)
+ const isPluginEnabled = () => {
+ if (!options.enabled) {
+ return false;
+ }
+ // Disable during build unless production is explicitly allowed
+ if (currentCommand === 'build' && !options.enableInProduction) {
+ return false;
+ }
+ return true;
+ };
+
+ return {
+ name: '@xagi/vite-plugin-design-mode',
+ enforce: 'pre',
+
+ resolveId(id) {
+ // Skip virtual module when plugin is off
+ if (!isPluginEnabled()) {
+ return null;
+ }
+ if (id === VIRTUAL_CLIENT_MODULE_ID) {
+ return RESOLVED_VIRTUAL_CLIENT_MODULE_ID;
+ }
+ return null;
+ },
+
+ load(id) {
+ // Skip when plugin is off
+ if (!isPluginEnabled()) {
+ return null;
+ }
+
+ if (id === RESOLVED_VIRTUAL_CLIENT_MODULE_ID) {
+ const clientEntryPath = resolveClientEntryPath(clientRuntimeMode);
+ if (!existsSync(clientEntryPath)) {
+ throw new Error(
+ `[appdev-design-mode] Client entry not found: ${clientEntryPath}`
+ );
+ }
+ // Rewrite relative imports to absolute paths so the virtual module resolves deps
+ const code = readFileSync(clientEntryPath, 'utf-8');
+ const clientDir = dirname(clientEntryPath);
+
+ // Rewrite relative imports to absolute paths
+ const rewrittenCode = code.replace(
+ /from ['"]\.\/([^'"]+)['"]/g,
+ (match, moduleName) => {
+ const absolutePath = resolve(clientDir, moduleName);
+ return `from '${absolutePath}'`;
+ }
+ );
+
+ return rewrittenCode;
+ }
+
+ // **NEW: Process tsx/jsx files in load hook BEFORE React plugin**
+ // This allows us to add attributes to JSX elements before they are compiled
+ const shouldProcess = shouldProcessFile(id, options);
+
+ if (shouldProcess) {
+ try {
+ // Read the original file content
+ const code = readFileSync(id, 'utf-8');
+
+ // Transform source to add design-mode attributes before framework plugins run.
+ const transformedCode = id.endsWith('.vue')
+ ? transformVueSfcTemplate(code, id, options)
+ : transformSourceCode(code, id, options);
+
+ // Return the transformed code
+ // React plugin will then process this code to compile JSX
+ return transformedCode;
+ } catch (error) {
+ // Log error in verbose mode for debugging
+ if (options.verbose) {
+ console.error('[appdev-design-mode] Transform error for', id, ':', error);
+ }
+ // Return null to let other plugins handle it
+ return null;
+ }
+ }
+
+ return null;
+ },
+
+ config(config, { command }) {
+ currentCommand = command;
+ const projectRoot = config.root ?? process.cwd();
+ const runtime = resolveFrameworkMode(projectRoot, options.framework);
+ resolvedFramework = runtime.framework;
+ hasReactRuntime = runtime.hasReactRuntime;
+ clientRuntimeMode = hasReactRuntime ? 'react' : 'vue';
+
+ basePath = config.base || '/';
+ // Normalize base: leading slash; trailing slash unless root
+ if (!basePath.startsWith('/')) {
+ basePath = '/' + basePath;
+ }
+ if (basePath !== '/' && !basePath.endsWith('/')) {
+ basePath = basePath + '/';
+ }
+
+ // No extra config when plugin disabled
+ if (!isPluginEnabled()) {
+ return {};
+ }
+
+ // Merge with user fs.allow
+ const existingAllow = config.server?.fs?.allow || [];
+ const pluginDistPath = resolve(__dirname, '..');
+
+ const allowedPaths = new Set();
+
+ existingAllow.forEach((path: string) => {
+ // Normalize to absolute paths
+ const normalizedPath = resolve(path);
+ allowedPaths.add(normalizedPath);
+ });
+
+ allowedPaths.add(resolve(projectRoot));
+
+ // node_modules (dependency resolution)
+ const nodeModulesPath = resolve(projectRoot, 'node_modules');
+ allowedPaths.add(nodeModulesPath);
+
+ // Plugin package root (client bundle)
+ allowedPaths.add(pluginDistPath);
+
+ if (options.verbose && !hasReactRuntime && !hasWarnedVueLiteRuntime) {
+ hasWarnedVueLiteRuntime = true;
+ console.warn(
+ '[appdev-design-mode] React runtime not detected; using Vue3 client runtime.'
+ );
+ }
+
+ const pluginConfig: Record = {
+ define: {
+ __APPDEV_DESIGN_MODE__: true,
+ __APPDEV_DESIGN_MODE_VERBOSE__: options.verbose,
+ },
+ server: {
+ fs: {
+ // User entries plus required plugin paths
+ allow: Array.from(allowedPaths),
+ },
+ },
+ };
+
+ if (hasReactRuntime) {
+ pluginConfig.esbuild = {
+ logOverride: { 'this-is-undefined-in-esm': 'silent' },
+ jsx: 'automatic',
+ jsxDev: false,
+ };
+ pluginConfig.optimizeDeps = {
+ include: ['react', 'react-dom'],
+ };
+ }
+
+ return pluginConfig;
+ },
+
+ configureServer(server) {
+ // Skip middleware when plugin off
+ if (!isPluginEnabled()) {
+ return;
+ }
+
+ // Register client code middleware
+ server.middlewares.use(async (req, res, next) => {
+ // Match client script URL with or without base prefix
+ const url = req.url || '';
+ const isClientRequest =
+ url === '/@appdev-design-mode/client.js' ||
+ url.endsWith('/@appdev-design-mode/client.js') ||
+ url.includes('@appdev-design-mode/client.js');
+
+ if (isClientRequest) {
+ try {
+ // Load bundled client via virtual module (ssr: false = browser)
+ const result = await server.transformRequest(
+ VIRTUAL_CLIENT_MODULE_ID,
+ { ssr: false }
+ );
+
+ if (result && result.code) {
+ res.statusCode = 200;
+ res.setHeader('Content-Type', 'application/javascript');
+ res.setHeader('Cache-Control', 'no-cache');
+ res.end(result.code);
+ } else {
+ throw new Error('transformRequest returned no code');
+ }
+ } catch (error: any) {
+ res.statusCode = 500;
+ res.setHeader('Content-Type', 'text/plain');
+ res.end(
+ `[appdev-design-mode] Failed to load client bundle: ${error.message}`
+ );
+ if (options.verbose) {
+ console.error(
+ '[appdev-design-mode] Client bundle error:',
+ error
+ );
+ }
+ }
+ return;
+ }
+
+ next();
+ });
+
+ // Register the main API middleware - this handles all /__appdev_design_mode/* endpoints
+ server.middlewares.use(
+ '/__appdev_design_mode',
+ createServerMiddleware(options, server.config.root)
+ );
+ },
+
+ transformIndexHtml(html, ctx) {
+ if (!isPluginEnabled()) {
+ return html;
+ }
+
+ // Client script URL respects Vite base (subpath deploys)
+ const clientScriptPath = `${basePath}@appdev-design-mode/client.js`.replace(/\/+/g, '/');
+
+ // HTTP endpoint avoids /@fs and some node_modules access edge cases
+ return {
+ html,
+ tags: [
+ // Inject prefix config for runtime client
+ {
+ tag: 'script',
+ attrs: {
+ type: 'text/javascript',
+ },
+ injectTo: 'head',
+ children: `window.__APPDEV_DESIGN_MODE_CONFIG__ = ${JSON.stringify({
+ attributePrefix: options.attributePrefix,
+ framework: resolvedFramework,
+ })};`,
+ },
+ {
+ tag: 'script',
+ attrs: {
+ type: 'module',
+ src: clientScriptPath,
+ },
+ injectTo: 'body',
+ },
+ ],
+ };
+ },
+
+ async transform(code, id, transformOptions) {
+ if (!isPluginEnabled()) {
+ return code;
+ }
+
+ // Virtual client module: compile TSX with esbuild (not user's React plugin)
+ if (id === RESOLVED_VIRTUAL_CLIENT_MODULE_ID) {
+ const { transformWithEsbuild } = await import('vite');
+ const result = await transformWithEsbuild(code, id, {
+ loader: clientRuntimeMode === 'react' ? 'tsx' : 'ts',
+ jsx: 'automatic',
+ format: 'esm',
+ });
+ return result.code;
+ }
+
+ // Plugin package sources: esbuild only (avoids duplicate Babel runs in pnpm, etc.)
+ const pluginDistPath = resolve(__dirname, '..');
+ if (id.startsWith(pluginDistPath) && (id.endsWith('.tsx') || id.endsWith('.ts'))) {
+ const { transformWithEsbuild } = await import('vite');
+ const result = await transformWithEsbuild(code, id, {
+ loader: id.endsWith('.tsx') ? 'tsx' : 'ts',
+ jsx: 'automatic',
+ format: 'esm',
+ });
+ return result.code;
+ }
+
+ // User source files are already processed in load hook, skip here to avoid double processing
+ return code;
+ },
+
+ buildStart() {
+ if (options.verbose) {
+ console.log('[appdev-design-mode] Plugin started');
+ }
+ },
+
+ buildEnd() {
+ if (options.verbose) {
+ console.log('[appdev-design-mode] Plugin ended');
+ }
+ },
+ };
+}
+
+/** Whether `filePath` matches include globs and not exclude patterns. */
+function shouldProcessFile(
+ filePath: string,
+ options: Required
+): boolean {
+ if (options.exclude.some(pattern => filePath.includes(pattern))) {
+ return false;
+ }
+
+ // Include globs
+ const isIncluded = options.include.some(pattern => {
+ // Ignore negation patterns in include (rely on exclude option)
+ if (pattern.startsWith('!')) {
+ return false;
+ }
+
+ // Convert glob to regex using placeholders to avoid escaping issues
+ let regex = pattern;
+
+ // 1) Placeholders for glob tokens
+ // IMPORTANT: Replace **/ as a unit first to avoid double slashes
+ regex = regex.replace(/\*\*\//g, '%%GLOBSTAR_SLASH%%');
+ regex = regex.replace(/\*\*/g, '%%GLOBSTAR%%');
+ regex = regex.replace(/\*/g, '%%WILDCARD%%');
+ regex = regex.replace(/\{([^}]+)\}/g, (_, group) =>
+ `%%BRACE_START%%${group.replace(/,/g, '%%COMMA%%')}%%BRACE_END%%`
+ );
+
+ // 2. Escape special regex characters
+ regex = regex.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
+
+ // 3. Restore placeholders with regex equivalents
+ // **/ matches zero or more path segments (e.g., "", "foo/", "foo/bar/")
+ regex = regex.replace(/%%GLOBSTAR_SLASH%%/g, '(([^/]+/)*)');
+ // ** alone (rare) matches any characters
+ regex = regex.replace(/%%GLOBSTAR%%/g, '.*');
+ regex = regex.replace(/%%WILDCARD%%/g, '[^/]*');
+ regex = regex.replace(/%%BRACE_START%%/g, '(');
+ regex = regex.replace(/%%BRACE_END%%/g, ')');
+ regex = regex.replace(/%%COMMA%%/g, '|');
+
+ // 4. Create RegExp
+ // Allow partial match for absolute paths by prepending .* if not already there
+ // and not anchored
+ if (!regex.startsWith('.*') && !regex.startsWith('^')) {
+ regex = '.*' + regex;
+ }
+
+ const re = new RegExp(regex + '$'); // Anchor to end
+ return re.test(filePath);
+ });
+
+ return isIncluded;
+}
+
+export default appdevDesignModePlugin;
+
+function resolveFrameworkMode(
+ projectRoot: string,
+ configured: 'auto' | 'react' | 'vue'
+): { framework: 'react' | 'vue'; hasReactRuntime: boolean } {
+ const packageMeta = readProjectPackageMeta(projectRoot);
+ const vueMajor = getVueMajorVersion(packageMeta.deps.vue);
+ const hasVue2Plugin = Boolean(packageMeta.deps['@vitejs/plugin-vue2']);
+
+ if (configured === 'react') {
+ return { framework: 'react', hasReactRuntime: true };
+ }
+
+ if (configured === 'vue') {
+ // Explicit vue mode means the user expects Vue support; fail fast if Vue 2 is detected.
+ if (hasVue2Plugin || vueMajor === 2) {
+ throw new Error(
+ '[appdev-design-mode] Vue 2 is not supported. Please upgrade to Vue 3 and use @vitejs/plugin-vue.'
+ );
+ }
+ return { framework: 'vue', hasReactRuntime: false };
+ }
+
+ const hasReact = Boolean(packageMeta.deps.react && packageMeta.deps['react-dom']);
+
+ if (hasReact) {
+ return { framework: 'react', hasReactRuntime: true };
+ }
+
+ // Auto mode should still hard-reject Vue 2 projects to avoid silent partial behavior.
+ if (hasVue2Plugin || vueMajor === 2) {
+ throw new Error(
+ '[appdev-design-mode] Detected Vue 2 project. This plugin supports Vue 3 only.'
+ );
+ }
+
+ if (vueMajor === 3) {
+ return { framework: 'vue', hasReactRuntime: false };
+ }
+
+ return { framework: 'react', hasReactRuntime: true };
+}
+
+function readProjectPackageMeta(
+ projectRoot: string
+): { deps: Record } {
+ const packageJsonPath = resolve(projectRoot, 'package.json');
+ if (!existsSync(packageJsonPath)) {
+ return { deps: {} };
+ }
+
+ try {
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
+ dependencies?: Record;
+ devDependencies?: Record;
+ };
+ return { deps: { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) } };
+ } catch {
+ return { deps: {} };
+ }
+}
+
+function getVueMajorVersion(versionRange?: string): 2 | 3 | null {
+ if (!versionRange) {
+ return null;
+ }
+ // Strip leading range operators/spaces and read the first semver-like number.
+ const match = versionRange.trim().match(/(\d+)/);
+ if (!match) {
+ return null;
+ }
+ const major = Number(match[1]);
+ if (major === 2 || major === 3) {
+ return major;
+ }
+ return null;
+}
+
+function resolveClientEntryPath(mode: ClientRuntimeMode): string {
+ const packageName =
+ mode === 'react'
+ ? '@xagi/design-mode-client-react'
+ : '@xagi/design-mode-client-vue';
+
+ const packageEntry = resolvePackageEntry(packageName);
+ const candidates =
+ mode === 'react'
+ ? [packageEntry, resolve(__dirname, '../../client-react/src/index.tsx')]
+ : [packageEntry, resolve(__dirname, '../../client-vue/src/index.ts')];
+
+ for (const candidate of candidates) {
+ if (existsSync(candidate)) {
+ return candidate;
+ }
+ }
+
+ throw new Error(
+ `[appdev-design-mode] Cannot resolve client entry for runtime mode: ${mode}`
+ );
+}
+
+function resolvePackageEntry(packageName: string): string {
+ try {
+ return require.resolve(packageName);
+ } catch {
+ return '';
+ }
+}
+
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/types/index.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/types/index.ts
new file mode 100644
index 00000000..cacb608f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/types/index.ts
@@ -0,0 +1,53 @@
+import type { Plugin } from 'vite';
+
+export interface DesignModeOptions {
+ enabled?: boolean;
+ enableInProduction?: boolean;
+ attributePrefix?: string;
+ verbose?: boolean;
+ exclude?: string[];
+ include?: string[];
+ enableBackup?: boolean; // Enable file backups (default false)
+ enableHistory?: boolean; // Enable update history (default false)
+ framework?: 'auto' | 'react' | 'vue';
+}
+
+export interface SourceMappingInfo {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+ elementType: string;
+ componentName?: string;
+ functionName?: string;
+ elementId: string;
+ attributePrefix: string;
+}
+
+export interface ElementSourceRequest {
+ elementId: string;
+}
+
+export interface ElementSourceResponse {
+ sourceInfo: SourceMappingInfo & {
+ fileContent: string;
+ contextLines: string;
+ targetLineContent: string;
+ };
+}
+
+export interface ElementModifyRequest {
+ elementId: string;
+ newStyles: string;
+ oldStyles?: string;
+}
+
+export interface ElementModifyResponse {
+ success: boolean;
+ message: string;
+ sourceInfo: SourceMappingInfo;
+}
+
+// Type declaration for module exports
+declare const _default: (options?: DesignModeOptions) => Plugin;
+
+export default _default;
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/types/messages.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/types/messages.ts
new file mode 100644
index 00000000..4b38d76c
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/types/messages.ts
@@ -0,0 +1,340 @@
+// Message types for iframe ↔ parent window communication
+
+export interface SourceInfo {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+ elementType?: string;
+ componentName?: string;
+ functionName?: string;
+ elementId?: string;
+ importPath?: string;
+ isUIComponent?: boolean; // Whether this is a UI-kit component (e.g. under components/ui)
+}
+
+export interface ElementInfo {
+ tagName: string;
+ className: string;
+ textContent: string;
+ sourceInfo: SourceInfo;
+ isStaticText: boolean;
+ isStaticClass?: boolean; // Whether className is a pure static string (editable)
+ componentName?: string;
+ componentPath?: string;
+ props?: Record;
+ hierarchy?: {
+ tagName: string;
+ componentName?: string;
+ fileName?: string;
+ }[];
+}
+
+// Message validation types
+export interface MessageValidationResult {
+ isValid: boolean;
+ errors?: string[];
+}
+
+export interface MessageValidator {
+ validate: (message: any) => MessageValidationResult;
+}
+
+// Bridge Ready Message
+export interface BridgeReadyMessage {
+ type: 'BRIDGE_READY';
+ payload: {
+ timestamp: number;
+ environment: 'iframe' | 'main';
+ };
+ timestamp?: number;
+ requestId?: string;
+}
+
+// iframe → Parent messages
+export interface ElementSelectedMessage {
+ type: 'ELEMENT_SELECTED';
+ payload: {
+ elementInfo: ElementInfo;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface ElementDeselectedMessage {
+ type: 'ELEMENT_DESELECTED';
+ requestId?: string;
+ timestamp?: number;
+ payload?: null; // Added optional payload to match usage
+}
+
+export interface ContentUpdatedMessage {
+ type: 'CONTENT_UPDATED';
+ payload: {
+ sourceInfo: SourceInfo;
+ oldValue: string;
+ newValue: string;
+ realtime?: boolean; // Whether this is a realtime (throttled) update
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface ContentUpdatedCallbackMessage {
+ type: 'CONTENT_UPDATED_CALLBACK';
+ payload: {
+ sourceInfo: SourceInfo;
+ oldValue: string;
+ newValue: string;
+ realtime?: boolean; // Whether this is a realtime (throttled) update
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface StyleUpdatedMessage {
+ type: 'STYLE_UPDATED';
+ payload: {
+ sourceInfo: SourceInfo;
+ oldClass: string;
+ newClass: string;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface DesignModeChangedMessage {
+ type: 'DESIGN_MODE_CHANGED';
+ enabled: boolean;
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Parent → iframe messages
+export interface ToggleDesignModeMessage {
+ type: 'TOGGLE_DESIGN_MODE';
+ enabled: boolean;
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface UpdateStyleMessage {
+ type: 'UPDATE_STYLE';
+ payload: {
+ sourceInfo: SourceInfo;
+ newClass: string;
+ persist?: boolean;
+ };
+ requestId?: string;
+ timestamp?: number;
+ enabled?: boolean; // Added to fix TS error where enabled was accessed on this type (though likely a bug in usage, adding optional property avoids strict error if discriminated union fails)
+}
+
+export interface UpdateContentMessage {
+ type: 'UPDATE_CONTENT';
+ payload: {
+ sourceInfo: SourceInfo;
+ newContent: string;
+ persist?: boolean;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+export interface BatchUpdateItem {
+ type: 'style' | 'content';
+ sourceInfo: SourceInfo;
+ newValue: string;
+ originalValue?: string;
+}
+
+export interface BatchUpdateMessage {
+ type: 'BATCH_UPDATE';
+ payload: {
+ updates: BatchUpdateItem[];
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Add to Chat Message
+export interface AddToChatMessage {
+ type: 'ADD_TO_CHAT';
+ payload: {
+ content: string;
+ context?: {
+ elementInfo?: ElementInfo;
+ sourceInfo?: SourceInfo;
+ };
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Copy Element Message
+export interface CopyElementMessage {
+ type: 'COPY_ELEMENT';
+ payload: {
+ elementInfo: {
+ tagName: string;
+ className: string;
+ content: string;
+ sourceInfo?: SourceInfo;
+ };
+ textContent: string; // JSON string for clipboard
+ success: boolean; // Whether copy succeeded
+ error?: string; // Error message when copy failed
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Error message
+export interface ErrorMessage {
+ type: 'ERROR';
+ payload: {
+ code: string;
+ message: string;
+ details?: any;
+ };
+ requestId?: string;
+ timestamp?: number;
+}
+
+// Acknowledgement message
+export interface AcknowledgementMessage {
+ type: 'ACKNOWLEDGEMENT';
+ payload: {
+ messageType: string;
+ };
+ requestId: string; // Response MUST have requestId
+ timestamp?: number;
+}
+
+// Heartbeat and health-check messages
+export interface HeartbeatMessage {
+ type: 'HEARTBEAT';
+ payload?: {
+ timestamp: number;
+ };
+ timestamp?: number;
+}
+
+export interface HealthCheckMessage {
+ type: 'HEALTH_CHECK';
+ requestId?: string; // Made optional for sender
+ timestamp?: number; // Made optional for sender
+}
+
+export interface HealthCheckResponseMessage {
+ type: 'HEALTH_CHECK_RESPONSE';
+ payload: {
+ status: 'healthy' | 'unhealthy' | 'degraded' | 'connecting' | 'unnecessary';
+ version?: string;
+ uptime?: number;
+ message?: string;
+ response?: any;
+ error?: string;
+ isIframe?: boolean;
+ isConnected?: boolean;
+ origin?: string;
+ userAgent?: string;
+ location?: string;
+ };
+ requestId: string; // Response MUST have requestId
+ timestamp?: number;
+}
+
+// Union types
+export type IframeToParentMessage =
+ | ElementSelectedMessage
+ | ElementDeselectedMessage
+ | ContentUpdatedMessage
+ | StyleUpdatedMessage
+ | DesignModeChangedMessage
+ | ErrorMessage
+ | AcknowledgementMessage
+ | HeartbeatMessage
+ | HealthCheckResponseMessage
+ | BridgeReadyMessage
+ | AddToChatMessage
+ | CopyElementMessage
+ | ContentUpdatedCallbackMessage;
+
+export type ParentToIframeMessage =
+ | ToggleDesignModeMessage
+ | UpdateStyleMessage
+ | UpdateContentMessage
+ | BatchUpdateMessage
+ | HealthCheckMessage
+ | HeartbeatMessage; // Added HeartbeatMessage here too for bidirectional support
+
+export type DesignModeMessage = IframeToParentMessage | ParentToIframeMessage;
+
+// Promise-based request/response types
+export type RequestMessage = ParentToIframeMessage & {
+ requestId: string;
+ timestamp: number;
+};
+
+export type ResponseMessage = IframeToParentMessage & {
+ requestId: string;
+ success?: boolean;
+ error?: string;
+ timestamp: number;
+};
+
+export type RequestResponseMessage = RequestMessage | ResponseMessage | AcknowledgementMessage;
+
+// Message handler type
+export interface MessageHandler {
+ type: T['type'];
+ handler: (message: T) => Promise | any;
+ validator?: MessageValidator;
+}
+
+// Config-related types
+export interface IframeModeConfig {
+ enabled: boolean;
+ hideUI: boolean;
+ enableSelection: boolean;
+ enableDirectEdit: boolean;
+}
+
+export interface BatchUpdateConfig {
+ enabled: boolean;
+ debounceMs: number;
+}
+
+export interface BridgeConfig {
+ timeout: number;
+ retryAttempts: number;
+ retryDelay: number;
+ heartbeatInterval: number;
+ debug: boolean;
+}
+
+// Message utility helpers
+export interface MessageUtils {
+ generateRequestId: () => string;
+ createTimestamp: () => number;
+ isValidMessage: (message: any) => boolean;
+ createResponse: (
+ originalMessage: ParentToIframeMessage,
+ payload: T extends { payload: infer P } ? P : never,
+ success?: boolean,
+ error?: string
+ ) => T;
+}
+
+// Bridge interface
+export interface BridgeInterface {
+ send: (message: T) => Promise;
+ sendWithResponse: (
+ message: T,
+ responseType: R['type']
+ ) => Promise;
+ on: (type: T['type'], handler: (message: T) => void) => void;
+ off: (type: string, handler: Function) => void;
+ isConnected: () => boolean;
+ disconnect: () => void;
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/src/utils/babelHelpers.ts b/qiming-vite-plugin-design-mode/packages/plugin/src/utils/babelHelpers.ts
new file mode 100644
index 00000000..61c1ff56
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/src/utils/babelHelpers.ts
@@ -0,0 +1,87 @@
+import * as t from '@babel/types';
+
+/**
+ * Whether the name looks like a React component (PascalCase).
+ */
+export function isReactComponentName(name: string): boolean {
+ return /^[A-Z]/.test(name);
+}
+
+/**
+ * Base name of a JSX element (identifier or member property).
+ */
+export function getJSXElementBaseName(name: any): string {
+ if (t.isJSXIdentifier(name)) {
+ return name.name;
+ } else if (t.isJSXMemberExpression(name)) {
+ return name.property.name;
+ }
+ return 'unknown';
+}
+
+/**
+ * Whether a JSX attribute is a string literal for the given name.
+ */
+export function isStringLiteralAttribute(
+ attr: t.JSXAttribute,
+ name: string
+): attr is t.JSXAttribute & { value: t.StringLiteral } {
+ return (
+ t.isJSXIdentifier(attr.name) &&
+ attr.name.name === name &&
+ t.isStringLiteral(attr.value)
+ );
+}
+
+/**
+ * Read a string literal attribute value from a JSX opening element.
+ */
+export function extractStringAttributeValue(
+ node: t.JSXOpeningElement,
+ attributeName: string
+): string | null {
+ const attr = node.attributes.find(a =>
+ t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === attributeName
+ ) as t.JSXAttribute;
+
+ if (attr && t.isStringLiteral(attr.value)) {
+ return attr.value.value;
+ }
+
+ return null;
+}
+
+/**
+ * Serialize file:line:column into a single string.
+ */
+export function createSourcePositionString(
+ fileName: string,
+ lineNumber: number,
+ columnNumber: number
+): string {
+ return `${fileName}:${lineNumber}:${columnNumber}`;
+}
+
+/**
+ * Parse a file:line:column string back into parts.
+ */
+export function parseSourcePositionString(position: string): {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+} | null {
+ const parts = position.split(':');
+ if (parts.length !== 3) return null;
+
+ const [fileName, lineNumberStr, columnNumberStr] = parts;
+ const lineNumber = parseInt(lineNumberStr);
+ const columnNumber = parseInt(columnNumberStr);
+
+ if (isNaN(lineNumber) || isNaN(columnNumber)) return null;
+
+ return {
+ fileName,
+ lineNumber,
+ columnNumber
+ };
+}
diff --git a/qiming-vite-plugin-design-mode/packages/plugin/tsconfig.json b/qiming-vite-plugin-design-mode/packages/plugin/tsconfig.json
new file mode 100644
index 00000000..421b495f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/packages/plugin/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "lib": ["ES2020", "DOM"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "outDir": "./dist",
+ "rootDir": "./src"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/qiming-vite-plugin-design-mode/pnpm-lock.yaml b/qiming-vite-plugin-design-mode/pnpm-lock.yaml
new file mode 100644
index 00000000..167a4d2b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/pnpm-lock.yaml
@@ -0,0 +1,1620 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ '@types/babel__core':
+ specifier: ^7.20.0
+ version: 7.20.5
+ '@types/babel__standalone':
+ specifier: ^7.1.9
+ version: 7.1.9
+ '@types/babel__traverse':
+ specifier: ^7.20.0
+ version: 7.28.0
+ '@types/node':
+ specifier: ^20.19.39
+ version: 20.19.39
+ '@types/react':
+ specifier: ^18.3.28
+ version: 18.3.28
+ '@types/react-dom':
+ specifier: ^18.3.1
+ version: 18.3.7(@types/react@18.3.28)
+ typescript:
+ specifier: ^5.0.0
+ version: 5.9.3
+ vite:
+ specifier: ^5.4.21
+ version: 5.4.21(@types/node@20.19.39)
+ vitest:
+ specifier: ^1.0.0
+ version: 1.6.1(@types/node@20.19.39)
+
+ packages/client-react:
+ dependencies:
+ '@xagi/design-mode-shared':
+ specifier: 1.2.0
+ version: 1.2.0
+ react:
+ specifier: ^18.0.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.0.0
+ version: 18.3.1(react@18.3.1)
+ tailwind-merge:
+ specifier: ^3.4.0
+ version: 3.5.0
+ devDependencies:
+ '@types/react':
+ specifier: ^18.0.0
+ version: 18.3.28
+ '@types/react-dom':
+ specifier: ^18.0.0
+ version: 18.3.7(@types/react@18.3.28)
+ typescript:
+ specifier: ^5.0.0
+ version: 5.9.3
+
+ packages/client-shared:
+ devDependencies:
+ typescript:
+ specifier: ^5.0.0
+ version: 5.9.3
+
+ packages/client-vue:
+ dependencies:
+ '@xagi/design-mode-shared':
+ specifier: 1.2.0
+ version: 1.2.0
+ tailwind-merge:
+ specifier: ^3.4.0
+ version: 3.5.0
+ vue:
+ specifier: ^3.3.0
+ version: 3.5.33(typescript@5.9.3)
+ devDependencies:
+ '@vue/compiler-sfc':
+ specifier: ^3.3.0
+ version: 3.5.33
+ typescript:
+ specifier: ^5.0.0
+ version: 5.9.3
+
+ packages/plugin:
+ dependencies:
+ '@babel/standalone':
+ specifier: ^7.23.0
+ version: 7.29.2
+ '@babel/traverse':
+ specifier: ^7.24.0
+ version: 7.29.0
+ '@babel/types':
+ specifier: 7.29.0
+ version: 7.29.0
+ '@vue/compiler-dom':
+ specifier: ^3.5.32
+ version: 3.5.33
+ '@vue/compiler-sfc':
+ specifier: ^3.5.32
+ version: 3.5.33
+ '@xagi/design-mode-client-react':
+ specifier: 1.2.0
+ version: 1.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@xagi/design-mode-client-vue':
+ specifier: 1.2.0
+ version: 1.2.0(vue@3.5.33(typescript@5.9.3))
+ '@xagi/design-mode-shared':
+ specifier: 1.2.0
+ version: 1.2.0
+ devDependencies:
+ '@types/babel__core':
+ specifier: ^7.20.0
+ version: 7.20.5
+ '@types/babel__standalone':
+ specifier: ^7.1.9
+ version: 7.1.9
+ '@types/babel__traverse':
+ specifier: ^7.20.0
+ version: 7.28.0
+ '@types/node':
+ specifier: ^20.0.0
+ version: 20.19.39
+ typescript:
+ specifier: ^5.0.0
+ version: 5.9.3
+ vite:
+ specifier: ^5.0.0
+ version: 5.4.21(@types/node@20.19.39)
+
+packages:
+
+ '@babel/code-frame@7.29.0':
+ resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.1':
+ resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.29.2':
+ resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/standalone@7.29.2':
+ resolution: {integrity: sha512-VSuvywmVRS8efooKrvJzs6BlVSxRvAdLeGrAKUrWoBx1fFBSeE/oBpUZCQ5BcprLyXy04W8skzz7JT8GqlNRJg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.0':
+ resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.29.0':
+ resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+ engines: {node: '>=6.9.0'}
+
+ '@esbuild/aix-ppc64@0.21.5':
+ resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.21.5':
+ resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.21.5':
+ resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.21.5':
+ resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.21.5':
+ resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.21.5':
+ resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.21.5':
+ resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.21.5':
+ resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.21.5':
+ resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.21.5':
+ resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.21.5':
+ resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.21.5':
+ resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.21.5':
+ resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.21.5':
+ resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.21.5':
+ resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.21.5':
+ resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-x64@0.21.5':
+ resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-x64@0.21.5':
+ resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.21.5':
+ resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.21.5':
+ resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.21.5':
+ resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.21.5':
+ resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ '@jest/schemas@29.6.3':
+ resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@rollup/rollup-android-arm-eabi@4.60.2':
+ resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.60.2':
+ resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.60.2':
+ resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.60.2':
+ resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.60.2':
+ resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.60.2':
+ resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.2':
+ resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.60.2':
+ resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-arm64-gnu@4.60.2':
+ resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-arm64-musl@4.60.2':
+ resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.2':
+ resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-loong64-musl@4.60.2':
+ resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.60.2':
+ resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.2':
+ resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.60.2':
+ resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.2':
+ resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-linux-s390x-gnu@4.60.2':
+ resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-gnu@4.60.2':
+ resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/rollup-linux-x64-musl@4.60.2':
+ resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/rollup-openbsd-x64@4.60.2':
+ resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.60.2':
+ resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.60.2':
+ resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.60.2':
+ resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.60.2':
+ resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.60.2':
+ resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@sinclair/typebox@0.27.10':
+ resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__standalone@7.1.9':
+ resolution: {integrity: sha512-IcCNPLqpevUD7UpV8QB0uwQPOyoOKACFf0YtYWRHcmxcakaje4Q7dbG2+jMqxw/I8Zk0NHvEps66WwS7z/UaaA==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/node@20.19.39':
+ resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==}
+
+ '@types/prop-types@15.7.15':
+ resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+ '@types/react-dom@18.3.7':
+ resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+
+ '@types/react@18.3.28':
+ resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==}
+
+ '@vitest/expect@1.6.1':
+ resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==}
+
+ '@vitest/runner@1.6.1':
+ resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==}
+
+ '@vitest/snapshot@1.6.1':
+ resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==}
+
+ '@vitest/spy@1.6.1':
+ resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==}
+
+ '@vitest/utils@1.6.1':
+ resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==}
+
+ '@vue/compiler-core@3.5.33':
+ resolution: {integrity: sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==}
+
+ '@vue/compiler-dom@3.5.33':
+ resolution: {integrity: sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==}
+
+ '@vue/compiler-sfc@3.5.33':
+ resolution: {integrity: sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==}
+
+ '@vue/compiler-ssr@3.5.33':
+ resolution: {integrity: sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==}
+
+ '@vue/reactivity@3.5.33':
+ resolution: {integrity: sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A==}
+
+ '@vue/runtime-core@3.5.33':
+ resolution: {integrity: sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ==}
+
+ '@vue/runtime-dom@3.5.33':
+ resolution: {integrity: sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw==}
+
+ '@vue/server-renderer@3.5.33':
+ resolution: {integrity: sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw==}
+ peerDependencies:
+ vue: 3.5.33
+
+ '@vue/shared@3.5.33':
+ resolution: {integrity: sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ==}
+
+ '@xagi/design-mode-client-react@1.2.0':
+ resolution: {integrity: sha512-BT+b4e3AgWWAH5gjBBAI3vEfKdmIpbTRSssyqy4J/U1jar0dSUYX5GYZ+szr9TxNFo7mHDUf+mIMh5d1VAdY0A==}
+ peerDependencies:
+ react: ^18.0.0
+ react-dom: ^18.0.0
+
+ '@xagi/design-mode-client-vue@1.2.0':
+ resolution: {integrity: sha512-o1ENgruSLtUVYirBrCE5UDoyu+sNXmQX7EyjVcHvKpMBZ64wUdEMswoCYLbU4hnz/7vx1bbSNpRlxvn+lk/mTA==}
+ peerDependencies:
+ vue: ^3.3.0
+
+ '@xagi/design-mode-shared@1.2.0':
+ resolution: {integrity: sha512-CK7icLDAdcmyQmEfe68zj6ahXR6Z3Of5oHqULztf71hRRcyWUlgU21P7l+9OHDdCsBDXKnN65hXHCtnkYQdxvA==}
+
+ acorn-walk@8.3.5:
+ resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
+ engines: {node: '>=0.4.0'}
+
+ acorn@8.16.0:
+ resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
+ assertion-error@1.1.0:
+ resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
+
+ cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
+ chai@4.5.0:
+ resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
+ engines: {node: '>=4'}
+
+ check-error@1.0.3:
+ resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+
+ confbox@0.1.8:
+ resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-eql@4.1.4:
+ resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
+ engines: {node: '>=6'}
+
+ diff-sequences@29.6.3:
+ resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ entities@7.0.1:
+ resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
+ engines: {node: '>=0.12'}
+
+ esbuild@0.21.5:
+ resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ execa@8.0.1:
+ resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
+ engines: {node: '>=16.17'}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ get-func-name@2.0.2:
+ resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
+
+ get-stream@8.0.1:
+ resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
+ engines: {node: '>=16'}
+
+ human-signals@5.0.0:
+ resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
+ engines: {node: '>=16.17.0'}
+
+ is-stream@3.0.0:
+ resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-tokens@9.0.1:
+ resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ local-pkg@0.5.1:
+ resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
+ engines: {node: '>=14'}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ loupe@2.3.7:
+ resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ mimic-fn@4.0.0:
+ resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
+ engines: {node: '>=12'}
+
+ mlly@1.8.2:
+ resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ npm-run-path@5.3.0:
+ resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ onetime@6.0.0:
+ resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
+ engines: {node: '>=12'}
+
+ p-limit@5.0.0:
+ resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
+ engines: {node: '>=18'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-key@4.0.0:
+ resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
+ engines: {node: '>=12'}
+
+ pathe@1.1.2:
+ resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ pathval@1.1.1:
+ resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ pkg-types@1.3.1:
+ resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
+
+ postcss@8.5.10:
+ resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ pretty-format@29.7.0:
+ resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+
+ react-is@18.3.1:
+ resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
+ rollup@4.60.2:
+ resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ std-env@3.10.0:
+ resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+
+ strip-final-newline@3.0.0:
+ resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
+ engines: {node: '>=12'}
+
+ strip-literal@2.1.1:
+ resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
+
+ tailwind-merge@3.5.0:
+ resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinypool@0.8.4:
+ resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
+ engines: {node: '>=14.0.0'}
+
+ tinyspy@2.2.1:
+ resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
+ engines: {node: '>=14.0.0'}
+
+ type-detect@4.1.0:
+ resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
+ engines: {node: '>=4'}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ ufo@1.6.3:
+ resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ vite-node@1.6.1:
+ resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+
+ vite@5.4.21:
+ resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+
+ vitest@1.6.1:
+ resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/node': ^18.0.0 || >=20.0.0
+ '@vitest/browser': 1.6.1
+ '@vitest/ui': 1.6.1
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ vue@3.5.33:
+ resolution: {integrity: sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ yocto-queue@1.2.2:
+ resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
+ engines: {node: '>=12.20'}
+
+snapshots:
+
+ '@babel/code-frame@7.29.0':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/generator@7.29.1':
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/parser@7.29.2':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/standalone@7.29.2': {}
+
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+
+ '@babel/traverse@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.29.2
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.29.0':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@esbuild/aix-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm@0.21.5':
+ optional: true
+
+ '@esbuild/android-x64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-x64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/linux-loong64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-s390x@0.21.5':
+ optional: true
+
+ '@esbuild/linux-x64@0.21.5':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/sunos-x64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/win32-x64@0.21.5':
+ optional: true
+
+ '@jest/schemas@29.6.3':
+ dependencies:
+ '@sinclair/typebox': 0.27.10
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@rollup/rollup-android-arm-eabi@4.60.2':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.60.2':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.60.2':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.60.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.60.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.60.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.60.2':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.60.2':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.60.2':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.60.2':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.60.2':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.60.2':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.60.2':
+ optional: true
+
+ '@sinclair/typebox@0.27.10': {}
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@types/babel__standalone@7.1.9':
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+ '@types/babel__core': 7.20.5
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@types/estree@1.0.8': {}
+
+ '@types/node@20.19.39':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/prop-types@15.7.15': {}
+
+ '@types/react-dom@18.3.7(@types/react@18.3.28)':
+ dependencies:
+ '@types/react': 18.3.28
+
+ '@types/react@18.3.28':
+ dependencies:
+ '@types/prop-types': 15.7.15
+ csstype: 3.2.3
+
+ '@vitest/expect@1.6.1':
+ dependencies:
+ '@vitest/spy': 1.6.1
+ '@vitest/utils': 1.6.1
+ chai: 4.5.0
+
+ '@vitest/runner@1.6.1':
+ dependencies:
+ '@vitest/utils': 1.6.1
+ p-limit: 5.0.0
+ pathe: 1.1.2
+
+ '@vitest/snapshot@1.6.1':
+ dependencies:
+ magic-string: 0.30.21
+ pathe: 1.1.2
+ pretty-format: 29.7.0
+
+ '@vitest/spy@1.6.1':
+ dependencies:
+ tinyspy: 2.2.1
+
+ '@vitest/utils@1.6.1':
+ dependencies:
+ diff-sequences: 29.6.3
+ estree-walker: 3.0.3
+ loupe: 2.3.7
+ pretty-format: 29.7.0
+
+ '@vue/compiler-core@3.5.33':
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@vue/shared': 3.5.33
+ entities: 7.0.1
+ estree-walker: 2.0.2
+ source-map-js: 1.2.1
+
+ '@vue/compiler-dom@3.5.33':
+ dependencies:
+ '@vue/compiler-core': 3.5.33
+ '@vue/shared': 3.5.33
+
+ '@vue/compiler-sfc@3.5.33':
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@vue/compiler-core': 3.5.33
+ '@vue/compiler-dom': 3.5.33
+ '@vue/compiler-ssr': 3.5.33
+ '@vue/shared': 3.5.33
+ estree-walker: 2.0.2
+ magic-string: 0.30.21
+ postcss: 8.5.10
+ source-map-js: 1.2.1
+
+ '@vue/compiler-ssr@3.5.33':
+ dependencies:
+ '@vue/compiler-dom': 3.5.33
+ '@vue/shared': 3.5.33
+
+ '@vue/reactivity@3.5.33':
+ dependencies:
+ '@vue/shared': 3.5.33
+
+ '@vue/runtime-core@3.5.33':
+ dependencies:
+ '@vue/reactivity': 3.5.33
+ '@vue/shared': 3.5.33
+
+ '@vue/runtime-dom@3.5.33':
+ dependencies:
+ '@vue/reactivity': 3.5.33
+ '@vue/runtime-core': 3.5.33
+ '@vue/shared': 3.5.33
+ csstype: 3.2.3
+
+ '@vue/server-renderer@3.5.33(vue@3.5.33(typescript@5.9.3))':
+ dependencies:
+ '@vue/compiler-ssr': 3.5.33
+ '@vue/shared': 3.5.33
+ vue: 3.5.33(typescript@5.9.3)
+
+ '@vue/shared@3.5.33': {}
+
+ '@xagi/design-mode-client-react@1.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@xagi/design-mode-shared': 1.2.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ tailwind-merge: 3.5.0
+
+ '@xagi/design-mode-client-vue@1.2.0(vue@3.5.33(typescript@5.9.3))':
+ dependencies:
+ '@xagi/design-mode-shared': 1.2.0
+ tailwind-merge: 3.5.0
+ vue: 3.5.33(typescript@5.9.3)
+
+ '@xagi/design-mode-shared@1.2.0': {}
+
+ acorn-walk@8.3.5:
+ dependencies:
+ acorn: 8.16.0
+
+ acorn@8.16.0: {}
+
+ ansi-styles@5.2.0: {}
+
+ assertion-error@1.1.0: {}
+
+ cac@6.7.14: {}
+
+ chai@4.5.0:
+ dependencies:
+ assertion-error: 1.1.0
+ check-error: 1.0.3
+ deep-eql: 4.1.4
+ get-func-name: 2.0.2
+ loupe: 2.3.7
+ pathval: 1.1.1
+ type-detect: 4.1.0
+
+ check-error@1.0.3:
+ dependencies:
+ get-func-name: 2.0.2
+
+ confbox@0.1.8: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ csstype@3.2.3: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ deep-eql@4.1.4:
+ dependencies:
+ type-detect: 4.1.0
+
+ diff-sequences@29.6.3: {}
+
+ entities@7.0.1: {}
+
+ esbuild@0.21.5:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.21.5
+ '@esbuild/android-arm': 0.21.5
+ '@esbuild/android-arm64': 0.21.5
+ '@esbuild/android-x64': 0.21.5
+ '@esbuild/darwin-arm64': 0.21.5
+ '@esbuild/darwin-x64': 0.21.5
+ '@esbuild/freebsd-arm64': 0.21.5
+ '@esbuild/freebsd-x64': 0.21.5
+ '@esbuild/linux-arm': 0.21.5
+ '@esbuild/linux-arm64': 0.21.5
+ '@esbuild/linux-ia32': 0.21.5
+ '@esbuild/linux-loong64': 0.21.5
+ '@esbuild/linux-mips64el': 0.21.5
+ '@esbuild/linux-ppc64': 0.21.5
+ '@esbuild/linux-riscv64': 0.21.5
+ '@esbuild/linux-s390x': 0.21.5
+ '@esbuild/linux-x64': 0.21.5
+ '@esbuild/netbsd-x64': 0.21.5
+ '@esbuild/openbsd-x64': 0.21.5
+ '@esbuild/sunos-x64': 0.21.5
+ '@esbuild/win32-arm64': 0.21.5
+ '@esbuild/win32-ia32': 0.21.5
+ '@esbuild/win32-x64': 0.21.5
+
+ estree-walker@2.0.2: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ execa@8.0.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 8.0.1
+ human-signals: 5.0.0
+ is-stream: 3.0.0
+ merge-stream: 2.0.0
+ npm-run-path: 5.3.0
+ onetime: 6.0.0
+ signal-exit: 4.1.0
+ strip-final-newline: 3.0.0
+
+ fsevents@2.3.3:
+ optional: true
+
+ get-func-name@2.0.2: {}
+
+ get-stream@8.0.1: {}
+
+ human-signals@5.0.0: {}
+
+ is-stream@3.0.0: {}
+
+ isexe@2.0.0: {}
+
+ js-tokens@4.0.0: {}
+
+ js-tokens@9.0.1: {}
+
+ jsesc@3.1.0: {}
+
+ local-pkg@0.5.1:
+ dependencies:
+ mlly: 1.8.2
+ pkg-types: 1.3.1
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ loupe@2.3.7:
+ dependencies:
+ get-func-name: 2.0.2
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ merge-stream@2.0.0: {}
+
+ mimic-fn@4.0.0: {}
+
+ mlly@1.8.2:
+ dependencies:
+ acorn: 8.16.0
+ pathe: 2.0.3
+ pkg-types: 1.3.1
+ ufo: 1.6.3
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ npm-run-path@5.3.0:
+ dependencies:
+ path-key: 4.0.0
+
+ onetime@6.0.0:
+ dependencies:
+ mimic-fn: 4.0.0
+
+ p-limit@5.0.0:
+ dependencies:
+ yocto-queue: 1.2.2
+
+ path-key@3.1.1: {}
+
+ path-key@4.0.0: {}
+
+ pathe@1.1.2: {}
+
+ pathe@2.0.3: {}
+
+ pathval@1.1.1: {}
+
+ picocolors@1.1.1: {}
+
+ pkg-types@1.3.1:
+ dependencies:
+ confbox: 0.1.8
+ mlly: 1.8.2
+ pathe: 2.0.3
+
+ postcss@8.5.10:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ pretty-format@29.7.0:
+ dependencies:
+ '@jest/schemas': 29.6.3
+ ansi-styles: 5.2.0
+ react-is: 18.3.1
+
+ react-dom@18.3.1(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+
+ react-is@18.3.1: {}
+
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
+ rollup@4.60.2:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.60.2
+ '@rollup/rollup-android-arm64': 4.60.2
+ '@rollup/rollup-darwin-arm64': 4.60.2
+ '@rollup/rollup-darwin-x64': 4.60.2
+ '@rollup/rollup-freebsd-arm64': 4.60.2
+ '@rollup/rollup-freebsd-x64': 4.60.2
+ '@rollup/rollup-linux-arm-gnueabihf': 4.60.2
+ '@rollup/rollup-linux-arm-musleabihf': 4.60.2
+ '@rollup/rollup-linux-arm64-gnu': 4.60.2
+ '@rollup/rollup-linux-arm64-musl': 4.60.2
+ '@rollup/rollup-linux-loong64-gnu': 4.60.2
+ '@rollup/rollup-linux-loong64-musl': 4.60.2
+ '@rollup/rollup-linux-ppc64-gnu': 4.60.2
+ '@rollup/rollup-linux-ppc64-musl': 4.60.2
+ '@rollup/rollup-linux-riscv64-gnu': 4.60.2
+ '@rollup/rollup-linux-riscv64-musl': 4.60.2
+ '@rollup/rollup-linux-s390x-gnu': 4.60.2
+ '@rollup/rollup-linux-x64-gnu': 4.60.2
+ '@rollup/rollup-linux-x64-musl': 4.60.2
+ '@rollup/rollup-openbsd-x64': 4.60.2
+ '@rollup/rollup-openharmony-arm64': 4.60.2
+ '@rollup/rollup-win32-arm64-msvc': 4.60.2
+ '@rollup/rollup-win32-ia32-msvc': 4.60.2
+ '@rollup/rollup-win32-x64-gnu': 4.60.2
+ '@rollup/rollup-win32-x64-msvc': 4.60.2
+ fsevents: 2.3.3
+
+ scheduler@0.23.2:
+ dependencies:
+ loose-envify: 1.4.0
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ siginfo@2.0.0: {}
+
+ signal-exit@4.1.0: {}
+
+ source-map-js@1.2.1: {}
+
+ stackback@0.0.2: {}
+
+ std-env@3.10.0: {}
+
+ strip-final-newline@3.0.0: {}
+
+ strip-literal@2.1.1:
+ dependencies:
+ js-tokens: 9.0.1
+
+ tailwind-merge@3.5.0: {}
+
+ tinybench@2.9.0: {}
+
+ tinypool@0.8.4: {}
+
+ tinyspy@2.2.1: {}
+
+ type-detect@4.1.0: {}
+
+ typescript@5.9.3: {}
+
+ ufo@1.6.3: {}
+
+ undici-types@6.21.0: {}
+
+ vite-node@1.6.1(@types/node@20.19.39):
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.3
+ pathe: 1.1.2
+ picocolors: 1.1.1
+ vite: 5.4.21(@types/node@20.19.39)
+ transitivePeerDependencies:
+ - '@types/node'
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
+ vite@5.4.21(@types/node@20.19.39):
+ dependencies:
+ esbuild: 0.21.5
+ postcss: 8.5.10
+ rollup: 4.60.2
+ optionalDependencies:
+ '@types/node': 20.19.39
+ fsevents: 2.3.3
+
+ vitest@1.6.1(@types/node@20.19.39):
+ dependencies:
+ '@vitest/expect': 1.6.1
+ '@vitest/runner': 1.6.1
+ '@vitest/snapshot': 1.6.1
+ '@vitest/spy': 1.6.1
+ '@vitest/utils': 1.6.1
+ acorn-walk: 8.3.5
+ chai: 4.5.0
+ debug: 4.4.3
+ execa: 8.0.1
+ local-pkg: 0.5.1
+ magic-string: 0.30.21
+ pathe: 1.1.2
+ picocolors: 1.1.1
+ std-env: 3.10.0
+ strip-literal: 2.1.1
+ tinybench: 2.9.0
+ tinypool: 0.8.4
+ vite: 5.4.21(@types/node@20.19.39)
+ vite-node: 1.6.1(@types/node@20.19.39)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 20.19.39
+ transitivePeerDependencies:
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
+ vue@3.5.33(typescript@5.9.3):
+ dependencies:
+ '@vue/compiler-dom': 3.5.33
+ '@vue/compiler-sfc': 3.5.33
+ '@vue/runtime-dom': 3.5.33
+ '@vue/server-renderer': 3.5.33(vue@3.5.33(typescript@5.9.3))
+ '@vue/shared': 3.5.33
+ optionalDependencies:
+ typescript: 5.9.3
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ yocto-queue@1.2.2: {}
diff --git a/qiming-vite-plugin-design-mode/pnpm-workspace.yaml b/qiming-vite-plugin-design-mode/pnpm-workspace.yaml
new file mode 100644
index 00000000..18ec407e
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+packages:
+ - 'packages/*'
diff --git a/qiming-vite-plugin-design-mode/scripts/release-dry-run-beta.sh b/qiming-vite-plugin-design-mode/scripts/release-dry-run-beta.sh
new file mode 100644
index 00000000..cc62c181
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/release-dry-run-beta.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# 这个脚本用于 beta 发布前演练,不会执行真实 npm publish。
+# 目标是提前发现“构建、打包内容、依赖声明、发布前校验”类问题。
+#
+# 演练内容:
+# 1) 运行 release-preflight(registry / 版本一致 / workspace 协议)
+# 2) 构建所有包
+# 3) 在每个发布包目录执行 npm publish --dry-run --tag beta,检查将发布的 tarball 内容
+
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT_DIR"
+
+echo "[dry-run-beta] step 1/3: preflight checks..."
+bash ./scripts/release-preflight.sh
+
+echo "[dry-run-beta] step 2/3: build all packages..."
+pnpm -r --filter './packages/**' build
+
+echo "[dry-run-beta] step 3/3: simulate npm publish (beta tag)..."
+(
+ cd packages/client-shared
+ npm publish --access=public --tag beta --dry-run
+)
+(
+ cd packages/client-react
+ npm publish --access=public --tag beta --dry-run
+)
+(
+ cd packages/client-vue
+ npm publish --access=public --tag beta --dry-run
+)
+(
+ cd packages/plugin
+ npm publish --access=public --tag beta --dry-run
+)
+
+echo "[dry-run-beta] done. no package was actually published."
diff --git a/qiming-vite-plugin-design-mode/scripts/release-dry-run.sh b/qiming-vite-plugin-design-mode/scripts/release-dry-run.sh
new file mode 100644
index 00000000..43ecfd32
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/release-dry-run.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# 这个脚本用于发布前演练,不会执行真实 npm publish。
+# 目标是提前发现“构建、打包内容、依赖声明、发布前校验”类问题。
+#
+# 演练内容:
+# 1) 运行 release-preflight(registry / 版本一致 / workspace 协议)
+# 2) 构建所有包
+# 3) 在每个发布包目录执行 npm publish --dry-run,检查将发布的 tarball 内容
+
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT_DIR"
+
+echo "[dry-run] step 1/3: preflight checks..."
+bash ./scripts/release-preflight.sh
+
+echo "[dry-run] step 2/3: build all packages..."
+pnpm -r --filter './packages/**' build
+
+echo "[dry-run] step 3/3: simulate npm publish..."
+(
+ cd packages/client-shared
+ npm publish --access=public --tag next --dry-run
+)
+(
+ cd packages/client-react
+ npm publish --access=public --tag next --dry-run
+)
+(
+ cd packages/client-vue
+ npm publish --access=public --tag next --dry-run
+)
+(
+ cd packages/plugin
+ npm publish --access=public --tag next --dry-run
+)
+
+echo "[dry-run] done. no package was actually published."
diff --git a/qiming-vite-plugin-design-mode/scripts/release-preflight.sh b/qiming-vite-plugin-design-mode/scripts/release-preflight.sh
new file mode 100644
index 00000000..4d964f01
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/release-preflight.sh
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# 这个脚本用于发布前的硬性校验。
+# 设计目标:尽量在真正 npm publish 之前发现问题,避免发布一半才失败。
+#
+# 校验项:
+# 1) 当前 npm registry 必须是 npmjs 官方源(防止发到镜像源)
+# 2) 根包与 4 个发布包版本号必须完全一致(统一版本策略)
+# 3) 发布包 package.json 中禁止出现 workspace:*(外部安装无法解析)
+
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT_DIR"
+
+echo "[preflight] checking npm registry..."
+REGISTRY="$(npm config get registry)"
+if [[ "$REGISTRY" != "https://registry.npmjs.org/" ]]; then
+ echo "[preflight] error: registry is '$REGISTRY', expected 'https://registry.npmjs.org/'"
+ echo "[preflight] hint: run 'nrm use npm' first."
+ exit 1
+fi
+
+echo "[preflight] checking version consistency..."
+node <<'NODE'
+const fs = require("fs");
+const path = require("path");
+
+const root = process.cwd();
+const packageFiles = [
+ "package.json",
+ "packages/client-shared/package.json",
+ "packages/client-react/package.json",
+ "packages/client-vue/package.json",
+ "packages/plugin/package.json",
+];
+
+const versions = packageFiles.map((file) => {
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, file), "utf8"));
+ return { file, version: pkg.version };
+});
+
+const uniqueVersions = [...new Set(versions.map((item) => item.version))];
+if (uniqueVersions.length !== 1) {
+ console.error("[preflight] error: versions are not aligned:");
+ for (const item of versions) {
+ console.error(` - ${item.file}: ${item.version}`);
+ }
+ process.exit(1);
+}
+
+console.log(`[preflight] version aligned: ${uniqueVersions[0]}`);
+NODE
+
+echo "[preflight] checking workspace protocol leakage..."
+if rg "workspace:\\*" packages/*/package.json >/dev/null; then
+ echo "[preflight] error: found 'workspace:*' in publishable package dependencies."
+ echo "[preflight] hint: replace with explicit semver before publish."
+ exit 1
+fi
+
+echo "[preflight] all checks passed."
diff --git a/qiming-vite-plugin-design-mode/scripts/release-publish-beta.sh b/qiming-vite-plugin-design-mode/scripts/release-publish-beta.sh
new file mode 100644
index 00000000..8a1cd5e1
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/release-publish-beta.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# 这个脚本按依赖拓扑发布到 npm beta tag。
+# 发布顺序不能乱:
+# 1) shared 必须先发布(react/vue/plugin 都依赖它)
+# 2) react 与 vue 互不依赖,可以并发发布提升速度
+# 3) plugin 最后发布(依赖 shared + react + vue)
+
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT_DIR"
+
+echo "[publish-beta] publishing @xagi/design-mode-shared..."
+(
+ cd packages/client-shared
+ npm publish --access=public --tag beta
+)
+
+echo "[publish-beta] publishing @xagi/design-mode-client-react and @xagi/design-mode-client-vue in parallel..."
+(
+ cd packages/client-react
+ npm publish --access=public --tag beta
+) &
+PID_REACT=$!
+
+(
+ cd packages/client-vue
+ npm publish --access=public --tag beta
+) &
+PID_VUE=$!
+
+wait "$PID_REACT"
+wait "$PID_VUE"
+
+echo "[publish-beta] publishing @xagi/vite-plugin-design-mode..."
+(
+ cd packages/plugin
+ npm publish --access=public --tag beta
+)
+
+echo "[publish-beta] done."
diff --git a/qiming-vite-plugin-design-mode/scripts/release-publish-next.sh b/qiming-vite-plugin-design-mode/scripts/release-publish-next.sh
new file mode 100644
index 00000000..bed9ac46
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/release-publish-next.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# 这个脚本按依赖拓扑发布到 npm next tag。
+# 发布顺序不能乱:
+# 1) shared 必须先发布(react/vue/plugin 都依赖它)
+# 2) react 与 vue 互不依赖,可以并发发布提升速度
+# 3) plugin 最后发布(依赖 shared + react + vue)
+
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT_DIR"
+
+echo "[publish-next] publishing @xagi/design-mode-shared..."
+(
+ cd packages/client-shared
+ npm publish --access=public --tag next
+)
+
+echo "[publish-next] publishing @xagi/design-mode-client-react and @xagi/design-mode-client-vue in parallel..."
+(
+ cd packages/client-react
+ npm publish --access=public --tag next
+) &
+PID_REACT=$!
+
+(
+ cd packages/client-vue
+ npm publish --access=public --tag next
+) &
+PID_VUE=$!
+
+wait "$PID_REACT"
+wait "$PID_VUE"
+
+echo "[publish-next] publishing @xagi/vite-plugin-design-mode..."
+(
+ cd packages/plugin
+ npm publish --access=public --tag next
+)
+
+echo "[publish-next] done."
+echo ""
+echo "[publish-next] --- 给集成方 / 模板维护者(复制即可)---"
+echo " pnpm add @xagi/vite-plugin-design-mode@next -D"
+echo " 请勿在 README 中写死 beta.N;next 即当前灰度线。"
+echo "[publish-next] ---"
diff --git a/qiming-vite-plugin-design-mode/scripts/release-sync-npmmirror.sh b/qiming-vite-plugin-design-mode/scripts/release-sync-npmmirror.sh
new file mode 100644
index 00000000..12cc3a37
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/release-sync-npmmirror.sh
@@ -0,0 +1,198 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# -----------------------------------------------------------------------------
+# 手动触发 npmmirror 同步(适用于当前 monorepo 的 4 个发布包)。
+#
+# 目标:
+# 1) 发布完成后,主动触发 npmmirror 拉取 npm 官方源的最新包元数据与 tarball
+# 2) 可选做只读校验,确认镜像上是否已经可见指定版本
+#
+# 接口:
+# PUT https://registry.npmmirror.com/<包名>/sync
+#
+# 模式:
+# 默认 sync - 对目标包执行 PUT .../sync
+# --dry dry - 仅打印将调用的 URL,不发请求
+# --check check - 不发 PUT,仅检查镜像 registry 是否已有该版本
+#
+# 包列表:
+# - 不传包名:使用默认 4 包(shared/react/vue/plugin)
+# - 传包名参数:仅处理传入包名(可多个)
+#
+# 环境变量:
+# NPM_MIRROR_VERSION 目标版本;未设置时读取根 package.json version
+# NPM_MIRROR_REGISTRY 镜像 registry;默认 https://registry.npmmirror.com
+# NPM_MIRROR_RETRIES 失败重试次数;默认 3
+# NPM_MIRROR_SLEEP_SECS 重试间隔秒数;默认 2
+# -----------------------------------------------------------------------------
+
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT_DIR"
+
+REGISTRY="${NPM_MIRROR_REGISTRY:-https://registry.npmmirror.com}"
+RETRIES="${NPM_MIRROR_RETRIES:-3}"
+SLEEP_SECS="${NPM_MIRROR_SLEEP_SECS:-2}"
+
+MODE="sync"
+declare -a USER_PACKAGES=()
+
+print_help() {
+ cat <<'EOF'
+用法:
+ ./scripts/release-sync-npmmirror.sh [--dry | --check] [package...]
+
+示例:
+ ./scripts/release-sync-npmmirror.sh
+ ./scripts/release-sync-npmmirror.sh --dry
+ ./scripts/release-sync-npmmirror.sh --check
+ ./scripts/release-sync-npmmirror.sh @xagi/vite-plugin-design-mode
+
+说明:
+ 默认模式会对每个包调用 PUT //sync
+ --dry 仅打印将调用的 URL
+ --check 使用 npm view --registry= 校验版本是否可见
+
+环境变量:
+ NPM_MIRROR_VERSION 目标版本(默认读取根 package.json version)
+ NPM_MIRROR_REGISTRY 默认 https://registry.npmmirror.com
+ NPM_MIRROR_RETRIES 默认 3
+ NPM_MIRROR_SLEEP_SECS 默认 2
+EOF
+}
+
+# 参数解析:支持模式参数与可选包名参数混合。
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --dry)
+ MODE="dry"
+ shift
+ ;;
+ --check)
+ MODE="check"
+ shift
+ ;;
+ -h|--help)
+ print_help
+ exit 0
+ ;;
+ --)
+ # 兼容 pnpm `script -- --flag` 形式:仅丢弃分隔符,后续参数继续按正常规则解析。
+ shift
+ ;;
+ -*)
+ echo "未知参数: $1(支持 --dry / --check / --help)" >&2
+ exit 2
+ ;;
+ *)
+ USER_PACKAGES+=("$1")
+ shift
+ ;;
+ esac
+done
+
+TARGET_VERSION="${NPM_MIRROR_VERSION:-}"
+if [[ -z "$TARGET_VERSION" ]]; then
+ TARGET_VERSION="$(node -pe "require('./package.json').version")"
+fi
+
+# 默认发布包列表与 release 脚本保持一致。
+declare -a DEFAULT_PACKAGES=(
+ "@xagi/design-mode-shared"
+ "@xagi/design-mode-client-react"
+ "@xagi/design-mode-client-vue"
+ "@xagi/vite-plugin-design-mode"
+)
+
+declare -a PACKAGES=()
+if [[ "${#USER_PACKAGES[@]}" -gt 0 ]]; then
+ PACKAGES=("${USER_PACKAGES[@]}")
+else
+ PACKAGES=("${DEFAULT_PACKAGES[@]}")
+fi
+
+echo "版本: ${TARGET_VERSION}"
+echo "模式: ${MODE}"
+echo "镜像: ${REGISTRY}"
+echo "包数: ${#PACKAGES[@]}"
+echo "----------"
+
+sync_one() {
+ local pkg="$1"
+ local url="${REGISTRY%/}/${pkg}/sync"
+ local body_file
+ body_file="$(mktemp)"
+
+ local attempt=1
+ while [[ "$attempt" -le "$RETRIES" ]]; do
+ if curl -fsS -X PUT "$url" -o "$body_file" 2>/dev/null; then
+ local body
+ body="$(tr -d '\n' < "$body_file" || true)"
+ rm -f "$body_file"
+ echo "OK ${pkg} (attempt ${attempt}/${RETRIES}) ${body}"
+ return 0
+ fi
+ if [[ "$attempt" -lt "$RETRIES" ]]; then
+ echo "RETRY ${pkg} (attempt ${attempt}/${RETRIES}), sleep ${SLEEP_SECS}s..."
+ sleep "$SLEEP_SECS"
+ fi
+ attempt=$((attempt + 1))
+ done
+
+ echo "FAIL ${pkg}"
+ cat "$body_file" 2>/dev/null || true
+ rm -f "$body_file"
+ return 1
+}
+
+check_one() {
+ local pkg="$1"
+ local got
+ got="$(npm view "${pkg}@${TARGET_VERSION}" version --registry="${REGISTRY}" 2>/dev/null | tr -d '\r\n' || true)"
+ if [[ "$got" == "$TARGET_VERSION" ]]; then
+ echo "OK ${pkg}@${TARGET_VERSION}"
+ return 0
+ fi
+ echo "MISS ${pkg}@${TARGET_VERSION} (镜像返回: ${got:-无/404})"
+ return 1
+}
+
+FAILED=0
+declare -a FAILED_PACKAGES=()
+
+for pkg in "${PACKAGES[@]}"; do
+ case "$MODE" in
+ dry)
+ echo "PUT ${REGISTRY%/}/${pkg}/sync"
+ ;;
+ sync)
+ if ! sync_one "$pkg"; then
+ FAILED=1
+ FAILED_PACKAGES+=("$pkg")
+ fi
+ ;;
+ check)
+ if ! check_one "$pkg"; then
+ FAILED=1
+ FAILED_PACKAGES+=("$pkg")
+ fi
+ ;;
+ esac
+done
+
+echo "----------"
+if [[ "$FAILED" -ne 0 ]]; then
+ echo "失败包:"
+ for pkg in "${FAILED_PACKAGES[@]}"; do
+ echo " - ${pkg}"
+ done
+ if [[ "$MODE" == "check" ]]; then
+ echo "提示: npm 官方可能已发布但镜像尚未同步,先执行 sync 模式后再 check。"
+ else
+ echo "提示: 可稍后重试;也可先确认 npm 官方 registry 已存在对应版本。"
+ fi
+ exit 1
+fi
+
+echo "完成。"
diff --git a/qiming-vite-plugin-design-mode/scripts/release-verify-beta.sh b/qiming-vite-plugin-design-mode/scripts/release-verify-beta.sh
new file mode 100644
index 00000000..81b4f1ad
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/release-verify-beta.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# 这个脚本用于 beta 发布后验证:
+# 1) 核验 plugin 的 beta dist-tag 是否指向目标版本
+# 2) 核验 4 个发布包在 registry 上可见
+#
+# 用法:
+# ./scripts/release-verify-beta.sh # 默认读取根 package.json 版本
+# ./scripts/release-verify-beta.sh 1.1.0-beta.5 # 手动指定版本
+
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT_DIR"
+
+TARGET_VERSION="${1:-}"
+if [[ -z "$TARGET_VERSION" ]]; then
+ TARGET_VERSION="$(node -p "require('./package.json').version")"
+fi
+
+echo "[verify-beta] target version: $TARGET_VERSION"
+echo "[verify-beta] checking plugin dist-tags..."
+npm view @xagi/vite-plugin-design-mode dist-tags
+
+CURRENT_BETA="$(npm view @xagi/vite-plugin-design-mode dist-tags.beta)"
+if [[ "$CURRENT_BETA" != "$TARGET_VERSION" ]]; then
+ echo "[verify-beta] error: dist-tags.beta is '$CURRENT_BETA', expected '$TARGET_VERSION'"
+ echo "[verify-beta] hint: run 'npm dist-tag add @xagi/vite-plugin-design-mode@$TARGET_VERSION beta'"
+ exit 1
+fi
+
+echo "[verify-beta] checking published versions exist..."
+npm view "@xagi/design-mode-shared@$TARGET_VERSION" version
+npm view "@xagi/design-mode-client-react@$TARGET_VERSION" version
+npm view "@xagi/design-mode-client-vue@$TARGET_VERSION" version
+npm view "@xagi/vite-plugin-design-mode@$TARGET_VERSION" version
+
+echo "[verify-beta] all checks passed."
diff --git a/qiming-vite-plugin-design-mode/scripts/release-verify-next.sh b/qiming-vite-plugin-design-mode/scripts/release-verify-next.sh
new file mode 100644
index 00000000..183637b0
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/release-verify-next.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# 这个脚本用于发布后验证:
+# 1) 核验 plugin 的 next dist-tag 是否指向目标版本
+# 2) 核验 4 个发布包在 registry 上可见
+#
+# 用法:
+# ./scripts/release-verify-next.sh # 默认读取根 package.json 版本
+# ./scripts/release-verify-next.sh 1.1.0-beta.5 # 手动指定版本
+
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT_DIR"
+
+TARGET_VERSION="${1:-}"
+if [[ -z "$TARGET_VERSION" ]]; then
+ TARGET_VERSION="$(node -p "require('./package.json').version")"
+fi
+
+echo "[verify-next] target version: $TARGET_VERSION"
+
+# npm 注册表在刚 publish 后可能短暂不一致;这里做有限次重试,避免 release:next 误报失败。
+MAX_TRIES="${VERIFY_NEXT_MAX_TRIES:-18}"
+SLEEP_SECS="${VERIFY_NEXT_SLEEP_SECS:-2}"
+
+all_checks_ok() {
+ local current_next
+ current_next="$(npm view @xagi/vite-plugin-design-mode dist-tags.next 2>/dev/null || true)"
+ if [[ "$current_next" != "$TARGET_VERSION" ]]; then
+ return 1
+ fi
+ npm view "@xagi/design-mode-shared@$TARGET_VERSION" version >/dev/null 2>&1 || return 1
+ npm view "@xagi/design-mode-client-react@$TARGET_VERSION" version >/dev/null 2>&1 || return 1
+ npm view "@xagi/design-mode-client-vue@$TARGET_VERSION" version >/dev/null 2>&1 || return 1
+ npm view "@xagi/vite-plugin-design-mode@$TARGET_VERSION" version >/dev/null 2>&1 || return 1
+ return 0
+}
+
+try=1
+while [[ "$try" -le "$MAX_TRIES" ]]; do
+ if all_checks_ok; then
+ echo "[verify-next] checking plugin dist-tags..."
+ npm view @xagi/vite-plugin-design-mode dist-tags
+ echo "[verify-next] checking published versions exist..."
+ npm view "@xagi/design-mode-shared@$TARGET_VERSION" version
+ npm view "@xagi/design-mode-client-react@$TARGET_VERSION" version
+ npm view "@xagi/design-mode-client-vue@$TARGET_VERSION" version
+ npm view "@xagi/vite-plugin-design-mode@$TARGET_VERSION" version
+ echo "[verify-next] all checks passed (attempt $try/$MAX_TRIES)."
+ echo "[verify-next] hint for consumers: pnpm add @xagi/vite-plugin-design-mode@next -D"
+ exit 0
+ fi
+ echo "[verify-next] registry not fully consistent yet (attempt $try/$MAX_TRIES), sleeping ${SLEEP_SECS}s..."
+ sleep "$SLEEP_SECS"
+ try=$((try + 1))
+done
+
+echo "[verify-next] error: after $MAX_TRIES attempts, dist-tag next or one of the packages is not visible for $TARGET_VERSION"
+echo "[verify-next] current dist-tags:"
+npm view @xagi/vite-plugin-design-mode dist-tags 2>/dev/null || true
+echo "[verify-next] hint: wait and re-run: ./scripts/release-verify-next.sh $TARGET_VERSION"
+echo "[verify-next] or: npm dist-tag add @xagi/vite-plugin-design-mode@$TARGET_VERSION next"
+exit 1
diff --git a/qiming-vite-plugin-design-mode/scripts/release-version-sync.sh b/qiming-vite-plugin-design-mode/scripts/release-version-sync.sh
new file mode 100644
index 00000000..89778506
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/release-version-sync.sh
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# 一次性同步多包版本号与内部依赖版本。
+# 这样可以避免手工修改多个 package.json 时漏改或改错。
+#
+# 用法:
+# ./scripts/release-version-sync.sh 1.1.0-beta.6
+
+if [[ $# -ne 1 ]]; then
+ echo "Usage: $0 "
+ exit 1
+fi
+
+NEW_VERSION="$1"
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT_DIR"
+
+echo "[version-sync] syncing all package versions to $NEW_VERSION..."
+node - "$NEW_VERSION" <<'NODE'
+const fs = require("fs");
+const path = require("path");
+
+const newVersion = process.argv[2];
+const root = process.cwd();
+
+function readJson(file) {
+ return JSON.parse(fs.readFileSync(path.join(root, file), "utf8"));
+}
+
+function writeJson(file, data) {
+ fs.writeFileSync(path.join(root, file), JSON.stringify(data, null, 2) + "\n");
+}
+
+const files = {
+ root: "package.json",
+ shared: "packages/client-shared/package.json",
+ react: "packages/client-react/package.json",
+ vue: "packages/client-vue/package.json",
+ plugin: "packages/plugin/package.json",
+};
+
+const rootPkg = readJson(files.root);
+const sharedPkg = readJson(files.shared);
+const reactPkg = readJson(files.react);
+const vuePkg = readJson(files.vue);
+const pluginPkg = readJson(files.plugin);
+
+rootPkg.version = newVersion;
+sharedPkg.version = newVersion;
+reactPkg.version = newVersion;
+vuePkg.version = newVersion;
+pluginPkg.version = newVersion;
+
+reactPkg.dependencies = reactPkg.dependencies || {};
+vuePkg.dependencies = vuePkg.dependencies || {};
+pluginPkg.dependencies = pluginPkg.dependencies || {};
+
+reactPkg.dependencies["@xagi/design-mode-shared"] = newVersion;
+vuePkg.dependencies["@xagi/design-mode-shared"] = newVersion;
+pluginPkg.dependencies["@xagi/design-mode-shared"] = newVersion;
+pluginPkg.dependencies["@xagi/design-mode-client-react"] = newVersion;
+pluginPkg.dependencies["@xagi/design-mode-client-vue"] = newVersion;
+
+writeJson(files.root, rootPkg);
+writeJson(files.shared, sharedPkg);
+writeJson(files.react, reactPkg);
+writeJson(files.vue, vuePkg);
+writeJson(files.plugin, pluginPkg);
+NODE
+
+echo "[version-sync] done."
diff --git a/qiming-vite-plugin-design-mode/scripts/test-cli.sh b/qiming-vite-plugin-design-mode/scripts/test-cli.sh
new file mode 100644
index 00000000..75e9d14f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/scripts/test-cli.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+
+# CLI smoke test: install / uninstall flows
+
+set -e
+
+echo "🚀 Starting CLI tests..."
+echo ""
+
+# Colors
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# 1. Build
+echo -e "${BLUE}📦 Step 1: Building...${NC}"
+npm run build
+echo -e "${GREEN}✓ Build done${NC}"
+echo ""
+
+# 2. Enter demo project
+TEST_DIR="examples/demo"
+if [ ! -d "$TEST_DIR" ]; then
+ echo -e "${YELLOW}⚠️ Test directory missing: $TEST_DIR${NC}"
+ exit 1
+fi
+
+cd "$TEST_DIR"
+echo -e "${BLUE}📁 Entering: $TEST_DIR${NC}"
+echo ""
+
+# 3. Backup config files
+echo -e "${BLUE}💾 Step 2: Backing up config files...${NC}"
+if [ -f "vite.config.ts" ]; then
+ cp vite.config.ts vite.config.ts.backup
+ echo -e "${GREEN}✓ Backed up vite.config.ts${NC}"
+fi
+if [ -f "package.json" ]; then
+ cp package.json package.json.backup
+ echo -e "${GREEN}✓ Backed up package.json${NC}"
+fi
+echo ""
+
+# 4. Test install
+echo -e "${BLUE}📥 Step 3: Testing install...${NC}"
+echo "Running: node ../../packages/plugin/dist/cli/index.js install"
+echo ""
+node ../../packages/plugin/dist/cli/index.js install
+echo ""
+echo -e "${GREEN}✓ Install test done${NC}"
+echo ""
+
+read -p "Press Enter to continue with uninstall test..."
+echo ""
+
+# 5. Test uninstall
+echo -e "${BLUE}📤 Step 4: Testing uninstall...${NC}"
+echo "Running: node ../../packages/plugin/dist/cli/index.js uninstall"
+echo ""
+node ../../packages/plugin/dist/cli/index.js uninstall
+echo ""
+echo -e "${GREEN}✓ Uninstall test done${NC}"
+echo ""
+
+# 6. Restore backups?
+read -p "Restore backup files? (y/n) " -n 1 -r
+echo ""
+if [[ $REPLY =~ ^[Yy]$ ]]; then
+ echo -e "${BLUE}🔄 Restoring backups...${NC}"
+ if [ -f "vite.config.ts.backup" ]; then
+ mv vite.config.ts.backup vite.config.ts
+ echo -e "${GREEN}✓ Restored vite.config.ts${NC}"
+ fi
+ if [ -f "package.json.backup" ]; then
+ mv package.json.backup package.json
+ echo -e "${GREEN}✓ Restored package.json${NC}"
+ fi
+fi
+
+echo ""
+echo -e "${GREEN}✅ All tests finished.${NC}"
+
diff --git a/qiming-vite-plugin-design-mode/test/README.md b/qiming-vite-plugin-design-mode/test/README.md
new file mode 100644
index 00000000..a50b46da
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/test/README.md
@@ -0,0 +1,185 @@
+# 测试文档
+
+本目录包含 `@xagi/vite-plugin-design-mode` 插件的单元测试。
+
+## 测试结构
+
+```
+test/
+├── index.test.ts # 主插件入口测试
+├── core/
+│ ├── serverMiddleware.test.ts # 服务器中间件测试
+│ ├── astTransformer.test.ts # AST转换器测试
+│ ├── codeUpdater.test.ts # 代码更新器测试
+│ └── sourceMapper.test.ts # 源码映射器测试
+└── utils/
+ └── babelHelpers.test.ts # Babel辅助函数测试
+```
+
+## 运行测试
+
+### 运行所有测试
+
+```bash
+npm test
+# 或
+pnpm test
+```
+
+### 运行测试(单次运行,不监听)
+
+```bash
+npm run test:run
+```
+
+### 运行测试并生成覆盖率报告
+
+```bash
+npm run test:coverage
+```
+
+覆盖率报告将生成在 `coverage/` 目录中。
+
+### 使用UI界面运行测试
+
+```bash
+npm run test:ui
+```
+
+这将打开一个交互式的测试UI界面。
+
+## 测试覆盖范围
+
+### 核心功能测试
+
+1. **主插件入口 (`index.test.ts`)**
+ - 插件初始化
+ - 配置选项处理
+ - Vite hooks 集成
+ - 文件过滤逻辑
+
+2. **服务器中间件 (`serverMiddleware.test.ts`)**
+ - 健康检查端点
+ - 获取源码端点
+ - 修改源码端点
+ - 错误处理
+
+3. **AST转换器 (`astTransformer.test.ts`)**
+ - JSX代码转换
+ - 源码映射属性注入
+ - TypeScript支持
+ - 嵌套元素处理
+
+4. **代码更新器 (`codeUpdater.test.ts`)**
+ - 样式更新
+ - 内容更新
+ - 文件路径处理
+ - 错误处理
+
+5. **源码映射器 (`sourceMapper.test.ts`)**
+ - Babel插件创建
+ - 元素ID生成
+ - 位置信息注入
+ - 自定义属性前缀
+
+6. **Babel辅助函数 (`babelHelpers.test.ts`)**
+ - React组件名识别
+ - JSX元素名称提取
+ - 属性值提取
+ - 位置字符串处理
+
+## 编写新测试
+
+### 测试文件命名
+
+- 测试文件应以 `.test.ts` 或 `.spec.ts` 结尾
+- 测试文件应放在与被测试文件相同的目录结构中
+
+### 测试结构
+
+```typescript
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { functionToTest } from '../../src/module';
+
+describe('模块名称', () => {
+ beforeEach(() => {
+ // 每个测试前的设置
+ vi.clearAllMocks();
+ });
+
+ describe('功能描述', () => {
+ it('应该执行预期行为', () => {
+ // 测试代码
+ expect(result).toBe(expected);
+ });
+ });
+});
+```
+
+### Mock使用
+
+使用 Vitest 的 `vi.mock()` 来模拟依赖:
+
+```typescript
+vi.mock('fs', () => {
+ return {
+ readFileSync: vi.fn(),
+ writeFileSync: vi.fn(),
+ };
+});
+```
+
+### 异步测试
+
+使用 `async/await` 处理异步操作:
+
+```typescript
+it('应该处理异步操作', async () => {
+ const result = await asyncFunction();
+ expect(result).toBeDefined();
+});
+```
+
+## 测试最佳实践
+
+1. **测试隔离**: 每个测试应该独立运行,不依赖其他测试的状态
+2. **清晰命名**: 测试描述应该清楚地说明测试的目的
+3. **覆盖边界情况**: 测试正常流程、错误情况和边界条件
+4. **Mock外部依赖**: 使用Mock来隔离被测试的代码
+5. **保持测试简单**: 每个测试应该只测试一个功能点
+6. **使用描述性的断言**: 使用有意义的错误消息
+
+## 持续集成
+
+测试可以在CI/CD流程中自动运行:
+
+```yaml
+# GitHub Actions 示例
+- name: Run tests
+ run: npm run test:run
+
+- name: Generate coverage
+ run: npm run test:coverage
+```
+
+## 故障排除
+
+### 测试失败
+
+1. 检查测试环境是否正确配置
+2. 确认所有依赖已安装
+3. 查看测试输出中的错误信息
+4. 检查Mock是否正确设置
+
+### 覆盖率低
+
+1. 检查是否有未测试的代码路径
+2. 添加更多边界情况测试
+3. 确保所有公共API都有测试覆盖
+
+## 相关资源
+
+- [Vitest 文档](https://vitest.dev/)
+- [Testing Library](https://testing-library.com/)
+- [Jest 迁移指南](https://vitest.dev/guide/migration.html)
+
diff --git a/qiming-vite-plugin-design-mode/test/core/astTransformer.test.ts b/qiming-vite-plugin-design-mode/test/core/astTransformer.test.ts
new file mode 100644
index 00000000..2d4c0ec8
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/test/core/astTransformer.test.ts
@@ -0,0 +1,214 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { transformSourceCode } from '../../packages/plugin/src/core/astTransformer';
+import type { DesignModeOptions } from '../../packages/plugin/src/types';
+
+describe('astTransformer', () => {
+ const mockOptions: Required = {
+ enabled: true,
+ enableInProduction: false,
+ attributePrefix: 'data-source',
+ verbose: false,
+ exclude: ['node_modules'],
+ include: ['**/*.{js,jsx,ts,tsx}'],
+ };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe('transformSourceCode', () => {
+ it('应该转换简单的JSX代码', () => {
+ const code = `
+ function App() {
+ return Hello World
;
+ }
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ expect(result).toBeDefined();
+ expect(result).toContain('data-source');
+ expect(result).toContain('div');
+ });
+
+ it('应该为JSX元素添加源码映射属性', () => {
+ const code = `
+ function App() {
+ return (
+
+
Title
+
+ );
+ }
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ expect(result).toContain('data-source-element-id');
+ expect(result).toContain('data-source-position');
+ expect(result).toContain('data-source-info');
+ });
+
+ it('应该处理TypeScript代码', () => {
+ const code = `
+ interface Props {
+ title: string;
+ }
+
+ function App(props: Props) {
+ return {props.title}
;
+ }
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ expect(result).toBeDefined();
+ expect(result).toContain('data-source');
+ });
+
+ it('应该处理嵌套的JSX元素', () => {
+ const code = `
+ function App() {
+ return (
+
+ );
+ }
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ // 应该为多个元素添加属性
+ const matches = result.match(/data-source-element-id/g);
+ expect(matches?.length).toBeGreaterThan(1);
+ });
+
+ it('应该处理带属性的JSX元素', () => {
+ const code = `
+ function App() {
+ return (
+
+ {}} disabled>
+ Click me
+
+
+ );
+ }
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ expect(result).toContain('data-source');
+ expect(result).toContain('container');
+ });
+
+ it('应该处理函数组件', () => {
+ const code = `
+ const Button = ({ label }: { label: string }) => {
+ return {label};
+ };
+
+ function App() {
+ return ;
+ }
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ expect(result).toBeDefined();
+ expect(result).toContain('data-source');
+ });
+
+ it('应该处理类组件', () => {
+ const code = `
+ class App extends React.Component {
+ render() {
+ return Hello
;
+ }
+ }
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ expect(result).toBeDefined();
+ expect(result).toContain('data-source');
+ });
+
+ it('应该在转换失败时返回原始代码', () => {
+ const code = 'invalid syntax {{{{';
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ // 应该返回原始代码或处理后的代码
+ expect(result).toBeDefined();
+ });
+
+ it('应该使用自定义属性前缀', () => {
+ const customOptions: Required = {
+ ...mockOptions,
+ attributePrefix: 'data-appdev',
+ };
+
+ const code = `
+ function App() {
+ return Test
;
+ }
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', customOptions);
+
+ expect(result).toContain('data-appdev');
+ expect(result).not.toContain('data-source');
+ });
+
+ it('应该处理空代码', () => {
+ const code = '';
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ expect(result).toBeDefined();
+ });
+
+ it('应该处理只有注释的代码', () => {
+ const code = `
+ // This is a comment
+ /* Another comment */
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ expect(result).toBeDefined();
+ });
+
+ it('应该处理复杂的JSX表达式', () => {
+ const code = `
+ function App() {
+ const items = [1, 2, 3];
+ return (
+
+ {items.map(item => (
+
{item}
+ ))}
+
+ );
+ }
+ `;
+
+ const result = transformSourceCode(code, 'test.tsx', mockOptions);
+
+ expect(result).toBeDefined();
+ expect(result).toContain('data-source');
+ });
+ });
+});
+
diff --git a/qiming-vite-plugin-design-mode/test/core/codeUpdater.test.ts b/qiming-vite-plugin-design-mode/test/core/codeUpdater.test.ts
new file mode 100644
index 00000000..bc6b908b
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/test/core/codeUpdater.test.ts
@@ -0,0 +1,346 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { handleUpdate } from '../../packages/plugin/src/core/codeUpdater';
+import * as fs from 'fs';
+import type { IncomingMessage, ServerResponse } from 'http';
+
+// Mock fs 模块
+vi.mock('fs', async () => {
+ const actualFs = await vi.importActual('fs');
+ const mockFs = {
+ ...actualFs,
+ statSync: vi.fn(),
+ readFileSync: vi.fn(),
+ writeFileSync: vi.fn(),
+ existsSync: vi.fn(),
+ };
+ return {
+ ...mockFs,
+ default: mockFs,
+ };
+});
+
+describe('codeUpdater', () => {
+ const mockRoot = '/test/project';
+
+ let mockReq: Partial;
+ let mockRes: Partial;
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.mocked(fs.statSync).mockReturnValue({
+ size: 1024,
+ isFile: () => true,
+ isDirectory: () => false,
+ } as any);
+
+ mockReq = {
+ method: 'POST',
+ on: vi.fn(),
+ };
+
+ mockRes = {
+ statusCode: 200,
+ setHeader: vi.fn(),
+ end: vi.fn(),
+ };
+ });
+
+ describe('handleUpdate', () => {
+ it('应该拒绝非POST请求', async () => {
+ mockReq.method = 'GET';
+
+ await handleUpdate(mockReq as IncomingMessage, mockRes as ServerResponse, mockRoot);
+
+ expect(mockRes.statusCode).toBe(405);
+ expect(mockRes.end).toHaveBeenCalledWith('Method Not Allowed');
+ });
+
+ it('应该处理样式更新请求', async () => {
+ const sourceCode = `
+ function App() {
+ return Content
;
+ }
+ `;
+
+ vi.mocked(fs.existsSync).mockReturnValue(true);
+ vi.mocked(fs.readFileSync).mockReturnValue(sourceCode);
+ vi.mocked(fs.writeFileSync).mockImplementation(() => {});
+
+ const requestBody = JSON.stringify({
+ filePath: 'src/App.tsx',
+ line: 2,
+ column: 20,
+ newValue: 'new-class',
+ type: 'style',
+ });
+
+ let dataCallback: (chunk: Buffer) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ }) as any;
+
+ const updatePromise = handleUpdate(mockReq as IncomingMessage, mockRes as ServerResponse, mockRoot);
+
+ // 模拟数据流
+ if (dataCallback!) {
+ dataCallback(Buffer.from(requestBody));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await updatePromise;
+
+ expect(fs.existsSync).toHaveBeenCalled();
+ expect(fs.readFileSync).toHaveBeenCalled();
+ });
+
+ it('应该处理内容更新请求', async () => {
+ const sourceCode = `
+ function App() {
+ return Old Content
;
+ }
+ `;
+
+ vi.mocked(fs.existsSync).mockReturnValue(true);
+ vi.mocked(fs.readFileSync).mockReturnValue(sourceCode);
+ vi.mocked(fs.writeFileSync).mockImplementation(() => {});
+
+ const requestBody = JSON.stringify({
+ filePath: 'src/App.tsx',
+ line: 2,
+ column: 20,
+ newValue: 'New Content',
+ type: 'content',
+ });
+
+ let dataCallback: (chunk: Buffer) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ }) as any;
+
+ const updatePromise = handleUpdate(mockReq as IncomingMessage, mockRes as ServerResponse, mockRoot);
+
+ if (dataCallback!) {
+ dataCallback(Buffer.from(requestBody));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await updatePromise;
+
+ expect(fs.existsSync).toHaveBeenCalled();
+ expect(fs.readFileSync).toHaveBeenCalled();
+ });
+
+ it('应该处理文件不存在的情况', async () => {
+ vi.mocked(fs.existsSync).mockReturnValue(false);
+
+ const requestBody = JSON.stringify({
+ filePath: 'src/NonExistent.tsx',
+ line: 1,
+ column: 0,
+ newValue: 'test',
+ type: 'style',
+ });
+
+ let dataCallback: (chunk: Buffer) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ }) as any;
+
+ const updatePromise = handleUpdate(mockReq as IncomingMessage, mockRes as ServerResponse, mockRoot);
+
+ if (dataCallback!) {
+ dataCallback(Buffer.from(requestBody));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await updatePromise;
+
+ expect(mockRes.statusCode).toBe(400);
+ const response = JSON.parse(mockRes.end?.mock.calls[0]?.[0] || '{}');
+ expect(response.success).toBe(false);
+ expect(response.message).toContain('File not found');
+ });
+
+ it('应该处理绝对路径', async () => {
+ const absolutePath = '/absolute/path/to/file.tsx';
+ const sourceCode = 'function App() { return Test
; }';
+
+ vi.mocked(fs.existsSync).mockReturnValue(true);
+ vi.mocked(fs.readFileSync).mockReturnValue(sourceCode);
+ vi.mocked(fs.writeFileSync).mockImplementation(() => {});
+
+ const requestBody = JSON.stringify({
+ filePath: absolutePath,
+ line: 1,
+ column: 0,
+ newValue: 'test',
+ type: 'style',
+ });
+
+ let dataCallback: (chunk: Buffer) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ }) as any;
+
+ const updatePromise = handleUpdate(mockReq as IncomingMessage, mockRes as ServerResponse, mockRoot);
+
+ if (dataCallback!) {
+ dataCallback(Buffer.from(requestBody));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await updatePromise;
+
+ expect(mockRes.statusCode).toBe(400);
+ const response = JSON.parse(mockRes.end?.mock.calls[0]?.[0] || '{}');
+ expect(response.success).toBe(false);
+ expect(response.message).toContain('Access denied');
+ });
+
+ it('应该处理相对路径', async () => {
+ const relativePath = 'src/App.tsx';
+ const sourceCode = 'function App() { return Test
; }';
+
+ vi.mocked(fs.existsSync).mockReturnValue(true);
+ vi.mocked(fs.readFileSync).mockReturnValue(sourceCode);
+ vi.mocked(fs.writeFileSync).mockImplementation(() => {});
+
+ const requestBody = JSON.stringify({
+ filePath: relativePath,
+ line: 1,
+ column: 0,
+ newValue: 'test',
+ type: 'style',
+ });
+
+ let dataCallback: (chunk: Buffer) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ }) as any;
+
+ const updatePromise = handleUpdate(mockReq as IncomingMessage, mockRes as ServerResponse, mockRoot);
+
+ if (dataCallback!) {
+ dataCallback(Buffer.from(requestBody));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await updatePromise;
+
+ // 应该解析为绝对路径
+ expect(fs.existsSync).toHaveBeenCalled();
+ });
+
+ it('应该处理无效的JSON请求体', async () => {
+ let dataCallback: (chunk: Buffer) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ }) as any;
+
+ const updatePromise = handleUpdate(mockReq as IncomingMessage, mockRes as ServerResponse, mockRoot);
+
+ if (dataCallback!) {
+ dataCallback(Buffer.from('invalid json'));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await updatePromise;
+
+ expect(mockRes.statusCode).toBe(500);
+ });
+
+ it('应该处理找不到匹配元素的情况', async () => {
+ const sourceCode = `
+ function App() {
+ return Content
;
+ }
+ `;
+
+ vi.mocked(fs.existsSync).mockReturnValue(true);
+ vi.mocked(fs.readFileSync).mockReturnValue(sourceCode);
+
+ const requestBody = JSON.stringify({
+ filePath: 'src/App.tsx',
+ line: 999, // 不存在的行
+ column: 0,
+ newValue: 'test',
+ type: 'style',
+ });
+
+ let dataCallback: (chunk: Buffer) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ }) as any;
+
+ const updatePromise = handleUpdate(mockReq as IncomingMessage, mockRes as ServerResponse, mockRoot);
+
+ if (dataCallback!) {
+ dataCallback(Buffer.from(requestBody));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await updatePromise;
+
+ // 应该返回400错误,因为找不到匹配的元素
+ const response = JSON.parse(mockRes.end?.mock.calls[0]?.[0] || '{}');
+ expect(response.success).toBe(false);
+ });
+ });
+});
+
diff --git a/qiming-vite-plugin-design-mode/test/core/serverMiddleware.test.ts b/qiming-vite-plugin-design-mode/test/core/serverMiddleware.test.ts
new file mode 100644
index 00000000..2b956ab4
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/test/core/serverMiddleware.test.ts
@@ -0,0 +1,296 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import * as fs from 'fs';
+import * as path from 'path';
+import { createServerMiddleware } from '../../packages/plugin/src/core/serverMiddleware';
+import type { DesignModeOptions } from '../../packages/plugin/src/types';
+
+// Mock fs 模块
+vi.mock('fs', async () => {
+ const actualFs = await vi.importActual('fs');
+ const mockFs = {
+ ...actualFs,
+ promises: {
+ ...actualFs.promises,
+ access: vi.fn(),
+ readFile: vi.fn(),
+ writeFile: vi.fn(),
+ stat: vi.fn(),
+ },
+ };
+ return {
+ ...mockFs,
+ default: mockFs,
+ };
+});
+
+describe('serverMiddleware', () => {
+ const mockOptions: Required = {
+ enabled: true,
+ enableInProduction: false,
+ attributePrefix: 'data-source',
+ verbose: false,
+ exclude: ['node_modules'],
+ include: ['**/*.{js,jsx,ts,tsx}'],
+ };
+
+ const mockRootDir = '/test/project';
+
+ let mockReq: any;
+ let mockRes: any;
+
+ beforeEach(() => {
+ // 重置 mock
+ vi.clearAllMocks();
+
+ // 创建模拟的请求对象
+ mockReq = {
+ url: '',
+ method: 'GET',
+ on: vi.fn(),
+ };
+
+ // 创建模拟的响应对象
+ mockRes = {
+ statusCode: 200,
+ headers: {},
+ end: vi.fn(),
+ setHeader: vi.fn(),
+ };
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ describe('createServerMiddleware', () => {
+ it('应该创建中间件函数', () => {
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ expect(typeof middleware).toBe('function');
+ });
+
+ it('应该处理健康检查请求', async () => {
+ mockReq.url = '/health';
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ await middleware(mockReq, mockRes);
+
+ expect(mockRes.statusCode).toBe(200);
+ expect(mockRes.end).toHaveBeenCalled();
+
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.status).toBe('ok');
+ expect(responseData.plugin).toBe('@xagi/vite-plugin-design-mode');
+ expect(responseData.timestamp).toBeDefined();
+ });
+
+ it('应该处理获取源码请求 - 成功', async () => {
+ const elementId = 'src/App.tsx:4:5_div_test';
+ mockReq.url = `/get-source?elementId=${encodeURIComponent(elementId)}`;
+
+ const mockFileContent = 'import React from "react";\n\nfunction App() {\n return Test
;\n}';
+ vi.mocked(fs.promises.access).mockResolvedValue(undefined);
+ vi.mocked(fs.promises.stat).mockResolvedValue({ mtime: new Date() } as any);
+ vi.mocked(fs.promises.readFile).mockResolvedValue(mockFileContent);
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ await middleware(mockReq, mockRes);
+
+ expect(mockRes.statusCode).toBe(200);
+ expect(fs.promises.readFile).toHaveBeenCalled();
+
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.sourceInfo).toBeDefined();
+ expect(responseData.sourceInfo.fileName).toBe('src/App.tsx');
+ expect(responseData.sourceInfo.lineNumber).toBe(4);
+ expect(responseData.sourceInfo.columnNumber).toBe(5);
+ });
+
+ it('应该处理获取源码请求 - 缺少elementId参数', async () => {
+ mockReq.url = '/get-source';
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ await middleware(mockReq, mockRes);
+
+ expect(mockRes.statusCode).toBe(400);
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.error).toContain('Missing elementId');
+ });
+
+ it('应该处理获取源码请求 - 无效的elementId格式', async () => {
+ mockReq.url = '/get-source?elementId=invalid';
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ await middleware(mockReq, mockRes);
+
+ expect(mockRes.statusCode).toBe(400);
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.error).toContain('Invalid elementId');
+ });
+
+ it('应该处理修改源码请求 - 成功', async () => {
+ const elementId = 'src/App.tsx:4:5_div_test';
+ mockReq.url = '/modify-source';
+ mockReq.method = 'POST';
+
+ const mockFileContent = 'import React from "react";\n\nfunction App() {\n return Test
;\n}';
+ const mockUpdatedContent = 'import React from "react";\n\nfunction App() {\n return Test
;\n}';
+
+ vi.mocked(fs.promises.access).mockResolvedValue(undefined);
+ vi.mocked(fs.promises.readFile).mockResolvedValue(mockFileContent);
+ vi.mocked(fs.promises.writeFile).mockResolvedValue(undefined);
+
+ // 模拟请求体
+ const requestBody = JSON.stringify({
+ elementId,
+ newStyles: 'new',
+ oldStyles: 'old',
+ });
+
+ let dataCallback: (chunk: any) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ });
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ const middlewarePromise = middleware(mockReq, mockRes);
+
+ // 模拟数据流
+ if (dataCallback!) {
+ dataCallback(Buffer.from(requestBody));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await middlewarePromise;
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ expect(mockRes.statusCode).toBe(200);
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.success).toBe(true);
+ });
+
+ it('应该处理修改源码请求 - 方法不允许', async () => {
+ mockReq.url = '/modify-source';
+ mockReq.method = 'GET';
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ await middleware(mockReq, mockRes);
+
+ expect(mockRes.statusCode).toBe(405);
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.error).toContain('Method not allowed');
+ });
+
+ it('应该处理修改源码请求 - 缺少必需参数', async () => {
+ mockReq.url = '/modify-source';
+ mockReq.method = 'POST';
+
+ const requestBody = JSON.stringify({});
+
+ let dataCallback: (chunk: any) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ });
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ const middlewarePromise = middleware(mockReq, mockRes);
+
+ if (dataCallback!) {
+ dataCallback(Buffer.from(requestBody));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await middlewarePromise;
+
+ expect(mockRes.statusCode).toBe(400);
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.error).toContain('Missing required parameters');
+ });
+
+ it('应该处理404请求', async () => {
+ mockReq.url = '/unknown';
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ await middleware(mockReq, mockRes);
+
+ expect(mockRes.statusCode).toBe(404);
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.error).toBe('Not found');
+ });
+
+ it('应该处理文件读取错误', async () => {
+ const elementId = 'src/App.tsx:4:5_div_test';
+ mockReq.url = `/get-source?elementId=${encodeURIComponent(elementId)}`;
+
+ vi.mocked(fs.promises.access).mockResolvedValue(undefined);
+ vi.mocked(fs.promises.readFile).mockRejectedValue(new Error('File not found'));
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ await middleware(mockReq, mockRes);
+
+ expect(mockRes.statusCode).toBe(500);
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.error).toBe('File not found');
+ });
+
+ it('应该处理文件写入错误', async () => {
+ const elementId = 'src/App.tsx:4:5_div_test';
+ mockReq.url = '/modify-source';
+ mockReq.method = 'POST';
+
+ const mockFileContent = 'import React from "react";\n\nfunction App() {\n return Test
;\n}';
+ vi.mocked(fs.promises.access).mockResolvedValue(undefined);
+ vi.mocked(fs.promises.readFile).mockResolvedValue(mockFileContent);
+ vi.mocked(fs.promises.writeFile).mockRejectedValue(new Error('Permission denied'));
+
+ const requestBody = JSON.stringify({
+ elementId,
+ newStyles: 'new',
+ });
+
+ let dataCallback: (chunk: any) => void;
+ let endCallback: () => void;
+
+ mockReq.on = vi.fn((event: string, callback: any) => {
+ if (event === 'data') {
+ dataCallback = callback;
+ } else if (event === 'end') {
+ endCallback = callback;
+ }
+ });
+
+ const middleware = createServerMiddleware(mockOptions, mockRootDir);
+ const middlewarePromise = middleware(mockReq, mockRes);
+
+ if (dataCallback!) {
+ dataCallback(Buffer.from(requestBody));
+ }
+ if (endCallback!) {
+ endCallback();
+ }
+
+ await middlewarePromise;
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ expect(mockRes.statusCode).toBe(500);
+ const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
+ expect(responseData.error).toBe('Permission denied');
+ });
+ });
+});
+
diff --git a/qiming-vite-plugin-design-mode/test/core/sourceMapper.test.ts b/qiming-vite-plugin-design-mode/test/core/sourceMapper.test.ts
new file mode 100644
index 00000000..a121163f
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/test/core/sourceMapper.test.ts
@@ -0,0 +1,262 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { createSourceMappingPlugin } from '../../packages/plugin/src/core/sourceMapper';
+import type { DesignModeOptions } from '../../packages/plugin/src/types';
+import * as babel from '@babel/standalone';
+
+describe('sourceMapper', () => {
+ const mockOptions: Required = {
+ enabled: true,
+ enableInProduction: false,
+ attributePrefix: 'data-source',
+ verbose: false,
+ exclude: ['node_modules'],
+ include: ['**/*.{js,jsx,ts,tsx}'],
+ };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe('createSourceMappingPlugin', () => {
+ it('应该创建Babel插件', () => {
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ expect(plugin).toBeDefined();
+ expect(plugin.visitor).toBeDefined();
+ expect(plugin.visitor.JSXOpeningElement).toBeDefined();
+ });
+
+ it('应该为JSX元素添加源码映射属性', () => {
+ const code = `
+ function App() {
+ return Hello
;
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ expect(result?.code).toContain('data-source');
+ });
+
+ it('应该生成正确的elementId', () => {
+ const code = `
+ function App() {
+ return Hello
;
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('src/App.tsx', mockOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ expect(result?.code).toContain('data-source-element-id');
+ });
+
+ it('应该添加位置信息属性', () => {
+ const code = `
+ function App() {
+ return Hello
;
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ expect(result?.code).toContain('data-source-position');
+ });
+
+ it('应该添加完整的源码信息属性', () => {
+ const code = `
+ function App() {
+ return Hello
;
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ expect(result?.code).toContain('data-source-info');
+ });
+
+ it('应该使用自定义属性前缀', () => {
+ const customOptions: Required = {
+ ...mockOptions,
+ attributePrefix: 'data-appdev',
+ };
+
+ const code = `
+ function App() {
+ return Hello
;
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', customOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ expect(result?.code).toContain('data-appdev');
+ expect(result?.code).not.toContain('data-source');
+ });
+
+ it('应该处理嵌套的JSX元素', () => {
+ const code = `
+ function App() {
+ return (
+
+
+
+ );
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ // 应该为多个元素添加属性
+ const matches = result?.code.match(/data-source-element-id/g);
+ expect(matches?.length).toBeGreaterThan(1);
+ });
+
+ it('应该处理带id属性的元素', () => {
+ const code = `
+ function App() {
+ return Hello
;
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ expect(result?.code).toContain('data-source-element-id');
+ });
+
+ it('应该处理带className的元素', () => {
+ const code = `
+ function App() {
+ return Hello
;
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ expect(result?.code).toContain('data-source-element-id');
+ });
+
+ it('应该处理函数组件', () => {
+ const code = `
+ const Button = () => {
+ return Click;
+ };
+
+ function App() {
+ return ;
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ expect(result?.code).toContain('data-source');
+ });
+
+ it('应该处理没有位置信息的元素', () => {
+ const code = `
+ function App() {
+ return Hello
;
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ // 即使没有位置信息,插件也应该正常工作
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ });
+
+ it('应该处理复杂的JSX结构', () => {
+ const code = `
+ function App() {
+ return (
+
+ );
+ }
+ `;
+
+ const plugin = createSourceMappingPlugin('test.tsx', mockOptions);
+
+ const result = babel.transform(code, {
+ plugins: [plugin],
+ presets: ['react'],
+ });
+
+ expect(result?.code).toBeDefined();
+ // 应该为多个元素添加属性
+ const matches = result?.code.match(/data-source-element-id/g);
+ expect(matches?.length).toBeGreaterThan(3);
+ });
+ });
+});
+
diff --git a/qiming-vite-plugin-design-mode/test/core/vueSfcTransformer.test.ts b/qiming-vite-plugin-design-mode/test/core/vueSfcTransformer.test.ts
new file mode 100644
index 00000000..07316a8d
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/test/core/vueSfcTransformer.test.ts
@@ -0,0 +1,63 @@
+import { describe, it, expect } from 'vitest';
+import { transformVueSfcTemplate } from '../../packages/plugin/src/core/vueSfcTransformer';
+import type { DesignModeOptions } from '../../packages/plugin/src/types';
+
+const options: Required = {
+ enabled: true,
+ enableInProduction: false,
+ attributePrefix: 'data-source',
+ verbose: false,
+ exclude: ['node_modules'],
+ include: ['src/**/*.{js,jsx,ts,tsx,vue}'],
+ enableBackup: false,
+ enableHistory: false,
+ framework: 'auto',
+};
+
+function decodeHtmlAttr(value: string): string {
+ return value
+ .replace(/"/g, '"')
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+}
+
+describe('vueSfcTransformer', () => {
+ it('injects normalized source info fields with legacy compatibility aliases', () => {
+ const source = `
+
+
Hello Vue
+
+
+
+`;
+
+ const transformed = transformVueSfcTemplate(source, 'src/pages/Home.vue', options);
+ const attrMatch = transformed.match(/data-source-info="([^"]+)"/);
+
+ expect(attrMatch).toBeTruthy();
+ const sourceInfo = JSON.parse(decodeHtmlAttr(attrMatch![1])) as Record;
+
+ expect(sourceInfo.fileName).toBe('src/pages/Home.vue');
+ expect(sourceInfo.lineNumber).toBeTypeOf('number');
+ expect(sourceInfo.columnNumber).toBeTypeOf('number');
+ expect(sourceInfo.line).toBe(sourceInfo.lineNumber);
+ expect(sourceInfo.column).toBe(sourceInfo.columnNumber);
+ });
+
+ it('removes stale mapping attrs before reinjection', () => {
+ const source = `
+
+ Hello
+
+`;
+
+ const transformed = transformVueSfcTemplate(source, 'src/pages/Home.vue', options);
+ const infoAttrs = transformed.match(/data-source-info=/g) ?? [];
+
+ expect(infoAttrs.length).toBe(1);
+ expect(transformed).not.toContain('old.vue');
+ });
+});
diff --git a/qiming-vite-plugin-design-mode/test/core/vueSfcUpdater.test.ts b/qiming-vite-plugin-design-mode/test/core/vueSfcUpdater.test.ts
new file mode 100644
index 00000000..a1662647
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/test/core/vueSfcUpdater.test.ts
@@ -0,0 +1,60 @@
+import { describe, it, expect } from 'vitest';
+import { applyVueSfcTemplateUpdate } from '../../packages/plugin/src/core/vueSfcUpdater';
+
+function getLineColumnByToken(source: string, token: string): { line: number; column: number } {
+ const index = source.indexOf(token);
+ if (index < 0) {
+ throw new Error(`Token not found: ${token}`);
+ }
+ const prefix = source.slice(0, index);
+ const lines = prefix.split('\n');
+ return {
+ line: lines.length,
+ column: (lines[lines.length - 1] ?? '').length + 1,
+ };
+}
+
+describe('vueSfcUpdater', () => {
+ it('updates static class in Vue template', () => {
+ const source = `
+
+
+
+`;
+ const { line, column } = getLineColumnByToken(source, 'Hello Vue
');
+ });
+
+ it('updates static text in Vue template', () => {
+ const source = `
+
+
+
+`;
+ const { line, column } = getLineColumnByToken(source, 'Hello Agent
');
+ });
+});
diff --git a/qiming-vite-plugin-design-mode/test/index.test.ts b/qiming-vite-plugin-design-mode/test/index.test.ts
new file mode 100644
index 00000000..939d177a
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/test/index.test.ts
@@ -0,0 +1,284 @@
+// @ts-nocheck
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import appdevDesignModePlugin from '../packages/plugin/src/index';
+import type { Plugin } from 'vite';
+
+describe('@xagi/vite-plugin-design-mode', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe('插件初始化', () => {
+ it('应该创建插件实例', () => {
+ const plugin = appdevDesignModePlugin();
+ expect(plugin).toBeDefined();
+ expect(plugin.name).toBe('@xagi/vite-plugin-design-mode');
+ });
+
+ it('应该使用默认选项', () => {
+ const plugin = appdevDesignModePlugin();
+ expect(plugin).toBeDefined();
+
+ // 插件应该具有预期的方法
+ expect(typeof plugin.config).toBe('function');
+ expect(typeof plugin.configureServer).toBe('function');
+ expect(typeof plugin.transform).toBe('function');
+ expect(typeof plugin.transformIndexHtml).toBe('function');
+ expect(typeof plugin.buildStart).toBe('function');
+ expect(typeof plugin.buildEnd).toBe('function');
+ });
+
+ it('应该接受自定义选项', () => {
+ const plugin = appdevDesignModePlugin({
+ enabled: false,
+ verbose: true,
+ attributePrefix: 'data-test',
+ exclude: ['custom-exclude'],
+ include: ['custom-include'],
+ });
+
+ expect(plugin).toBeDefined();
+ expect(plugin.name).toBe('@xagi/vite-plugin-design-mode');
+ });
+ });
+
+ describe('config hook', () => {
+ it('应该在开发模式下启用', () => {
+ const plugin = appdevDesignModePlugin({ enabled: true });
+
+ const result = plugin.config?.({}, { command: 'serve' });
+
+ expect(result).toBeDefined();
+ expect(result?.define).toBeDefined();
+ expect(result?.define?.__APPDEV_DESIGN_MODE__).toBe(true);
+ });
+
+ it('应该在构建模式下禁用(如果未启用生产模式)', () => {
+ const plugin = appdevDesignModePlugin({
+ enabled: true,
+ enableInProduction: false,
+ });
+
+ const result = plugin.config?.({}, { command: 'build' });
+
+ expect(result).toEqual({});
+ });
+
+ it('应该在构建模式下启用(如果启用生产模式)', () => {
+ const plugin = appdevDesignModePlugin({
+ enabled: true,
+ enableInProduction: true,
+ });
+
+ const result = plugin.config?.({}, { command: 'build' });
+
+ expect(result).toBeDefined();
+ expect(result?.define?.__APPDEV_DESIGN_MODE__).toBe(true);
+ });
+
+ it('应该在禁用时返回空配置', () => {
+ const plugin = appdevDesignModePlugin({ enabled: false });
+
+ const result = plugin.config?.({}, { command: 'serve' });
+
+ expect(result).toEqual({});
+ });
+
+ it('应该设置verbose标志', () => {
+ const plugin = appdevDesignModePlugin({ verbose: true });
+
+ const result = plugin.config?.({}, { command: 'serve' });
+
+ expect(result?.define?.__APPDEV_DESIGN_MODE_VERBOSE__).toBe(true);
+ });
+ });
+
+ describe('transform hook', () => {
+ it('应该在禁用时返回原始代码', async () => {
+ const plugin = appdevDesignModePlugin({ enabled: false });
+
+ const code = 'function App() { return Test
; }';
+ const result = await plugin.transform?.(code, 'test.tsx', {});
+
+ expect(result).toBe(code);
+ });
+
+ it('应该处理匹配的文件', async () => {
+ const plugin = appdevDesignModePlugin({ enabled: true });
+
+ const code = 'function App() { return Test
; }';
+ const result = await plugin.transform?.(code, 'src/App.tsx', {});
+
+ expect(result).toBeDefined();
+ // 应该被转换(添加了源码映射属性)
+ if (typeof result === 'object' && result !== null) {
+ expect(result.code).toBeDefined();
+ }
+ });
+
+ it('应该排除node_modules中的文件', async () => {
+ const plugin = appdevDesignModePlugin({ enabled: true });
+
+ const code = 'function App() { return Test
; }';
+ const result = await plugin.transform?.(code, 'node_modules/test.tsx', {});
+
+ expect(result).toBe(code);
+ });
+
+ it('应该处理转换错误', async () => {
+ const plugin = appdevDesignModePlugin({
+ enabled: true,
+ verbose: false,
+ });
+
+ // 无效的代码应该返回原始代码
+ const code = 'invalid syntax {{{{';
+ const result = await plugin.transform?.(code, 'test.tsx', {});
+
+ expect(result).toBeDefined();
+ });
+ });
+
+ describe('shouldProcessFile', () => {
+ it('应该处理匹配include模式的文件', async () => {
+ const plugin = appdevDesignModePlugin({
+ enabled: true,
+ include: ['**/*.tsx'],
+ });
+
+ const code = 'function App() { return Test
; }';
+ const result = await plugin.transform?.(code, 'src/App.tsx', {});
+
+ expect(result).toBeDefined();
+ });
+
+ it('应该排除匹配exclude模式的文件', async () => {
+ const plugin = appdevDesignModePlugin({
+ enabled: true,
+ exclude: ['test'],
+ });
+
+ const code = 'function App() { return Test
; }';
+ const result = await plugin.transform?.(code, 'test/App.tsx', {});
+
+ expect(result).toBe(code);
+ });
+
+ it('应该处理glob模式', async () => {
+ const plugin = appdevDesignModePlugin({
+ enabled: true,
+ include: ['src/**/*.{ts,tsx}'],
+ });
+
+ const code = 'function App() { return Test
; }';
+ const result1 = await plugin.transform?.(code, 'src/App.tsx', {});
+ const result2 = await plugin.transform?.(code, 'src/components/Button.tsx', {});
+
+ expect(result1).toBeDefined();
+ expect(result2).toBeDefined();
+ });
+ });
+
+ describe('configureServer hook', () => {
+ it('应该在开发模式下配置服务器', () => {
+ const plugin = appdevDesignModePlugin({ enabled: true });
+
+ const mockServer = {
+ config: {
+ command: 'serve' as const,
+ root: '/test',
+ },
+ middlewares: {
+ use: vi.fn(),
+ },
+ } as any;
+
+ plugin.configureServer?.(mockServer);
+
+ expect(mockServer.middlewares.use).toHaveBeenCalled();
+ });
+
+ it('应该在禁用时不配置服务器', () => {
+ const plugin = appdevDesignModePlugin({ enabled: false });
+
+ const mockServer = {
+ config: {
+ command: 'serve' as const,
+ root: '/test',
+ },
+ middlewares: {
+ use: vi.fn(),
+ },
+ } as any;
+
+ plugin.configureServer?.(mockServer);
+
+ // 不应该调用use(或者调用但立即返回)
+ // 这里我们只检查不会抛出错误
+ expect(mockServer).toBeDefined();
+ });
+ });
+
+ describe('transformIndexHtml hook', () => {
+ it('应该在启用时注入客户端脚本', () => {
+ const plugin = appdevDesignModePlugin({ enabled: true });
+
+ const html = '';
+ const result = plugin.transformIndexHtml?.(html, {
+ path: '/',
+ filename: 'index.html',
+ });
+
+ if (typeof result === 'object' && result !== null) {
+ expect(result.tags).toBeDefined();
+ expect(result.tags?.length).toBeGreaterThan(0);
+ expect(result.tags?.[0]?.tag).toBe('script');
+ }
+ });
+
+ it('应该在禁用时不注入脚本', () => {
+ const plugin = appdevDesignModePlugin({ enabled: false });
+
+ const html = '';
+ const result = plugin.transformIndexHtml?.(html, {
+ path: '/',
+ filename: 'index.html',
+ });
+
+ expect(result).toBe(html);
+ });
+ });
+
+ describe('buildStart 和 buildEnd hooks', () => {
+ it('应该在verbose模式下输出日志', () => {
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
+
+ const plugin = appdevDesignModePlugin({ verbose: true });
+
+ plugin.buildStart?.();
+ expect(consoleSpy).toHaveBeenCalledWith(
+ '[appdev-design-mode] Plugin started'
+ );
+
+ plugin.buildEnd?.();
+ expect(consoleSpy).toHaveBeenCalledWith(
+ '[appdev-design-mode] Plugin ended'
+ );
+
+ consoleSpy.mockRestore();
+ });
+
+ it('应该在非verbose模式下不输出日志', () => {
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
+
+ const plugin = appdevDesignModePlugin({ verbose: false });
+
+ plugin.buildStart?.();
+ plugin.buildEnd?.();
+
+ expect(consoleSpy).not.toHaveBeenCalled();
+
+ consoleSpy.mockRestore();
+ });
+ });
+});
diff --git a/qiming-vite-plugin-design-mode/test/utils/babelHelpers.test.ts b/qiming-vite-plugin-design-mode/test/utils/babelHelpers.test.ts
new file mode 100644
index 00000000..ee5f29ff
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/test/utils/babelHelpers.test.ts
@@ -0,0 +1,211 @@
+import { describe, it, expect } from 'vitest';
+import {
+ isReactComponentName,
+ getJSXElementBaseName,
+ isStringLiteralAttribute,
+ extractStringAttributeValue,
+ createSourcePositionString,
+ parseSourcePositionString,
+} from '../../packages/plugin/src/utils/babelHelpers';
+import * as t from '@babel/types';
+
+describe('babelHelpers', () => {
+ describe('isReactComponentName', () => {
+ it('应该识别大写开头的组件名', () => {
+ expect(isReactComponentName('App')).toBe(true);
+ expect(isReactComponentName('Button')).toBe(true);
+ expect(isReactComponentName('MyComponent')).toBe(true);
+ });
+
+ it('应该识别小写开头的非组件名', () => {
+ expect(isReactComponentName('app')).toBe(false);
+ expect(isReactComponentName('button')).toBe(false);
+ expect(isReactComponentName('myComponent')).toBe(false);
+ });
+
+ it('应该处理特殊字符', () => {
+ expect(isReactComponentName('_App')).toBe(false);
+ expect(isReactComponentName('$Component')).toBe(false);
+ expect(isReactComponentName('123Component')).toBe(false);
+ });
+ });
+
+ describe('getJSXElementBaseName', () => {
+ it('应该从JSXIdentifier获取名称', () => {
+ const identifier = t.jSXIdentifier('div');
+ const name = getJSXElementBaseName(identifier);
+ expect(name).toBe('div');
+ });
+
+ it('应该从JSXMemberExpression获取属性名', () => {
+ const memberExpr = t.jSXMemberExpression(
+ t.jSXIdentifier('React'),
+ t.jSXIdentifier('Component')
+ );
+ const name = getJSXElementBaseName(memberExpr);
+ expect(name).toBe('Component');
+ });
+
+ it('应该处理未知类型', () => {
+ const unknown = t.stringLiteral('test');
+ const name = getJSXElementBaseName(unknown as any);
+ expect(name).toBe('unknown');
+ });
+ });
+
+ describe('isStringLiteralAttribute', () => {
+ it('应该识别字符串字面量属性', () => {
+ const attr = t.jSXAttribute(
+ t.jSXIdentifier('className'),
+ t.stringLiteral('test')
+ );
+
+ const result = isStringLiteralAttribute(attr, 'className');
+ expect(result).toBe(true);
+ });
+
+ it('应该识别非字符串字面量属性', () => {
+ const attr = t.jSXAttribute(
+ t.jSXIdentifier('className'),
+ t.jSXExpressionContainer(t.identifier('classNameVar'))
+ );
+
+ const result = isStringLiteralAttribute(attr, 'className');
+ expect(result).toBe(false);
+ });
+
+ it('应该识别属性名不匹配的情况', () => {
+ const attr = t.jSXAttribute(
+ t.jSXIdentifier('id'),
+ t.stringLiteral('test')
+ );
+
+ const result = isStringLiteralAttribute(attr, 'className');
+ expect(result).toBe(false);
+ });
+ });
+
+ describe('extractStringAttributeValue', () => {
+ it('应该提取字符串属性值', () => {
+ const openingElement = t.jSXOpeningElement(
+ t.jSXIdentifier('div'),
+ [
+ t.jSXAttribute(
+ t.jSXIdentifier('className'),
+ t.stringLiteral('container')
+ ),
+ ]
+ );
+
+ const value = extractStringAttributeValue(openingElement, 'className');
+ expect(value).toBe('container');
+ });
+
+ it('应该返回null当属性不存在时', () => {
+ const openingElement = t.jSXOpeningElement(
+ t.jSXIdentifier('div'),
+ []
+ );
+
+ const value = extractStringAttributeValue(openingElement, 'className');
+ expect(value).toBeNull();
+ });
+
+ it('应该返回null当属性不是字符串字面量时', () => {
+ const openingElement = t.jSXOpeningElement(
+ t.jSXIdentifier('div'),
+ [
+ t.jSXAttribute(
+ t.jSXIdentifier('className'),
+ t.jSXExpressionContainer(t.identifier('classNameVar'))
+ ),
+ ]
+ );
+
+ const value = extractStringAttributeValue(openingElement, 'className');
+ expect(value).toBeNull();
+ });
+
+ it('应该提取id属性值', () => {
+ const openingElement = t.jSXOpeningElement(
+ t.jSXIdentifier('div'),
+ [
+ t.jSXAttribute(
+ t.jSXIdentifier('id'),
+ t.stringLiteral('app')
+ ),
+ ]
+ );
+
+ const value = extractStringAttributeValue(openingElement, 'id');
+ expect(value).toBe('app');
+ });
+ });
+
+ describe('createSourcePositionString', () => {
+ it('应该创建正确的位置字符串', () => {
+ const position = createSourcePositionString('src/App.tsx', 10, 5);
+ expect(position).toBe('src/App.tsx:10:5');
+ });
+
+ it('应该处理不同的文件名', () => {
+ const position = createSourcePositionString('components/Button.tsx', 20, 15);
+ expect(position).toBe('components/Button.tsx:20:15');
+ });
+
+ it('应该处理行号和列号为0的情况', () => {
+ const position = createSourcePositionString('test.tsx', 0, 0);
+ expect(position).toBe('test.tsx:0:0');
+ });
+ });
+
+ describe('parseSourcePositionString', () => {
+ it('应该解析正确的位置字符串', () => {
+ const result = parseSourcePositionString('src/App.tsx:10:5');
+
+ expect(result).not.toBeNull();
+ expect(result?.fileName).toBe('src/App.tsx');
+ expect(result?.lineNumber).toBe(10);
+ expect(result?.columnNumber).toBe(5);
+ });
+
+ it('应该处理不同的文件名', () => {
+ const result = parseSourcePositionString('components/Button.tsx:20:15');
+
+ expect(result).not.toBeNull();
+ expect(result?.fileName).toBe('components/Button.tsx');
+ expect(result?.lineNumber).toBe(20);
+ expect(result?.columnNumber).toBe(15);
+ });
+
+ it('应该返回null当格式不正确时', () => {
+ expect(parseSourcePositionString('invalid')).toBeNull();
+ expect(parseSourcePositionString('src/App.tsx:10')).toBeNull();
+ expect(parseSourcePositionString('src/App.tsx:10:5:extra')).toBeNull();
+ });
+
+ it('应该返回null当行号或列号不是数字时', () => {
+ expect(parseSourcePositionString('src/App.tsx:abc:5')).toBeNull();
+ expect(parseSourcePositionString('src/App.tsx:10:def')).toBeNull();
+ expect(parseSourcePositionString('src/App.tsx:abc:def')).toBeNull();
+ });
+
+ it('应该处理行号和列号为0的情况', () => {
+ const result = parseSourcePositionString('test.tsx:0:0');
+
+ expect(result).not.toBeNull();
+ expect(result?.lineNumber).toBe(0);
+ expect(result?.columnNumber).toBe(0);
+ });
+
+ it('应该处理负数行号或列号', () => {
+ // 虽然负数在实际情况中不应该出现,但函数应该能处理
+ const result = parseSourcePositionString('test.tsx:-1:-1');
+
+ expect(result).not.toBeNull();
+ expect(result?.lineNumber).toBe(-1);
+ expect(result?.columnNumber).toBe(-1);
+ });
+ });
+});
+
diff --git a/qiming-vite-plugin-design-mode/tsconfig.json b/qiming-vite-plugin-design-mode/tsconfig.json
new file mode 100644
index 00000000..e03e14f7
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "lib": [
+ "ES2020",
+ "DOM"
+ ],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "jsx": "react-jsx",
+ "baseUrl": ".",
+ "paths": {
+ "@xagi/design-mode-shared": ["./packages/client-shared/src"],
+ "@xagi/design-mode-shared/*": ["./packages/client-shared/src/*"],
+ "@xagi/design-mode-client-react": ["./packages/client-react/src"],
+ "@xagi/design-mode-client-vue": ["./packages/client-vue/src"]
+ }
+ },
+ "exclude": [
+ "node_modules",
+ "**/dist",
+ "**/node_modules"
+ ]
+}
\ No newline at end of file
diff --git a/qiming-vite-plugin-design-mode/vitest.config.ts b/qiming-vite-plugin-design-mode/vitest.config.ts
new file mode 100644
index 00000000..afafdba3
--- /dev/null
+++ b/qiming-vite-plugin-design-mode/vitest.config.ts
@@ -0,0 +1,42 @@
+import { defineConfig } from 'vitest/config';
+import { resolve } from 'path';
+
+export default defineConfig({
+ test: {
+ // Test environment
+ environment: 'node',
+
+ // Test file glob patterns
+ include: ['test/**/*.test.ts', 'test/**/*.spec.ts'],
+
+ // Coverage
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json', 'html'],
+ exclude: [
+ 'node_modules/',
+ 'test/',
+ 'dist/',
+ 'examples/',
+ '**/*.d.ts',
+ '**/*.config.*',
+ ],
+ },
+
+ // Globals
+ globals: true,
+
+ // Timeout
+ testTimeout: 10000,
+
+ // Setup files
+ setupFiles: [],
+ },
+
+ resolve: {
+ alias: {
+ '@': resolve(__dirname, './src'),
+ },
+ },
+});
+
diff --git a/qiming-xagi-frontend-templates/.cursor/plans/---radix-ui----5c033265.plan.md b/qiming-xagi-frontend-templates/.cursor/plans/---radix-ui----5c033265.plan.md
new file mode 100644
index 00000000..82d6b763
--- /dev/null
+++ b/qiming-xagi-frontend-templates/.cursor/plans/---radix-ui----5c033265.plan.md
@@ -0,0 +1,251 @@
+
+# 添加 Radix UI 组件到模板
+
+## 第一阶段: react-next 模板
+
+### 1. 更新依赖包
+
+在 `packages/react-next/package.json` 中添加缺失的 Radix UI 依赖:
+
+- @radix-ui/react-accordion
+- @radix-ui/react-alert-dialog
+- @radix-ui/react-aspect-ratio
+- @radix-ui/react-avatar
+- @radix-ui/react-checkbox
+- @radix-ui/react-collapsible
+- @radix-ui/react-icons
+- @radix-ui/react-label
+- @radix-ui/react-menubar
+- @radix-ui/react-navigation-menu
+- @radix-ui/react-popover
+- @radix-ui/react-progress
+- @radix-ui/react-radio-group
+- @radix-ui/react-scroll-area
+- @radix-ui/react-select
+- @radix-ui/react-separator
+- @radix-ui/react-slider
+- @radix-ui/react-switch
+- @radix-ui/react-toggle
+- @radix-ui/react-toggle-group
+
+已有组件(保留):
+
+- @radix-ui/react-dialog ✓
+- @radix-ui/react-dropdown-menu ✓
+- @radix-ui/react-slot ✓
+- @radix-ui/react-tabs ✓
+- @radix-ui/react-toast ✓
+- @radix-ui/react-tooltip ✓
+
+### 2. 添加配置文件
+
+在 `packages/react-next/` 目录下添加:
+
+- **components.json**: shadcn/ui 配置文件,设置组件路径别名和样式
+- **biome.json**: Biome 代码检查配置(可选,补充 ESLint)
+
+### 3. 创建新的 UI 组件文件
+
+在 `packages/react-next/src/components/ui/` 目录下创建以下组件:
+
+**需要创建的组件**:
+
+- accordion.tsx
+- alert-dialog.tsx
+- aspect-ratio.tsx
+- avatar.tsx
+- checkbox.tsx
+- collapsible.tsx
+- label.tsx
+- menubar.tsx
+- navigation-menu.tsx
+- popover.tsx
+- progress.tsx
+- radio-group.tsx
+- scroll-area.tsx
+- select.tsx
+- separator.tsx
+- slider.tsx
+- switch.tsx
+- toggle.tsx
+- toggle-group.tsx
+
+**已有组件(重新创建以匹配新风格)**:
+
+- button.tsx (重新创建)
+- card.tsx (重新创建)
+- dialog.tsx (重新创建)
+- dropdown-menu.tsx (重新创建)
+- input.tsx (重新创建)
+- tabs.tsx (重新创建)
+- textarea.tsx (重新创建)
+- tooltip.tsx (重新创建)
+
+所有组件将使用:
+
+- TypeScript 类型定义
+- Radix UI 原语封装
+- Tailwind CSS 样式
+- class-variance-authority 进行样式变体管理
+- cn() 工具函数进行类名合并
+- forwardRef 支持 ref 传递
+- 详细的中文注释
+
+## 第二阶段: react-vite 模板
+
+### 1. 创建目录结构
+
+创建 `packages/react-vite/src/components/ui/` 目录
+
+### 2. 添加配置文件
+
+在 `packages/react-vite/` 目录下添加:
+
+- **components.json**: shadcn/ui 配置文件,设置组件路径别名和样式
+- **biome.json**: Biome 代码检查配置(可选,补充 ESLint)
+
+### 3. 更新依赖包
+
+在 `packages/react-vite/package.json` 中添加所有 Radix UI 依赖(与参考 package.json 相同的完整列表)
+
+### 4. 复制所有 UI 组件
+
+将 react-next 模板中创建的所有 UI 组件文件复制到 react-vite 模板的 `src/components/ui/` 目录
+
+## 实施细节
+
+### 组件实现标准
+
+每个组件文件包含:
+
+1. 导入必要的 React 和 Radix UI 依赖
+2. 导入 cn 工具函数
+3. TypeScript 接口定义
+4. forwardRef 包装的组件实现
+5. 使用 Tailwind CSS 类进行样式化
+6. displayName 设置
+7. 导出所有子组件和类型
+
+### 示例组件结构
+
+```typescript
+import * as React from "react"
+import * as PrimitiveName from "@radix-ui/react-primitive-name"
+import { cn } from "@/lib/utils"
+
+const Component = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Component.displayName = PrimitiveName.Root.displayName
+
+export { Component }
+```
+
+### 依赖版本
+
+使用参考 package.json 中的版本号:
+
+- @radix-ui/react-* 使用最新稳定版本
+- 确保与 React 18 兼容
+
+## 第三阶段: Form 表单方案集成
+
+### 1. 添加 Form 相关依赖
+
+在两个模板的 package.json 中添加:
+
+- react-hook-form (^7.56.1)
+- @hookform/resolvers (^5.2.2)
+- zod (^3.24.3)
+
+### 2. 创建 Form 组件
+
+在 `src/components/ui/` 目录创建:
+
+- **form.tsx**: 基于 React Hook Form 的表单组件
+- **label.tsx**: 表单标签组件(已在 Radix UI 组件列表中)
+
+Form 组件包含:
+
+- Form
+- FormField
+- FormItem
+- FormLabel
+- FormControl
+- FormDescription
+- FormMessage
+
+### 3. 创建示例表单
+
+在两个模板中创建表单使用示例,展示:
+
+- 表单验证(使用 Zod)
+- 错误处理
+- 提交处理
+- 与 Radix UI 组件集成(Input, Select, Checkbox 等)
+
+## 第四阶段: 更新文档
+
+### 1. 更新 AGENTS.md
+
+在两个模板的 AGENTS.md 中添加:
+
+- Form 表单开发指南
+- 表单验证最佳实践
+- 完整的 Radix UI 组件列表
+- 表单组件使用示例
+
+### 2. 更新 CLAUDE.md
+
+在两个模板的 CLAUDE.md 中添加:
+
+- Form 组件生成提示词模板
+- 表单验证模式示例
+- React Hook Form + Zod 集成模式
+- 常见表单场景的代码示例
+
+### 3. 文档更新内容
+
+**AGENTS.md 新增章节**:
+
+```markdown
+### Form Development
+- React Hook Form for form state management
+- Zod for schema validation
+- Integration with Radix UI components
+- Error handling and display patterns
+
+### Available Components
+- All 27 Radix UI components (完整列表)
+- Form components (Form, FormField, FormItem, etc.)
+- Custom input components with validation
+```
+
+**CLAUDE.md 新增章节**:
+
+```markdown
+#### For Form Development
+Claude, create a form using React Hook Form and Zod:
+1. Define Zod schema for validation
+2. Use Form components from src/components/ui/form.tsx
+3. Integrate with Radix UI input components
+4. Implement proper error handling
+5. Add submit handler with loading states
+```
+
+### To-dos
+
+- [ ] 更新 react-next/package.json 添加所有缺失的 Radix UI 依赖包
+- [ ] 在 react-next/src/components/ui/ 创建所有新的 Radix UI 组件文件(19个新组件)
+- [ ] 重新创建 react-next 中已有的8个组件以匹配新风格
+- [ ] 为 react-vite 创建 src/components/ui/ 目录结构
+- [ ] 更新 react-vite/package.json 添加所有 Radix UI 依赖包
+- [ ] 将所有 UI 组件从 react-next 复制到 react-vite 模板
+- [ ] 运行 pnpm install 安装新添加的依赖包
\ No newline at end of file
diff --git a/qiming-xagi-frontend-templates/.env.example b/qiming-xagi-frontend-templates/.env.example
new file mode 100644
index 00000000..945ed8b4
--- /dev/null
+++ b/qiming-xagi-frontend-templates/.env.example
@@ -0,0 +1,5 @@
+# 远程服务器配置
+REMOTE_HOST="your-remote-host"
+REMOTE_USER="your-remote-user"
+REMOTE_PASSWORD="your-remote-password"
+REMOTE_PATH="/home/your-remote-user/nuwax/docker/config/rcoder/template"
diff --git a/qiming-xagi-frontend-templates/.github/workflows/release.yml b/qiming-xagi-frontend-templates/.github/workflows/release.yml
new file mode 100644
index 00000000..c43bafde
--- /dev/null
+++ b/qiming-xagi-frontend-templates/.github/workflows/release.yml
@@ -0,0 +1,87 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+permissions:
+ contents: write
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: pnpm
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Extract version from tag
+ id: version
+ run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
+
+ - name: Pack templates (release mode)
+ run: |
+ node scripts/pack-template.js react-vite --release
+ node scripts/pack-template.js vue3-vite --release
+ node scripts/pack-template.js react-next --release
+
+ - name: List packed files
+ run: ls -lh zip/
+
+ - name: Generate release notes
+ id: notes
+ run: |
+ VERSION=${{ steps.version.outputs.VERSION }}
+ TAG=${GITHUB_REF_NAME}
+ NOTES="## ${TAG}"$'\n\n'
+ NOTES+="### 📦 Download URLs"$'\n\n'
+ NOTES+="| Template | Version | Download |"$'\n'
+ NOTES+="|----------|---------|----------|"$'\n'
+ NOTES+="| React + Vite | ${VERSION} | \`react-vite-template_${VERSION}.zip\` |"$'\n'
+ NOTES+="| Vue 3 + Vite | ${VERSION} | \`vue3-vite-template_${VERSION}.zip\` |"$'\n'
+ NOTES+="| React + Next.js | ${VERSION} | \`react-next-template_${VERSION}.zip\` |"$'\n\n'
+ NOTES+="### 🔗 快速下载"$'\n\n'
+ NOTES+="\`\`\`bash"$'\n'
+ NOTES+="# 指定版本"$'\n'
+ NOTES+="curl -sLO https://github.com/nuwax-ai/xagi-frontend-templates/releases/download/${TAG}/react-vite-template_${VERSION}.zip"$'\n\n'
+ NOTES+="# 获取最新版本"$'\n'
+ NOTES+="curl -sL https://raw.githubusercontent.com/nuwax-ai/xagi-frontend-templates/main/latest.json | jq -r '.templates[\"react-vite\"].url' | xargs curl -sLO"$'\n'
+ NOTES+='\`\`\'
+ echo "NOTES<> "$GITHUB_OUTPUT"
+ echo "$NOTES" >> "$GITHUB_OUTPUT"
+ echo "EOFMARKER" >> "$GITHUB_OUTPUT"
+
+ - name: Create GitHub Release
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh release create ${GITHUB_REF_NAME} \
+ zip/*.zip \
+ --title "${GITHUB_REF_NAME}" \
+ --notes "${{ steps.notes.outputs.NOTES }}"
+
+ - name: Generate latest.json
+ run: node scripts/generate-latest-json.js ${{ steps.version.outputs.VERSION }}
+
+ - name: Commit latest.json back to main
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git checkout main
+ git add latest.json
+ git diff --cached --quiet || git commit -m "chore: update latest.json for ${GITHUB_REF_NAME}"
+ git push origin main
diff --git a/qiming-xagi-frontend-templates/.gitignore b/qiming-xagi-frontend-templates/.gitignore
new file mode 100644
index 00000000..00f94e80
--- /dev/null
+++ b/qiming-xagi-frontend-templates/.gitignore
@@ -0,0 +1,37 @@
+# 依赖文件
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# 构建输出
+dist/
+build/
+
+# 环境变量文件
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# IDE 文件
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# 操作系统文件
+.DS_Store
+Thumbs.db
+
+# 日志文件
+*.log
+
+# 临时文件
+*.tmp
+*.temp
+
+zip/*
+
+.claude/*
diff --git a/qiming-xagi-frontend-templates/.npmrc b/qiming-xagi-frontend-templates/.npmrc
new file mode 100644
index 00000000..23837176
--- /dev/null
+++ b/qiming-xagi-frontend-templates/.npmrc
@@ -0,0 +1,24 @@
+# pnpm 配置文件 - 优化国内网络访问和包管理体验
+
+# 镜像源配置
+registry=https://registry.npmmirror.com
+@types:registry=https://registry.npmmirror.com
+@typescript-eslint:registry=https://registry.npmmirror.com
+@next:registry=https://registry.npmmirror.com
+@vercel:registry=https://registry.npmmirror.com
+@vue:registry=https://registry.npmmirror.com
+@vitejs:registry=https://registry.npmmirror.com
+
+# pnpm 特定配置
+auto-install-peers=true
+strict-peer-dependencies=false
+save-exact=true
+prefer-offline=true
+progress=false
+
+# 构建优化
+ignore-build-scripts=true
+
+# 工作区配置
+save-workspace-protocol=true
+package-import-method=auto
\ No newline at end of file
diff --git a/qiming-xagi-frontend-templates/.prettierrc b/qiming-xagi-frontend-templates/.prettierrc
new file mode 100644
index 00000000..42669a3b
--- /dev/null
+++ b/qiming-xagi-frontend-templates/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "printWidth": 100,
+ "bracketSpacing": true,
+ "arrowParens": "avoid",
+ "endOfLine": "lf"
+}
\ No newline at end of file
diff --git a/qiming-xagi-frontend-templates/INTEGRATION_SPEC.md b/qiming-xagi-frontend-templates/INTEGRATION_SPEC.md
new file mode 100644
index 00000000..50e2af79
--- /dev/null
+++ b/qiming-xagi-frontend-templates/INTEGRATION_SPEC.md
@@ -0,0 +1,1042 @@
+# Nuwax 应用开发平台模板接入规范
+
+> **版本**: 1.0.0
+> **更新日期**: 2026-04-24
+> **适用模板**: `react-vite`、`vue3-vite`
+
+---
+
+## 目录
+
+- [1. 概述](#1-概述)
+- [2. 环境要求](#2-环境要求)
+- [3. 模板目录结构](#3-模板目录结构)
+- [4. 配置文件规范](#4-配置文件规范)
+- [5. 样式规范 TailwindCSS](#5-样式规范-tailwindcss)
+- [6. 路由规范 Hash 模式](#6-路由规范-hash-模式)
+- [7. 核心模块约定](#7-核心模块约定)
+- [8. UI 组件库](#8-ui-组件库)
+- [9. 构建与质量门禁](#9-构建与质量门禁)
+- [10. 交付物格式](#10-交付物格式)
+- [11. 禁止事项](#11-禁止事项)
+- [12. React / Vue 对照表](#12-react-vue-对照表)
+- [13. 验收 Checklist](#13-验收-checklist)
+
+---
+
+## 1. 概述
+
+本文档定义了接入 Nuwax 应用开发平台的前端模板必须遵循的结构、配置和代码规范。
+
+接入方需按照本规范交付符合平台标准的模板项目,以确保:
+
+- 模板可在 Nuwax 应用开发平台上正确运行(Hash 路由、静态托管兼容)
+- 构建产物符合平台部署要求
+- 开发体验与平台工具链一致
+
+**本规范中每一项均标注**:
+
+| 标记 | 含义 |
+|------|------|
+| **[必要]** | 不满足则无法通过验收,模板不可接入平台 |
+| **[可选]** | 推荐采用,但可由接入方根据业务需求自行决定 |
+
+---
+
+## 2. 环境要求
+
+**[必要]** 以下为模板开发和构建的最低环境要求:
+
+| 工具 | 最低版本 |
+|------|----------|
+| Node.js | >= 18.0.0 |
+| pnpm | >= 8.0.0 |
+
+---
+
+## 3. 模板目录结构
+
+### 3.1 React + Vite 模板 **[必要]**
+
+```
+/
+├── index.html # [必要] 入口 HTML
+├── package.json # [必要] 包配置
+├── meta.json # [必要] 模板元信息
+├── vite.config.ts # [必要] Vite 构建配置
+├── tsconfig.json # [必要] TypeScript 配置
+├── tsconfig.node.json # [必要] Node 环境 TS 配置
+├── tailwind.config.js # [必要] TailwindCSS 配置
+├── postcss.config.js # [必要] PostCSS 配置
+├── components.json # [可选] shadcn/ui 组件配置
+├── AGENTS.md # [可选] AI Agent 规则文件
+├── CLAUDE.md # [可选] AI Agent 规则文件(与 AGENTS.md 保持一致)
+├── .prettierrc # [可选] Prettier 格式化配置
+├── .eslintrc.json # [可选] ESLint 配置
+├── .editorconfig # [可选] 编辑器配置
+└── src/
+ ├── main.tsx # [必要] 应用入口
+ ├── App.tsx # [必要] 根组件(RouterProvider)
+ ├── index.css # [必要] 全局样式(含 Tailwind 指令)
+ ├── vite-env.d.ts # [必要] Vite 类型声明
+ ├── pages/ # [必要] 页面组件目录
+ │ ├── Home.tsx # [必要] 首页
+ │ └── NotFound.tsx # [必要] 404 页面
+ ├── components/ # [必要] 组件目录
+ │ └── ui/ # [可选] UI 基础组件(shadcn/ui)
+ ├── lib/ # [必要] 工具库目录
+ │ ├── api.ts # [可选] HTTP 客户端
+ │ ├── services.ts # [可选] 数据服务层
+ │ └── utils.ts # [必要] 工具函数(至少含 cn())
+ ├── router/ # [必要] 路由配置目录
+ │ └── index.tsx # [必要] 路由注册
+ └── examples/ # [可选] 示例页面
+```
+
+### 3.2 Vue 3 + Vite 模板 **[必要]**
+
+```
+/
+├── index.html # [必要] 入口 HTML
+├── package.json # [必要] 包配置
+├── meta.json # [必要] 模板元信息
+├── vite.config.ts # [必要] Vite 构建配置
+├── tsconfig.json # [必要] TypeScript 配置
+├── tsconfig.node.json # [必要] Node 环境 TS 配置
+├── tailwind.config.js # [必要] TailwindCSS 配置
+├── postcss.config.js # [必要] PostCSS 配置
+├── components.json # [可选] shadcn-vue 组件配置
+├── AGENTS.md # [可选] AI Agent 规则文件
+├── CLAUDE.md # [可选] AI Agent 规则文件(与 AGENTS.md 保持一致)
+├── .prettierrc # [可选] Prettier 格式化配置
+├── .eslintrc.cjs # [可选] ESLint 配置
+├── .editorconfig # [可选] 编辑器配置
+└── src/
+ ├── main.ts # [必要] 应用入口
+ ├── App.vue # [必要] 根组件()
+ ├── style.css # [必要] 全局样式(含 Tailwind 指令)
+ ├── pages/ # [必要] 页面组件目录
+ │ ├── Home.vue # [必要] 首页
+ │ └── NotFound.vue # [必要] 404 页面
+ ├── components/ # [必要] 组件目录
+ │ └── ui/ # [可选] UI 基础组件(shadcn-vue)
+ ├── lib/ # [必要] 工具库目录
+ │ ├── api.ts # [可选] HTTP 客户端
+ │ ├── services.ts # [可选] 数据服务层
+ │ └── utils.ts # [必要] 工具函数(至少含 cn())
+ ├── router/ # [必要] 路由配置目录
+ │ └── index.ts # [必要] 路由注册
+ └── examples/ # [可选] 示例页面
+```
+
+---
+
+## 4. 配置文件规范
+
+### 4.1 `package.json` **[必要]**
+
+以下字段和 scripts 必须存在:
+
+```jsonc
+{
+ "name": "@xagi-templates/", // [必要] 遵循 @xagi-templates 命名空间(可自定义 name,但须保持 scoped 格式)
+ "version": "", // [必要] 语义化版本,与 meta.json 保持一致
+ "private": true, // [必要] 标记为私有包
+ "type": "module", // [必要] ESM 模块系统
+ "scripts": {
+ "dev": "vite", // [必要] 启动开发服务器
+ "build": "tsc && vite build", // [必要] 生产构建(Vue: "vite build")
+ "type-check": "tsc --noEmit", // [必要] 类型检查(Vue: "vue-tsc --noEmit")
+ "lint": "eslint src ...", // [必要] 代码规范检查
+ "check": "pnpm run type-check && pnpm run lint" // [必要] 组合质量门禁
+ },
+ "engines": {
+ "node": ">=18.0.0" // [必要] Node 版本约束
+ }
+}
+```
+
+**[可选]** 推荐包含的 scripts:
+
+```jsonc
+{
+ "scripts": {
+ "build:production": "NODE_ENV=production vite build",
+ "preview": "vite preview",
+ "lint:fix": "eslint src --fix",
+ "clean": "rm -rf dist",
+ "format": "prettier --write .",
+ "format:check": "prettier --check ."
+ }
+}
+```
+
+### 4.2 `vite.config.ts` **[必要]**
+
+**[必要]** 必须配置 `@` 路径别名指向 `./src`:
+
+```ts
+// react-vite
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import path from 'path'
+
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
+})
+```
+
+```ts
+// vue3-vite
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import path from 'path'
+
+export default defineConfig({
+ plugins: [vue()],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
+})
+```
+
+### 4.3 `tsconfig.json` **[必要]**
+
+**[必要]** 以下配置项必须包含:
+
+```jsonc
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true, // [必要] 严格模式
+ "noUnusedLocals": true, // [必要] 禁止未使用的局部变量
+ "noUnusedParameters": true, // [必要] 禁止未使用的参数
+ "noFallthroughCasesInSwitch": true, // [必要] switch 禁止贯穿
+ "skipLibCheck": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "allowImportingTsExtensions": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"] // [必要] 与 vite.config.ts 别名一致
+ }
+ }
+}
+```
+
+React 模板额外需要:**[必要]**
+
+```jsonc
+{
+ "compilerOptions": {
+ "jsx": "react-jsx"
+ }
+}
+```
+
+Vue 模板额外需要:**[必要]**
+
+```jsonc
+{
+ "compilerOptions": {
+ "jsx": "preserve"
+ },
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
+}
+```
+
+### 4.4 `meta.json` **[必要]**
+
+模板元信息,平台据此识别模板类型:
+
+```jsonc
+{
+ "name": "React + Vite", // 或 "Vue 3 + Vite"
+ "description": "模板描述",
+ "version": "", // [必要] 与 package.json 版本保持一致
+ "author": "",
+ "framework": "react", // [必要] "react" 或 "vue"
+ "bundler": "vite", // [必要] 固定为 "vite"
+ "language": "typescript", // [必要] 固定为 "typescript"
+ "category": "frontend"
+}
+```
+
+### 4.5 `index.html` **[必要]**
+
+React 模板:
+
+```html
+
+
+
+
+
+ React Vite App
+
+
+
+
+
+
+```
+
+Vue 模板:
+
+```html
+
+
+
+
+
+ Vue3 Vite App
+
+
+
+
+
+
+```
+
+**[必要]** 关键要求:
+- React 挂载点 `id="root"`,Vue 挂载点 `id="app"`
+- `
+