Files
datalive-design/改进数据结构合计.md

4767 lines
207 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 改进数据结构设计
**Session ID:** ses_225b130dcffeK0m8Ys1edhMomF
**Created:** 2026/4/30 01:34:51
**Updated:** 2026/4/30 10:19:51
---
## User
[search-mode]
MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
- explore agents (codebase patterns, file structures, ast-grep)
- librarian agents (remote repos, official docs, GitHub examples)
Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
NEVER stop at first result - be exhaustive.
[analyze-mode]
ANALYSIS MODE. Gather context before diving deep:
CONTEXT GATHERING (parallel):
- 1-2 explore agents (codebase patterns, implementations)
- 1-2 librarian agents (if external library involved)
- Direct tools: Grep, AST-grep, LSP for targeted searches
IF COMPLEX - DO NOT STRUGGLE ALONE. Consult specialists:
- **Oracle**: Conventional problems (architecture, debugging, complex logic)
- **Artistry**: Non-conventional problems (different approach needed)
SYNTHESIZE findings before proceeding.
---
MANDATORY delegate_task params: ALWAYS include load_skills=[] and run_in_background when calling delegate_task.
Example: delegate_task(subagent_type="explore", prompt="...", run_in_background=true, load_skills=[])
---
Create or update `AGENTS.md` for this repository.
The goal is a compact instruction file that helps future OpenCode sessions avoid mistakes and ramp up quickly. Every line should answer: "Would an agent likely miss this without help?" If not, leave it out.
User-provided focus or constraints (honor these):
## How to investigate
Read the highest-value sources first:
- `README*`, root manifests, workspace config, lockfiles
- build, test, lint, formatter, typecheck, and codegen config
- CI workflows and pre-commit / task runner config
- existing instruction files (`AGENTS.md`, `CLAUDE.md`, `.cursor/rules/`, `.cursorrules`, `.github/copilot-instructions.md`)
- repo-local OpenCode config such as `opencode.json`
If architecture is still unclear after reading config and docs, inspect a small number of representative code files to find the real entrypoints, package boundaries, and execution flow. Prefer reading the files that explain how the system is wired together over random leaf files.
Prefer executable sources of truth over prose. If docs conflict with config or scripts, trust the executable source and only keep what you can verify.
## What to extract
Look for the highest-signal facts for an agent working in this repo:
- exact developer commands, especially non-obvious ones
- how to run a single test, a single package, or a focused verification step
- required command order when it matters, such as `lint -> typecheck -> test`
- monorepo or multi-package boundaries, ownership of major directories, and the real app/library entrypoints
- framework or toolchain quirks: generated code, migrations, codegen, build artifacts, special env loading, dev servers, infra deploy flow
- repo-specific style or workflow conventions that differ from defaults
- testing quirks: fixtures, integration test prerequisites, snapshot workflows, required services, flaky or expensive suites
- important constraints from existing instruction files worth preserving
Good `AGENTS.md` content is usually hard-earned context that took reading multiple files to infer.
## Questions
Only ask the user questions if the repo cannot answer something important. Use the `question` tool for one short batch at most.
Good questions:
- undocumented team conventions
- branch / PR / release expectations
- missing setup or test prerequisites that are known but not written down
Do not ask about anything the repo already makes clear.
## Writing rules
Include only high-signal, repo-specific guidance such as:
- exact commands and shortcuts the agent would otherwise guess wrong
- architecture notes that are not obvious from filenames
- conventions that differ from language or framework defaults
- setup requirements, environment quirks, and operational gotchas
- references to existing instruction sources that matter
Exclude:
- generic software advice
- long tutorials or exhaustive file trees
- obvious language conventions
- speculative claims or anything you could not verify
- content better stored in another file referenced via `opencode.json` `instructions`
When in doubt, omit.
Prefer short sections and bullets. If the repo is simple, keep the file simple. If the repo is large, summarize the few structural facts that actually change how an agent should work.
If `AGENTS.md` already exists at `D:\Projects\Private\datalive-design`, improve it in place rather than rewriting blindly. Preserve verified useful guidance, delete fluff or stale claims, and reconcile it with the current codebase.
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 8.3s)
_Thinking:_
I'm starting by exploring the repository structure to get a sense of what we're working with.
**Tool: bash**
**Input:**
```json
{
"command": "ls -la",
"description": "List root directory contents",
"workdir": "D:\\Projects\\Private\\datalive-design"
}
```
**Output:**
```
Get-ChildItem: A parameter cannot be found that matches parameter name 'la'.
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 6.5s)
**Tool: bash**
**Input:**
```json
{
"command": "Get-ChildItem -Force | Select-Object Mode, LastWriteTime, Length, Name",
"description": "List root directory contents",
"workdir": "D:\\Projects\\Private\\datalive-design"
}
```
**Output:**
```
Mode  LastWriteTime  Length Name
----  -------------  ------ ----
d--h- 2026/4/30 1:32:12 .git
d---- 2026/4/9 21:21:45 docs
d---- 2025/12/18 1:45:26 node_modules
d---- 2026/4/14 14:19:15 src
-a--- 2025/12/18 1:45:26 388 .gitignore
-a--- 2026/4/14 14:15:06 5517 AGENTS.md
-a--- 2025/12/18 1:45:26 1207 bun.lock
-a--- 2026/4/2 0:10:14 206 package.json
-a--- 2025/12/18 1:45:26 230 README.md
-a--- 2025/12/18 1:45:26 713 tsconfig.json
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 7.1s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\AGENTS.md"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\AGENTS.md</path>
<type>file</type>
<content>
1: # DatAlive 低代码设计器数据结构设计
2:
3: ## 项目介绍
4:
5: 本项目的实现目标是设计一个尽可能通用的低代码设计器平台的核心数据结构,该平台能够支持以拖拽和配置组件、路由、状态、事件、过滤器、页面等实体元素,以可视化的交互方式搭建数字大屏、中后台系统以及产品官网等多类应用。
6:
7: 其中,当前阶段的首要目标场景是**数字大屏**。围绕该场景,平台的核心 `Schema` 结构已经进行了更深入的分析、约束与推演;而对于中后台系统与产品官网场景,现阶段主要完成的是字段层面的前瞻性预留,以保证整体结构具备后续扩展空间,但尚未进行同等深度的专项分析与完备性验证。
8:
9: 所谓“核心数据结构”,主要是在平台中设计应用或大屏系统时所依赖的一套统一配置蓝图。
10:
11: 这套蓝图既需要能够描述设计时配置,也需要能够描述运行时配置,其中:
12:
13: - 设计时配置,指用户在设计器中拖拽、配置组件、路由、状态、事件、过滤器、页面等元素时,需要被持久化保存的配置信息。
14: - 运行时配置,指用户完成设计后,在最终应用运行时需要被加载和消费的配置信息。
15:
16: 在本项目中,`Schema` 的角色应被明确定位为:**整个应用配置的持久化表现形式与统一交换格式**。
17:
18: 用户保存到数据库的是这份 `Schema`,导出到本地的是这份 `Schema`,从外部导入到平台中的也是这份 `Schema`。但平台在运行过程中,**不会直接以 `Schema` 作为工作内存来驱动各项能力**。相反,平台会在导入 `Schema` 后,将其解析、校验、规范化,并拆分到与各引擎职责对应的内部状态 `Store` 中;而在保存、导出或发布时,再将各引擎 `Store` 中的稳定数据重新聚合为 `Schema`,用于持久化或输出。
19:
20: 因此可以认为:
21:
22: - `Schema` 面向持久化、导入导出与跨边界交换;
23: - `Store` 面向平台运行时、引擎协作与高频状态操作。
24:
25: 此外,平台在整体运行语义上需要明确区分 `设计态(Design Mode`、`预览态(Preview Mode` 与 `运行态(Runtime Mode)` 三种模式。三态的切换应被视为平台的**运行时上下文(AppMode** 差异,而不是 `Schema` 本身的结构差异。换言之,`AppMode` 不属于需要持久化存储的 `Schema` 蓝图,而应由外部宿主容器或平台运行时环境显式注入。
26:
27: 其中:
28:
29: - `设计态` 用于可视化编辑、拖拽编排与配置应用结构;
30: - `运行态` 用于面向最终用户提供纯净、完整的应用运行体验;
31: - `预览态` 则用于在不进入正式运行环境的前提下,以尽可能接近真实运行态的方式对应用进行预览、验证与调试。
32:
33: `预览态` 既不同于可任意编辑配置的 `设计态`,也不同于面向最终用户的纯净 `运行态`,而是一种介于两者之间的**受控运行模式**。在该模式下,平台应尽可能复用与 `运行态` 相同的 `Schema` 渲染主链路,放行业务交互、数据请求、事件流转与页面跳转,使用户或实施人员能够提前验证应用在真实运行中的行为表现。
34:
35: 同时,`预览态` 还应具备必要的白盒调试与观测能力,用于辅助定位应用配置中的问题,例如:
36:
37: - 数据请求的发起、成功、失败与错误信息;
38: - 过滤器接收的输入参数是否正确,以及过滤结果是否异常;
39: - 事件流的触发顺序、条件命中情况、动作执行过程与中断原因;
40: - 表达式求值、生命周期触发、关键动作执行等运行日志;
41: - 将错误、警告与日志尽可能关联到具体的组件、事件、过滤器、数据源或动作节点。
42:
43: 因此,`预览态` 需要被纳入平台整体架构设计中,但其影响应主要体现在运行时上下文、引擎挂载策略、错误处理策略以及内部 `Store` 的组织方式上,而不应膨胀为一套独立的 `Schema` 结构维度。
44:
45: `Schema` 仍只负责承载跨模式稳定且需要被持久化的应用配置,而模式切换过程中产生的会话状态、派生状态、调试状态、缓存状态与临时控制状态,应由运行时 `Store` 负责管理。
46:
47: 在完成核心 `Schema` 结构设计的里程碑阶段后,项目下一阶段的重点将转向整个平台的技术路线与实现方案设计。
48:
49: 这意味着,后续工作不再仅仅聚焦于“应用配置如何被描述”,而是要进一步回答:平台应如何基于既定 `Schema` 组织内部状态 `Store`、构建运行时上下文、拆分核心模块,并驱动各个引擎协同工作。
50:
51: 其中,一个关键目标是逐步明确并落地平台的五大核心引擎,包括:
52:
53: - 工作区引擎
54: - 画布引擎
55: - 渲染引擎
56: - 数据引擎
57: - 交互引擎
58:
59: 现阶段的实现目标,是在既定 `Schema` 蓝图的基础上,进一步规划整个平台的技术架构、状态模型与引擎协作机制,为后续设计器与运行时系统的工程化实现提供稳定依据。
60:
61: 在技术栈方面,本项目希望积极拥抱 `React` + `Vite` 的最新生态体系,包括但不限于 `React`、`React Router` 或 `Tanstack Router`、`Tanstack Query`、`Zustand` 等。
62:
63: ## 项目规则
64:
65: ### 协作规则
66:
67: 1. 严禁直接修改项目代码,仅展示你的方案,由我来手动修改代码。
(End of file - total 67 lines)
</content>
[Project README: D:\Projects\Private\datalive-design\README.md]
# datalive-design
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.3.4. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
[Category+Skill Reminder]
**Built-in**: playwright, frontend-ui-ux, git-master, dev-browser, review-work, ai-slop-remover
**⚡ YOUR SKILLS (PRIORITY)**: (none)
> User-installed skills OVERRIDE built-in defaults. ALWAYS prefer YOUR SKILLS when domain matches.
```typescript
task(category="visual-engineering", load_skills=["playwright"], run_in_background=true)
```
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.7s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\package.json"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\package.json</path>
<type>file</type>
<content>
1: {
2: "name": "datalive-design",
3: "module": "src/index.ts",
4: "type": "module",
5: "private": true,
6: "devDependencies": {
7: "@types/bun": "latest"
8: },
9: "peerDependencies": {
10: "typescript": "^5"
11: }
12: }
(End of file - total 12 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.3s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\tsconfig.json"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\tsconfig.json</path>
<type>file</type>
<content>
1: {
2: "compilerOptions": {
3: // Environment setup & latest features
4: "lib": ["ESNext"],
5: "target": "ESNext",
6: "module": "Preserve",
7: "moduleDetection": "force",
8: "jsx": "react-jsx",
9: "allowJs": true,
10:
11: // Bundler mode
12: "moduleResolution": "bundler",
13: "allowImportingTsExtensions": true,
14: "verbatimModuleSyntax": true,
15: "noEmit": true,
16:
17: // Best practices
18: "strict": true,
19: "skipLibCheck": true,
20: "noFallthroughCasesInSwitch": true,
21: "noUncheckedIndexedAccess": true,
22: "noImplicitOverride": true,
23:
24: // Some stricter flags (disabled by default)
25: "noUnusedLocals": false,
26: "noUnusedParameters": false,
27: "noPropertyAccessFromIndexSignature": false
28: }
29: }
(End of file - total 29 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 6.1s)
**Tool: bash**
**Input:**
```json
{
"command": "Get-ChildItem -Recurse src -Force | Select-Object FullName",
"description": "List src directory tree",
"workdir": "D:\\Projects\\Private\\datalive-design"
}
```
**Output:**
```
FullName
--------
D:\Projects\Private\datalive-design\src\schema
D:\Projects\Private\datalive-design\src\index.ts
D:\Projects\Private\datalive-design\src\schema\application
D:\Projects\Private\datalive-design\src\schema\component
D:\Projects\Private\datalive-design\src\schema\data-source
D:\Projects\Private\datalive-design\src\schema\filter
D:\Projects\Private\datalive-design\src\schema\interaction
D:\Projects\Private\datalive-design\src\schema\mutation
D:\Projects\Private\datalive-design\src\schema\page
D:\Projects\Private\datalive-design\src\schema\query
D:\Projects\Private\datalive-design\src\schema\route
D:\Projects\Private\datalive-design\src\schema\shared
D:\Projects\Private\datalive-design\src\schema\variable
D:\Projects\Private\datalive-design\src\schema\index.ts
D:\Projects\Private\datalive-design\src\schema\application\application.ts
D:\Projects\Private\datalive-design\src\schema\application\index.ts
D:\Projects\Private\datalive-design\src\schema\component\component-data.ts
D:\Projects\Private\datalive-design\src\schema\component\component-layout.ts
D:\Projects\Private\datalive-design\src\schema\component\component.ts
D:\Projects\Private\datalive-design\src\schema\component\index.ts
D:\Projects\Private\datalive-design\src\schema\data-source\data-source-by-database.ts
D:\Projects\Private\datalive-design\src\schema\data-source\data-source-by-rest.ts
D:\Projects\Private\datalive-design\src\schema\data-source\data-source.ts
D:\Projects\Private\datalive-design\src\schema\data-source\index.ts
D:\Projects\Private\datalive-design\src\schema\filter\filter.ts
D:\Projects\Private\datalive-design\src\schema\filter\index.ts
D:\Projects\Private\datalive-design\src\schema\interaction\action.ts
D:\Projects\Private\datalive-design\src\schema\interaction\index.ts
D:\Projects\Private\datalive-design\src\schema\interaction\interaction.ts
D:\Projects\Private\datalive-design\src\schema\mutation\index.ts
D:\Projects\Private\datalive-design\src\schema\mutation\mutation-config.ts
D:\Projects\Private\datalive-design\src\schema\mutation\mutation.ts
D:\Projects\Private\datalive-design\src\schema\page\index.ts
D:\Projects\Private\datalive-design\src\schema\page\page-layout.ts
D:\Projects\Private\datalive-design\src\schema\page\page.ts
D:\Projects\Private\datalive-design\src\schema\query\index.ts
D:\Projects\Private\datalive-design\src\schema\query\query-config.ts
D:\Projects\Private\datalive-design\src\schema\query\query.ts
D:\Projects\Private\datalive-design\src\schema\route\index.ts
D:\Projects\Private\datalive-design\src\schema\route\route.ts
D:\Projects\Private\datalive-design\src\schema\shared\condition.ts
D:\Projects\Private\datalive-design\src\schema\shared\css-properties.ts
D:\Projects\Private\datalive-design\src\schema\shared\dynamic-expression.ts
D:\Projects\Private\datalive-design\src\schema\shared\entity.ts
D:\Projects\Private\datalive-design\src\schema\shared\index.ts
D:\Projects\Private\datalive-design\src\schema\shared\json-value.ts
D:\Projects\Private\datalive-design\src\schema\shared\rest-request.ts
D:\Projects\Private\datalive-design\src\schema\variable\index.ts
D:\Projects\Private\datalive-design\src\schema\variable\variable.ts
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.1s)
**Tool: bash**
**Input:**
```json
{
"command": "Get-ChildItem -Recurse docs -Force | Select-Object FullName",
"description": "List docs directory contents",
"workdir": "D:\\Projects\\Private\\datalive-design"
}
```
**Output:**
```
FullName
--------
D:\Projects\Private\datalive-design\docs\̬.md
D:\Projects\Private\datalive-design\docs\ܹ淶.md
D:\Projects\Private\datalive-design\docs\ѡ淶.md
D:\Projects\Private\datalive-design\docs\ʵʵֹ淶.md
D:\Projects\Private\datalive-design\docs\Ӧģʽܹ淶.md
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 29.9s)
_Thinking:_
The filenames in the docs directory contain Chinese characters that got corrupted in the display, so I'll switch to using Glob to handle them properly.
**Tool: glob**
**Input:**
```json
{
"pattern": "docs/*.md"
}
```
**Output:**
```
Found 5 file(s)
D:\Projects\Private\datalive-design\docs\实体生命周期引擎实现规范.md
D:\Projects\Private\datalive-design\docs\设计器悬浮组件架构规范.md
D:\Projects\Private\datalive-design\docs\设计器静态依赖分析方案.md
D:\Projects\Private\datalive-design\docs\应用运行模式架构规范.md
D:\Projects\Private\datalive-design\docs\设计器选中与分组交互规范.md
[Agent Usage Reminder]
You called a search/fetch tool directly without leveraging specialized agents.
RECOMMENDED: Use task with explore/librarian agents for better results:
```
// Parallel exploration - fire multiple agents simultaneously
task(subagent_type="explore", load_skills=[], prompt="Find all files matching pattern X")
task(subagent_type="explore", load_skills=[], prompt="Search for implementation of Y")
task(subagent_type="librarian", load_skills=[], prompt="Lookup documentation for Z")
// Then continue your work while they run in background
// System will notify you when each completes
```
WHY:
- Agents can perform deeper, more thorough searches
- Background tasks run in parallel, saving time
- Specialized agents have domain expertise
- Reduces context window usage in main session
ALWAYS prefer: Multiple parallel task calls > Direct tool calls
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.5s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\index.ts</path>
<type>file</type>
<content>
(End of file - total 0 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.4s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\index.ts</path>
<type>file</type>
<content>
(End of file - total 0 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.8s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\application\\application.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\application\application.ts</path>
<type>file</type>
<content>
1: import type { DataSource } from '../data-source';
2: import type { Filter } from '../filter';
3: import type { Interaction } from '../interaction';
4: import type { Mutation } from '../mutation';
5: import type { Page } from '../page';
6: import type { Query } from '../query';
7: import type { Route } from '../route';
8: import type { Variable } from '../variable';
9:
10: export interface Application {
11: /**
12: * 应用的唯一全局标识符
13: */
14: id: string;
15: /**
16: * 应用的名称 (如:"智慧能源管理平台")
17: * 仅用于控制台展示或浏览器 title
18: */
19: title: string;
20: /**
21: * 创建时间
22: */
23: createdTime: string;
24: /**
25: * 更新时间
26: */
27: updatedTime: string;
28: /**
29: * 路由体系
30: *
31: * 决定了应用由哪些页面组成,以及页面间的跳转关系(嵌套路由/重定向)。
32: *
33: * 引擎需根据运行环境,动态采用 BrowserRouter 或 MemoryRouter 进行接管。
34: */
35: routes: Route[];
36: /**
37: * 页面
38: *
39: * 包含了该应用下所有的画板实例。
40: *
41: * 每个 Page 决定了自身的排版模式(Free/Flex/Flow),并作为根节点挂载了一棵庞大的组件树。
42: */
43: pages: Page[];
44: /**
45: * 变量
46: *
47: * 强类型的状态容器。它是组件通信、属性动态绑定、控制流(Condition)的核心依赖。
48: *
49: * 引擎需将其映射为一个支持响应式订阅的 Store(如 Zustand),确保一处修改,全局重绘。
50: */
51: variables: Variable[];
52: /**
53: * 数据源
54: *
55: * 定义底层的通信协议(如 RESTful API 的 baseUrl 和公共鉴权 Headers,或数据库的连接配置)。
56: *
57: * 它是为上层的 Query/Mutation 提供网络 IO 能力的基础设施。
58: */
59: dataSources: DataSource[];
60: /**
61: * 查询
62: *
63: * 专用于向后端发起无副作用的数据获取请求(如获取列表、详情)。
64: *
65: * 引擎实现指引:
66: *
67: * 1. 它可以享受平台提供的高级状态机特性:如页面加载预热(prefetch)、定时轮询(polling)、自动缓存(cache)。
68: * 2. 它会被映射为类似 Tanstack Query 的 `useQuery` 实例。
69: * 3. 组件通过 `dataBinding` 机制,声明式地订阅它的结果(data, isLoading),实现数据驱动视图。
70: */
71: queries: Query[];
72: /**
73: * 变更
74: *
75: * 专用于修改服务端状态(如提交表单、删除数据、上传文件)。
76: *
77: * 引擎实现指引:
78: *
79: * 1. 坚决不能在页面加载时自动执行!它没有 polling 和 prefetch 配置。
80: * 2. 它会被映射为类似 Tanstack Query 的 `useMutation` 实例。
81: * 3. 它只能作为一种【命令式动作 (Imperative Action)】,在组件的交互事件 (Interactions) 中被手动调用。
82: * 4. 调用成功后,通常需要配合触发其他 Action (如显示 Toast、或刷新某个 Query)。
83: */
84: mutations: Mutation[];
85: /**
86: * 数据过滤器
87: *
88: * 独立的纯函数字符串集合。
89: *
90: * 专供组件在订阅 `Query` 后,进行局部的数据格式化、过滤或多源聚合操作。
91: *
92: * 确保了一份原始数据(Query)能被洗成多种形态供不同图表复用。
93: */
94: filters: Filter[];
95: /**
96: * 实体交互
97: *
98: * 挂载于应用最顶层的交互配置。
99: *
100: * 例如:监听 `onAppLaunch`(应用启动)事件,去触发某个全局鉴权 API,或初始化某个关键 Variable。
101: */
102: interactions: Interaction[];
103: }
(End of file - total 103 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.3s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\component\\component.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\component\component.ts</path>
<type>file</type>
<content>
1: import type {
2: DynamicExpression,
3: Condition,
4: CSSProperties,
5: Entity,
6: JsonValue,
7: } from '../shared';
8: import type { Interaction } from '../interaction';
9: import type { ComponentData } from './component-data';
10: import type { ComponentLayout } from './component-layout';
11:
12: export interface Component extends Entity {
13: /**
14: * 组件类型
15: *
16: * 组件类型用于标识组件的类型,例如 `input`、`button` 等,就是组件名。
17: */
18: type: string;
19: /**
20: * 组件来源类型
21: *
22: * 组件来源类型用于标识组件的来源,例如 `builtin` 表示内置组件,`remote` 表示远程组件。
23: */
24: sourceType: 'builtin' | 'remote';
25: children?: Component[];
26: /**
27: * 条件渲染
28: */
29: condition?: Condition;
30: layout: ComponentLayout;
31: /**
32: * 组件通用样式
33: *
34: * 承载跨组件通用的、标准的 CSS 视觉属性,仅作用于组件的最外层 DOM 容器。
35: * 必须为静态键值对,不支持动态表达式(DynamicExpression),以保证渲染性能并斩断“无意义的 React 渲染瀑布”。
36: * 任何基于数据流或业务状态的动态视觉变化,应通过 props 或事件触发的 Action 来实现。
37: *
38: * @example { backgroundColor: '#fff', borderRadius: 4, boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }
39: */
40: style: CSSProperties;
41: /**
42: * 组件私有属性
43: *
44: * 承载与具体组件类型(type)强绑定的业务语义、内部结构和私有状态。
45: * 支持动态表达式(DynamicExpression),允许属性值随数据流或环境变量动态变化。
46: * 如果组件没有任何自定义属性,该值必须为空对象 `{}`,以消除空值检查成本。
47: *
48: * @example { text: '提交', disabled: false, size: 'large', type: 'primary' }
49: */
50: props: Record<string, ComponentPropValue>;
51: /**
52: * 图层管理属性
53: *
54: * 仅影响编辑器中的交互行为和图层面板状态,不影响运行时渲染。
55: * e.g. 锁定、隐藏、选中策略
56: */
57: layer: {
58: locked: boolean; // 锁定后在画布中不可被选中或移动
59: hidden: boolean; // 在设计器中隐藏(辅助线/临时隐藏),但运行时可能可见
60: };
61: data: ComponentData;
62: interactions: Interaction[];
63: }
64:
65: export type ComponentPropValue = JsonValue | DynamicExpression | undefined;
(End of file - total 65 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.2s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\component\\component-data.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\component\component-data.ts</path>
<type>file</type>
<content>
1: import type { JsonValue } from '../shared';
2:
3: export interface ComponentDataByStatic {
4: type: 'static';
5: value: JsonValue;
6: }
7:
8: export interface ComponentDataByQuery {
9: type: 'query';
10: queryIds: string[];
11: filterIds: string[];
12: }
13:
14: export type ComponentData = ComponentDataByStatic | ComponentDataByQuery;
(End of file - total 14 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.4s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\component\\component-layout.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\component\component-layout.ts</path>
<type>file</type>
<content>
1: export type ComponentLayout =
2: | ComponentLayoutByFree
3: | ComponentLayoutByFlex
4: | ComponentLayoutByFlow;
5:
6: export interface ComponentLayoutByFree {
7: mode: 'free';
8: top: number;
9: left: number;
10: width: number;
11: height: number;
12: visibility: string; // 组件可见性
13: opacity: number; // 组件透明度
14: pointerEvents: string; // 事件穿透
15: // allowDrag: boolean; // 允许拖动
16: // allowDrop: boolean; // 允许放置
17: /**
18: * 空间形变数据 (支持大屏的高级 2D/3D 视觉需求)
19: */
20: transform?: {
21: rotateX?: number; // 绕X轴翻转 (3D)
22: rotateY?: number; // 绕Y轴翻转 (3D)
23: rotateZ?: number; // 平面旋转 (即原来的 rotation)
24: scaleX?: number; // X轴缩放
25: scaleY?: number; // Y轴缩放
26: };
27: }
28:
29: // TODO: 弹性布局配置,暂不实现
30: export interface ComponentLayoutByFlex {
31: mode: 'flex';
32: }
33:
34: // TODO: 流式布局配置,暂不实现
35: export interface ComponentLayoutByFlow {
36: mode: 'flow';
37: }
(End of file - total 37 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.2s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\page\\page.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\page\page.ts</path>
<type>file</type>
<content>
1: import type { Component } from '../component';
2: import type { CSSProperties, Entity } from '../shared';
3: import type { Interaction } from '../interaction';
4: import type { PageLayout } from './page-layout';
5:
6: /**
7: * 页面容器
8: *
9: * 【架构定位】:
10: *
11: * 它是低代码视图层(View Layer)的顶级画板。
12: *
13: * 负责统领全局的排版模式(Layout)、提供背景画板(Style)、并挂载整棵组件树(Components)。
14: */
15: export interface Page extends Entity {
16: /**
17: * 页面排版模式
18: *
19: * 决定了当前页面的渲染法则(大屏绝对定位 vs 中后台自适应)。
20: */
21: layout: PageLayout;
22: /**
23: * 页面级静态样式
24: *
25: * 核心架构指引:
26: *
27: * 坚决抵制“在画布底层垫一个巨大的图片/色块组件来充当背景”的反模式!
28: * 那会导致极其严重的误触(需要频繁锁定)、层叠上下文污染(Z-index 战争)和流式响应式坍塌。
29: *
30: * 必须由引擎将此处的 `style`(如 backgroundColor, backgroundImage, padding
31: * 原生地挂载到包裹整个页面的最外层 HTML `<div>` 容器上,实现真正的、零成本的背景渲染。
32: */
33: style: CSSProperties;
34: /**
35: * 组件树
36: *
37: * 核心架构指引:
38: *
39: * 1. 多根节点支持:必须是数组,允许用户在白板上自由平铺多个逻辑根节点。
40: * 引擎在底层会自动将 Page 实例映射为一个真实的 DOM 容器(如 `<div className="datalive-page">`)来包裹这棵树。
41: * 这避免了强制要求唯一 `rootComponent` 带来的无谓 DOM 嵌套(如强迫套一个 Group)和反直觉体验。
42: * 2. 嵌套结构:为了保持 Schema 作为“蓝图”的直观可读性,这里存储的是完整的嵌套组件树。
43: * 将组件打平为全局字典 (Normalization) 仅是引擎在运行时 Zustand Store 里的性能优化手段,
44: * 绝不应污染此处持久化的静态 Schema。
45: */
46: components: Component[];
47: /**
48: * 交互
49: *
50: * 页面级生命周期与事件
51: *
52: * 挂载于当前页面的高阶控制流枢纽。
53: *
54: * 引擎实现指引:
55: *
56: * 1. 常见的触发事件 (event)`onLoad` (页面加载完成), `onUnload` (离开页面), `onResize` (尺寸变化)。
57: * 2. 典型的业务场景:在 `onLoad` 时,派发 Action 去触发某些没有开启 prefetch 的懒加载 Query
58: * 或者初始化/重置某些局部的 Variables。
59: */
60: interactions: Interaction[];
61: }
(End of file - total 61 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.0s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\page\\page-layout.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\page\page-layout.ts</path>
<type>file</type>
<content>
1: /**
2: * 自由布局,用于数字大屏或看板页面
3: */
4: export interface FreeLayout {
5: mode: 'free';
6: width: number;
7: height: number;
8: /**
9: * 缩放模式
10: *
11: * - `fit`: 等比缩放,确保大屏内容完整显示
12: * - `fill`: 放弃长宽比,强行拉伸大屏填满容器
13: * - `flow`: 流式布局,宽度填满,纵向滚动
14: * - `none`: 无视屏幕尺寸,不进行缩放,以真实像素值进行渲染
15: */
16: scaleMode: 'fit' | 'fill' | 'flow' | 'none'; // 等比缩放/全屏拉伸/纵向滚动/保持比例
17: }
18:
19: /**
20: * 弹性布局,用于中后台页面
21: */
22: export interface FlexLayout {
23: mode: 'flex';
24: minWidth: number;
25: }
26:
27: /**
28: * 流式布局,用于门户/官网场景
29: */
30: export interface FlowLayout {
31: mode: 'flow';
32: maxWidth: number;
33: }
34:
35: export type PageLayout = FreeLayout | FlexLayout | FlowLayout;
(End of file - total 35 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.3s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\route\\route.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\route\route.ts</path>
<type>file</type>
<content>
1: import type { Entity } from '../shared';
2: import type { Interaction } from '../interaction';
3:
4: /**
5: * 路由
6: *
7: * 【架构定位】:
8: *
9: * 1. 它是连接 URL 与 Page(页面实例)的唯一桥梁。
10: * 2. 它是构建复杂应用(如中后台系统、多页官网)的核心导航骨架。
11: *
12: * 【引擎实现指引】
13: *
14: * 1. 隔离策略:
15: * - 设计模式 (design) 下,渲染引擎必须使用 `Memory Router`(或重写 `history.pushState`),
16: * 以防止用户在配置路由跳转时,导致整个设计器外壳发生灾难性的真刷新或沙盒逃逸。
17: * - 运行/预览模式 (runtime/preview) 下,引擎切换为原生的 `Browser Router`。
18: * 2. 嵌套挂载:
19: * 当遇到带有 `children` 的路由时,引擎会将其对应的 `pageId` 视为一个“父级布局骨架(Layout Skeleton)”。
20: * 引擎在该骨架中必须寻找一个特殊的内置组件(如 `type: 'Outlet'` 或 `RouterView`),
21: * 以作为子路由对应 `Page` 的挂载插槽。
22: */
23: export interface Route extends Entity {
24: /**
25: * 路由路径
26: *
27: * 定义了匹配当前路由的 URL 片段。
28: *
29: * 支持静态路径(如 '/home'、'/dashboard')和参数路径(如 '/user/:id')。
30: *
31: * 在根数组中,通常包含一个 '/' 作为默认首页。
32: */
33: path: string;
34: /**
35: * 路由重定向
36: *
37: * 如果配置了此字段,当 URL 匹配到当前 path 时,引擎不渲染组件,
38: * 而是立即执行 301/302 级别的重定向,跳转到目标 path。
39: *
40: * 常见于将根路径 '/' 重定向到具体的首页 '/home'。
41: */
42: redirect?: string;
43: /**
44: * 绑定的页面 ID
45: *
46: * 核心映射:当前路由命中时,引擎将去全局的 `Application.pages` 数组中
47: * 查找对应的 Page 实例,并渲染该 Page 携带的庞大组件树。
48: *
49: * 注:如果这是一个纯粹的重定向路由 (redirect存在) 或父级空壳路由,此项可为空。
50: */
51: pageId?: string;
52: /**
53: * 子路由
54: *
55: * 构建复杂应用(如带侧边栏、顶部导航的后台框架)的核心。
56: *
57: * 架构规约:
58: *
59: * 如果此数组不为空,则当前路由的 `pageId` 对应的 Page,不再是一个终端页面,
60: * 而是一个带有“插槽”的布局容器。它的内部组件树中必须包含一个 `type: 'Outlet'` 的组件,
61: * 用于动态渲染其子孙 Route 所绑定的 Page。
62: */
63: children?: Route[];
64: /**
65: * 交互
66: *
67: * 路由级生命周期与拦截器
68: *
69: * 在进入或离开该路由时触发的高阶控制流。
70: *
71: * 引擎实现指引:
72: * 1. 常用于“路由守卫 (Navigation Guards)”:例如在进入 '/admin' 前,
73: * 执行一个校验 Token 的 Action,若校验失败,则执行一个 Redirect Action 踢回登录页。
74: * 2. 常见事件如:`onEnter` (进入前), `onLeave` (离开前)。
75: *
76: * 如果当前路由无拦截逻辑,则必须为空数组 `[]`(而非 undefined)。
77: */
78: interactions: Interaction[];
79: }
(End of file - total 79 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.9s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\interaction\\interaction.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\interaction\interaction.ts</path>
<type>file</type>
<content>
1: import type { Entity } from '../shared';
2: import type { Action } from './action';
3:
4: /**
5: * 实体交互
6: *
7: * 泛化事件侦听器
8: *
9: * 【架构定位】:
10: *
11: * 低代码平台中负责响应任何“状态突变”的唯一入口。
12: * 它的名字虽叫 Interaction(人机交互),但在架构底层,它不仅监听物理鼠标键盘,
13: * 更监听系统级的数据流转。
14: *
15: * 【引擎实现指引】:
16: *
17: * 根据它所挂载的宿主实体不同,引擎应将其映射为不同的事件绑定:
18: * 1. 挂载于 Component:映射为 DOM 原生事件或 React 组件暴露的业务事件(如 `onClick`, `onScroll`, `onRowSelect`)。
19: * 2. 挂载于 Route/Page:映射为生命周期钩子(如 `onEnter`, `onLoad`)。
20: * 3. 挂载于 Query/Mutation/Variable:映射为数据流的状态回调(如 `onSuccess`, `onError`, `onChange`)。
21: */
22: export interface Interaction extends Entity {
23: /**
24: * 触发时机/事件名称 (Event Name)
25: *
26: * 引擎会根据此名称去注册对应的监听器。
27: */
28: event: string;
29: /**
30: * 触发频率控制 (Debounce & Throttle)
31: *
32: * 处理高频数据流(如 Input 搜索框的 `onChange` 联动触发后端 Query)的核心防御机制。
33: *
34: * 引擎实现指引:
35: *
36: * 如果 enabled 为 true,引擎在派发底层的 `actions` 数组前,
37: * 必须先经过 Lodash 或 es-toolkit 的 `debounce` 或 `throttle` 包装器。
38: */
39: control: {
40: enabled: boolean;
41: type: 'debounce' | 'throttle';
42: delay: number;
43: };
44: /**
45: * 原生事件修饰符 (Event Modifiers)
46: *
47: * 引擎实现指引:
48: *
49: * 仅当该 Interaction 是由原生 DOM 事件(如 onClick 冒泡或 Form 默认提交)触发时生效。
50: * 如果是 `Query.onSuccess` 这种纯逻辑事件,此配置应被忽略。
51: */
52: modifiers: {
53: preventDefault: boolean;
54: stopPropagation: boolean;
55: };
56: /**
57: * 动作执行序列 (Action Chain)
58: *
59: * 当事件触发(且通过频率控制与修饰符拦截)后,引擎需要按顺序串行(或并行)执行的命令集合。
60: *
61: * 必须为一个数组,允许一个按钮点击同时触发“保存数据”和“跳转页面”两个动作。
62: */
63: actions: Action[];
64: }
(End of file - total 64 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.5s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\interaction\\action.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\interaction\action.ts</path>
<type>file</type>
<content>
1: import type { Entity, Condition, DynamicExpression, JsonValue } from '../shared';
2:
3: /**
4: * 执行动作的目标实体
5: *
6: * 泛化 RPC 命令动作 (Generalized RPC Command Action)
7: *
8: * 【架构定位】:
9: *
10: * 这是整个交互体系的巅峰设计。
11: * 我们坚决拒绝将 Action 枚举化为 `ShowToast`, `Navigate`, `SetVariable` 等海量具体类型。
12: * 相反,我们采用极其抽象的“一切皆实体(Entity),一切皆方法(Method)”模型。
13: *
14: * 架构优势:
15: *
16: * 这种极致抽象保持了 Schema 结构的极简与统一。未来设计器新增几百种组件或系统能力时,
17: * Schema 结构一行代码都不用改;渲染引擎只需要一个通用的“方法派发器(Method Dispatcher)”即可支撑无限的扩展能力。
18: */
19: export interface Action extends Entity {
20: /**
21: * 命令目标 (Target Entity)
22: *
23: * 告诉引擎:这个命令是发给谁的?
24: *
25: * 目标类型举例:
26: * - `type: 'component'`: 调用画布上的组件(如让图表刷新,或动态修改其 layout)。
27: * - `type: 'variable'`: 调用全局变量(如修改当前用户的值)。
28: * - `type: 'query'`: 调用全局查询(如手动触发一次 `execute()` 发起网络请求)。
29: * - `type: 'system'`: 极其关键!调用引擎提供的全局系统服务(如 `id: 'router'` 进行跳转,或 `id: 'message'` 弹出 Toast)。
30: */
31: target: {
32: type: string;
33: id: string;
34: };
35: /**
36: * 目标方法名 (Method Name)
37: *
38: * 目标实体向外暴露的、可供执行的函数名。
39: *
40: * 例:组件暴露的 `updateLayout`,系统路由服务暴露的 `navigateTo`。
41: */
42: method: string;
43: /**
44: * 执行动作的条件
45: *
46: * 动作级拦截器 (Action Execution Condition)
47: *
48: * 在执行此单一动作前的高阶逻辑判断。
49: *
50: * 若存在此字段且求值为 false,引擎将静默跳过此动作的执行,继续执行下一个动作。
51: */
52: condition?: Condition;
53: /**
54: * 方法调用入参 (Method Parameters Payload)
55: *
56: * 传递给 `method` 的额外数据。
57: *
58: * 采用“双模设计 (Dual-Mode)”:
59: * 既支持固定的 JSON 字典(fixed),也支持极其强大的代码逃生舱(code),
60: * 允许在触发动作的一瞬间,通过 JS 代码动态拼装入参(如提取事件源 event 的值)。
61: */
62: params?: ActionParams;
63: }
64:
65: export type ActionParams = ActionParamsByFixed | ActionParamsByCode;
66:
67: /**
68: * 静态表单参数模式
69: *
70: * 适用于大部分常规场景(如打开弹窗时传个死参数)。
71: * payload 里的值在设计时即已确定。
72: */
73: export interface ActionParamsByFixed {
74: type: 'fixed';
75: payload: Record<string, JsonValue>;
76: }
77:
78: /**
79: * 动态代码参数模式 (代码逃生舱)
80: *
81: * 业务场景:
82: * 当你需要“点击表格的某一行,提取这一行的数据 ID,传给下一个页面或 Mutation”时。
83: * 你必须在此时动态执行一段 JS 函数,从上下文中提取事件源的数据,返回一个 params 对象。
84: */
85: export interface ActionParamsByCode extends DynamicExpression {}
(End of file - total 85 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.8s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\shared\\entity.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\shared\entity.ts</path>
<type>file</type>
<content>
1: /**
2: * 实体基类 (Entity Base)
3: *
4: * 【架构定位】:
5: *
6: * 它是低代码平台中所有独立配置节点(如 Component, Variable, Query 等)的万物之源。
7: *
8: * 强制所有核心节点继承此接口,是为了确保整个 Schema 树在进行
9: * 扁平化映射(Normalization)、全局事件总线寻址、以及状态树订阅时,
10: * 拥有一个绝对统一、可依赖的唯一标识符(ID)。
11: */
12: export interface Entity {
13: /**
14: * 实体的全局唯一标识符 (UUID / Unique ID)
15: *
16: * 引擎防呆指引:
17: * 必须保证该 ID 在整个应用 (Application) 的同类实体池中全局唯一。
18: * 渲染引擎在构建 React 列表或遍历 DOM 树时,将强依赖此字段作为 `key`。
19: * 同时,它也是 Action.target.id 进行 RPC 调用的核心靶点。
20: */
21: id: string;
22: /**
23: * 实体的显示名称 (Display Label)
24: *
25: * 核心架构边界:
26: * 它仅仅用于设计器左侧的“图层面板”、“变量列表”或右侧“属性面板”向实施人员展示一个友好的中文名称
27: * (如:“提交按钮”、“获取用户列表查询”)。
28: *
29: * 坚决抵制在任何底层的 JS 表达式求值或数据流绑定中引用 `label`!
30: * (代码逻辑的引用必须依赖不可变的 `key` 或是系统分配的 `id`
31: */
32: label: string;
33: }
(End of file - total 33 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.5s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\shared\\dynamic-expression.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\shared\dynamic-expression.ts</path>
<type>file</type>
<content>
1: /**
2: * 动态代码表达式 (Dynamic Expression)
3: *
4: * 【架构定位】:
5: *
6: * 这是整个低代码平台实现“图灵完备性”和“动态数据流”的终极逃生舱 (Ultimate Escape Hatch)。
7: *
8: * 无论是组件属性 (props) 的动态求值、查询条件 (Condition) 的逻辑判断,
9: * 还是数据清洗管道 (Filter) 的转换逻辑,全部收敛于此接口。
10: *
11: * 【引擎沙箱与执行指引 (Sandbox Execution Guide)】:
12: *
13: * 1. 纯函数体特性:
14: * `code` 字段保存的绝不是一个完整的函数定义(如 `function foo() {}`),
15: * 而是**函数体内部的代码片段 (Function Body)**。
16: * 因此,用户在编写这段代码时,**必须显式地使用 `return` 语句返回最终的计算结果!**
17: *
18: * 2. 动态编译 (JIT Compilation)
19: * 渲染引擎在解析到带有 `type: 'code'` 的对象时,必须在运行时动态编译它。
20: * 标准做法是使用 `new Function(paramNames, codeString)`。
21: *
22: * 3. 全局上下文注入 (Context Injection)
23: * 引擎在调用编译后的函数时,必须将当前应用的全局状态快照,封装为一个 `context` 对象注入其中。
24: * `context` 至少包含:
25: * - `variables`: 当前所有全局变量的最新值。
26: * - `queries`: 当前所有数据查询的最新状态 (data, isLoading, error)。
27: * - (可选) `event`: 当该表达式在交互动作 (Interaction) 中被执行时,触发它的源生事件对象。
28: *
29: * 示例:
30: *
31: * {
32: * type: 'code',
33: * code: "const user = context.variables.currentUser; return user.role === 'admin' ? 'red' : 'blue';"
34: * }
35: */
36: export interface DynamicExpression {
37: /**
38: * 引擎执行标识 (Type Discriminator)
39: *
40: * 明确告诉解析器:这是一个需要被丢进 JS 沙箱里去算的代码对象,
41: * 绝不是一个普通的拥有 `code` 属性的静态字典。
42: */
43: type: 'code';
44: /**
45: * 动态求值的纯函数代码体 (Function Body String)
46: *
47: * 引擎必须通过形如 `const fn = new Function('context', code)` 的方式实例化。
48: *
49: * 用户代码中必须包含合法的 `return` 语句(除非是用于执行纯副作用的 Action,但也建议 return)。
50: */
51: code: string;
52: }
(End of file - total 52 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.0s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\shared\\condition.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\shared\condition.ts</path>
<type>file</type>
<content>
1: import type { DynamicExpression } from './dynamic-expression';
2:
3: /**
4: * 核心逻辑条件分支 (Logical Condition)
5: *
6: * 【架构定位】:
7: *
8: * 低代码平台中负责“控制流拦截 (Control Flow Gatekeeping)”的最小原子单元。
9: *
10: * 广泛被复用于:
11: *
12: * 1. Component 的 `condition` (物理级挂载/销毁,类似于 v-if 或 Conditional Rendering)
13: * 2. Action 的 `condition` (交互动作是否被允许执行的拦截器)
14: * 3. Query 的 `enabled` (串行请求的依赖挂起开关)
15: *
16: * 【双模演进路线】:
17: *
18: * - Code 模式:MVP 阶段的基石,通过纯函数赋予实施人员图灵完备的逻辑判断能力。
19: * - Rules 模式:远期规划,通过基于 JSON 序列化的“可视化规则树”降低非研发人员的使用门槛。
20: */
21: export type Condition = ConditionByCode | ConditionByRules;
22:
23: /**
24: * 纯代码条件判断 (Code Mode)
25: *
26: * 赋予实施人员极其灵活且强大的逻辑判断能力。
27: *
28: * 引擎执行指引 (JIT 编译):
29: *
30: * `code` 是一段接收 `context` 为入参的纯函数字符串。
31: * 引擎必须使用 `new Function('context', code)` 动态执行它。
32: * 实施人员可以从 `context` 中解构出当前应用的全局运行时状态(如 `variables`, `queries`),
33: * 并利用这些状态编写复杂的条件组合(如 `return context.variables.role === 'admin' && context.queries.checkAuth.data.isValid`)。
34: *
35: * 引擎防呆校验:
36: *
37: * 该函数执行的返回值**必须被引擎强行转换为 `boolean` 类型**(如使用 `!!result`),
38: * 任何非布尔值的返回都会导致控制流产生不可预期的歧义行为。
39: */
40: export interface ConditionByCode extends DynamicExpression {}
41:
42: // TODO: 预留规则条件,用于可视化配置,暂不实现
43: /**
44: * 可视化规则树判断 (Rules Mode - 暂不实现)
45: *
46: * 专为“无代码(No-Code)”和业务人员设计的图形化逻辑配置模式。
47: *
48: * 业务场景:
49: * 当用户在设计器界面上,通过点击下拉框拼凑出诸如:
50: * [ “当前用户角色” “等于” “管理员” ] AND [ “当前订单状态” “不等于” “已取消” ]
51: * 这样的逻辑表达式时,设计器将把这些图形化选项序列化为一棵基于 JSON 的 Rule Tree。
52: *
53: * 引擎执行指引:
54: * 引擎需内置一个规则解析器 (Rule Evaluator),递归遍历这棵 JSON 树,
55: * 提取其中的运算符 (operator) 和变量引用 (field),最终运算得出一个 `boolean` 值。
56: */
57: export type ConditionByRules = {
58: type: 'rules';
59: // rules: Rule[];
60: };
(End of file - total 60 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.2s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\shared\\json-value.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\shared\json-value.ts</path>
<type>file</type>
<content>
1: /**
2: * JSON 原生可序列化值 (Strict JSON Serializable Value)
3: *
4: * 【架构定位】:
5: *
6: * 它是低代码 Schema 抵御脏数据污染的“类型铁壁 (Type Barrier)”。
7: *
8: * 核心背景:
9: *
10: * 低代码的 `design-mode` 配置蓝图,最终宿命是调用 `JSON.stringify` 存入数据库。
11: * 如果在 Schema 的属性(如 `props`, `data`)中滥用 `any` 或 `object`
12: * 实施人员极易在配置中混入 `Function`, `Date`, `RegExp` 或类实例等非法对象,
13: * 这将导致序列化时数据丢失(变成死字符串或 undefined),引发毁灭性的页面崩溃。
14: *
15: * 此递归类型(Recursive Type)在 TypeScript 编译阶段,
16: * 就极其原教旨主义地封杀了所有非标准 JSON 类型的侵入。
17: */
18: export type JsonValue =
19: | string
20: | number
21: | boolean
22: | null
23: | JsonValue[]
24: | { [key: string]: JsonValue };
(End of file - total 24 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.0s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\shared\\rest-request.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\shared\rest-request.ts</path>
<type>file</type>
<content>
1: import type { DynamicExpression } from './dynamic-expression';
2: import type { JsonValue } from './json-value';
3:
4: /**
5: * RESTful 请求配置
6: *
7: * 【架构定位】:
8: *
9: * - 低代码平台底层网络 I/O 的通用描述符。
10: * - 广泛被 QueryConfig 和 MutationConfig 复用。
11: *
12: * 采用“顶层双模设计”,
13: * 在提供常规的可视化表单配置(fixed)的同时,保留了极客级别的代码逃生舱(code)。
14: */
15: export type RestRequest = RestRequestByFixed | RestRequestByCode;
16:
17: /**
18: * 可视化表单请求配置
19: *
20: * 适用于 90% 的标准 HTTP 请求。
21: * 引擎会将这些配置项与全局的 DataSource 配置进行深度合并(Merge)。
22: */
23: export interface RestRequestByFixed {
24: type: 'fixed';
25: /**
26: * 关联的全局数据源 ID
27: *
28: * 引擎实现指引:
29: *
30: * 如果指定了 DataSource,引擎应提取该数据源的 baseUrl、params、headers
31: * 并与当前请求的配置进行合并(当前请求的优先级更高)。
32: */
33: dataSourceId?: string;
34: /**
35: * 请求路径
36: *
37: * 可以是完整的绝对路径(若无 dataSourceId 或需要跨域调用第三方 API),
38: * 也可以是相对路径(如 `/users`,引擎会自动拼接 DataSource 的 baseUrl)。
39: */
40: url: string;
41: /**
42: * URL 查询参数 (Query String)
43: *
44: * 引擎防呆设计 (Poka-yoke)
45: *
46: * `params` 最终会被序列化拼接在 URL 后面(如 `?name=A&age=18`)。
47: * 坚决抵制在此处传入对象或数组!如果传入 `{ a: { b: 1 } }`
48: * 多数底层客户端(如原生 Fetch 或默认配置的 Axios)会将其序列化为 `?a=[object Object]`
49: * 这会导致后端网关解析崩溃且极其难以排查。
50: * 因此,类型被严格锁定为基础数据类型(Primitive Types)。
51: *
52: * 【核心架构:null 的显式抹除语义 (Explicit Erasure)】
53: *
54: * 引入 `null` 是为了解决局部请求“抹除”全局 DataSource 继承配置的刚需。
55: * 如果全局配置了 `params: { tenantId: '123' }`,而当前请求不需要带此参数,
56: * 用户必须在当前配置中显式设置 `params: { tenantId: null }`。
57: * 引擎在合并配置时,一旦遇到值为 `null` 的键,必须从最终的请求参数中将其物理删除。
58: */
59: params?: Record<string, string | number | boolean | null | undefined>;
60: /**
61: * HTTP 请求头 (Headers)
62: *
63: * 同上理,HTTP Headers 协议本身只接受字符串。
64: * 如果在 Axios 中传了一个对象(如 `headers: { Authorization: { token: '123' } }`),
65: * Axios 就会直接把这个对象序列化成 `[object Object]` 发给后端!
66: * 必须锁定为基础数据类型。
67: *
68: * 同样支持 `null` 值的显式抹除语义(常用于抹除全局继承的 Token 以调用第三方开放 API)。
69: */
70: headers?: Record<string, string | number | boolean | null | undefined>;
71: /**
72: * HTTP 请求体 (Payload / Body)
73: *
74: * 引擎实现指引:
75: * 不强制 body 一定是一个对象,用户可以根据需要自定义。
76: * 当 method 为 GET 时,此字段通常被底层 HTTP 客户端忽略。
77: *
78: * 必须使用 JsonValue 以守住存入数据库时的序列化底线,防止混入 Function 等非法对象。
79: */
80: body?: JsonValue;
81: }
82:
83: /**
84: * 纯代码请求配置 (Code Mode 逃生舱)
85: *
86: * 解决低代码平台“图灵完备性不足”的终极武器。
87: *
88: * 业务场景:
89: *
90: * 当实施人员面临极度变态的鉴权或加密需求时
91: * (例如:需要提取当前时间戳、加上全局变量 token,混合后进行 MD5 加密,
92: * 最后生成动态的 Headers、签名 Params 和变异的 URL)。
93: * 这在可视化的 fixed 模式下是根本无法完成的。
94: *
95: * 引擎实现指引:
96: *
97: * `code` 是一段纯函数字符串。它接收全局上下文 `context` 作为参数
98: * (可解构出 `variables`, `queries` 等运行时状态)。
99: * 它必须 `return` 一个符合 `Omit<RestRequestByFixed, 'type'>` 结构的对象。
100: * 引擎在执行该函数(JIT 编译)后,拿着返回的对象去发真实的 HTTP 请求。
101: */
102: export interface RestRequestByCode extends DynamicExpression {
103: /**
104: * 关联的全局数据源 ID
105: *
106: * 即使在使用代码动态生成请求体时,也依然可以继承底层 DataSource 的公共配置(如 baseUrl)。
107: */
108: dataSourceId?: string;
109: }
(End of file - total 109 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.5s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\shared\\css-properties.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\shared\css-properties.ts</path>
<type>file</type>
<content>
1: export interface CSSProperties {
2: [key: string]: string | number | undefined;
3: }
(End of file - total 3 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 3.9s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\shared\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\shared\index.ts</path>
<type>file</type>
<content>
1: export * from './dynamic-expression';
2: export * from './condition';
3: export * from './css-properties';
4: export * from './entity';
5: export * from './json-value';
6: export * from './rest-request';
(End of file - total 6 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.4s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\variable\\variable.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\variable\variable.ts</path>
<type>file</type>
<content>
1: import type { Entity, JsonValue } from '../shared';
2: import type { Interaction } from '../interaction';
3:
4: /**
5: * 全局变量
6: *
7: * 【架构定位】:
8: *
9: * 1. 它是低代码平台的“响应式状态中枢(Reactive State Hub)”。
10: * 2. 它是连接组件(Component)的输入/输出、驱动查询(Query)动态刷新的核心媒介。
11: *
12: * 【引擎实现指引】:
13: *
14: * 渲染引擎启动时,必须扫描所有 Variable 节点,提取其 `key` 和初始 `value`
15: * 并在内存中构建一个支持深度订阅的全局 Store(如 Zustand)。
16: *
17: * 当任何 Action(如 `setVariable`)修改了该 Store 中的值时,
18: * 引擎的响应式机制会自动触发所有绑定了该变量的组件重绘或 Query 重新请求。
19: */
20: export interface Variable extends Entity {
21: /**
22: * 变量的键名,用于在表达式中作为变量名来引用
23: *
24: * 核心架构边界:
25: *
26: * 与仅用于 UI 展示的 `label` (如:"当前选中用户") 不同,`key` 是供 JIT 编译器在代码块中求值的真实引用名。
27: * (例如:在代码式的配置中通过 `context.variables.currentUser` 来访问它)
28: *
29: * 引擎约束:
30: *
31: * 1. 必须严格符合 JavaScript 变量命名规范(正则:/^[a-zA-Z_$][a-zA-Z0-9_$]*$/)。
32: * 2. 在当前应用 (Application) 的所有 Variables 列表中必须全局唯一。
33: * 3. 它是不可变的“主键”级数据,修改它可能导致海量已绑定的组件或查询报错(引用失效)。
34: */
35: key: string;
36: /**
37: * 变量的类型,运行时类型守卫
38: *
39: * 决定了该变量在内存中允许存储的合法数据类型。
40: *
41: * 引擎在执行 `setVariable` Action 时,必须先根据此字段进行运行时类型校验 (Runtime Type Checking)
42: * 以防止用户将一个对象强行赋给一个声明为 'number' 的变量,从而引发灾难性的级联崩溃。
43: */
44: type: 'string' | 'number' | 'boolean' | 'object' | 'array';
45: /**
46: * 是否为可选变量,默认false
47: */
48: optional: boolean;
49: /**
50: * 是否允许变量为null,默认false
51: */
52: nullable: boolean;
53: /**
54: * 变量保存的值(初始值)
55: *
56: * 在 Schema 中,它仅仅代表应用首次加载时(或变量被重置时)的一个“静态快照”。
57: *
58: * 引擎约束:
59: *
60: * 为确保该初始快照可以被安全地序列化存入数据库,它必须是一个合法的纯净 JSON 结构,
61: * 坚决抵制在此处使用 Function, Date 等无法被 `JSON.stringify` 转换的类型。
62: */
63: value: JsonValue;
64: /**
65: * 交互
66: *
67: * 变量级生命周期与状态监听
68: *
69: * 挂载于状态节点本身的高阶控制流。
70: *
71: * 引擎实现指引:
72: *
73: * 这是低代码中最强大的“连锁反应触发器(Chain Reaction Trigger)”。
74: * 当变量的值发生突变时触发(如 `event: 'onChange'`)。
75: *
76: * 典型场景:
77: *
78: * 1. 表单联动:当 `selectedProvince` 变量改变时,触发 Action 去清空 `selectedCity` 变量。
79: * 2. 异常监控:当 `errorCount` 变量突破阈值时,触发 Action 弹出一个警告对话框 (Modal)。
80: *
81: * 如果该变量无任何监听逻辑,必须为一个显式的空数组 `[]`。
82: */
83: interactions: Interaction[];
84: }
(End of file - total 84 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.9s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\query\\query.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\query\query.ts</path>
<type>file</type>
<content>
1: import type { Entity, Condition } from '../shared';
2: import type { Interaction } from '../interaction';
3: import type { QueryConfig } from './query-config';
4:
5: /**
6: * 数据查询配置
7: *
8: * 【架构定位】:
9: *
10: * 1. 它是低代码数据流“三层漏斗”模型的中间层(核心业务层)。
11: * 2. 遵循 CQRS 架构,专职处理幂等的“读操作(Read)”。
12: * 3. 它是连接底层 DataSource(协议层)与顶层 Component(视图层)的桥梁。
13: *
14: * 【引擎实现指引】:
15: *
16: * 渲染引擎应将其映射为一个强大的状态机(如 TanStack Query 的 `useQuery` 实例)。
17: *
18: * 它不应将 `isLoading`, `data`, `error` 等运行时快照存入此 Schema,
19: * 而是由引擎在全局内存(QueryClient)中动态维护这些衍生状态,供组件声明式地订阅。
20: */
21: export interface Query extends Entity {
22: /**
23: * 执行条件
24: *
25: * 决定了该查询是否允许被发送(无论是因为页面加载、轮询还是组件订阅)。
26: *
27: * 核心场景(串行依赖):
28: *
29: * 当 Query B 需要用到 Query A 的返回结果作为参数时,
30: * 可在此配置 `enabled: { type: 'code', code: 'return !!context.queries.QueryA.data' }`。
31: * 引擎会挂起 Query B,直到 Query A 成功返回并填充了数据,Query B 才会被自动放行。
32: *
33: * 如果不填,默认允许执行。
34: */
35: enabled?: Condition;
36: /**
37: * 页面加载时自动执行
38: *
39: * 性能优化的杀手锏。
40: *
41: * 如果为 true,引擎在路由命中(或应用启动)的极早期阶段,
42: * 在 UI 组件树还未开始渲染时,就提前向服务器发起请求。
43: * 这样当底层图表组件渲染完毕并尝试订阅此 Query 时,数据可能已经“热乎”地躺在缓存里了,
44: * 从而彻底消灭组件的初始白屏 Loading 时间。
45: */
46: prefetch: boolean;
47: /**
48: * 定时轮询策略
49: *
50: * 数字大屏(Dashboard)的灵魂特性。赋予数据流实时刷新的能力。
51: */
52: polling: {
53: /**
54: * 是否开启轮询
55: */
56: enabled: boolean;
57: /**
58: * 轮询间隔时间(单位:毫秒 ms)
59: *
60: * 引擎实现指引:在页面不可见(浏览器标签页隐藏)时,引擎应智能暂停轮询以节省性能。
61: */
62: interval: number;
63: };
64: /**
65: * 过滤器管道
66: *
67: * 极其关键的数据转换层(Transform Layer)。
68: * 包含了一系列被引用的全局 Filter ID。
69: *
70: * 引擎实现指引:
71: *
72: * 当底层 API 返回原始 JSON 后,在存入 Query 内存缓存池(或喂给组件)之前,
73: * 必须将原始数据像接水管一样,串行流过这些 Filter 函数。
74: * data = filter3(filter2(filter1(rawData)))
75: *
76: * 优势:让组件拿到的永远是极其纯净、格式规整的业务数据(如 `[{ x, y }]` 数组),
77: * 实现了“一份原始接口数据,清洗出多种衍生视图”的极致复用。
78: */
79: filterIds: string[];
80: /**
81: * 交互
82: *
83: * 请求生命周期与副作用控制流
84: *
85: * 挂载于查询状态突变时的高阶事件侦听器。
86: *
87: * 引擎实现指引:
88: *
89: * 1. `onSuccess` (请求成功):常用于触发数据获取后的连锁反应(如更新某个全局 Variable,或弹出“数据同步成功”的 Toast)。
90: * 2. `onError` (请求失败):常用于统一拦截错误,触发异常上报或显示全局 Error Modal。
91: *
92: * 这将原本散落在组件里的异步回调地狱,完美收拢到了数据请求自身的配置中。
93: * 若无副作用,必须为一个显式的空数组 `[]`。
94: */
95: interactions: Interaction[];
96: /**
97: * 具体的请求协议配置
98: *
99: * 采用极其强大的“多态联合类型(Polymorphism Union Type)”。
100: *
101: * 它决定了这究竟是一个静态的 Mock 数据(Static),
102: * 还是一个通过 HTTP 调用的 REST 请求(Rest),
103: * 抑或是一个直连数据库执行的 SQL 语句(Database)。
104: *
105: * 引擎通过读取 `config.type` 决定派发给哪个底层的 DataSource 客户端去执行。
106: */
107: config: QueryConfig;
108: }
(End of file - total 108 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.0s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\query\\query-config.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\query\query-config.ts</path>
<type>file</type>
<content>
1: import type { DynamicExpression, JsonValue, RestRequest } from '../shared';
2:
3: /**
4: * 数据查询核心配置
5: *
6: * 【架构定位】:
7: *
8: * 这是一个极其强大的多态联合类型 (Polymorphism Union Type)。
9: * 它决定了当前这个 `Query` 到底是通过什么“物理手段”去获取数据的。
10: *
11: * 引擎实现指引:
12: *
13: * 渲染引擎(或数据请求层)在执行 `Query` 时,必须写一个 `switch (config.type)`。
14: *
15: * - 如果是 'static'O(1) 复杂度直接返回内存里的死数据。
16: * - 如果是 'rest',提取其绑定的 dataSourceId,组装 Axios 请求发给网关。
17: * - 未来如果是 'database',则通过 WebSocket 或专用代理发给后端执行 SQL。
18: */
19: export type QueryConfig =
20: | QueryConfigByStatic
21: | QueryConfigByRest
22: | QueryConfigByGraphql
23: | QueryConfigByDatabase
24: | QueryConfigByCode;
25:
26: /**
27: * 静态查询配置
28: *
29: * 核心价值:
30: *
31: * 1. 跨组件复用:定义如“全国省份列表”等被多个组件同时绑定的全局字典数据。
32: * 2. 接口 Mock:当后端真实 API 尚未开发完毕时,作为临时数据源占位。
33: * 一旦后端就绪,只需在设计器将此配置一键切换为 `QueryConfigByRest`
34: * 所有绑定了此 Query 的前端组件无需任何修改即可平滑接入真数据。
35: */
36: export interface QueryConfigByStatic {
37: type: 'static';
38: /**
39: * 静态的、硬编码的全局数据。
40: * 必须是合法的纯净 JSON(使用 JsonValue 锁死序列化底线,坚决不用 any)。
41: */
42: data: JsonValue;
43: }
44:
45: /**
46: * RESTful 查询配置
47: *
48: * 占据了低代码数据获取 90% 以上场景的主力网络通信配置。
49: */
50: export interface QueryConfigByRest {
51: type: 'rest';
52: /**
53: * HTTP 请求方法
54: *
55: * 注:在 CQRS 架构中,即使是一个 POST 请求(如 GraphQL 的查询,或带有复杂查询体的高级搜索),
56: * 只要它是幂等的且不修改服务端状态,它在低代码中依然属于 `Query`(查询),而非 `Mutation`(变更)。
57: */
58: method: 'GET' | 'POST';
59: /**
60: * 请求体配置
61: *
62: * 极其关键的双模逃生舱设计 (Dual-Mode Design)
63: * 它引入了定义在 `core/rest-request.ts` 中的 `RestRequest` 类型
64: * (`RestRequestByFixed | RestRequestByCode`)。
65: *
66: * 允许用户既能通过可视化表单填死 url/params
67: * 又能通过手写 JS 代码,根据全局变量动态拼装出极其复杂的请求头和加密签名。
68: */
69: request: RestRequest;
70: /**
71: * 接口超时时间 (毫秒 ms)
72: *
73: * 如果不启用,引擎应使用全局 DataSource 配置的超时,或默认的浏览器超时(如 30s)。
74: */
75: timeout: {
76: enabled: boolean;
77: duration: number;
78: };
79: }
80:
81: // TODO: GraphQL 查询配置,暂不实现
82: export interface QueryConfigByGraphql {
83: type: 'graphql';
84: }
85:
86: // TODO: 数据库查询配置,暂不实现
87: export interface QueryConfigByDatabase {
88: type: 'database';
89: }
90:
91: // TODO: 代码查询配置,暂不实现
92: export interface QueryConfigByCode extends DynamicExpression {}
(End of file - total 92 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.8s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\mutation\\mutation.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\mutation\mutation.ts</path>
<type>file</type>
<content>
1: import type { Entity, RestRequest } from '../shared';
2: import type { Interaction } from '../interaction';
3: import type { MutationConfig } from './mutation-config';
4:
5: /**
6: * 数据变更操作
7: *
8: * 【架构定位】:
9: *
10: * 1. 它是低代码数据流“三层漏斗”模型的写操作层(Write/Command)。
11: * 2. 遵循 CQRS 架构,专职处理具有副作用(Side-effects)的操作(如:提交表单、删除数据、点赞)。
12: *
13: * 【引擎实现指引】:
14: *
15: * 渲染引擎应将其映射为一个手动触发的执行器(如 TanStack Query 的 `useMutation` 实例)。
16: * 它绝对没有自动触发的能力(没有 prefetch,没有 polling,没有 enabled 挂起),
17: * 它只能在用户发生了交互动作(Interaction)后,通过派发 `invokeMethod` Action 被动调用执行。
18: */
19: export interface Mutation extends Entity {
20: /**
21: * 具体的请求协议配置
22: *
23: * 采用极其强大的“多态联合类型(Polymorphism Union Type)”。
24: * 决定了这到底是一个通过 HTTP 调用的 REST 请求(Rest),
25: * 抑或是一个直连数据库执行的 INSERT/UPDATE/DELETE 语句(Database)。
26: *
27: * 引擎通过读取 `config.type` 决定派发给哪个底层的 DataSource 客户端去执行。
28: */
29: config: MutationConfig;
30: /**
31: * 交互
32: *
33: * 请求生命周期与副作用控制流
34: *
35: * 挂载于数据变更操作成功或失败时的高阶事件侦听器。
36: * 这是整个低代码业务逻辑编排中最核心的“指挥中心”!
37: *
38: * 引擎实现指引:
39: *
40: * 1. 极其典型的业务串联 (Success Chain)
41: * 当表单提交成功 (`onSuccess`) -> 派发 Action 触发“获取用户列表(Query)”重新请求(刷新数据)
42: * -> 派发 Action 显示“提交成功”的全局 Toast -> 派发 Action 清空绑定的输入框变量。
43: * 2. `onError` (请求失败):用于统一拦截接口异常,并在前端触发容错或告警流程。
44: *
45: * 若无副作用,必须为一个显式的空数组 `[]`。
46: */
47: interactions: Interaction[];
48: }
(End of file - total 48 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.4s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\mutation\\mutation-config.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\mutation\mutation-config.ts</path>
<type>file</type>
<content>
1: import type { DynamicExpression, RestRequest } from '../shared';
2:
3: /**
4: * 数据变更核心配置引擎
5: *
6: * 【架构定位】:
7: *
8: * 这是一个多态联合类型 (Polymorphism Union Type)。
9: * 它决定了当前这个 `Mutation` 到底是通过什么“物理手段”去修改服务端状态的。
10: *
11: * 核心架构差异(与 QueryConfig 的对比):
12: *
13: * 坚决不允许存在 `MutationConfigByStatic`
14: * 因为修改一个内存里的死数据(Mock)是没有任何业务意义的,写操作必定伴随着网络 I/O 或数据库操作。
15: */
16: export type MutationConfig =
17: | MutationConfigByRest
18: | MutationConfigByGraphql
19: | MutationConfigByDatabase
20: | MutationConfigByCode;
21:
22: /**
23: * 1. RESTful 协议变更配置 (REST API)
24: *
25: * 占据了低代码数据变更 90% 以上场景的主力网络通信配置。
26: */
27: export interface MutationConfigByRest {
28: type: 'rest';
29: /**
30: * HTTP 请求方法
31: *
32: * 引擎防呆指引:
33: * 既然是写操作(Mutation),绝大部分符合 REST 语义的设计必须是以下四种动词。
34: * 坚决不要在此处允许出现 GET 请求!
35: */
36: method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
37: /**
38: * 请求体配置
39: *
40: * 极其关键的双模逃生舱设计 (Dual-Mode Design)
41: * 它引入了定义在 `core/rest-request.ts` 中的 `RestRequest` 类型
42: * (`RestRequestByFixed | RestRequestByCode`)。
43: *
44: * 这是低代码平台处理极度复杂的表单提交数据组装(如从多个 Variables 里提取数据并加密)的终极战场。
45: */
46: request: RestRequest;
47: /**
48: * 接口超时时间 (毫秒 ms)
49: *
50: * 变更操作(如上传大文件或复杂计算)往往比查询需要更长的超时容忍度。
51: * 如果启用,引擎应使用全局 DataSource 配置的超时,或默认的浏览器超时。
52: */
53: timeout: {
54: enabled: boolean;
55: duration: number;
56: };
57: }
58:
59: // TODO: GraphQL 变更配置,暂不实现
60: export interface MutationConfigByGraphql {
61: type: 'graphql';
62: }
63:
64: // TODO: 数据库变更配置,暂不实现
65: export interface MutationConfigByDatabase {
66: type: 'database';
67: }
68:
69: // TODO: 代码变更配置,暂不实现
70: export interface MutationConfigByCode extends DynamicExpression {}
(End of file - total 70 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.8s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\filter\\filter.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\filter\filter.ts</path>
<type>file</type>
<content>
1: import type { DynamicExpression, Entity } from '../shared';
2:
3: /**
4: * 数据过滤器
5: *
6: * 【架构定位】:
7: *
8: * 低代码数据流管线中的“纯函数转换层 (Pure Function Transformer)”。
9: *
10: * 核心价值:
11: *
12: * 1. 解决后端接口返回的数据结构(如嵌套极深的 JSON)与前端图表组件要求的数据结构(如平铺的 [{x, y}] 数组)不匹配的终极痛点。
13: * 2. 它是全局实体,意味着写好一个“通用时间格式化”的 Filter,可以被大屏上 100 个不同的图表同时挂载复用。
14: *
15: * 【引擎实现指引】:
16: *
17: * 1. 它通常被组件的 `ComponentDataQuery` 通过 `filterIds` 数组引用。
18: * 2. 引擎在处理包含多个 Filter 的数组时,必须采用“管道模式 (Pipeline)”:
19: * 前一个 Filter 的返回值,必须作为后一个 Filter 的入参 `data` 传入。
20: * 即:finalData = filter3(filter2(filter1(rawData)))。
21: */
22: export type Filter = FilterByCode | FilterByRules;
23:
24: /**
25: * 纯代码过滤器
26: *
27: * 赋予实施人员处理复杂数据转换(如 map, reduce, 多数组 join)的图灵完备能力。
28: *
29: * 引擎执行指引 (JIT 编译):
30: *
31: * 引擎在执行该 `code` 时,注入的 `context` 参数必须比普通情况多出一个极其关键的属性:`data`。
32: *
33: * 注入上下文示例:
34: *
35: * - `context.data`: 当前需要被清洗的原始数据(或者是上一个 Filter 吐出来的数据)。
36: * - `context.variables`: 全局变量(允许 Filter 根据当前状态动态调整清洗逻辑,例如:`if (variables.isMetric) return data * 1000`)。
37: *
38: * 引擎防呆校验:
39: *
40: * 该函数执行后,**必须有明确的 `return` 返回值**。
41: * 引擎若检测到返回 `undefined`,应考虑抛出警告,防止开发者漏写 return 导致数据流在管道中断裂。
42: */
43: export interface FilterByCode extends Entity, DynamicExpression {}
44:
45: // TODO: 预留规则过滤器,用于可视化配置,暂不实现
46: /**
47: * 可视化规则过滤器
48: *
49: * 专为“无代码(No-Code)”和业务人员设计的图形化清洗管道。
50: *
51: * 业务场景:
52: *
53: * 用户不需要写 JS 代码,而是通过点击界面上的算子(Operators)进行串联。
54: * 例如配置一个数组:
55: * [
56: * { action: 'pick', field: 'records' }, // 第一步:提取 records 字段
57: * { action: 'filter', field: 'age', op: '>', val: 18 }, // 第二步:过滤成年人
58: * { action: 'map', from: 'userName', to: 'label' } // 第三步:字段映射重命名
59: * ]
60: *
61: * 引擎执行指引:
62: *
63: * 引擎需内置一系列标准的清洗算子处理函数,根据上述 JSON 规则树,
64: * 在内存中动态组装并执行数据转换逻辑。
65: */
66: export interface FilterByRules extends Entity {
67: type: 'rules';
68: // rules: Rule[];
69: }
(End of file - total 69 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.6s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\data-source\\data-source.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\data-source\data-source.ts</path>
<type>file</type>
<content>
1: import type { DataSourceByDatabase } from './data-source-by-database';
2: import type { DataSourceByRest } from './data-source-by-rest';
3:
4: /**
5: * 数据源池
6: *
7: * 【架构定位】:
8: *
9: * 它是低代码数据流“三层漏斗”模型的最底层(第一层)。
10: *
11: * 核心职责:
12: *
13: * 1. 隔离通信协议:将 RESTful、GraphQL、Database(MySQL/PG) 等底层的网络协议或连接池配置,
14: * 与上层的业务逻辑(Query)彻底解耦。
15: * 2. 跨查询复用:为多个属于同一业务域的 Query 提供统一的 `baseUrl` 和鉴权 `headers` (如 Token)
16: * 实现一处配置,全局生效。
17: *
18: * 【引擎实现指引】:
19: *
20: * 当应用启动时,渲染引擎应扫描此列表,并在内存中为每个 DataSource 实例化一个专门的网络客户端
21: * (如针对 type: 'rest' 实例化一个配置了拦截器的 Axios 实例),以供上层 Query 调用。
22: */
23: export type DataSource = DataSourceByRest | DataSourceByDatabase;
(End of file - total 23 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.0s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\data-source\\data-source-by-rest.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\data-source\data-source-by-rest.ts</path>
<type>file</type>
<content>
1: import type { DynamicExpression, Entity } from '../shared';
2:
3: /**
4: * RESTful 协议数据源
5: *
6: * 专用于配置基于 HTTP 的网络通信实体。
7: */
8: export interface DataSourceByRest extends Entity {
9: /**
10: * 引擎路由标识:告诉引擎必须使用 Axios 或 Fetch 客户端来处理此数据源下的请求。
11: */
12: type: 'rest';
13: /**
14: * RESTful 数据源的核心配置
15: *
16: * 采用“顶层双模设计”:
17: * 既支持常规的可视化表单配置,也支持代码模式(应对极其复杂的动态鉴权计算,如时间戳签名)。
18: */
19: config: DataSourceConfigByRest;
20: }
21:
22: export type DataSourceConfigByRest =
23: | DataSourceConfigByRestWithFixed
24: | DataSourceConfigByRestWithCode;
25:
26: /**
27: * REST 数据源的常规可视化配置
28: *
29: * 适用于 90% 的标准 HTTP 请求场景。
30: */
31: export interface DataSourceConfigByRestWithFixed {
32: type: 'fixed';
33: /**
34: * 基础路径 (Base URL)
35: *
36: * 必须是一个完整的绝对 URL(如 `https://api.datalive.com/v1`)。
37: *
38: * 保持 URL 的绝对独立性是防呆设计的关键,避免相对路径引发的拼接灾难。
39: */
40: baseUrl: string;
41: /**
42: * 全局默认的 URL Query 参数
43: *
44: * 引擎在发送该数据源下的任何 Query 时,都会自动将此对象拼接到 URL 后。
45: *
46: * 为确保 Axios/Fetch 序列化的稳定性,仅允许基础数据类型(Primitive Types),坚决抵制对象或数组嵌套。
47: */
48: params?: Record<string, string | number | boolean | null | undefined>;
49: /**
50: * 全局默认的请求头
51: *
52: * 常用于存放如 `Authorization: Bearer <token>` 或 `x-tenant-id` 等鉴权信息。
53: *
54: * 同样仅限基础数据类型。
55: */
56: headers?: Record<string, string | number | boolean | null | undefined>;
57: }
58:
59: /**
60: * REST 数据源的代码配置模式
61: *
62: * 引擎执行指引:
63: *
64: * 传入的 code 函数必须 return 一个符合 `Omit<DataSourceRestConfigFixed, 'type'>` 结构的对象。
65: * 这赋予了实施人员在发请求前执行极度复杂的加密运算的终极权力。
66: */
67: export interface DataSourceConfigByRestWithCode extends DynamicExpression {}
(End of file - total 67 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.4s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\data-source\\data-source-by-database.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\data-source\data-source-by-database.ts</path>
<type>file</type>
<content>
1: import type { Entity } from '../shared';
2:
3: // TODO: 数据库数据源, 暂不实现
4: export interface DataSourceByDatabase extends Entity {
5: type: 'database';
6: }
(End of file - total 6 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.7s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\application\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\application\index.ts</path>
<type>file</type>
<content>
1: export * from './application';
(End of file - total 1 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.6s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\component\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\component\index.ts</path>
<type>file</type>
<content>
1: export * from './component';
2: export * from './component-data';
3: export * from './component-layout';
(End of file - total 3 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.6s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\interaction\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\interaction\index.ts</path>
<type>file</type>
<content>
1: export * from './action';
2: export * from './interaction';
(End of file - total 2 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.4s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\page\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\page\index.ts</path>
<type>file</type>
<content>
1: export * from './page';
2: export * from './page-layout';
(End of file - total 2 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.7s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\query\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\query\index.ts</path>
<type>file</type>
<content>
1: export * from './query';
(End of file - total 1 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.9s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\mutation\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\mutation\index.ts</path>
<type>file</type>
<content>
1: export * from './mutation';
2: export * from './mutation-config';
(End of file - total 2 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.4s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\filter\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\filter\index.ts</path>
<type>file</type>
<content>
1: export * from './filter';
(End of file - total 1 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.3s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\route\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\route\index.ts</path>
<type>file</type>
<content>
1: export * from './route';
(End of file - total 1 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.6s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\variable\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\variable\index.ts</path>
<type>file</type>
<content>
1: export * from './variable';
(End of file - total 1 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.6s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\src\\schema\\data-source\\index.ts"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\src\schema\data-source\index.ts</path>
<type>file</type>
<content>
1: export * from './data-source';
2: export * from './data-source-by-database';
3: export * from './data-source-by-rest';
(End of file - total 3 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.3s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\.gitignore"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\.gitignore</path>
<type>file</type>
<content>
1: # dependencies (bun install)
2: node_modules
3:
4: # output
5: out
6: dist
7: *.tgz
8:
9: # code coverage
10: coverage
11: *.lcov
12:
13: # logs
14: logs
15: _.log
16: report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17:
18: # dotenv environment variable files
19: .env
20: .env.development.local
21: .env.test.local
22: .env.production.local
23: .env.local
24:
25: # caches
26: .eslintcache
27: .cache
28: *.tsbuildinfo
29:
30: # IntelliJ based IDEs
31: .idea
32:
33: # Finder (MacOS) folder config
34: .DS_Store
(End of file - total 34 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.4s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\bun.lock"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\bun.lock</path>
<type>file</type>
<content>
1: {
2: "lockfileVersion": 1,
3: "configVersion": 1,
4: "workspaces": {
5: "": {
6: "name": "datalive-design",
7: "devDependencies": {
8: "@types/bun": "latest",
9: },
10: "peerDependencies": {
11: "typescript": "^5",
12: },
13: },
14: },
15: "packages": {
16: "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
17:
18: "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
19:
20: "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
21:
22: "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
23:
24: "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
25: }
26: }
(End of file - total 26 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.8s)
**Tool: glob**
**Input:**
```json
{
"pattern": ".cursor/**/*"
}
```
**Output:**
```
No files found
[Agent Usage Reminder]
You called a search/fetch tool directly without leveraging specialized agents.
RECOMMENDED: Use task with explore/librarian agents for better results:
```
// Parallel exploration - fire multiple agents simultaneously
task(subagent_type="explore", load_skills=[], prompt="Find all files matching pattern X")
task(subagent_type="explore", load_skills=[], prompt="Search for implementation of Y")
task(subagent_type="librarian", load_skills=[], prompt="Lookup documentation for Z")
// Then continue your work while they run in background
// System will notify you when each completes
```
WHY:
- Agents can perform deeper, more thorough searches
- Background tasks run in parallel, saving time
- Specialized agents have domain expertise
- Reduces context window usage in main session
ALWAYS prefer: Multiple parallel task calls > Direct tool calls
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.1s)
**Tool: glob**
**Input:**
```json
{
"pattern": "**/opencode.json"
}
```
**Output:**
```
No files found
[Agent Usage Reminder]
You called a search/fetch tool directly without leveraging specialized agents.
RECOMMENDED: Use task with explore/librarian agents for better results:
```
// Parallel exploration - fire multiple agents simultaneously
task(subagent_type="explore", load_skills=[], prompt="Find all files matching pattern X")
task(subagent_type="explore", load_skills=[], prompt="Search for implementation of Y")
task(subagent_type="librarian", load_skills=[], prompt="Lookup documentation for Z")
// Then continue your work while they run in background
// System will notify you when each completes
```
WHY:
- Agents can perform deeper, more thorough searches
- Background tasks run in parallel, saving time
- Specialized agents have domain expertise
- Reduces context window usage in main session
ALWAYS prefer: Multiple parallel task calls > Direct tool calls
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.1s)
**Tool: glob**
**Input:**
```json
{
"pattern": "CLAUDE.md"
}
```
**Output:**
```
No files found
[Agent Usage Reminder]
You called a search/fetch tool directly without leveraging specialized agents.
RECOMMENDED: Use task with explore/librarian agents for better results:
```
// Parallel exploration - fire multiple agents simultaneously
task(subagent_type="explore", load_skills=[], prompt="Find all files matching pattern X")
task(subagent_type="explore", load_skills=[], prompt="Search for implementation of Y")
task(subagent_type="librarian", load_skills=[], prompt="Lookup documentation for Z")
// Then continue your work while they run in background
// System will notify you when each completes
```
WHY:
- Agents can perform deeper, more thorough searches
- Background tasks run in parallel, saving time
- Specialized agents have domain expertise
- Reduces context window usage in main session
ALWAYS prefer: Multiple parallel task calls > Direct tool calls
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.1s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\docs\\应用运行模式架构规范.md"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\docs\应用运行模式架构规范.md</path>
<type>file</type>
<content>
1: # DatAlive 应用运行模式 (AppMode) 架构设计规范
2:
3: 在低代码/无代码平台的设计中,渲染引擎需要根据不同的上下文环境展现出不同的行为特征。为了保证底层架构的清晰与解耦,DatAlive 明确确立了 **AppMode(应用运行模式)** 的三态规范,并对“预览模式”与“运行模式”的功能边界进行了严格的界定。
4:
5: ## 1. 架构定位:AppMode 是运行时上下文,绝非 Schema
6:
7: 在架构设计的初期,极易将运行模式与应用的数据结构混为一谈。必须极其严厉地明确以下铁律:
8:
9: > **`AppMode` 绝对不属于需要存入数据库持久化的 `design-mode` Schema 蓝图!**
10: > Schema 描述的是“应用长什么样,有什么逻辑”,而 `AppMode` 决定的是“渲染引擎现在该用什么姿势来解析这份 Schema”。
11:
12: 在渲染引擎的前端代码实现中,`AppMode` 应当作为顶级组件的 Props(或全局环境变量)由外部宿主容器(Host Shell)显式注入:
13:
14: ```tsx
15: // AppMode 的三态枚举(定义于引擎层,而非 Schema 层)
16: export type AppMode =
17: | 'design' // 设计态:引擎进入“沙盒模式”,劫持交互与路由,渲染视觉辅助线。
18: | 'preview' // 预览态:放行业务事件,提供沉浸式体验,但挂载强大的白盒调试工具。
19: | 'runtime'; // 运行态:绝对纯净的运行环境,无任何设计器代码注入。
20: ```
21:
22: ### 1.1 为什么拒绝使用 `publish` 作为模式?
23: 在架构设计中,必须将 **“底层渲染状态(Render State / AppMode)”** 与 **“应用版本生命周期(Lifecycle Status)”** 彻底解耦。
24: * `runtime`(运行时)是一个渲染引擎的“物理运行环境”。
25: * `publish`(发布)是一个应用的“版本流转动作”(如 `draft`, `testing`, `published`)。
26: 即便是本地测试的一个未发布的草稿版本,当它脱离设计器独立在手机上扫码预览时,其底层的渲染模式依然是 `runtime`。
27:
28: ---
29:
30: ## 2. 预览模式 (Preview) vs 运行模式 (Runtime):同源异构的双生子
31:
32: 如果抛开后端服务和数据存储介质不谈,纯粹从前端功能视角来看,**预览模式**和**运行模式**在底层逻辑上是 100% 同源的(共享同一套 Schema 渲染管线),但在外在表现上却截然不同。
33:
34: **一个形象的比喻:**
35: * **预览模式(Preview)**:是导演(开发者)在看**监视器**。画面里可能有收音麦克风穿帮、有场记板、有剧本标注。发现演员台词念错了,导演会喊“咔(报错)”,然后立刻重来。
36: * **运行模式(Runtime)**:是观众在电影院看**最终上映的电影**。画面绝对干净,剧情连贯,即便有个别穿帮镜头(Bug),电影也会继续放下去,绝不会突然黑屏跳出一段剧本代码。
37:
38: ### 2.1 核心功能差异对比
39:
40: | 维度 | 预览模式 (Preview Mode) | 运行模式 (Runtime Mode) | 设计模式 (Design Mode) |
41: | :--- | :--- | :--- | :--- |
42: | **目标用户** | 应用搭建者、实施人员 | 最终业务人员(如客服、仓管) | 平台实施人员 |
43: | **路由引擎 (Routing)** | 原生 `BrowserRouter` 或 Hash,独立窗口展示。 | 干净的业务地址(如 `/apps/my-dashboard`)。 | **强制隔离**:必须使用 `MemoryRouter`,防止点击 a 标签导致设计器真刷新或逃逸。 |
44: | **异常处理 (Error)** | **严厉的教练**:大张旗鼓地报错(Error Boundary 标红),将 JS 堆栈暴露在控制台。 | **温柔的服务员**:静默容错,降级 UI 或弹出温和的 Toast,绝不暴露底层堆栈。 | 忽略破坏性错误,保护画布不崩溃。 |
45: | **破坏性操作 (Mutation)**| 允许执行,但通常通过拦截器指向 Mock 或沙箱环境。 | 毫无保留地真实执行业务逻辑。 | **强力封杀**:引擎必须静默拦截所有的 `Mutation` 动作,防止在画布点击按钮删除了真实数据。 |
46: | **环境纯净度** | 包裹在调试器上下文中,注入各种 DevTools。 | 轻量级纯净容器。剔除所有设计器相关的 JS 胖包和无用 DOM。 | 挂载 `react-moveable` 等庞大的拖拽与辅助渲染引擎。 |
47:
48: ---
49:
50: ## 3. 预览模式专属辅助工具 (Preview Utilities) 深度展开
51:
52: 为了让“导演”能更好地排查“演员”的问题,预览模式必须配备一系列强大的领域特定(Domain-Specific)辅助工具。传统的浏览器 F12 DevTools 对低代码编译后的虚拟 DOM 是“瞎”的,因此必须集成以下工具:
53:
54: ### 3.1 低代码控制台 (Low-Code Console / Debugger)
55: 这是预览模式的核心灵魂,负责将低代码引擎内部的“黑盒状态”白盒化。
56: 1. **全局状态漫游器 (State Explorer)**
57: * 实时展示当前应用中所有的全局变量(`Variables`)、查询快照(`Queries`)和组件暴露的运行时属性。
58: * 允许开发者手动 Override(覆盖)某个变量的值,实时观察页面的响应式变化。
59: 2. **数据流监控 (Network / Query Monitor)**
60: * 拦截并展示所有的 `Query` 和 `Mutation` 执行情况(完美契合 TanStack Query DevTools)。
61: 3. **动作轨迹时间轴 (Action Tracer / Timeline)**(最难也是最核心):
62: * 解决低代码中最难排查的“连锁反应”Bug。
63: * 以瀑布流日志打印执行顺序:`[事件触发] -> [变量修改] -> [Query执行] -> [成功回调代码执行]`,让数据流转路径一目了然。
64: 4. **代码沙箱异常日志 (Code Sandbox Logger)**
65: * 专门捕获用户手写的 `DynamicExpression` (如 `type: 'code'`) 中抛出的 JS 异常,将其劫持并优雅地打印在低代码控制台中。
66:
67: ### 3.2 组件审查器 (Component Inspector)
68: 在预览模式下,开发者有时需要快速定位 UI 问题,但又不想切回设计模式:
69: * **悬浮高亮 (Hover Outline)**:按住特定快捷键(如 `Alt` / `Option`)在页面上移动鼠标,被掠过的组件会显示高亮边框和其 `Entity.id`。
70: * **快捷跳转 (Quick Jump)**:点击被高亮的组件,利用 `postMessage` 与父级设计器通信,设计器立即在画布中选中该组件,右侧面板自动展开其配置项。
71:
72: ---
73:
74: ## 4. MVP 阶段的务实落地策略
75:
76: 在 DatAlive 的 MVP(最小可行性产品)阶段,我们采取 **“架构上严格区分,实现上极简降级”** 的策略:
77:
78: 1. **引擎入口的强约束**:在渲染引擎的顶层组件定义中,严格要求传入 `mode: AppMode`,并根据此字段进行核心模块(如 Router 和 拖拽层)的条件分支渲染。
79: 2. **借力生态实现控制台**:暂不自研复杂的 Action Tracer,而是直接在预览模式接入 `<ReactQueryDevtools />`(监控请求状态机)和开启 Redux/Zustand DevTools Middleware(监控变量变化),以极低成本获得企业级调试能力。
80: 3. **前端纯净路由闭环**:即使没有后端,运行模式(Runtime)也必须在一个独立的 HTML 页面下读取 `localStorage` 进行渲染,以此强制验证渲染引擎是否真正脱离了设计器的胖包上下文。
(End of file - total 80 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.0s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\docs\\实体生命周期引擎实现规范.md"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\docs\实体生命周期引擎实现规范.md</path>
<type>file</type>
<content>
1: # DatAlive 实体生命周期引擎实现规范 (Entity Lifecycle Implementation)
2:
3: ## 1. 设计理念:引擎驱动生命周期 (Engine-Driven Lifecycle)
4:
5: 在基于 React 19+ 生态的低代码架构中,由于 Strict Mode、Concurrent Rendering 和 Suspense 的存在,React 组件的真实挂载/卸载行为往往不可预测(例如组件可能被多次挂载/卸载以检查副作用)。
6:
7: 因此,DatAlive 平台中的“业务生命周期”**严禁直接等价于 React 的生命周期(如 `useEffect`)**。我们必须采用**引擎驱动**模式:由底层的 `RuntimeEngine` 根据路由状态、全局数据流或用户行为,**显式地派发(Emit)**各个实体的生命周期事件。React 仅仅作为“被动接收状态并渲染”的视图层。
8:
9: 在底层 Schema 的定义中,无论是路由跳转、数据请求回调还是用户点击,这一切所谓的“生命周期钩子”,均被统一收口并泛化为 **`Interaction`(泛化事件侦听器)**。
10:
11: ---
12:
13: ## 2. 实体生命周期映射与 Interaction 触发策略
14:
15: 目前设计器中允许配置 `interactions: Interaction[]` 字段的实体,及其引擎侧的触发策略定义如下:
16:
17: ### 2.1 Application (应用根节点)
18: 负责整个低代码应用启动和全局级别的状态拦截。
19:
20: | Interaction.event (触发器) | 触发时机 | 引擎实现策略 |
21: | :--- | :--- | :--- |
22: | `onLaunch` | 应用配置加载完成,准备渲染根节点前 | 在 React `createRoot().render()` 执行前,由引擎主动解析执行。可用于初始化全局变量、鉴权校验等。 |
23: | `onError` | 全局未捕获的运行时异常(含数据流异常) | 绑定在 React ErrorBoundary 顶层,以及 Zustand / TanStack Query 的全局 Error Handler 中。 |
24:
25: ### 2.2 Route & Page (路由与页面容器)
26: 视图层的状态闭环单元,处理路由切换带来的副作用(Route Guards)。
27:
28: | Interaction.event (触发器) | 触发时机 | 引擎实现策略 |
29: | :--- | :--- | :--- |
30: | `onEnter` (Route级) | 路由准备进入该页面前 (Route Guards) | 挂载在路由监听层。可在此处派发 Action 发起鉴权,若鉴权失败可触发 Redirect Action 踢回登录页。 |
31: | `onLoad` (Page级) | 页面组件挂载,且前置预热数据已就绪 | 在页面容器初始化时触发。常用于重置当前页面的局部 Variables,或发起非预热的懒加载 Query。 |
32: | `onLeave` (Route级) | 路由准备离开该页面之前 | 路由切换前触发。用于清理定时器、WebSocket 或清空重型数据变量。 |
33: | `onResize` (Page级) | (大屏特有) 画布或窗口尺寸发生变化 | 引擎统一在 `window.addEventListener('resize')` 中做防抖处理,然后派发给当前激活的 Page。 |
34:
35: ### 2.3 Component (视图原子组件)
36: 最核心的用户交互载体,其事件主要由 DOM 交互驱动,少部分由挂载周期驱动。
37:
38: | Interaction.event (触发器) | 触发时机 | 引擎实现策略 |
39: | :--- | :--- | :--- |
40: | **交互类**: `onClick`, `onMouseEnter`, `onChange` 等 | 用户与组件进行真实物理交互时 | 通过标准 React Props 传递给具体的组件实现,例如 `<Button onClick={(e) => engine.dispatchInteraction('btn_1', 'onClick', e)} />`。 |
41: | **生命周期类**: `onMount`, `onUnmount` | 组件实例在画布中真实渲染/销毁时 | 不直接使用原生的 `useEffect`。需封装专门的 `useLowcodeMount` Hook,内部处理 React 18+ Strict Mode 的双次调用问题(幂等拦截),再向引擎上报。 |
42:
43: ### 2.4 Variable (响应式全局变量)
44: 低代码状态驱动逻辑的核心连锁反应触发器。
45:
46: | Interaction.event (触发器) | 触发时机 | 引擎实现策略 |
47: | :--- | :--- | :--- |
48: | `onChange` | 变量的值发生实质性突变时 | 在 Zustand Store 的 Reducer 层拦截,或通过 `store.subscribe` 监听。**必须进行深比较(Deep Equal)防止重复触发**。这是实现复杂表单联动(如省市级联清空)的核心机制。 |
49:
50: ### 2.5 Query / Mutation (CQRS 数据源请求)
51: 异步控制流的核心,与后端交互的桥梁。
52:
53: | Interaction.event (触发器) | 触发时机 | 引擎实现策略 |
54: | :--- | :--- | :--- |
55: | `onSuccess` | 数据请求成功并返回 2xx 状态码时 | 完美契合 TanStack Query。在其 `onSuccess` 回调中拦截并交由引擎执行配置的 Actions(如弹出成功 Toast,或触发其他 Query 的刷新)。 |
56: | `onError` | 请求失败、网络异常或业务报错时 | 在 TanStack Query 的 `onError` 回调中拦截。可在此处派发全局异常处理 Action。 |
57: | `onSettled` | 无论成功或失败,请求流程结束时 | 在 TanStack Query 的 `onSettled` 回调中拦截。用于统一下发如“关闭某个特定按钮的 Loading 态”之类的重置动作。 |
58:
59: ---
60:
61: ## 3. 架构优势总结
62:
63: 通过将以上所有的生命周期和事件收敛为 Schema 中的 `Interaction` 实体模型,我们获得了以下压倒性的架构优势:
64:
65: 1. **万法归宗 (Unified Control Flow)**:引擎只需要一个通用的“事件分发器(Event Dispatcher)”,就能处理从 DOM 鼠标点击到网络回调的所有异步行为。
66: 2. **确定性 (Determinism)**:业务逻辑的执行不受 React 并发渲染机制的干扰。
67: 3. **可测试性 (Testability)**:可以在 Node.js 环境下加载 Schema,通过调用 `engine.dispatchInteraction('query_1', 'onSuccess')` 直接测试复杂的联动数据流,完全无需真实的 DOM 参与。
68: 4. **职责单一 (Separation of Concerns)**React 只负责 `UI = f(state)``RuntimeEngine` 负责 `Event -> Action -> State Update` 的逻辑编排。
(End of file - total 68 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.5s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\docs\\设计器悬浮组件架构规范.md"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\docs\设计器悬浮组件架构规范.md</path>
<type>file</type>
<content>
1: # DatAlive 悬浮组件 (Overlay Components) 架构设计规范
2:
3: 在低代码平台(如 DatAlive)中,悬浮组件(如 `Modal` 弹窗、`Drawer` 抽屉、全局 `Toast` 等)由于其“脱离标准文档流、需要交互唤起、默认隐藏”的特性,一直是设计器架构、状态管理和交互设计的难点。
4:
5: 本规范基于 DatAlive 的核心设计哲学,详细界定了悬浮组件在底层数据蓝图(Schema)、物料元数据(Component Meta)、运行时状态(Runtime Store)以及设计器用户界面(UI)之间的流转与协作机制。
6:
7: ---
8:
9: ## 1. 核心原则:底层数据统一,上层视图分离
10:
11: 在架构设计初期,开发者极易陷入“为特殊组件定制特殊结构”的陷阱。DatAlive 坚决摒弃在 Schema 中为悬浮组件设立“特权结构”(例如在 Page 节点下新增 `modals: Component[]` 数组)的做法。
12:
13: ### 1.1 Schema 实例层 (Instance Schema) 的绝对纯净
14: 在最终持久化存入数据库的 `design-mode` Schema 中,**万物皆为平等的 `Component` 节点**。
15:
16: * **统一的树模型**:悬浮组件仅仅是 `Page.components` 树中的一个普通节点。它拥有与普通组件完全一致的接口:`type`, `props`, `layout`, `style`, `children`。
17: * **嵌套无特权**:当用户向 `Modal` 内部拖入一个 `Table` 时,底层发生的数据结构变更仅仅是:向该 `Modal` 节点的 `children` 数组中 `push` 了一个 `Table` 节点。这份包含了弹窗及其内部所有子组件的完整 JSON 树,将原封不动地被导出或保存。
18: * **架构优势**
19: * **极简的渲染引擎**:引擎只需要一套极其简洁的递归遍历逻辑,即可完成包含弹窗在内的整棵组件树的解析,无需编写针对 `modals` 的额外逻辑。
20: * **交互动作泛化**:因为弹窗只是普通节点,所有的泛化 Action(如 `{"target": {"id": "modal_1"}, "method": "open"}`)可以无缝作用于它,无需修改目标选择器逻辑。
21:
22: ### 1.2 物料元数据层 (Component Meta) 的二元解耦
23: 悬浮组件之所以在 UI 上表现特殊,是因为它在**平台物料库的注册表(Component Registry)**中带有明确的静态元数据标识。我们必须严格区分“组件的静态特征”与“实例的运行时状态”。
24:
25: ```typescript
26: // 伪代码:在设计器源码(或 SDK)中注册 Modal 物料
27: Registry.register({
28: type: 'Modal',
29: name: '对话框',
30: category: '反馈',
31: icon: 'icon-modal',
32: isOverlay: true, // 🚨 核心标识:告知设计器这是一个悬浮容器
33: isContainer: true, // 允许向其内部拖入子组件
34: setters: [ ... ] // 属性配置器
35: });
36: ```
37:
38: * **彻底的解耦机制**:像 `isOverlay` 这种描述“物料类”固有属性的标识,**绝对不能**被抽离或序列化到用户的页面 Schema JSON 中。Schema 只保存实例特有的变动数据(如 `props.title`)。设计器在运行时通过 `type` 查字典(Type Mapping),动态获知该实例属于 `isOverlay: true` 的类别。
39:
40: ---
41:
42: ## 2. 设计器 UI 层面的协作机制 (MVC 映射)
43:
44: 基于上述底层架构,设计器在处理悬浮组件时,展现出极其优雅的 MVCModel-View-Controller)协作,确保“Schema”、“图层面板”和“主画布”三方的绝对同步。
45:
46: ### 2.1 唯一真相来源 (Single Source of Truth)
47: Zustand/Redux 内存中的 `Application Schema`(特别是当前 Page 的被扁平化或深层嵌套的 `components` 树)是唯一的 Model。视图层(图层面板、画布)本身不保存任何层级或显隐状态。
48:
49: ### 2.2 图层树面板的“动态分叉 (Forking)”
50: 为了避免主画布被多个隐藏的弹窗遮挡,设计器的左侧【图层面板】必须与普通组件隔离展示悬浮组件。
51:
52: * **纯粹的受控渲染**:图层面板订阅当前页面的 `components` 树。
53: * **动态过滤与分组**:遍历每个节点,通过 `type` 查验 Meta 字典。若 `Registry[type].isOverlay === true`,则将该节点分发至独立的“悬浮层(Overlays)”虚拟文件夹中进行渲染;否则留在“主图层”中。
54: * **所见即所得**:无论组件在图层树的哪个文件夹,它们在底层的 Schema 数组中依然是平级的。修改图层树的任何属性(如重命名、拖拽排序),都会 Dispatch Action 修改底层 Schema,触发全链路重绘。
55:
56: ### 2.3 画布渲染引擎的 Portal 挂载
57: 在运行模式(`runtime`)或预览模式(`preview`)下,渲染引擎解析到 `type: 'Modal'` 时,其对应的 React 组件内部实现应利用 `ReactDOM.createPortal` 技术。
58: 这使得 `Modal` 及其 `children` 被“传送”挂载到 `document.body` 节点下,从而脱离当前容器的 `overflow` 限制和局部的层叠上下文(Stacking Context),实现真正的全屏悬浮覆盖。
59:
60: ---
61:
62: ## 3. 设计态的交互困境与双路径解法
63:
64: 在设计态下,悬浮组件默认是隐藏的(通常由 `props.visible` 控制)。为了让实施人员能够向其内部拖入子组件(如表单、表格),平台提供两条并行的交互路径:
65:
66: ### 路径 A:画布强制唤醒 (Canvas Override) —— 面向直观操作
67: 这是为主流用户提供的所见即所得体验:
68: 1. **唤醒**:用户在左侧图层树的“悬浮层”目录中,点击该 `Modal` 节点旁的“眼睛(显示)”图标(修改 Schema 中的 `layer.hidden = false`)。
69: 2. **劫持**:设计器拦截其原有的业务可见性逻辑(如忽略绑定的 `visible` 变量),强制在主画布最顶层渲染该弹窗,并赋予极高的 z-index 以防止遮挡。
70: 3. **拖拽**:用户从物料区拖拽 `Table` 组件,直接在弹窗的实体区域(DropZone)内释放(Drop)。
71: 4. **写入**:拖拽引擎捕获释放事件,定位到目标容器,在底层修改 Schema 树,将 `Table` 插入 `Modal` 的 `children` 数组。
72:
73: ### 路径 B:图层树直接盲投 (Tree Drop) —— 面向精准控制
74: 这是为应对复杂层级遮挡或高级用户提供的极客体验:
75: 1. **绕过画布**:用户从物料区拖拽组件,**不经过中间的主画布**。
76: 2. **树形释放**:直接将组件拖拽至左侧【图层面板】中目标 `Modal` 所在的文件树节点上悬停并释放。
77: 3. **写入**:图层面板组件直接捕获 Drop 事件,并派发同样的 Action,将组件精准插入底层 Schema 对应节点的 `children` 数组。
78:
79: 这两条路径完美互补,且底层收敛于唯一的树状结构修改代码。这种“上层交互多态,底层逻辑唯一”的设计,确保了平台在处理复杂嵌套容器时的绝对健壮性。
(End of file - total 79 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 5.1s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\docs\\设计器静态依赖分析方案.md"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\docs\设计器静态依赖分析方案.md</path>
<type>file</type>
<content>
1: # DatAlive 设计器静态依赖分析方案 (Static Dependency Analysis)
2:
3: ## 1. 方案背景
4:
5: 在低代码平台(如 DatAlive)的架构设计中,实体间(如组件、交互、查询等)的动态变量关联是一个核心能力。为了提供超越简单模板语法(如 `{{variables.token}}`)的极致灵活性,本设计器在底层架构上全面拥抱了 **“基于纯函数的动态求值逃生舱”(Dynamic Expression / Code Escape Hatch**。
6:
7: 通过允许用户编写原生的 JavaScript/TypeScript 函数片段(即 Schema 中的 `DynamicExpression` 接口,如 `RestRequestByCode`、`ActionParamsByCode` 以及 `props` 的动态求值),平台能够支撑极其复杂的循环、数据转换与条件判断。
8:
9: 然而,这种黑盒化的代码片段如果缺乏平台级的管控,将导致设计器丧失“无用变量剔除”、“变量重命名级联更新”、“依赖关系拓扑图谱”等企业级的高级可视化工程能力。
10:
11: 因此,平台必须在**设计器外壳(Editor Shell**的底层引入**静态依赖分析(Static Dependency Analysis**机制,将用户编写的代码从“黑盒字符串”转化为平台可理解的“语法树模型”。
12:
13: ---
14:
15: ## 2. 数据结构设计基石(二元联合类型)
16:
17: 为了保持 Schema 的极致纯净并支撑未来的 AST 解析,所有支持动态求值的字段(如 `Action` 的 `params`、`Query` 的 `request` 等)均采用了 `fixed`(纯静态)与 `code`(动态求值)的二元联合类型设计:
18:
19: ```typescript
20: // 统一的参数联合类型示例 (ActionParams)
21: export type ActionParams = ActionParamsByFixed | ActionParamsByCode;
22:
23: export interface ActionParamsByFixed {
24: type: 'fixed';
25: /**
26: * 纯静态配置字典。
27: * 专用于设计器右侧属性面板上的简单固定值输入表单。
28: */
29: payload: Record<string, any>;
30: }
31:
32: export interface ActionParamsByCode extends DynamicExpression {
33: // 继承自 DynamicExpression
34: // type: 'code';
35: // code: string; (纯函数体字符串)
36: }
37: ```
38:
39: **分析边界**:设计器的依赖分析引擎**仅需要**拦截并解析那些 `type === 'code'` 的节点,而对于 `fixed` 节点,由于其结构是强类型的字典,引擎可以直接通过对象遍历提取依赖(如遇到特定的标识符),大幅降低了全局解析的性能开销。
40:
41: ---
42:
43: ## 3. 静态依赖分析的核心实现路径 (Toolchain Pipeline)
44:
45: 在获取到 Schema 中的 `code` 字符串后,设计器工具链需按以下管线执行依赖分析:
46:
47: ### 3.1 引入轻量级 AST 解析器
48: 在设计器编译层引入标准的 AST 解析器(如 `@babel/parser` 或 `acorn`)。当用户在代码编辑器(如 Monaco Editor)中完成 `code` 编写并失去焦点或保存时,立即触发异步解析。
49:
50: ### 3.2 标识符与上下文提取 (Identifier Extraction)
51: 遍历生成的抽象语法树(AST),重点拦截对上下文对象(`context`)的成员访问表达式(`MemberExpression`)。
52: - **静态属性访问**:例如 `context.variables.currentUser`AST 会明确暴露出 `variables` 和 `currentUser` 节点。
53: - **解构赋值分析**:例如 `const { variables } = context; return variables.token;`,通过作用域(Scope)和绑定(Binding)分析追踪其真实来源。
54:
55: ### 3.3 构建全局依赖图谱 (Dependency Graph)
56: 将提取出的依赖特征(如“依赖了变量 `token`”、“依赖了查询 `getUser`”)提炼为元数据(Metadata)。这部分元数据:
57: - **缓存在设计器的内存 Store 中**:用于在左侧“变量管理面板”中实时显示该变量被多少个组件引用,或在用户尝试删除某变量时弹出“该变量正被 3 个组件使用,确认删除?”的拦截警告。
58: - **持久化隔离**:这部分图谱数据**绝生存**于纯净的 Schema 数据库中,它仅作为设计态的辅助元数据。
59:
60: ### 3.4 变量重构与级联更新 (Refactoring & Cascading)
61: 当用户在全局变量面板中修改变量的 `key`(例如将 `token` 修改为 `accessToken`)时:
62: 1. **精准定位**:通过依赖图谱瞬间定位到所有使用了该变量的 `DynamicExpression` 节点。
63: 2. **智能重写**:利用 AST 转换工具(如 `@babel/traverse` 或 `jscodeshift`),将对应的 `MemberExpression` 节点属性名静默修改为新名称。
64: 3. **重新生成代码**:通过 `@babel/generator` 将修改后的 AST 重新序列化为代码字符串,更新回 Schema Store 触发保存。
65:
66: ---
67:
68: ## 4. 工程权衡与边界处理 (Trade-offs & Edge Cases)
69:
70: ### 4.1 性能考量
71: 在浏览器中高频运行 AST 解析器会带来极大的性能开销。
72: - **策略**AST 解析引擎应被放置在 **Web Worker** 中异步执行,绝对避免阻塞设计器的主线程(UI 线程);同时配合防抖(Debounce)机制,仅在用户结束连续输入后触发全量分析。
73:
74: ### 4.2 动态键名的分析盲区 (Analysis Blind Spots)
75: 对于高度动态的代码,静态分析存在天然的盲区:
76: - **场景**:例如 `const key = 'user' + 'Id'; return context.variables[key];`。
77: - **策略**:平台不承诺对计算属性(Computed Properties)和极度动态拼接的键名提供精准追踪。当 AST 遇到动态 `MemberExpression` 且无法推断其字面量时,设计器界面可以抛出警告级别的提示(Warning),告知实施人员:**“该动态依赖无法被设计器的重命名或剔除引擎接管,若修改上游变量名,需您自行维护此处的代码一致性。”**
78:
79: ---
80:
81: ## 5. 总结
82: 本方案确立了 DatAlive **“基于纯函数的动态求值逃生舱”** 结合 **“底层 AST 静态分析工具链”** 的工程路线。
83:
84: 它在赋予高级实施人员图灵完备的极客灵活性的同时,通过外围的解析引擎完美补齐了低代码平台在复杂逻辑下的重构与依赖管理能力。这是实现企业级低代码平台(Pro-Code 与 Low-Code 深度融合)不可或缺的架构护城河。
(End of file - total 84 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 4.5s)
**Tool: read**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\docs\\设计器选中与分组交互规范.md"
}
```
**Output:**
```
<path>D:\Projects\Private\datalive-design\docs\设计器选中与分组交互规范.md</path>
<type>file</type>
<content>
1: # DatAlive 设计器选中与分组交互规范 (Editor Selection & Drill-down Strategy)
2:
3: > 本文档定义了 DatAlive 设计器中核心的“选中(Selection)”与“下钻(Drill-down)”交互逻辑,旨在解决多层级组件嵌套下的精准选中难题。该规范参考了 Figma 等主流设计工具的交互模式,并严格适配 DatAlive 的底层 Schema 架构。
4:
5: ## 1. 核心概念 (Core Concepts)
6:
7: 为了实现自然流畅的深层嵌套组件操作,我们必须在编辑器**运行时的内存状态**中区分两个维度的“选中”概念:
8:
9: ### 1.1 选中态 (Selection State)
10: * **含义**:当前用户正在操作的目标组件。
11: * **表现**:显示实线边框、8个缩放控制点(Resize Handles)、右侧属性面板显示该组件属性。
12: * **操作**:此时按 Delete 删除该组件,按方向键移动该组件,或拖拽控制点改变其 `layout` 属性。
13: * **数据结构**`selectedIds: string[]`(支持多选)。
14:
15: ### 1.2 激活态 / 下钻态 (Activation State / Drill-down Context)
16: * **含义**:当前用户“进入”了某个容器组件(如 Group, Card, Modal)的内部上下文。
17: * **表现**:该容器本身不再被选中(无控制点),但显示虚线边框(或高亮背景),提示用户“你正在编辑这个容器内部的内容”。该容器外部的元素通常会变暗或不可响应点击(Focus Mode 专注模式)。
18: * **操作**:此时点击该容器内的任何子元素,将直接选中子元素,而不再选中该容器本身。
19: * **数据结构**`activeContainerId: string | null`。
20: * `null`: 默认状态,处在当前 `Page` 的画布根层级。
21: * `string`: 处在某个具体容器组件的内部。
22:
23: ---
24:
25: ## 2. 状态定义 (Store Definition)
26:
27: 前端状态管理(如 Zustand/Redux)在设计态(`AppMode === 'design'`)应维护以下瞬时字段(这些字段**绝对不存入**持久化的 Schema 数据库中):
28:
29: ```typescript
30: interface EditorSelectionState {
31: // 当前选中的组件 ID 列表
32: selectedIds: string[];
33:
34: // 当前激活的容器组件 ID(下钻上下文)
35: // null 表示未激活任何深层容器,处于 Root Page Context
36: activeContainerId: string | null;
37:
38: // 【架构约束】:为实现 O(1) 的层级查找,
39: // 引擎在加载 Schema 树 (Page.components) 时,必须在内存中构建一个扁平化的辅助字典,
40: // 动态推导出每个组件的 parentId(持久化 Schema 中坚决不存 parentId)。
41: // componentNodeMap: Record<string, { id: string, parentId: string | null, type: string }>;
42: }
43: ```
44:
45: ---
46:
47: ## 3. 交互行为矩阵 (Interaction Matrix)
48:
49: ### 3.1 单击行为 (Click)
50: **核心规则**:始终在 `activeContainerId` 定义的上下文中,寻找最高层级的子节点(冒泡拦截)。
51:
52: | 场景 | 当前状态 | 动作 | 结果状态 | 逻辑说明 |
53: | :--- | :--- | :--- | :--- | :--- |
54: | **基础选中** | `active: null` | 点击 Button A (根节点) | `sel: ['Button A']`<br>`active: null` | 最简单的选中。 |
55: | **选中容器** | `active: null` | 点击 Button B (属于 Group 1) | `sel: ['Group 1']`<br>`active: null` | 点击容器内元素,自动冒泡选中其最外层容器。 |
56: | **组内选中** | `active: 'Group 1'` | 点击 Button B (属于 Group 1) | `sel: ['Button B']`<br>`active: 'Group 1'` | 因为已激活 Group 1,点击其子元素直接选中子元素。 |
57: | **深层冒泡** | `active: 'Group 1'` | 点击 Button C (属于 Group 2,且 Group 2 在 Group 1 内) | `sel: ['Group 2']`<br>`active: 'Group 1'` | 冒泡到当前激活层级(Group 1)的直接子节点(Group 2)。 |
58: | **退出下钻** | `active: 'Group 1'` | 点击 Button A (根节点,在 Group 1 外) | `sel: ['Button A']`<br>`active: null` | 点击目标不在当前激活容器内,自动重置 `activeContainerId` 为 null,并选中目标。 |
59: | **空白重置** | 任意 | 点击画布空白处 | `sel: []`<br>`active: null` | 清空所有状态。 |
60:
61: ### 3.2 双击行为 (Double Click)
62: **核心规则**:进入(Drill-down)当前选中的容器组件内部。
63:
64: | 场景 | 当前状态 | 动作 | 结果状态 | 逻辑说明 |
65: | :--- | :--- | :--- | :--- | :--- |
66: | **进入容器** | `sel: ['Group 1']` | 双击 Group 1 | `sel: []` <br>`active: 'Group 1'` | **关键交互**:进入 Group 1 内部。此时 Group 1 变为容器上下文。 |
67: | **连续下钻** | `sel: ['Group 2']`<br>`active: 'Group 1'` | 双击 Group 2 | `sel: []`<br>`active: 'Group 2'` | 在 Group 1 内部继续下钻进入 Group 2。 |
68: | **文本编辑** | `sel: ['Button']` | 双击 Button | `active: 不变` | 【架构约束】:只有 `ComponentRegistry[type].isContainer === true` 的组件才允许双击下钻。原子组件双击通常触发“文本行内编辑”或忽略。 |
69:
70: ### 3.3 修饰键直选 (Cmd/Ctrl + Click)
71: **核心规则**:穿透所有嵌套容器,直接选中鼠标下方最深层的原子组件。
72:
73: | 场景 | 当前状态 | 动作 | 结果状态 | 逻辑说明 |
74: | :--- | :--- | :--- | :--- | :--- |
75: | **穿透选中** | `active: null` | Cmd + 点击 Button C (在 Group 2 中) | `sel: ['Button C']`<br>`active: 'Group 2'` | 直接选中 Button C。为了方便后续操作,建议自动将 `activeContainerId` 设置为 Button C 的动态 `parentId`(即 Group 2)。 |
76:
77: ---
78:
79: ## 4. 算法实现逻辑 (Implementation Logic)
80:
81: 以下伪代码描述了画布引擎在接收到底层 DOM 点击事件时的核心处理流程,完美适配了 DatAlive 的无 `parentId` 纯净 Schema 树架构:
82:
83: ```typescript
84: /**
85: * 处理画布上的组件点击事件
86: * @param targetId - 鼠标直接点击到的最深层 DOM 对应的组件 ID
87: * @param isDoubleClick - 是否为双击事件
88: * @param isCmdPressed - 是否按住 Cmd/Ctrl 键
89: */
90: function handleComponentClick(targetId: string, isDoubleClick: boolean, isCmdPressed: boolean) {
91: const {
92: componentNodeMap, // 引擎在内存中扁平化生成的辅助字典 (包含动态 parentId)
93: activeContainerId,
94: setActiveContainerId,
95: setSelectedIds
96: } = useEditorStore.getState();
97:
98: const targetNode = componentNodeMap[targetId];
99: if (!targetNode) return;
100:
101: // 1. 处理 Cmd/Ctrl 直选(穿透所有层级)
102: if (isCmdPressed) {
103: setSelectedIds([targetId]);
104: // 体验优化:直选后,自动激活其直接父级容器,方便后续点击旁边的兄弟元素
105: setActiveContainerId(targetNode.parentId || null);
106: return;
107: }
108:
109: // 2. 处理双击下钻 (Drill-down)
110: if (isDoubleClick) {
111: // 【架构防呆】:必须去物料元数据注册表中查询该组件是否为合法的容器!
112: // 坚决不能写死 targetNode.type === 'Group'
113: const meta = ComponentRegistry[targetNode.type];
114: if (meta?.isContainer) {
115: setActiveContainerId(targetId);
116: setSelectedIds([]); // 清空选中,等待用户在组内进行下一步点击
117: }
118: return;
119: }
120:
121: // 3. 处理普通单击(向上冒泡拦截逻辑)
122: let currentId: string | null = targetId;
123: let candidateId = targetId;
124:
125: // 沿着辅助字典的 parentId 向上遍历,寻找在当前激活上下文中的正确选中目标
126: while (currentId) {
127: const node = componentNodeMap[currentId];
128: const parentId = node.parentId;
129:
130: // 关键判断 A:如果当前节点的父级就是 activeContainerId
131: // 说明当前节点就是我们要选中的目标 (因为它刚好在当前“激活结界”的第一层)
132: if (parentId === activeContainerId) {
133: candidateId = currentId;
134: break;
135: }
136:
137: // 关键判断 B:如果父级是 null (说明遍历到了 Root Page)
138: // 且当前没有任何容器被激活,目标就是这个最外层的根组件
139: if (parentId === null) {
140: candidateId = currentId;
141: break;
142: }
143:
144: // 继续向上冒泡
145: currentId = parentId;
146: }
147:
148: // 4. 跨层级逃逸检测(用户点击了 activeContainer 外部的其他组件)
149: // 引擎利用预计算的 nodePath 或向上追溯,判断算出来的 candidateId 是否根本不是 activeContainer 的后代
150: if (activeContainerId && !isDescendant(componentNodeMap, activeContainerId, candidateId)) {
151: // 强制退出当前的下钻状态,重置为画布根层级
152: setActiveContainerId(null);
153: // 在根层级的上下文中,重新计算一次单击冒泡逻辑
154: return handleComponentClick(targetId, false, false);
155: }
156:
157: // 5. 最终应用选中状态,触发属性面板和控制点 UI 的重绘
158: setSelectedIds([candidateId]);
159: }
160: ```
161:
162: ---
163:
164: ## 5. 视觉反馈规范 (Visual Feedback)
165:
166: 为了让实施人员清晰感知当前的层级上下文,画布引擎必须提供极其明确的视觉反馈:
167:
168: | 状态 | 选中组件表现 | 激活容器表现 (Active Container) | 其他非相关元素表现 |
169: | :--- | :--- | :--- | :--- |
170: | **普通选中** | 实线蓝色边框<br>8个白色方块控制点<br>可拖拽移动 | N/A | 正常显示 |
171: | **下钻编辑中** | 实线蓝色边框<br>8个白色方块控制点<br>可拖拽移动 | **粗虚线灰色边框**<br>无控制点(不可拖拽)<br>左上角显示层级面包屑 (e.g. "Group A > Card B") | **半透明 (Opacity 0.6)**<br>屏蔽鼠标事件 (Focus Mode) |
172: | **悬停 (Hover)** | 蓝色细边框 (Outline)<br>不显示控制点 | N/A | 正常显示 |
173:
174: ---
175:
176: ## 6. 键盘快捷键辅助 (Shortcuts)
177:
178: 在复杂的层级树中,提供高效的键盘导航是专业设计器的标配:
179:
180: * **`Esc` (逃逸键)**:
181: * 场景 1:如果 `selectedIds` 有值,清空选中(`selectedIds = []`)。
182: * 场景 2:如果 `selectedIds` 为空,但 `activeContainerId` 不为空(处于下钻态),则**向上跳出一级**(将 `activeContainerId` 设为其 `parentId`)。
183:
184: * **`Enter` (深入键)**:
185: * 如果当前仅选中了一个 `isContainer: true` 的组件,直接进入该容器(等同于鼠标双击)。
(End of file - total 185 lines)
</content>
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 86.6s)
_Thinking:_
So DatAlive is a low-code/no-code design platform still in the schema design phase with no implementation yet, targeting digital dashboards primarily and middle-end systems secondarily, with plans for additional workspace engines down the line.
The tech stack centers on React with Vite, routing via Tanstack Router, data fetching through Tanstack Query, and state management with Zustand, all running on the Bun runtime with TypeScript in strict mode. The project currently lacks tests, linting, and build scripts, and there's a critical rule that I can only propose solutions without directly modifying the code—the user handles all actual changes.
The architecture maintains a clear separation between Schema (the persistent representation) and Store (the runtime working memory), with AppMode determining context across design, preview, and runtime states, and all entities following a base interface pattern with distinct roles for keys, labels, and IDs, while file organization uses kebab-case naming conventions and schema modules are structured under domain-specific directories.
The codebase uses tagged union patterns like `QueryConfigByRest` and `ComponentDataByStatic`, defaults entity arrays to empty rather than undefined, validates serializability through `JsonValue` type guards, and provides escape hatches via `DynamicExpression` for code-based logic and dual-mode `Condition` and `Filter` structures. The docs folder contains five design specifications covering runtime modes, entity lifecycle, dependency analysis, floating components, and selection interactions, and the existing AGENTS.md file documents the collaboration rule, current phase focused on schema design, the tech direction, and references to five planned engines.
For agents working on this project, the critical constraints are: never modify code directly and only present plans, recognize this is purely a schema design phase with no implementation yet, understand that no build or test scripts exist and TypeScript validation runs via `tsc --noEmit`, use Bun as the package manager, follow kebab-case file naming and `<Type>By<Variant>` tagged union conventions, maintain the schema versus store architectural separation, ensure all schema nodes inherit from `Entity` with strict boundaries between `label`, `key`, and `id`, use `JsonValue` as the serialization boundary, leverage `DynamicExpression` and dual-mode patterns for code escape hatches, default entity collections to empty arrays, consult the design specs before making changes, and remember there's no CI, tests, or linting—only manual verification through type checking.
Now I'm restructuring AGENTS.md to be more useful for agents by keeping the project description concise, making the collaboration rule much more prominent since it's the most critical constraint, adding concrete agent guidance around available commands and conventions, and referencing the docs folder for design specifications. Writing the agent guidelines... 现在我在梳理 REST 配置中 `null` 的特殊语义——它表示从全局配置中物理删除该键,而不是简单置空。同时,数组字段必须显式使用空数组 `[]` 而不是 `undefined`,这样引擎才能正确处理。在命名上,文件采用 kebab-case,联合类型用 `<Type>By<Variant>` 的模式,每个 schema 域都通过 barrel 文件导出,跨域引用时走 barrel 路径。目前的域目录包括 application、component、data-source 等十个模块,所有注释和文档用中文,代码标识符用英文。
在实现新增或修改 Schema 字段前,需要先对齐 `docs/` 下的四份设计文档——应用运行模式、实体生命周期、静态依赖分析和悬浮组件架构——确保不违反既有规范。远期技术栈方向是 React + Vite + 路由库 + 数据查询库 + 状态管理库,但当前阶段不要引入运行时实现代码。最后要避免的常见误区是不要把 `parentId` 写进 Schema(它由 Store 推导),也不要为悬浮组件在 Page 上新增数组字段。
I have enough context to update `AGENTS.md`. The repo is a TypeScript-only Schema design project (no implementation yet, no scripts in `package.json`, all comments and docs in Chinese), with a critical collaboration rule that an agent would otherwise violate. Let me check a couple more things to confirm before writing.
**Tool: bash**
**Input:**
```json
{
"command": "$env:CI='true'; $env:DEBIAN_FRONTEND='noninteractive'; $env:GIT_TERMINAL_PROMPT='0'; $env:GCM_INTERACTIVE='never'; $env:HOMEBREW_NO_AUTO_UPDATE='1'; $env:GIT_EDITOR=':'; $env:EDITOR=':'; $env:VISUAL=''; $env:GIT_SEQUENCE_EDITOR=':'; $env:GIT_MERGE_AUTOEDIT='no'; $env:GIT_PAGER='cat'; $env:PAGER='cat'; $env:npm_config_yes='true'; $env:PIP_NO_INPUT='1'; $env:YARN_ENABLE_IMMUTABLE_INSTALLS='false'; git log --oneline -20",
"description": "Check recent commit history",
"workdir": "D:\\Projects\\Private\\datalive-design"
}
```
**Output:**
```
a2f09fe refactor: 将 /core 重命名为 /shared
6d99132 refactor: 简化schema结构,更新AGENTS.md
c3846da docs: 新增 DatAlive 设计器核心架构设计文档
12b9ab3 feat(schema): 新增低代码设计器核心数据结构定义
7013c0f initial commit
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 7.3s)
**Tool: bash**
**Input:**
```json
{
"command": "$env:CI='true'; $env:DEBIAN_FRONTEND='noninteractive'; $env:GIT_TERMINAL_PROMPT='0'; $env:GCM_INTERACTIVE='never'; $env:HOMEBREW_NO_AUTO_UPDATE='1'; $env:GIT_EDITOR=':'; $env:EDITOR=':'; $env:VISUAL=''; $env:GIT_SEQUENCE_EDITOR=':'; $env:GIT_MERGE_AUTOEDIT='no'; $env:GIT_PAGER='cat'; $env:PAGER='cat'; $env:npm_config_yes='true'; $env:PIP_NO_INPUT='1'; $env:YARN_ENABLE_IMMUTABLE_INSTALLS='false'; git status",
"description": "Check git status",
"workdir": "D:\\Projects\\Private\\datalive-design"
}
```
**Output:**
```
On branch master
Your branch is up to date with 'gitea/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/schema/component/component.ts
modified: src/schema/shared/css-properties.ts
no changes added to commit (use "git add" and/or "git commit -a")
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 54.0s)
_Thinking:_
Now I'm reviewing the existing AGENTS.md to understand what needs updating—it's mostly descriptive prose about the project, so I'll condense that while keeping the critical collaboration rule front and center, then add practical guidance like specific commands, naming conventions, schema requirements, and common mistakes to watch for, with pointers to the relevant documentation.
Now I'll rewrite `AGENTS.md` with high-signal, agent-actionable content. The most critical insight to preserve and elevate: line 67 of the existing file forbids the agent from modifying code — most agents would miss this entirely.
**Tool: write**
**Input:**
```json
{
"filePath": "D:\\Projects\\Private\\datalive-design\\AGENTS.md",
"content": "# DatAlive 低代码设计器 - Agent 指南\n\n## 头号铁律:不要直接改代码\n\n> **严禁直接修改项目源代码。** Agent 的工作模式:阅读 → 给出方案/代码片段 → 由用户手动落地。\n> 未经用户明确指示前,不要使用 `Edit` / `Write` / `ast_grep_replace` / `lsp_rename` 等会改动文件的工具。\n> 这是项目自定的最高优先级协作规则(见历史 commit `6d99132`),优先级高于一切默认行为。\n\n## 项目阶段(决定 Agent 期望)\n\n- 当前仅有**核心 Schema 类型定义**,全部位于 `src/schema/`。\n- `src/index.ts`、`src/schema/index.ts` **当前为空**,没有运行时代码、组件、引擎、测试。\n- README 中的 `bun run index.ts` 不会有任何输出 —— 不要尝试\"运行\"或\"测试\"项目。\n- 远期路线(未实现,勿擅自引入):`React + Vite + React Router/Tanstack Router + Tanstack Query + Zustand`,五大引擎(工作区 / 画布 / 渲染 / 数据 / 交互)。\n\n## 工具链与命令\n\n- 包管理:**Bun**lockfile = `bun.lock`)。**勿用 npm/pnpm/yarn**。\n- `package.json` **没有 `scripts` 字段**,不存在 lint / test / build / typecheck 命令。\n- 类型检查唯一手段:`bunx tsc --noEmit``tsconfig.json` 已 `noEmit: true` + `strict` 全开 + `noUncheckedIndexedAccess`)。\n- 没有 ESLint / Prettier / Vitest / CI / pre-commit / codegen / dev server。\n\n## 语言\n\n- 注释、文档、PR/commit 信息均用**中文**;类型与变量用英文。新增内容请保持。\n- `docs/` 下文件名为中文,部分终端(PowerShell + GBK)会显示乱码 —— 用 `Glob \"docs/*.md\"` 或 `Read` 直接以 UTF-8 读取,不要试图通过 shell ls 解读。\n\n## Schema 架构铁律\n\n修改 `src/schema/**` 前必读。这些约定在多个文件的 JSDoc 中反复强调,违反会污染整个蓝图:\n\n1. **Schema vs Store 二元分离**\n - Schema 仅承载需要持久化、可导入导出的稳定配置。\n - 派生状态(`isLoading / data / error`)、`AppMode`、`parentId`、设计器选中态、缓存等**永不进 Schema**。\n2. **`AppMode` 不是 Schema 字段**\n - `design / preview / runtime` 三态由宿主容器在运行时注入。\n3. **所有可寻址实体继承 `Entity`** (`src/schema/shared/entity.ts`)`id`UUIDRPC 寻址 + React key+ `label`(仅 UI 展示)。\n - **`label` 严禁出现在表达式或数据流绑定中**,那里只能用 `id` 或 `Variable.key`。\n4. **`JsonValue`** (`src/schema/shared/json-value.ts`) 是序列化铁壁。Schema 中用户可填写的纯数据字段必须用它,不要用 `any` / `object` / `unknown`,更不能允许 `Function / Date / RegExp`。\n5. **`DynamicExpression`** (`src/schema/shared/dynamic-expression.ts`) 是统一逃生舱:`type: 'code'` + `code: string`**函数体片段**,需含 `return`)。引擎用 `new Function('context', code)` 编译,注入 `context: { variables, queries, event?, data? }`。新增 code 模式字段一律 `extends DynamicExpression`。\n6. **二元联合 `fixed | code` 模式** —— 见 `RestRequest`、`ActionParams`、`Condition`、`Filter`、`QueryConfig`、`MutationConfig`、`DataSourceConfigByRest`。新增可逃生字段时延续此命名与判别式。\n7. **`null` 在 REST 配置中具有显式抹除语义**`RestRequestByFixed.params/headers` 中的 `null` 表示从合并后的全局 DataSource 配置中**物理删除**该键,并非置空字符串。\n8. **数组字段必须显式 `[]`,不允许 `undefined`** —— `interactions` / `children` / `routes` / `pages` / `filterIds` 等。引擎依赖此约定避免兜底分支。\n9. **没有 `MutationConfigByStatic`** —— 写操作必有副作用,不允许 mock 变体。\n10. **Action 不枚举具体类型**(拒绝 `ShowToast` / `Navigate` 这类设计),统一用 `target: { type, id } + method + params` 的泛化 RPC 模型。\n11. **`Variable.key` 是表达式中可见的真实标识符**:必须满足 `/^[a-zA-Z_$][a-zA-Z0-9_$]*$/` 且全局唯一;与 `label` 严格区分。\n12. **`parentId` 永不持久化**:组件树是嵌套结构,扁平 `parentId` 字典由引擎在内存中按需推导。\n\n## 命名与组织约定\n\n- 文件名 **kebab-case**`component-data.ts`、`data-source-by-rest.ts`。\n- 标记联合命名 **`<Type>By<Variant>`**`ComponentDataByStatic`、`QueryConfigByRest`、`FilterByCode`、`DataSourceConfigByRestWithCode`。\n- 每个 schema 域目录有自己的 `index.ts` barrel;跨域请从 barrel `import type { ... } from '../shared'`,不要写 `'../shared/entity'`。\n- 现有域:`application / component / data-source / filter / interaction / mutation / page / query / route / shared / variable`。\n- `src/schema/index.ts` 当前为空;如需新增聚合导出,请在此处汇总。\n\n## 设计文档(在 `docs/`,修改 Schema 前对齐)\n\n- `应用运行模式架构规范.md` — `AppMode` 三态边界、`preview` 与 `runtime` 的同源异构差异。\n- `实体生命周期引擎实现规范.md` — `Interaction.event` 命名表(`onLaunch / onLoad / onEnter / onLeave / onChange / onSuccess / onError / onSettled / onMount / onUnmount` 等)以及谁挂载哪些事件。\n- `设计器静态依赖分析方案.md` — `DynamicExpression` 的 AST 解析、依赖图谱、变量重命名级联策略。\n- `设计器悬浮组件架构规范.md` — Modal/Drawer 不享受 Schema 特权;`isOverlay` 是 Component Registry 元数据,不进 Schema。\n- `设计器选中与分组交互规范.md` — `selectedIds` / `activeContainerId` 仅在设计态运行时 Store。\n\n## 常见误区\n\n- 不要在 `Page` 下新增 `modals: Component[]` 等\"特权数组\",悬浮组件统一走 `components` 树。\n- 不要把组件注册元数据(`isOverlay` / `isContainer` / `setters`)放进 Schema —— 那是物料库静态字典。\n- 不要把 `prefetch / polling / enabled` 等查询配置复制到 `Mutation``Mutation` 只能命令式触发。\n- 不要把 React 生命周期等价于业务生命周期 —— 全部走 `Interaction`,避免 Strict Mode 双调用陷阱。\n"
}
```
**Output:**
```
Wrote file successfully.
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 10.3s)
**Tool: bash**
**Input:**
```json
{
"command": "$env:CI='true'; $env:DEBIAN_FRONTEND='noninteractive'; $env:GIT_TERMINAL_PROMPT='0'; $env:GCM_INTERACTIVE='never'; $env:HOMEBREW_NO_AUTO_UPDATE='1'; $env:GIT_EDITOR=':'; $env:EDITOR=':'; $env:VISUAL=''; $env:GIT_SEQUENCE_EDITOR=':'; $env:GIT_MERGE_AUTOEDIT='no'; $env:GIT_PAGER='cat'; $env:PAGER='cat'; $env:npm_config_yes='true'; $env:PIP_NO_INPUT='1'; $env:YARN_ENABLE_IMMUTABLE_INSTALLS='false'; (Get-Item AGENTS.md).Length",
"description": "Check new AGENTS.md file size",
"workdir": "D:\\Projects\\Private\\datalive-design"
}
```
**Output:**
```
6135
```
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 15.4s)
Updated `AGENTS.md` (6135 bytes, was 5517). Key changes:
**Elevated to top:** the "do not modify code" rule (was buried at line 67 of the old file). This is the single most important constraint and would otherwise be violated by default agent behavior.
**Added agent-actionable sections:**
- **项目阶段** — explicit "schema-only, no implementation, don't try to run" so agents stop hunting for entrypoints.
- **工具链与命令** — Bun (not npm), no `scripts` in `package.json`, type check is `bunx tsc --noEmit` only, no lint/test/CI.
- **Schema 架构铁律** — 12 hard-earned invariants extracted from the JSDoc (Schema vs Store, `Entity`, `JsonValue`, `DynamicExpression`, dual-mode `fixed|code`, REST `null` erasure semantics, no `MutationConfigByStatic`, generalized RPC Action, `parentId` never persisted, …).
- **命名与组织约定** — kebab-case files, `<Type>By<Variant>` tagged unions, barrel `index.ts` per domain.
- **语言** — Chinese is the project language; PowerShell GBK terminal mangles `docs/` filenames (use `Glob`/`Read`).
- **设计文档** — pointers to all 5 spec files in `docs/`.
- **常见误区** — top pitfalls extracted from the design specs.
**Removed:** the long prose project background, repeated philosophical justifications, and verbose tech-stack discussion — those don't help an agent avoid mistakes and bloat the file.
Per project rule, I have not modified anything else. Let me know if you want me to adjust scope, tone, or split sections out into separate referenced files.
---
## User
你应该注意到我们的数据机构设计目前重点针对数字大屏场景,不过针对其他的场景我也预留了一些字段用户后期进行扩展,我想请你评估一下目前的数据结构设计是否已经能够覆盖各种数据大屏业务需求?
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 147.5s)
_Thinking:_
I'm analyzing whether the current data structure can handle all the requirements for digital dashboards, particularly around responsive design for different screen resolutions like 1920x1080 and other common sizes.
Looking at the schema, I can see the Application model includes pages, variables, data sources, queries, and interactions, but I need to check if there are specific fields for handling multi-screen layouts, real-time data refresh mechanisms like WebSocket or SSE, animation configurations, and visual effects. I'm also considering whether the schema supports theme systems, carousel functionality, data caching, authentication, role-based permissions, audit logging, internationalization, export capabilities, time range filtering, data replay features, performance optimization for large datasets, watermarks, keyboard shortcuts, event-based communication between components, data preprocessing, scheduled tasks, error handling, component templates, and OAuth authentication.
Now examining the detailed schema structure—Pages support layouts (free/flex/flow) with styling and components, Components have conditional rendering and data binding through queries or static values, and Interactions handle events with actions that can target other components. However, I notice the schema doesn't explicitly define fields for theme configuration at the application level, real-time push mechanisms like WebSocket or SSE subscriptions, animation or transition properties on components, visual effect parameters, data caching strategies, user authentication and role-based access control, audit logging, internationalization settings, export/print functionality, time range selectors, historical data replay, virtualization for performance, watermark configuration, keyboard shortcut mappings, an event bus for cross-component communication, data preprocessing pipelines, scheduled task definitions, error fallback UI components, component template/preset systems, master-detail view configurations, or data source authentication methods like OAuth.
The biggest gaps appear to be the theme system—dashboards rely heavily on consistent color palettes and dark/light modes but there's no centralized theme configuration at the Application level, forcing each component to manage its own styling.
Real-time data channels are also critical but missing—the schema only supports HTTP polling, whereas production dashboards need WebSocket connections, Server-Sent Events, and MQTT for IoT scenarios, with proper retry logic and backoff strategies.
Asset management is another gap—there's no registry for images, fonts, videos, or Lottie animations at the Application level, so currently assets must be hardcoded as URLs in component props.
Finally, the scaling and responsive design system needs refinement—while FreeLayout has basic scale modes, it lacks multi-resolution support for different screen sizes (1080p, 4K, ultra-wide), aspect ratio constraints, anchoring rules for components during scaling, and pixel-perfect rendering for high-DPI displays.
Looking at the remaining gaps: component-level animations are missing entirely—entrance/exit effects, continuous animations, timeline orchestration, and state transition animations are all absent despite being fundamental to digital dashboards. Page rotation and carousel functionality would enable auto-advancing between multiple pages with configurable transitions and pause-on-hover behavior. The schema also lacks global time range concepts and timezone handling, which are essential for control room dashboards. Error handling needs improvement with fallback data, stale-while-revalidate semantics, and retry policies defined in the schema itself. Caching strategies should include TTL, stale time, and garbage collection fields similar to TanStack Query. And data linking between components—drill-down interactions and cross-filtering—appears to be cut off but is clearly another high-priority feature for dashboards. Computed variables would need to auto-update based on other variables and queries rather than requiring filters or expressions in props each time. Moving through the remaining gaps: virtual scrolling and pagination belong in the component layer, fonts lack a registration system, large-screen features like maps and 3D are component-specific, and background elements (video, lottie, particles) can only be added via components rather than as first-class schema features. Data prefetching exists but without priority levels or dependency visualization, app-level splash screens aren't configurable, and lazy loading boundaries aren't marked despite remote source support.
The most critical additions for a digital dashboard platform are a theme system with application-level tokens, real-time data channels like WebSocket or MQTT, entrance and continuous animations, automatic page rotation, computed derived fields, centralized asset management, and responsive variants for different screen sizes and resolutions. Medium priorities include global time range selection, a custom event bus, and data cache strategy fields for staleness and garbage collection.
I'm also noting that retry and fallback strategies, cron-based scheduling, cross-chart data linking with drill-down capabilities, error state UI placeholders, and role-based visibility controls would strengthen the platform significantly. Lower priority features like internationalization, watermarking, and splash screens can likely be handled through existing extensibility mechanisms.
Now I'm structuring a comprehensive assessment that evaluates whether the current schema design adequately covers digital dashboard business requirements, organized by what's already supported, critical gaps ranked by priority, and suggested architectural directions for each gap without modifying any existing code. I'm settling on a structure for the assessment: start with a direct answer to whether the schema covers typical digital dashboard needs, then break down the gaps by priority and impact, note which limitations can be worked around with existing features, and finally call out what can be deferred. This should give the user a clear picture without overwhelming them with change requests they didn't ask for.
# 数字大屏场景覆盖度评估
## 总体结论
**当前 Schema 已能描述"一块屏、一种分辨率、HTTP 轮询、静态化样式"的标准大屏**,但若要覆盖工业控制中心、IoT 监控、政务驾驶舱、媒体演播等典型大屏的全部需求,仍存在 **5~7 个一级能力缺口** 需要在后续扩展。
下面按"覆盖良好 / 关键缺口 / 可绕路实现 / 可暂缓"四类逐项评估,每个缺口给出建议方向(仅描述结构思路,不动代码)。
---
## 一、覆盖良好的能力
| 能力 | 落点 |
|---|---|
| 单屏自由排版 + 缩放 | `FreeLayout.scaleMode: 'fit'\|'fill'\|'flow'\|'none'` |
| 2D/3D 形变 | `ComponentLayoutByFree.transform.{rotateX/Y/Z, scaleX/Y}` |
| 数据请求轮询 | `Query.polling.{enabled, interval}` |
| 数据预热 | `Query.prefetch` |
| 数据清洗管道 | `Filter[]` + `Query.filterIds` |
| CQRS 读写分离 | `Query` / `Mutation` |
| 串行依赖 / 条件挂起 | `Query.enabled: Condition` |
| 全局响应式状态 | `Variable` + `onChange` 联动 |
| 事件 → 动作链 | `Interaction.actions: Action[]` + 防抖/节流 |
| 图灵完备逃生舱 | `DynamicExpression``code` 模式覆盖 props/params/condition/filter/request |
| 路由级生命周期与守卫 | `Route.interactions` (`onEnter` / `onLeave`) |
| 设计/预览/运行三态 | `AppMode`(外部注入,不污染 Schema) |
> 这块"骨架"是合格的,已经能描述**一块定制好的大屏**。问题集中在它对**主题、多分辨率、实时通道、动效、轮播**这些大屏高频需求支持不足。
---
## 二、关键缺口(强烈建议补齐)
### 1. ⚠️ 主题系统(Theme Tokens)—— 缺失
**现状**:颜色、字体、字号全部散落在 `Component.style: CSSProperties` 字面量中。
**痛点**:换肤(亮/暗)、品牌色批量替换、多租户主题需要逐个组件改。
**建议方向**:在 `Application` 层引入 `themes: Theme[]``activeThemeId``Theme` 持有 token 字典(colorPrimary、bgScreen、chartPalette 等)。`CSSProperties` 允许引用 token
```ts
style: { color: { type: 'token', tokenId: 'colorPrimary' } | string }
```
### 2. ⚠️ 实时数据通道(WebSocket / SSE / MQTT)—— 缺失
**现状**`DataSource = DataSourceByRest | DataSourceByDatabase(TODO)`,只有"轮询"近似等价。
**痛点**:IoT、工控、监控大屏的核心需求是**服务端推送**,1Hz 轮询无法替代。
**建议方向**
- 新增 `DataSourceByWebSocket` / `DataSourceBySSE` / `DataSourceByMqtt`(保持 `<Type>By<Variant>` 命名约定)。
- 引入 `Subscription` 实体(与 `Query` 同级,但语义为"持续推流"),订阅方式与 `Query` 类似但不带 `polling`,自带 `onMessage` 类型 Interaction。
### 3. ⚠️ 动画 / 动效系统 —— 缺失
**现状**`transform` 只能定义**静态**形变,没有时间维度。
**痛点**:大屏几乎离不开入场动画、循环呼吸光效、数字滚动、过渡曲线。
**建议方向**:在 `Component` 上新增 `animations: Animation[]`
- `trigger: 'mount' | 'unmount' | 'dataChange' | 'manual'`
- `keyframes` + `easing` + `duration` + `loop`
- 通过 `Action.method: 'play' | 'stop' | 'reset'` 由交互触发。
- 高级阶段考虑 `AnimationTimeline` 实体协调多组件时序。
### 4. ⚠️ 页面轮播 / 多屏切换 —— 缺失
**现状**`Application.routes` 描述路由,但没有"自动轮播"概念。
**痛点**:指挥中心常见多页轮播(每 30s 切一屏)。
**建议方向**:在 `Route` 或新增的 `RotationGroup` 实体上配置 `autoplay: { enabled, interval, transition: 'fade'|'slide'|... }`。或更轻量:在 `Page` 新增 `rotation` 字段。
### 5. ⚠️ 计算变量 / 派生状态(Computed) —— 缺失
**现状**:要"A、B 变更后自动重算 C",只能用 `Variable.interactions[onChange]` 手动写 `setVariable(C)` 链,或在每个组件 props 里重复写代码表达式。
**痛点**:缺少**响应式 derived**的一级支持,导致联动逻辑碎片化、命名不可寻址。
**建议方向**:新增 `ComputedVariable extends Entity`,结构类似 `Variable``value` 替换为 `expression: DynamicExpression`,引擎自动追踪依赖(与现有"静态依赖分析方案"对齐)。Schema 层只持久化表达式与 `key`,运行时由 Store 缓存结果。
### 6. ⚠️ 多分辨率 / 多比例适配 —— 弱
**现状**`FreeLayout` 只持有一对 `width/height``scaleMode` 处理整体等比缩放,但同一份 Schema **无法**对 1920×1080 / 3840×2160 / 3840×1080(超宽)/ 1080×1920(竖屏)给出不同的布局。
**痛点**:竖屏数字标牌、超宽指挥屏、4K 与 1080p 共用源时,布局错位。
**建议方向**
- 简版:`FreeLayout` 增加 `breakpoints: { name, minAspect, maxAspect, overrides: Partial<...> }[]`
- 完整:`Page.layout` 升级为 `layouts: PageLayout[]`,由引擎根据宿主尺寸择优挂载。
- 组件层加 `anchor: 'top-left'|'center'|'bottom-right'|...` 控制缩放锚点(大屏特别需要)。
### 7. ⚠️ 全局静态资源(Assets Registry)—— 缺失
**现状**:图片/视频 URL 直接写死在 `props` 字面量。
**痛点**:CDN 域名变更需要全局搜索;离线大屏需要打包资源;字体声明无处挂载。
**建议方向**`Application.assets: Asset[]`,每个 Asset 持有 `id / type: 'image'|'video'|'font'|'lottie' / url / preload`。组件 props 引用 `assetId` 而非裸 URL。
---
## 三、可通过现有逃生舱实现,但建议升级为一级能力
| 能力 | 当前可行做法 | 提级建议 |
|---|---|---|
| **跨图表联动 / 钻取** | Action `target.type='component'` + 全局 Variable 中转 | 新增 `LinkageGroup` 实体描述"高亮组、时间轴组",避免每个大屏自行约定变量名 |
| **全局时间范围 / 全局过滤器** | 用 Variable 承载,所有 Query 在 `enabled` / `request.body` 里读取 | 新增 `GlobalFilter` 实体,自动注入到组下的 Query |
| **数据降级与重试** | `Query.interactions[onError]` 派发兜底 Action | `Query` 上新增 `retry: { count, backoff }``fallbackData: JsonValue` |
| **缓存策略** | 全靠引擎默认 | `Query` 暴露 `staleTime`/`gcTime`TanStack Query 一级字段) |
| **自定义事件总线** | 用 Variable + onChange 模拟广播 | `Action.target.type` 新增 `'event'`,对应 `CustomEvent` 实体 |
| **角色 / 权限可见性** | `Component.condition` 内手写 `context.user.role==='admin'` | 抽象 `Component.permission: { roles: string[] }` 一级字段 |
| **调度任务(cron** | `Query.polling.interval=86400000` 粗暴顶替 | `Query.schedule: { cron: string, timezone: string }` |
| **启动屏 / 全局加载** | 在第一个路由的 `onLoad` 里手撸 | `Application.splash` 一级字段(持有 assetId 与最短显示时长) |
---
## 四、可暂缓的能力(属于物料层 / 运行时层,不必污染 Schema)
- **图表组件内部能力**(虚拟化、分页、坐标系、地图投影)→ 应在 `Component Registry` 元数据与组件实现里解决。
- **可访问性 / aria** → 由组件实现承担。
- **截图防护 / 水印** → 运行时容器策略,不进 Schema。
- **埋点 / Analytics** → 引擎层中间件,不进 Schema。
- **国际化 i18n** → 后期可在 Application 加 `locales` 字典并约定 props 引用 `{ type: 'i18n', key: '...' }`,但短期内不阻塞数字大屏场景。
---
## 五、需要警惕的隐性风险(与"覆盖度"相关但不属于字段缺口)
1. **`new Function('context', code)` 与 CSP**:严格 CSP 环境(如政企内网)禁用 `unsafe-eval`,整个 `DynamicExpression` 体系会失效。需在引擎层规划"AOT 预编译 + 字符串映射表"或 Web Worker 隔离方案,否则大屏部署后会大面积报错。
2. **`Polling.interval` 没有上下界与抖动**:100 个图表同步 1s 轮询会形成"心跳风暴"。建议 Schema 层加 `jitter` 或运行时强制错峰。
3. **`Component.style: CSSProperties``string | number` 是宽口子**:高频出现的 `'100vh'``'10rem'``'calc(...)'` 在 4K 大屏 `scaleMode: 'fit'` 下会与缩放层冲突。需要约定"大屏优先用纯像素 number 而非 string"。
4. **`Mutation` 在预览态的副作用**:现有 Schema 没有"沙箱标记"。若要落地"预览态拦截 Mutation"(已在文档里承诺),需要在引擎层而非 Schema 层处理,需提前规划。
---
## 六、推荐的下一步(仅供决策参考,待你确认后再讨论结构细节)
按收益/复杂度排序,建议优先补齐:
1. **WebSocket/SSE 数据源**(数字大屏一级刚需,没有等价替代)
2. **主题系统**(影响所有组件 style 设计)
3. **计算变量**(解决联动逻辑碎片化,与已有"静态依赖分析方案"天然契合)
4. **多分辨率断点**(决定 Schema 是否能跨屏复用)
5. **页面轮播 / 全局时间范围**(指挥中心强需求)
6. **动画系统**(视觉冲击力,可分阶段:先 mount 入场,后做时间轴)
7. **资产注册表**(部署/换 CDN 时受益)
是否要我针对其中某一两项,输出更详细的字段级设计方案(仍只展示,不动代码)?
---
## User
- 对于WS数据源,我觉得相较于目前的数据源结构,想要扩展也不困难,我可以稍后再做改进;
- 对于计算变量,我的态度可能偏保守,我希望你可以与我现在的方案做比较然后给出更有说服力的理由以及实现方案;
- 如果我要做主题系统,现在的数据结构需要做哪些改进;
- 你提到多分辨率,我认为在实际业务场景中客户方肯定只有一块物理屏幕,我们不可能设计各页面尺寸不同的一套大屏;
- 其他的几个缺口我暂且认为并不关键,当然如果你有不同意见也可以再尝试说服我。
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 210.3s)
_Thinking:_
The user is asking me to focus on a few key areas: they're fine with handling the WS data source themselves, but they want me to dig deeper on computed variables by comparing my approach with their current solution and making a stronger case for it along with an implementation roadmap. They're also curious about what structural changes would be needed if they decided to implement a theme system, and there's a fourth point about multi-resolution that got cut off.
Looking at the multi-resolution point, they're pushing back because they see their customers as having a single physical display, so they don't see the value in supporting multiple resolutions. On the remaining gaps, they're open to hearing my perspective if I think any are critical.
I need to be careful here—I can't modify code directly, only present plans. The schema is already in place, and I should ground my arguments in real usage scenarios for digital dashboards rather than being pushy. For the computed variables specifically, I'm thinking through why their current approach of chaining variable changes and using setVariable might feel sufficient to them, but there are real problems with that pattern that I should articulate clearly. 相比之下,ComputedVariable 的优势在于它能自动追踪依赖关系,确保派生值始终基于最新的源数据重新计算,同时支持缓存优化。它与现有系统的静态分析方案天然兼容,能够被 props 绑定、Action 条件和 Interaction 监听,还能从 Query 数据派生,避免了命令式链式派发的循环风险和重复代码问题。
现在我在设计实现方案,定义 ComputedVariable 的核心结构,包括表达式求值、类型守卫和 onChange 交互,同时在 Application 层面添加对应的字段来存储这些派生变量。
我倾向于让 Variable 本身支持两种模式(静态和计算),通过 source 判别式来区分,这样既能保持 `context.variables.<key>` 的统一寻址,又不需要修改现有的 props 绑定和依赖分析逻辑。运行时需要在加载时构建依赖图检测循环引用,并实现求值缓存机制,同时在设计器层对不安全的交互操作给出警告。
现在转向主题系统的设计,需要先分析 Token 的定义位置和其他关键问题。
核心改动包括新增 Theme 实体来存储主题配置和 Token 映射,在 Application 层添加主题列表和默认主题字段,升级 CSSProperties 以支持 Token 引用和回退值,将当前主题状态交由运行时 Store 管理,并通过系统级 Action 来处理主题切换。
DynamicExpression 的 context 扩展了 theme 字段让代码能读取当前激活主题的 Token 值,而 Component.style 中的 Token 引用在运行时通过 CSS 变量解析为实际值以避免重渲染。
关于深色浅色的实现,我倾向于让所有主题共享同一套 Token 命名空间(如 colorBg、colorText),不同主题只是给相同 Token 赋予不同值,这样组件样式始终引用 Token 而不是具体颜色。对于 props 中的颜色字段,可以选择将 TokenRef 编码为 DynamicExpression 来保持与现有系统的兼容性。
需要修改的文件包括 CSS 属性定义、Token 类型导出、ComponentPropValue 类型扩展,以及在应用层添加主题配置和新建主题相关的 schema 文件。
关于多分辨率的问题,虽然客户通常只有一块物理屏幕,但实际场景中屏幕升级(如从 4K 到 8K)或在不同环境复用同一 Schema(演示用 1080p、部署用 4K)都很常见,所以保留多分辨率支持的灵活性是有价值的。
不过现有的 `scaleMode: 'fit'` 已经能处理大多数场景,所以这个需求可以接受延后。
对于其他功能缺口,我在评估动画系统、页面轮播、资产注册表和全局时间范围的优先级。动画系统在数字大屏中很常见,但如果只是组件级的入场动画而不需要跨组件编排,可以在实现层处理;页面轮播和资产注册表都是可以接受的延后项;全局时间范围在仪表板中比较常见,需要进一步评估。 I'm mostly on board with the user's assessment that those gaps aren't critical for MVP, but I want to make a concrete case for why ComputedVariable matters. Let me walk through a real scenario: a sales dashboard where you need to calculate completion rate from region selection and query results — without computed variables, you'd have to chain onChange handlers that manually update intermediate values, which quickly becomes a maintenance nightmare. For the theme system, I can sketch out a straightforward implementation approach. And I'll note that animation orchestration might be worth revisiting down the line, but for now component-level animation props plus action-based triggering should suffice. I'm seeing the ComputedVariable approach as a cleaner solution—it consolidates the calculation into a single expression that automatically tracks dependencies like region and the query data, eliminates the stale initial value problem, and naturally caches the result so four subscribed components only trigger one evaluation instead of four separate rerenders. The dependency graph becomes statically analyzable too, which aligns perfectly with the existing static analysis architecture and can even catch circular dependencies at load time.
Now I'm thinking through the theme system requirements. The key decisions are where tokens live in the data structure, how components reference them, and whether to use CSS variables for simple cases versus JavaScript resolution for computed colors. I should also acknowledge the user's point about multi-resolution—their argument for handling different physical sizes through the existing scaleMode actually covers the use case fully, so that concern is valid.
Let me structure the response by walking through the ComputedVariable implementation details, then laying out a concrete theme system plan covering token storage, reference syntax, and switching mechanics, before addressing any remaining gaps.
For the computed variable approach, I'm realizing the cleanest solution is to extend Variable with a discriminator field that marks it as either static or computed. This keeps the namespace unified so context.variables.X works consistently, and it lets any agent scan all variables to identify which ones have dependencies. Rather than allowing expressions directly in the value field—which would break the "initial snapshot" semantics—I'll propose a union type where ComputedVariable has an expression field instead of a static value.
Defining the types with the discriminator...
# 回应
先认下两点,然后重点讨论 ComputedVariable 与主题。
## 1. 多分辨率 —— 认可你的判断
你说得对。**一份 Schema 服务一块物理屏**是最常见的真实场景;现有 `FreeLayout.scaleMode: 'fit'` 已经能消化"展会临时投笔记本"之类的边缘情况。引入 breakpoints / 多套 layout 的复杂度收益不成正比,**这条暂时撤回**。
## 2. WS 数据源 —— 同意稍后扩展
`<Type>By<Variant>` 的命名约定 + `DataSource` 联合本来就是为这种扩展准备的。延后没问题。
---
## 3. ComputedVariable —— 我想再争取一下
我猜你"保守"的核心理由是:**"`Variable.interactions[onChange]` 已经能做到 A 变了改 C,何必新引入一个实体"**。
我不否认能"做到",但下面这个**真实大屏场景**会让两种方案的差距非常具体。
### 场景:销售大屏的"省份完成率"仪表盘
```
- Variable region: 'BJ' (用户下拉选择)
- Query salesData: 全国销售 (10s 轮询)
- Query targetData: 全国目标 (启动时拉一次)
- 4 个组件订阅: 完成率 = (sales / target) * 100 (按当前 region 取值)
```
#### 方案 A(你现有思路):用 `onChange` 链 + 中间 Variable
```ts
// 必须新增一个 Variable 来存这个派生值
Variable completionRate: { value: 0 }
// 必须在 3 个不同的 onChange 上写"几乎相同"的代码
region.interactions = [{ event:'onChange', actions:[setVariable(completionRate, <code>)] }]
salesData.interactions = [{ event:'onSuccess', actions:[setVariable(completionRate, <code>)] }]
targetData.interactions = [{ event:'onSuccess', actions:[setVariable(completionRate, <code>)] }]
```
#### 方案 BComputedVariable
```ts
ComputedVariable completionRate: {
expression: { type:'code', code: `
const r = context.variables.region;
const sales = context.queries.salesData.data?.[r]?.value ?? 0;
const target = context.queries.targetData.data?.[r]?.value ?? 1;
return (sales / target) * 100;
` }
}
```
### 两者**真实差距**(不是风格问题,是能力问题)
| 维度 | onChange 链方案 | ComputedVariable | 严重度 |
|---|---|---|---|
| **代码重复** | 3 份几乎相同的派发代码 | 1 份表达式 | 高 |
| **初始值** | 应用启动时 `completionRate = 0`stale),等一个 onChange 才修正 | 首次读取即求值,永不 stale | **致命** |
| **新增依赖**(如汇率 `rate` | 在 `rate` 上再挂 `onChange` + 在原 3 处 code 里都加 `rate` 引用 | 只改 expression 一处 | 高 |
| **缓存** | 4 个组件订阅 → 4 次 `setVariable` 触发的派发 → 4 次重渲染(每次都重新执行 setter code) | 表达式带 memo:依赖未变返回缓存,N 个订阅者只算一次 | 高 |
| **静态依赖分析**(你已规划) | 必须反向扫所有"写过 completionRate 的 Action 里读了谁",间接且模糊 | AST 直接扫 expression body**与你现有"设计器静态依赖分析方案.md"天然契合** | **关键** |
| **循环检测** | 不可能 —— `setVariable` 是命令式调用,链是动态的,运行时才会发现 A→B→A | 加载时静态拓扑排序即可警告循环 | 中 |
| **删除/重命名级联** | "删 region 时哪些 setVariable 受影响"靠扫所有 code | 依赖图谱直接定位 | 中 |
| **可寻址** | 派生值必须借壳一个真 Variable,占用 `variables` namespace | 共享 `variables` namespace(见下方实现),组件订阅写法一致 | 低 |
> **致命的那一行**:方案 A 的初始值 stale 不是"实现 bug",是结构性缺陷 —— 因为表达式只在事件发生后才执行。这意味着所有派生值在首次渲染时都是错的,要靠 `Application.onLaunch` 手动触发一次"全员重算",而这件事**没有任何字段约束实施人员必须做**,是设计期常见 bug 来源。
### 推荐实现(最小侵入,与现有架构同构)
`Variable` 升级为联合类型,用判别式区分两种实现,**`context.variables.<key>` 的访问语义完全不变**,所有现有 props 绑定零修改:
```ts
// src/schema/variable/variable.ts
export type Variable = StaticVariable | ComputedVariable;
interface VariableBase extends Entity {
key: string; // 现有规则不变
type: 'string'|'number'|'boolean'|'object'|'array';
interactions: Interaction[]; // onChange 仍可挂
}
export interface StaticVariable extends VariableBase {
source: 'static'; // 判别式
optional: boolean;
nullable: boolean;
value: JsonValue; // 现有静态初始值
}
export interface ComputedVariable extends VariableBase {
source: 'computed'; // 判别式
expression: DynamicExpression; // 复用现有逃生舱
// 不持有 value / optional / nullable —— 这些都由 expression 求值结果决定
}
```
引擎侧仅需新增三件事(不属于 Schema,本帖不展开):依赖图构建、求值缓存、循环检测警告。这三件能力**和你"静态依赖分析方案"想做的事完全是同一件事**。
### 结论
ComputedVariable 不是"语法糖",它把**派生值的真相收敛到唯一表达式**,把**依赖关系交给静态分析自动追踪**。`onChange` 链是命令式的事件流,永远做不到这两点。强烈建议纳入。
---
## 4. 主题系统 —— 实现方案(仅 Schema 层)
### 核心思路
- **Token 是语义层名字**`colorPrimary` / `bgScreen` / `chartCategorical1`),不是具体颜色值。
- **Theme 是 token 字典的一份具体取值**"亮"主题里 `colorPrimary='#1677ff'`"暗"主题里 `colorPrimary='#4096ff'`)。
- **组件只引用 token,永远不写死颜色**。
- **当前激活主题 ID** 属于运行时状态,**不进 Schema**(与 `AppMode` 同理)。
### Schema 改动清单
#### A. 新增 `src/schema/theme/`
```ts
// theme.ts
export interface Theme extends Entity {
/** 用于运行时 Action 切换的稳定标识 */
key: string; // e.g. 'light' | 'dark' | 'brand-red'
/** token 字典 —— 推荐扁平 + 命名空间前缀,避免嵌套带来的引用难度 */
tokens: Record<string, TokenValue>; // e.g. { 'color.primary': '#1677ff', 'size.titleFont': 24 }
}
/** Token 取值:直接量 或 引用其它 token 形成别名链 */
export type TokenValue =
| string
| number
| { type: 'alias'; tokenKey: string };
```
#### B. 扩展 `src/schema/shared/css-properties.ts`
把"只能写裸值"放开,允许引用 token
```ts
export interface TokenRef {
type: 'token';
tokenKey: string;
/** 当激活主题缺失该 token 时使用 */
fallback?: string | number;
}
export type CSSValue = string | number | TokenRef | undefined;
export interface CSSProperties {
[key: string]: CSSValue;
}
```
> 选 `TokenRef` 字面量对象(而不是 `'$colorPrimary'` 字符串约定)的理由:你已经强调过"`JsonValue` 是序列化铁壁、严防字符串约定漏入脏数据"。带判别式的对象与你这一架构哲学一致,且引擎可静态校验 `tokenKey` 是否真实存在。
#### C. `Application` 增加两个字段
```ts
export interface Application {
// ...existing
themes: Theme[]; // 至少包含一份默认主题
defaultThemeId: string; // 应用启动时挂载的主题
}
```
#### D. `ComponentPropValue` 加入 `TokenRef` 分支
```ts
export type ComponentPropValue =
| JsonValue
| DynamicExpression
| TokenRef // 新增:业务色板(图表配色、状态色等)也能引用 token
| undefined;
```
#### E. 主题切换走现有泛化 RPC,无需新机制
```ts
// 不需要新增 Schema 字段,复用 Action.target.type === 'system'
{ target: { type:'system', id:'theme' }, method:'switch', params: { type:'fixed', payload: { themeKey:'dark' } } }
```
#### F. `DynamicExpression` 上下文扩展(运行时约定,非 Schema)
为代码模式注入 `context.theme.<tokenKey>` —— 让 props 的 code 模式也能读 token。这条是引擎实现指引,不需要改 Schema。
### 关键决策点(请你拍板)
1. **是否允许 `props` 直接用 TokenRef**?(D 项)
- 推荐**允许**:图表配色、状态色(成功绿/警告黄)这些**业务色板**才是真正的换肤大头,单纯 `style` 上做主题不解决问题。
2. **Token 命名空间是扁平 + 点号分隔,还是嵌套对象**
- 推荐**扁平**`'color.primary'`):JsonValue 兼容性更好,依赖分析时 key 是字面量不是路径。
3. **运行时实现是 CSS 变量还是 JS resolve**
- 与 Schema 无关,但建议引擎侧用 **CSS 变量**:切主题只改 `:root` 的变量值,避免组件树重渲染。Schema 里的 `TokenRef` 在编译期映射为 `var(--color-primary)`
---
## 5. 其他缺口 —— 部分认可,唯一保留意见
| 缺口 | 我的态度 |
|---|---|
| 页面轮播 / 调度任务 / 资产注册表 / 自定义事件总线 / 角色权限 / 缓存策略 / 全局时间范围 | **同意暂缓**。这些都能用现有 Variable + Action + Interaction 组合出来,verbosity 不至于成为 MVP 阻塞点 |
| 跨图表联动 / 钻取 | **同意暂缓**。在 ComputedVariable 落地后,"hover 一个图表 → 高亮另一个"的写法会自然变得简洁很多(hover 改 Variable,多个图表订阅同一个 ComputedVariable 派生的高亮 ID)。优先做 Computed 的边际收益更高 |
| 数据降级与重试 / fallbackData | **同意暂缓**。但建议在 `Query` 上**预留一个 `retry?: { count, backoff }` 字段空位**写在文档里,未来引擎可以渐进开放,避免后续 schema 破坏性升级 |
| **动画 / 动效系统** | **保留意见**:长期重要,短期可放。理由 ↓ |
### 动画系统:唯一想留个伏笔的点
短期内,每个图表组件**自己实现**入场动画(让 `props.entranceAnimation` 这种 component-internal 字段处理),用 `Action` 触发即可,Schema 不需要动。
但如果未来出现"**跨组件时间轴编排**"需求("数据加载后,卡片 1 → 卡片 2 → 标题动画依次入场"),现有 Action 链 + setTimeout 的写法会很丑。
**建议现在做的事**:不动 Schema,但把这条放进未来路线图,**明确"组件级动画走 props,跨组件时间轴未来再开"**这个边界。这样组件物料库设计时就不会乱出"半个时间轴"的 props 接口。
---
## 总结
如果只采纳我两条建议,按性价比排序:
1. **ComputedVariable**(解决派生值真相分散 + 与你的静态分析架构同源)
2. **主题系统**(A~D 四项最小改动,Token 走 `TokenRef` 字面量保持类型安全)
是否要我针对 ComputedVariable 或主题,进一步细化"引擎侧依赖图构建 / CSS 变量编译"的运行时实现指引?或者你有不同意见,我们继续辩。
---
## User
暂时我先只考虑这两点,你可以进一步告诉我这两点在文档中指定的技术栈下应该如何改进数据结构以及在开发时如何实现功能。
---
## Assistant (Sisyphus - Ultraworker · Claude Opus 4.7 · 205.2s)
_Thinking:_
我需要深入探讨两个主要方向:ComputedVariable 和 Theme System,每个都涉及 schema 改进和运行时实现。根据项目文档,我们使用 React + Vite、Tanstack Router、Tanstack Query 和 Zustand 这套技术栈。我要记住的是只能提出方案而不能直接修改源代码,并且用中文交流。
对于 ComputedVariable,我在思考 schema 层的改动——需要区分 StaticVariable 和 ComputedVariable,后者用动态表达式而不是静态值。一个关键决策是 optional 和 nullable 字段对计算变量是否有意义,因为派生值的可选性实际上由表达式本身决定,所以可能不需要重复声明。另外要考虑计算变量是否能有 onChange 交互,应该允许但需要在运行时检测循环依赖。
现在的问题是在运行时实现中,ComputedVariable 应该采用 Tanstack Query 的缓存和依赖追踪风格,还是用 Zustand 的状态管理方式...
我在权衡三个选项:让 Zustand 成为一等公民会导致跨引擎订阅复杂,Tanstack Query 的 select 只能依赖单一查询,所以最实用的方案是用 Zustand 作为响应式核心,通过桥接层把 Tanstack Query 的状态映射进去,这样计算变量就能统一用 Zustand 的派生选择器来实现。
Building the computed registry system... I need to handle circular dependency detection during schema loading by checking the dependency graph with algorithms like Tarjan or DFS, and throw an error if cycles are found. The designer's static analysis using Babel parser should align with the runtime engine's dependency tracking, so both can share the same analysis module and feed into the "referenced by" panel. For TanStack Query integration, I need to bridge query cache updates into the Zustand mirror state while preventing reverse re-renders. On the UX side, I'm leaning toward a unified `useVariable` hook that transparently handles both static and computed variables rather than forcing consumers to distinguish between them. Now I'm moving into the theme system implementation, which involves creating new theme-related files and updating CSS properties to support token references.
Writing theme provider logic... 现在处理 props 中的 TokenRef,需要在运行时解析它们,因为图表这类组件用 JavaScript 控制颜色而不能依赖 CSS 变量。我创建一个 hook 来遍历 props,如果遇到 token 引用就从当前主题中查找对应的值,如果是动态表达式则求值,其他情况直接使用原值。
然后在求值动态表达式时,我通过 Proxy 把主题对象注入到上下文中,这样代码可以直接访问 token 值。主题切换只需要走现有的 RPC 机制,找到对应的主题后更新应用状态。
最后处理 token 别名链和缺失值... 现在我需要组织整个回答的结构:先详细展开 ComputedVariable 的四个层面(Schema、运行时、求值、React 集成),再用同样的框架讲主题系统,最后给出实施路线图。代码示例只是伪代码展示,用户需要自己实现。
# 一、ComputedVariable
## 1. Schema 最终结构
文件:`src/schema/variable/variable.ts`(重写为联合类型)
```ts
import type { Entity, JsonValue, DynamicExpression } from '../shared';
import type { Interaction } from '../interaction';
export type Variable = StaticVariable | ComputedVariable;
interface VariableBase extends Entity {
/** 在表达式中通过 context.variables.<key> 寻址;JS 标识符规范,全局唯一 */
key: string;
/** 运行时类型守卫,引擎在赋值/求值后强制校验 */
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
/** 值变化后的副作用链(onChange);ComputedVariable 同样可挂 */
interactions: Interaction[];
}
/** 静态变量:用户手动 setVariable 修改 */
export interface StaticVariable extends VariableBase {
source: 'static';
optional: boolean;
nullable: boolean;
/** 应用首次加载或被重置时的初始快照 */
value: JsonValue;
}
/** 计算变量:表达式驱动,依赖自动追踪 */
export interface ComputedVariable extends VariableBase {
source: 'computed';
/** 派生表达式;context 注入 variables / queries / computed (代理) / theme */
expression: DynamicExpression;
// 不持有 value / optional / nullable —— 全部由 expression 决定
}
```
> 关键决策:**共享同一份 `Application.variables` 数组**、**共享同一个 `key` 命名空间**。
> 这样组件 props 里现存的 `{ type:'code', code:'return context.variables.X' }` **不需要**知道 X 是 static 还是 computed,访问语义完全统一,迁移零成本。
`src/schema/application/application.ts` 保持 `variables: Variable[]` 不变,受益于联合类型。
`src/schema/variable/index.ts` 增加导出 `StaticVariable` / `ComputedVariable`
## 2. 运行时架构(React + Vite + Zustand + TanStack Query
整个低代码运行时的状态分布如下:
```
┌─────────────────────────────────────────────────────────────┐
│ TanStack Query (QueryClient) │
│ 持有 Query / Mutation 的真实异步状态 │
└──────────────────────┬──────────────────────────────────────┘
│ QueryCache.subscribe (单向桥接)
┌─────────────────────────────────────────────────────────────┐
│ Zustand AppStore │
│ ├─ variables: { [key]: any } (StaticVariable 主存) │
│ ├─ queriesMirror: { [id]: { data, isLoading, error } } │
│ │ 由 QueryCache.subscribe 桥接而来,只读 │
│ ├─ activeThemeId: string │
│ └─ editor: { selectedIds, activeContainerId, ... } (设计态) │
└──────────────────────┬──────────────────────────────────────┘
│ shallowEqual 订阅
┌─────────────────────────────────────────────────────────────┐
│ ComputedRegistry (新引擎模块) │
│ ├─ depGraph: key → Set<依赖标识> │
│ ├─ cache: key → { value, version } │
│ └─ subscribers: key → Set<callback> │
└──────────────────────┬──────────────────────────────────────┘
│ useSyncExternalStore
React 组件
```
核心要点:
- **Zustand 是响应式中枢**TanStack Query 通过 `QueryCache.subscribe` 把状态镜像写入 Zustand。这样 ComputedVariable 就只面对一个数据源(Zustand 切片),不需要双订阅。
- **ComputedRegistry 是独立模块**,懒求值 + 缓存 + 依赖追踪 + 循环检测。它本身不是 React 状态,通过 `useSyncExternalStore` 暴露给组件。
## 3. ComputedRegistry 实现要点
### 3.1 启动期:解析 + 建图
应用加载 Schema 后,对每个 `ComputedVariable`
```ts
class ComputedRegistry {
private compiled = new Map<string, (ctx: any) => any>();
private depGraph = new Map<string, Set<string>>(); // 'completionRate' → {'var:region','query:salesData','query:targetData'}
private cache = new Map<string, { value: any; ver: number }>();
private subs = new Map<string, Set<() => void>>();
private globalVer = 0;
register(cv: ComputedVariable) {
// 1. JIT 编译
this.compiled.set(cv.key, new Function('context', cv.expression.code) as any);
// 2. 静态依赖分析(复用 docs/设计器静态依赖分析方案.md 的 AST 模块)
// 返回形如 ['var:region', 'query:salesData.data', ...]
const deps = analyzeDependencies(cv.expression.code);
this.depGraph.set(cv.key, new Set(deps));
}
validateDAG() {
// Tarjan 强连通分量检测;有环抛错,标记到设计器告警面板
}
}
```
### 3.2 求值:懒计算 + memo
```ts
get(key: string): any {
const cached = this.cache.get(key);
if (cached && cached.ver === this.globalVer) return cached.value;
const fn = this.compiled.get(key);
const ctx = buildRuntimeContext(); // 见 3.4
const value = fn(ctx);
this.cache.set(key, { value, ver: this.globalVer });
return value;
}
```
> `globalVer` 是版本号策略 —— 任何依赖变化时,**精准失效**那条 computed 即可,不必全局清。下面 3.3 展开。
### 3.3 失效:依赖驱动的精准 invalidate
订阅 Zustand 与 TanStack Query 的变化:
```ts
// Zustand 侧
useAppStore.subscribe(
(state) => state.variables,
(next, prev) => {
const changedKeys = diffShallow(next, prev);
changedKeys.forEach(k => this.invalidateDep(`var:${k}`));
},
{ equalityFn: shallow }
);
// TanStack Query 侧
queryClient.getQueryCache().subscribe((event) => {
if (event.type === 'updated' && event.action.type === 'success') {
const queryId = event.query.queryKey[0] as string;
useAppStore.getState()._setQueryMirror(queryId, event.query.state);
this.invalidateDep(`query:${queryId}.data`);
}
});
private invalidateDep(depToken: string) {
for (const [computedKey, deps] of this.depGraph) {
if (deps.has(depToken)) {
this.cache.delete(computedKey);
this.subs.get(computedKey)?.forEach(cb => cb()); // 通知订阅者重读
// 级联:其他 computed 可能依赖这个 computed
this.invalidateDep(`computed:${computedKey}`);
}
}
}
```
### 3.4 context 构建:统一 `variables.<key>` 访问
```ts
function buildRuntimeContext() {
const store = useAppStore.getState();
return {
variables: new Proxy({} as Record<string, any>, {
get(_, key: string) {
const meta = engine.variableMeta.get(key);
if (meta?.source === 'computed') return engine.computed.get(key);
return store.variables[key]; // static
}
}),
queries: new Proxy({} as any, {
get(_, id: string) { return store.queriesMirror[id]; }
}),
theme: buildThemeProxy(), // 见后文主题章节
// event / data 由调用点(Interaction / Filter)按需追加
};
}
```
> 用 Proxy 而不是预先扁平化:**避免初始化期把所有 computed 都求一次值**(可能有 100+ ComputedVariable),保证懒求值。
## 4. React 集成
仅暴露**一个统一 hook**,组件无需关心是 static 还是 computed:
```ts
// hooks/use-variable.ts
export function useVariable<T = any>(key: string): T {
const meta = engine.variableMeta.get(key);
if (meta?.source === 'computed') {
return useSyncExternalStore(
(cb) => engine.computed.subscribe(key, cb),
() => engine.computed.get(key)
);
}
// static -> 直接走 zustand selector
return useAppStore(s => s.variables[key] as T);
}
```
`setVariable` Action 引擎侧实现要做防呆:
```ts
const variableService = {
set({ key, value }: { key: string; value: any }) {
const meta = engine.variableMeta.get(key);
if (meta?.source === 'computed') {
console.warn(`[datalive] 试图修改 computed 变量 "${key}",已忽略`);
return;
}
if (!matchesType(value, meta.type)) throw new TypeError(...);
useAppStore.setState((s) => ({ variables: { ...s.variables, [key]: value } }));
}
};
```
## 5. 与现有架构的协同
| 现有架构 | ComputedVariable 协同方式 |
|---|---|
| `docs/设计器静态依赖分析方案.md` | `analyzeDependencies(code)` 在**设计器和运行时复用同一个模块**(放进独立 package),设计器用它做依赖图谱面板,运行时用它建 depGraph |
| `Variable.interactions[onChange]` | ComputedVariable 同样支持 `onChange`:每次 `invalidateDep` 触发后,如果新值与旧值不同(深比较),派发 `onChange` Action 链 |
| `Action.target.type === 'variable'` | `set` 方法对 computed 静默拒绝(见上面),`get` 不需要变 |
| `AppMode === 'design'` | 设计态下 ComputedRegistry 仍照常运行,以便属性面板能看到派生值实时变化;但 `expression` 抛错时只在调试控制台打印,不让画布崩溃 |
| TanStack Query 的 `select` 选项 | **不替代** ComputedVariable:`select` 局限于单 Query 内的转换;ComputedVariable 处理跨 Query × Variable 的派生 |
## 6. 边界与防呆
1. **禁止在 ComputedVariable.interactions[onChange] 的 actions 里 `set` 它自己的依赖项**。引擎检测到 `setVariable(target.id ∈ deps)` 时拒绝执行并告警(避免事件循环)。
2. **expression 求值异常**:捕获后返回 `undefined` 并把错误关联到该 computed 的诊断面板(与 `预览态` 调试观测对齐)。
3. **类型不匹配**:`type` 字段在求值后做断言,不匹配视为 expression bug,告警但不抛。
4. **循环依赖**:启动期 Tarjan 检测,有环则把所有环上 computed 的值置为 `undefined` 并红色标记,而不是让运行时 stack overflow。
5. **跨 ComputedVariable 引用**:允许 `context.variables.A`(A 也是 computed),依赖图谱自动扩展为 `computed:A` 节点,失效时级联。
---
# 二、主题系统
## 1. Schema 最终结构
### 1.1 新建 `src/schema/theme/theme.ts`
```ts
import type { Entity } from '../shared';
export interface Theme extends Entity {
/** 用于 Action 切换主题的稳定键(如 'light' / 'dark' / 'brand-energy') */
key: string;
/** 扁平 token 字典;命名空间用点号或冒号分隔(如 'color.primary' / 'size.title') */
tokens: Record<string, TokenValue>;
}
/** 直接量 或 引用其他 token 形成别名链 */
export type TokenValue =
| string
| number
| { type: 'alias'; tokenKey: string };
```
### 1.2 新建 `src/schema/theme/token-ref.ts`
```ts
/** 可被 CSSProperties 与 props 复用的 token 引用 */
export interface TokenRef {
type: 'token';
tokenKey: string;
/** 当激活主题缺失该 token 时使用 */
fallback?: string | number;
}
```
### 1.3 修改 `src/schema/shared/css-properties.ts`
```ts
import type { TokenRef } from '../theme';
export type CSSValue = string | number | TokenRef | undefined;
export interface CSSProperties {
[key: string]: CSSValue;
}
```
> 这里 shared → theme 形成**反向引用**,需要在 `tsconfig` 里允许循环 import,或者把 `TokenRef` 直接放到 `shared/` 下。我倾向**把 `TokenRef` 放到 `shared/token-ref.ts`**(它是底层引用类型,不是 theme 业务概念),`Theme` 实体单独留在 `theme/`。这样依赖方向是 `theme → shared`,与现有目录关系一致。
### 1.4 修改 `src/schema/component/component.ts`
```ts
export type ComponentPropValue =
| JsonValue
| DynamicExpression
| TokenRef // 新增分支:业务色板/字号等也能引用 token
| undefined;
```
### 1.5 修改 `src/schema/application/application.ts`
```ts
export interface Application {
// ... existing
/** 主题列表;至少一份 */
themes: Theme[];
/** 启动时的默认主题 ID */
defaultThemeId: string;
}
```
## 2. 运行时架构(CSS Variables + Zustand)
主题切换的最大风险是 **重渲染风暴**(改主题导致所有组件 props/style 重算)。
**核心策略**:
- **`style` 里的 TokenRef** → 编译为 `var(--token-name)`,**完全交给浏览器**,主题切换零 React 重渲染。
- **`props` 里的 TokenRef**(图表配色等 JS 消费的颜色) → 由组件用 `useResolvedProps` 在订阅了 `activeThemeId` 后 resolve,只有真正消费 token 的组件才重渲染。
### 2.1 Zustand 切片
```ts
interface ThemeSlice {
activeThemeId: string;
themes: Theme[]; // 加载后冻结
switchTheme: (themeKey: string) => void;
}
```
`activeThemeId` 是**唯一的 Schema 外部状态**,由宿主容器或 system Action 注入。
### 2.2 启动期:把 token 注入 :root
```tsx
// engine/theme-provider.tsx
export function ThemeProvider({ children }: PropsWithChildren) {
const activeThemeId = useAppStore(s => s.activeThemeId);
const themes = useAppStore(s => s.themes);
useLayoutEffect(() => {
const theme = themes.find(t => t.id === activeThemeId);
if (!theme) return;
const root = document.documentElement;
const resolved = flattenTokens(theme.tokens); // 处理 alias 链
for (const [key, value] of Object.entries(resolved)) {
root.style.setProperty(`--${tokenKeyToCSSVar(key)}`, String(value));
}
return () => {
// 切换时不需要清除上一份,新主题会 setProperty 覆盖同名变量
};
}, [activeThemeId, themes]);
return <>{children}</>;
}
// 'color.primary' → 'color-primary'
const tokenKeyToCSSVar = (k: string) => k.replace(/[.:]/g, '-');
```
### 2.3 渲染引擎对 `style` 的解析
```ts
// engine/resolve-style.ts
export function resolveStyle(style: CSSProperties): React.CSSProperties {
const out: any = {};
for (const k in style) out[k] = resolveCSSValue(style[k]);
return out;
}
function resolveCSSValue(v: CSSValue): string | number | undefined {
if (v == null || typeof v === 'string' || typeof v === 'number') return v;
if (v.type === 'token') {
const cssVar = `--${tokenKeyToCSSVar(v.tokenKey)}`;
return v.fallback != null ? `var(${cssVar}, ${v.fallback})` : `var(${cssVar})`;
}
return undefined;
}
```
> **此函数与 `activeThemeId` 完全解耦**,纯函数。组件渲染时调用一次拿到 React.CSSProperties,主题切换时浏览器自动重绘对应元素 —— **零 React 重渲染**。
### 2.4 渲染引擎对 `props` 的解析
```ts
// engine/use-resolved-props.ts
export function useResolvedProps(
raw: Record<string, ComponentPropValue>
): Record<string, JsonValue> {
const activeTheme = useAppStore(s => s.themes.find(t => t.id === s.activeThemeId)!);
// 仅当 raw 中含有 TokenRef 时,activeTheme 才进入依赖
const hasToken = useMemo(() => Object.values(raw).some(isTokenRef), [raw]);
const themeDep = hasToken ? activeTheme : null;
return useMemo(() => {
const out: any = {};
for (const k in raw) {
const v = raw[k];
if (isTokenRef(v)) {
out[k] = resolveTokenInProps(v, activeTheme.tokens);
} else if (isDynamicExpression(v)) {
out[k] = evaluateExpression(v, buildRuntimeContext());
} else {
out[k] = v;
}
}
return out;
}, [raw, themeDep]); // 没有 TokenRef 的组件不会因主题切换重渲染
}
```
### 2.5 主题切换:复用泛化 RPC
不增加新 Schema 字段。在引擎的 system 服务表里注册:
```ts
// engine/system-services/theme.ts
const themeService = {
switch({ themeKey }: { themeKey: string }) {
const theme = useAppStore.getState().themes.find(t => t.key === themeKey);
if (!theme) return console.warn(`[theme] unknown key: ${themeKey}`);
useAppStore.setState({ activeThemeId: theme.id });
// CSS 变量更新由 ThemeProvider 的 useLayoutEffect 自动接管
}
};
// 注册到全局
engine.systemServices.register('theme', themeService);
```
Schema 端调用方式(零修改):
```json
{
"target": { "type": "system", "id": "theme" },
"method": "switch",
"params": { "type": "fixed", "payload": { "themeKey": "dark" } }
}
```
### 2.6 DynamicExpression 注入 `context.theme`
```ts
function buildThemeProxy() {
return new Proxy({} as any, {
get(_, key: string) {
const state = useAppStore.getState();
const theme = state.themes.find(t => t.id === state.activeThemeId);
if (!theme) return undefined;
if (key === 'id') return theme.id;
if (key === 'key') return theme.key;
const raw = theme.tokens[key];
return raw != null ? resolveTokenValue(raw, theme.tokens) : undefined;
}
});
}
```
允许实施人员在 `code` 模式中写 `return context.theme['color.primary']`
> **注意一个反直觉点**:在表达式里读 `context.theme['color.primary']` 是**实际颜色值**,在 `style` 里写 `TokenRef` 是**CSS 变量引用**。前者运行时 resolve,后者编译期映射。两者都正确,但语义不同 —— 文档里需要明确这一点。
### 2.7 Token alias 链与缺失处理
```ts
function resolveTokenValue(
v: TokenValue,
all: Record<string, TokenValue>,
visited = new Set<string>()
): string | number | undefined {
if (typeof v === 'string' || typeof v === 'number') return v;
if (v?.type === 'alias') {
if (visited.has(v.tokenKey)) {
console.warn('[theme] token 别名循环', v.tokenKey);
return undefined;
}
visited.add(v.tokenKey);
const target = all[v.tokenKey];
return target ? resolveTokenValue(target, all, visited) : undefined;
}
}
```
`flattenTokens``ThemeProvider` 注入前一次性把 alias 链全部解开,后续 CSS 变量都是直接量,运行时不再递归。
## 3. 与 React Router / TanStack Query 协同
| 集成点 | 注意事项 |
|---|---|
| **ThemeProvider 位置** | 必须在 `<RouterProvider>` 之外、`QueryClientProvider` 之内或之外均可。位置在 Router 外是为了避免路由切换时重新挂载 Provider 导致 `:root` 变量被清除。 |
| **TanStack Query** | 主题切换**绝对不能进入任何 query 的 queryKey**。如果某个 Query 的 request 配置依赖颜色(几乎不会),应通过 props 而非 queryKey 传入。 |
| **React Router** | `Route.interactions[onEnter]` 中可以派发 `theme.switch` Action,实现"进入暗黑模式专用页面"自动切深色。 |
## 4. 与设计器静态分析协同
设计器侧需要的能力(全部建立在已规划的 AST 工具链上,**无需新模块**):
1. **Token 引用图谱**:扫描所有 `Component.style` / `props` / `expression.code` 中的 `TokenRef``context.theme.X`,反向得到"`color.primary` 被 23 个组件引用"。
2. **重命名级联**:把 `color.primary` 改成 `color.brandPrimary` 时,同步更新所有 `TokenRef.tokenKey` 与 code 字符串中的 `context.theme['color.primary']`
3. **死 token 剔除**:某个 token 已无任何引用时高亮提示删除。
---
# 三、推荐实施顺序
按依赖关系与边际收益排:
1. **先完成 ComputedVariable Schema 定义**(联合类型 + 判别式),这一步零运行时,只是类型工程。
2. **抽出 `analyzeDependencies(code)` 模块**(独立 npm-style package),设计器与运行时共用 —— 这是后续静态分析、ComputedRegistry、theme 引用图谱的共同地基。
3. **搭 Zustand 主仓库** + **TanStack Query → Zustand 桥接层**,把"运行时状态唯一中枢"立起来。
4. **实现 ComputedRegistry**(register / get / invalidate / DAG 检测) + `useVariable` 统一 hook。
5. **再做主题 Schema 改动**(Theme + TokenRef + Application 字段)。
6. **实现 ThemeProvider + resolveStyle + useResolvedProps**,主题切换 system Action 收尾。
7. **设计器侧的 Token 引用图谱、依赖面板**等,作为附加能力分批跟进。
第 2 步是隐藏的关键:**先把 AST 分析模块独立出来**,后续两个能力都站在它上面。
---
是否还要细化哪一块?例如:`analyzeDependencies` 的 AST 提取细则、`QueryCache.subscribe` 桥接的边界 case(Mutation 不要镜像、stale 状态的处理)、或主题在 Echarts 这类深度自治组件里的注入策略?
---