xbintsc 0.3.35 → 0.3.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/AGENTS.md +95 -0
  2. package/README.md +31 -3
  3. package/README.zh-CN.md +23 -0
  4. package/dist/src/cli/hints.d.ts +54 -0
  5. package/dist/src/cli/hints.js +165 -0
  6. package/dist/src/cli/hints.js.map +1 -0
  7. package/dist/src/cli/main.js +73 -9
  8. package/dist/src/cli/main.js.map +1 -1
  9. package/dist/src/codegen/generator/module.js +10 -0
  10. package/dist/src/codegen/generator/module.js.map +1 -1
  11. package/dist/src/codegen/generator/state.js +4 -0
  12. package/dist/src/codegen/generator/state.js.map +1 -1
  13. package/dist/src/codegen/generator/tables.d.ts +26 -0
  14. package/dist/src/codegen/generator/tables.js +64 -12
  15. package/dist/src/codegen/generator/tables.js.map +1 -1
  16. package/dist/src/diagnostics/source-text.d.ts +22 -0
  17. package/dist/src/diagnostics/source-text.js +76 -0
  18. package/dist/src/diagnostics/source-text.js.map +1 -0
  19. package/dist/src/driver/bundler/graph.js +2 -1
  20. package/dist/src/driver/bundler/graph.js.map +1 -1
  21. package/dist/src/driver/compiler.js +3 -2
  22. package/dist/src/driver/compiler.js.map +1 -1
  23. package/dist/src/lexer/scanner/strings.js +16 -3
  24. package/dist/src/lexer/scanner/strings.js.map +1 -1
  25. package/dist/tests/cli/hints.test.d.ts +9 -0
  26. package/dist/tests/cli/hints.test.js +143 -0
  27. package/dist/tests/cli/hints.test.js.map +1 -0
  28. package/dist/tests/cli/main.test.js +6 -4
  29. package/dist/tests/cli/main.test.js.map +1 -1
  30. package/dist/tests/codegen/llvm.test.js +17 -2
  31. package/dist/tests/codegen/llvm.test.js.map +1 -1
  32. package/dist/tests/e2e/gc.test.d.ts +1 -0
  33. package/dist/tests/e2e/gc.test.js +168 -0
  34. package/dist/tests/e2e/gc.test.js.map +1 -0
  35. package/dist/tests/e2e/harness.d.ts +2 -0
  36. package/dist/tests/e2e/harness.js +1 -0
  37. package/dist/tests/e2e/harness.js.map +1 -1
  38. package/dist/tests/helpers.js +3 -2
  39. package/dist/tests/helpers.js.map +1 -1
  40. package/dist/tests/lexer/strings.test.js +14 -2
  41. package/dist/tests/lexer/strings.test.js.map +1 -1
  42. package/doc/DESIGN.md +117 -0
  43. package/doc/ai/README.md +63 -0
  44. package/doc/ai/build-recipe.md +137 -0
  45. package/doc/ai/cli.md +142 -0
  46. package/doc/ai/contributing.md +196 -0
  47. package/doc/ai/extensions.md +148 -0
  48. package/doc/ai/language-support.md +152 -0
  49. package/doc/ai/troubleshooting.md +163 -0
  50. package/doc/ai/zh-CN/README.md +56 -0
  51. package/doc/ai/zh-CN/build-recipe.md +132 -0
  52. package/doc/ai/zh-CN/cli.md +127 -0
  53. package/doc/ai/zh-CN/contributing.md +173 -0
  54. package/doc/ai/zh-CN/extensions.md +139 -0
  55. package/doc/ai/zh-CN/language-support.md +147 -0
  56. package/doc/ai/zh-CN/troubleshooting.md +150 -0
  57. package/doc/gui-scripts.md +350 -0
  58. package/doc/gui.md +646 -0
  59. package/doc/icon.md +265 -0
  60. package/doc/implemented.md +373 -0
  61. package/doc/node-implemented.md +588 -0
  62. package/doc/node-unimplemented.md +167 -0
  63. package/doc/post/announce.md +43 -0
  64. package/doc/requirements.md +145 -0
  65. package/doc/unimplemented.md +286 -0
  66. package/doc/xbintsc.config.schema.json +67 -0
  67. package/doc/zh-CN/DESIGN.md +104 -0
  68. package/doc/zh-CN/gui-scripts.md +329 -0
  69. package/doc/zh-CN/gui.md +588 -0
  70. package/doc/zh-CN/icon.md +241 -0
  71. package/doc/zh-CN/implemented.md +365 -0
  72. package/doc/zh-CN/node-implemented.md +533 -0
  73. package/doc/zh-CN/node-unimplemented.md +141 -0
  74. package/doc/zh-CN/plan-require-node-modules.md +284 -0
  75. package/doc/zh-CN/post/announce.md +47 -0
  76. package/doc/zh-CN/requirements.md +134 -0
  77. package/doc/zh-CN/unimplemented.md +247 -0
  78. package/llms.txt +45 -0
  79. package/package.json +6 -2
  80. package/runtime/ext_gui/dom_api_proto.cpp +5 -0
  81. package/runtime/ext_gui/gui.cpp +3 -1
  82. package/runtime/ext_gui/renderer.cpp +13 -11
  83. package/runtime/ext_gui/renderer_image.cpp +12 -8
  84. package/runtime/ext_gui/renderer_shaders.h +131 -4
  85. package/runtime/ext_gui/renderer_shaders_data.h +1809 -0
  86. package/runtime/ext_gui/renderer_text.cpp +12 -8
  87. package/runtime/ext_gui/shaders.hlsl +98 -0
  88. package/runtime/ext_gui/spirv/fill.frag +19 -0
  89. package/runtime/ext_gui/spirv/fill.vert +42 -0
  90. package/runtime/ext_gui/spirv/image.frag +16 -0
  91. package/runtime/ext_gui/spirv/quad.vert +30 -0
  92. package/runtime/ext_gui/spirv/text.frag +16 -0
  93. package/runtime/ext_gui/window.cpp +1 -0
  94. package/runtime/ext_node/buffer/parts/prototype.inc +1 -0
  95. package/runtime/ext_node/dgram/dgram.c +1 -0
  96. package/runtime/ext_node/events/events.c +1 -0
  97. package/runtime/ext_node/fs/fs_ops.c +10 -25
  98. package/runtime/ext_node/fs/glob.c +13 -30
  99. package/runtime/ext_node/fs/promises.c +1 -0
  100. package/runtime/ext_node/http/parts/prototypes.inc +5 -0
  101. package/runtime/ext_node/net/parts/prototypes.inc +2 -0
  102. package/runtime/ext_node/process/process.c +9 -7
  103. package/runtime/ext_node/stream/stream.c +1 -0
  104. package/runtime/ext_node/util/util.c +6 -6
  105. package/runtime/rt.h +10 -0
  106. package/runtime/rt_internal.h +63 -2
  107. package/runtime/xt_alloc.c +442 -5
  108. package/runtime/xt_generator.c +95 -1
  109. package/runtime/xt_loop.c +35 -1
  110. package/runtime/xt_promise.c +46 -0
  111. package/runtime/xt_stdlib2/error.inc +1 -0
  112. package/runtime/xt_stdlib2/regexp-match.inc +8 -7
  113. package/runtime/xt_symbol.c +2 -0
  114. package/runtime/xt_typed_array/construction.inc +142 -0
  115. package/runtime/xt_typed_array/elements.inc +92 -0
  116. package/runtime/xt_typed_array/methods.inc +329 -0
  117. package/runtime/xt_typed_array.c +6 -548
  118. package/runtime/xt_values/number-format.inc +26 -0
  119. package/scripts/build-gui-shaders.mjs +204 -0
  120. package/scripts/build-gui.ts +35 -0
  121. package/scripts/check-file-length.ts +89 -0
  122. package/src/cli/hints.ts +194 -0
  123. package/src/cli/main.ts +82 -9
  124. package/src/codegen/generator/module.ts +10 -0
  125. package/src/codegen/generator/state.ts +4 -0
  126. package/src/codegen/generator/tables.ts +60 -14
  127. package/src/diagnostics/source-text.ts +78 -0
  128. package/src/driver/bundler/graph.ts +2 -1
  129. package/src/driver/compiler.ts +3 -2
  130. package/src/lexer/scanner/strings.ts +16 -3
@@ -0,0 +1,588 @@
1
+ # xbintsc GUI 扩展(自托管 HTML/CSS 渲染器)
2
+
3
+ > 语言 / Language:[English](../gui.md) | **简体中文**
4
+
5
+ 状态:**M10** —— 功能(M1–M10)已全部完成:HTML 解析、CSS 选择器匹配、层叠(cascade)、计算样式与布局(块级、行内与 Flexbox)均已就位,引擎还会**绘制**:它构建一个由矩形、图像与排版后的文本 run 组成的显示列表,并通过 SDL_GPU 渲染。输入事件会经过命中测试并投递给原生 TS 处理器,`:hover`/`:focus` 会被动态匹配,`<img>` 依据其固有尺寸确定大小并从纹理绘制,CSS transition 会为绘制属性做动画。**M8** 增加了交互式 DOM:具有稳定标识的元素句柄、变更操作(`appendChild`、`textContent`、`classList`、`style` 等)以及支持捕获/冒泡的元素级事件。**M9** 会提前编译 `<script>` 主体(内联与 `<script src>`)——参见 `gui-scripts.md`。**M10** 增加 `requestAnimationFrame` 以及若干 DOM 辅助方法。本文档记录了一个跨平台 GUI 扩展的锁定决策、架构、里程碑计划与当前进度;该扩展使用自有的 GPU 加速引擎渲染 HTML/CSS UI。
6
+
7
+ ## 目标
8
+
9
+ 1. **跨平台 GUI**:由 xbintsc 把 TypeScript 编译为原生二进制:macOS、Linux、Windows。
10
+ 2. **HTML5/CSS 渲染**,使用**自研**引擎(解析器、层叠、布局、绘制),而非系统 WebView 或嵌入式浏览器。
11
+ 3. **GPU 加速**——硬件光栅化/合成是硬性要求,而不是优化项。
12
+ 4. **多窗口。**
13
+ 5. 引擎不得损害 xbintsc 作为二进制编译器的本质:核心编译器、词法器、解析器、绑定器与代码生成器必须保持平台无关,绝不滋生 GUI 分支。
14
+
15
+ ## 非目标(目前)
16
+
17
+ - 执行**运行时**页面 `<script>`(通过网络获取或动态创建的脚本)。**编译期**脚本由 xbintsc AOT 编译,并且确实会运行——参见 `gui-scripts.md`。逻辑也可以写在通过 `xt_call_with_this` 回调的原生 TS 中。
18
+ - JS 引擎(QuickJS/V8/...)。明确不在范围内;没有运行时解释器/JIT,因此 `<script>` 主体会提前编译。
19
+ - 完整的 Web 兼容性 / 浏览器。我们实现一个实用的 HTML/CSS 子集。
20
+
21
+ ## 锁定决策
22
+
23
+ | # | 决策 |
24
+ | --- | --- |
25
+ | 1 | **没有页面 JS 引擎。** 行为由原生 TS 实现,通过 `xt_call_with_this` 回调。`<script>` 主体由 xbintsc 自身 **AOT 编译**(无解释器)——参见 `gui-scripts.md`。 |
26
+ | 2 | 允许使用第三方**底层**库(GPU 后端、文本整形、图像解码)。HTML/CSS 解析 + 布局 + 绘制调度均为自研。 |
27
+ | 3 | **GPU 加速是强制要求。** |
28
+ | 4 | 引擎**非自托管**(它是 C/C++,不是 TS),但不得影响编译器的平台无关设计。以按平台预构建的归档形式交付,通过 `nativeObjects` 链接。 |
29
+ | 5 | **事件循环是通用的**:运行时暴露一个通用主循环钩子与一个轮询原语;任何 GUI 特定的东西都不会进入 `runtime/`。 |
30
+ | 6 | 核心对象模型支持**多窗口**。 |
31
+ | 7 | 第三方底层库**静态链接进 `gui.a`**,使发布版保持自包含;链接时只额外加入操作系统框架。 |
32
+
33
+ ## 架构
34
+
35
+ ```
36
+ TypeScript(由 xbintsc 编译为原生代码)
37
+ │ import { createWindow, run } from "gui"
38
+ ▼
39
+ gui 扩展绑定 (src/extensions/gui)
40
+ │ xt_gui_* 符号 (统一的 (argc, argv) ABI)
41
+ ▼
42
+ gui.a ── 自研引擎(C/C++)
43
+ ┌───────────────┬──────────────────┬───────────────────┐
44
+ ▼ ▼ ▼ ▼
45
+ HTML 解析器 CSS 层叠 + 布局 GPU 合成器
46
+ (子集) 选择器匹配 (块级/行内/flex) (SDL_GPU: Metal/
47
+ Vulkan/D3D12)
48
+ │
49
+ ▼
50
+ 窗口 + 输入(SDL3)── 多窗口
51
+ 文本(HarfBuzz + FreeType)── 字形图集 / SDF
52
+ 图像(stb_image)
53
+ ```
54
+
55
+ 引擎从不与编译器通信。编译器只看到一个带有 `nativeObjects()`、`linkerFlags()` 和 `modules()` 的 `Extension`。
56
+
57
+ ## 技术栈(已确认)
58
+
59
+ | 关注点 | 选择 | 理由 |
60
+ | --- | --- | --- |
61
+ | 窗口 + 输入 + 多窗口 | **SDL3** | 跨平台窗口、HiDPI、IME、剪贴板、拖放、关闭/隐藏事件 |
62
+ | GPU | **SDL_GPU**(SDL3) | 一条覆盖 Metal / Vulkan / D3D12 的渲染路径;避免三套后端 |
63
+ | 文本整形 | **HarfBuzz** | 正确处理复杂文种的整形 |
64
+ | 字形光栅化 | **FreeType** | 字形轮廓 → GPU 图集 / SDF |
65
+ | 图像 | **stb_image**(vendored 头文件) | 起步阶段用单头文件 |
66
+
67
+ 固定版本:SDL3 `release-3.2.10`、FreeType `2.13.3`、HarfBuzz `10.1.0`
68
+ (可用 `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` 覆盖)。
69
+
70
+ 如果否决 SDL3,备选是 **GLFW + OpenGL 3.3**(更简单,但 OpenGL 在 macOS 上
71
+ 已弃用,且不提供现代 GPU 抽象)。如果引擎用 Rust 编写,则用 **wgpu-native**。
72
+
73
+ > 这些 vendored 库会**静态链接进 `gui.a`**,因此发布的 xbintsc 保持
74
+ > “下载即用”;`linkerFlags()` 只添加操作系统框架。
75
+
76
+ ## 面向 TS 的 API
77
+
78
+ ```ts
79
+ import { createWindow, run, quit } from "gui";
80
+
81
+ const win = createWindow({ title: "Demo", width: 900, height: 600 });
82
+ win.setBackground("#14161c");
83
+ win.loadHTML(INDEX_HTML); // 解析 HTML/CSS 并计算样式
84
+ win.on("ready", () => console.log("first frame presented"));
85
+ win.on("close", () => console.log("window closed"));
86
+
87
+ run(); // 驱动主循环,直到所有窗口关闭
88
+ ```
89
+
90
+ 窗口句柄上已实现的方法:`setTitle` / `setSize` / `loadHTML` /
91
+ `getHTML` / `setBackground` / `close` / `isOpen` / `on` / `off`。触发的事件:
92
+ `ready`(首个呈现帧之后)、`load`、`close`,以及输入事件
93
+ `mousemove`、`mousedown`、`mouseup`、`click`、`wheel`、`keydown`、`keyup`。
94
+
95
+ 输入处理器接收单个负载对象(生命周期处理器不接收任何参数):
96
+
97
+ ```ts
98
+ win.on("click", (e) => console.log(e.x, e.y, e.button, e.target));
99
+ win.on("wheel", (e) => console.log(e.deltaX, e.deltaY));
100
+ win.on("keydown", (e) => console.log(e.key, e.code, e.ctrl, e.shift, e.alt, e.meta));
101
+ ```
102
+
103
+ | 字段 | 事件 | 含义 |
104
+ | --- | --- | --- |
105
+ | `x`、`y` / `clientX`、`clientY` | pointer、wheel | 相对于视口的逻辑像素 |
106
+ | `button` | pointer | `0` 左键、`1` 中键、`2` 右键(移动时为 `-1`) |
107
+ | `clicks` | pointer | 操作系统报告的点击次数 |
108
+ | `deltaX`、`deltaY` | wheel | 滚动量(`deltaY` 为正表示向下) |
109
+ | `key`、`code` | keyboard | 键名与物理扫描码名 |
110
+ | `repeat`、`ctrl`、`shift`、`alt`、`meta` | keyboard | 修饰键 |
111
+ | `target` | pointer、wheel | 该点下最深的元素,表示为类 CSS 描述符(`div#main.card`),可能缺省 |
112
+
113
+ ### 已实现的 HTML/CSS 子集(M3a)
114
+
115
+ **HTML 解析器**(`runtime/ext_gui/dom.{h,cpp}`):标签/属性/文本,注释与
116
+ doctype 跳过,实体解码(命名 + 十进制/十六进制),空元素,原始文本元素
117
+ (`<style>`/`<script>`),以及常见的隐式闭合规则(`li`、`dt`/`dd`、`option`、
118
+ `p`、标题、表格单元格/行)。
119
+
120
+ **CSS 解析器**(`runtime/ext_gui/css.{h,cpp}`):注释与 at-rule 暂时跳过,
121
+ 支持多选择器的规则以及声明。选择器:类型、`.class`、`#id`、属性
122
+ (`=`、`~=`、`|=`、`^=`、`$=`、`*=`)、四种组合器(后代、子、相邻兄弟与
123
+ 通用兄弟)以及伪类 `:first-child`、`:last-child`、`:only-child`、`:empty`、
124
+ `:root`、`:not(...)`、`:nth-child(an+b)`、`:disabled`、`:checked`,再加上
125
+ 有状态的 `:hover` 与 `:focus`(见 *已实现的输入*)。值:长度
126
+ (`px`、`%`、`em`、`rem`、`vw`、`vh`、`pt`、`pc`、`in`、`cm`、`mm`、`q`)、
127
+ 颜色(十六进制、`rgb()`/`rgba()`、一个命名子集)、数字、关键字与简写
128
+ (`margin`/`padding`/`border`/`flex`)。
129
+
130
+ **层叠与计算样式**(`runtime/ext_gui/style.{h,cpp}`):一份小的内置 UA
131
+ 样式表,作者规则按 `(!important, 特异性, 源码顺序)` 排序,然后是内联
132
+ `style=""`(特异性最高,但非 `!important` 的内联会输给 `!important`),
133
+ 再加上文本属性的 CSS 继承。相对长度保持未解析状态直到布局阶段,`font-size`
134
+ 除外(相对*父级*字号解析)以及 `line-height`。
135
+
136
+ ### 诊断
137
+
138
+ 为了让 HTML/CSS/绘制流水线在没有 GPU 的情况下也可测试,窗口句柄暴露只读钩子:
139
+
140
+ ```ts
141
+ win.computedStyle(selector, property) // e.g. ("#main", "width") -> "60%"
142
+ win.queryCount(selector) // 匹配元素的数量
143
+ win.getBoundingClientRect(selector) // { x, y, width, height }(边框盒)
144
+ win.documentTree() // 序列化的 DOM(调试用)
145
+ win.layoutTree() // 序列化的布局盒(调试用)
146
+ win.paintCount() // 显示列表中图形的数量
147
+ win.paintList() // 序列化的显示列表(调试用)
148
+ win.measureText(text, fontSize?, family?) // 整形后的推进宽度(像素)
149
+ win.fontMetrics(fontSize?, family?) // { ascent, descent, lineHeight, ready }
150
+ win.hitTest(x, y) // 最深的元素描述符,或 ""
151
+ win.sendEvent(type, options?) // 合成输入(测试用)
152
+ win.advance(ms) // 推进 CSS transition 时钟(测试用)
153
+ ```
154
+
155
+ 它们被 `tests/e2e/gui-*.test.ts` 用来断言解析、选择器匹配、特异性、继承、
156
+ `!important` 与布局几何。它们在之后仍可用于调试。
157
+
158
+ ### 交互式 DOM(M8)
159
+
160
+ `win.document` 返回文档句柄;元素句柄具有稳定标识,并提供可读写的属性、
161
+ 特性(attribute)、遍历与几何信息:
162
+
163
+ ```ts
164
+ const doc = win.document;
165
+ const box = doc.querySelector("#box");
166
+ const inner = doc.querySelector("#inner");
167
+ inner.textContent = "hi"; // 写入
168
+ inner.classList.add("hot");
169
+ inner.style.setProperty("color", "#0f0");
170
+ inner.setAttribute("data-role", "lead");
171
+ console.log(inner.id, inner.tagName, doc.querySelector("#box") === box);
172
+
173
+ const created = doc.createElement("div");
174
+ created.textContent = "added";
175
+ box.appendChild(created);
176
+ box.removeChild(created);
177
+
178
+ inner.addEventListener("click", (e) => console.log(e.target.id, e.currentTarget.id));
179
+ inner.click(); // 在该元素处合成一次点击
180
+ console.log(box.offsetWidth, box.offsetHeight); // 取整后的边框盒
181
+ console.log(box.contains(inner)); // 后代判断
182
+ ```
183
+
184
+ 变更操作会把文档标记为脏;引擎会延迟地(在下一次读取或下一帧之前)重新计算
185
+ 样式并重新布局。`inner = …`/`innerHTML = …` **不会**执行脚本。元素事件支持
186
+ 捕获与冒泡阶段、`stopPropagation`、`once`,并向上冒泡到 `document`/`window`。
187
+ 旧式 `win.on(type, fn)` 负载保留其**字符串** `e.target`(`div#id.class`);
188
+ 元素 `Event.target` 是一个句柄,其描述符与同一字符串匹配。
189
+
190
+ ### AOT 脚本(M9)
191
+
192
+ 导入一个包含内联 `<script lang="ts">` 主体的 `.html` 文件;加载器会把每个
193
+ 主体编译为一个原生函数,`win.loadHTML(page)` 会在文档解析完成后运行它们
194
+ (没有 JavaScript 引擎,没有运行时 `eval`):
195
+
196
+ ```ts
197
+ import { createWindow, run } from "gui";
198
+ import page from "./page.html";
199
+
200
+ const win = createWindow({ title: "counter", width: 320, height: 240 });
201
+ win.loadHTML(page);
202
+ run();
203
+ ```
204
+
205
+ ```html
206
+ <button id="b">0</button>
207
+ <script lang="ts">
208
+ const b = document.getElementById("b");
209
+ let n = 0;
210
+ b.addEventListener("click", () => { b.textContent = String(++n); });
211
+ </script>
212
+ ```
213
+
214
+ 这两个参数(`window`、`document`)只是普通的函数参数,因此脚本的局部变量
215
+ 无需任何全局对象机制。外部脚本同样可用:`<script src="./counter.ts">` 会在
216
+ 编译期读取,其 import 会被提升(重写为从 HTML 文件解析),其主体以同样方式
217
+ 包装——因此脚本模块可以 `import` 辅助函数,同时仍能看到 `document`。脚本是
218
+ **编译期资产**:运行时创建的 HTML(`innerHTML`、网络获取)永不执行,`src`
219
+ URL(`https://…`、`data:…`)会被忽略。一切都按文档顺序在解析后运行(实际上
220
+ 是 deferred)。完整设计参见 `gui-scripts.md`。
221
+
222
+ ### 动画帧(M10)
223
+
224
+ `requestAnimationFrame` 在下一帧运行一次回调;回调接收帧时间戳(毫秒),
225
+ 并且可以修改 DOM,DOM 会在同一帧内重新计算样式并重绘。在回调内部再次排队
226
+ 即可实现动画:
227
+
228
+ ```ts
229
+ const win = createWindow({ title: "anim", width: 320, height: 240 });
230
+ let n = 0;
231
+ const tick = (t: number) => {
232
+ win.document.getElementById("label").textContent = String(n++);
233
+ if (n < 120) win.requestAnimationFrame(tick); // 返回 id;cancelAnimationFrame(id) 可取消
234
+ };
235
+ win.on("ready", () => win.requestAnimationFrame(tick));
236
+ win.loadHTML("<div id='label'>0</div>");
237
+ run();
238
+ ```
239
+
240
+ ### 已实现的布局(M3)
241
+
242
+ `runtime/ext_gui/layout.{h,cpp}` 把带样式的 DOM 转换为具有绝对(相对视口)
243
+ 几何信息的 `LayoutBox` 树:
244
+
245
+ - **块级流** —— 块级子元素垂直堆叠(暂不支持外边距折叠);`display: none`
246
+ 不生成盒;`width: auto` 填满包含块,`height: auto` 包裹内容。盒模型
247
+ (margin/padding/border)会被解析,包括相对包含块宽度的百分比。
248
+ - **行内流** —— 连续的行内级子元素构成一个匿名行内格式化上下文,采用贪心、
249
+ 基于单词的断行、`text-align` 与 `line-height`。行内元素获得其后代几何信息
250
+ 的并集;`display: inline-block` 以 shrink-to-fit 宽度原子化布局。每个文本
251
+ 片段会记住它覆盖的 run,以便绘制时对它整形。文本使用 HarfBuzz/FreeType
252
+ 技术栈测量(见 *已实现的文本*)。
253
+ - **Flexbox** —— 单行 `row`/`column`(以及 `-reverse` 变体),支持 `gap`、
254
+ `flex-basis`/`flex-grow`/`flex-shrink`、`justify-content` 与 `align-items`
255
+ (当交叉轴尺寸确定时包括 `stretch`)。
256
+
257
+ 尚未实现:外边距折叠、多行 flex 换行、`position` 偏移
258
+ (`relative`/`absolute`/`fixed`)、`overflow` 裁剪与浮动。
259
+
260
+ **Document**(`runtime/ext_gui/document.{h,cpp}`):持有 DOM 树,把 `<style>`
261
+ 文本汇总为一份样式表,为某个视口计算样式与布局,并提供
262
+ `querySelector`/`querySelectorAll`/`styleOf`/`boxOf`。
263
+
264
+ ### 已实现的绘制(M4a/M4b)
265
+
266
+ `runtime/ext_gui/paint.{h,cpp}` 按画家顺序遍历布局树,生成一个与后端无关的
267
+ `DisplayList`,其中有两个并列的列表:**矩形**(背景与四条实心边框,带
268
+ `border-radius`)与**文本 run**(每个携带其文本、颜色与已解析的 `FontSpec`)。
269
+ 把它们分开可以让渲染器先绘制所有矩形,再在其上绘制所有文本,每个列表一次
270
+ 绘制调用。`DisplayList::dump()` 为 `paintList()`/`paintCount()` 提供数据。
271
+
272
+ `runtime/ext_gui/renderer.{h,cpp}` 把该列表转换为每个窗口两个批处理顶点缓冲
273
+ (一个用于图形,一个用于字形四边形),通过两条 SDL_GPU 图形管线绘制:
274
+
275
+ - 共享管线会依据设备支持的着色器格式延迟创建。SDL_GPU 的三个后端各自只接受一种
276
+ 二进制格式,且都不会在运行时编译 GLSL/HLSL,因此引擎三种都随包提供,由
277
+ `selectShader`(`renderer_shaders.h`)挑选设备可用的那一种:Metal 用 **MSL**
278
+ (SDL 从内嵌源码编译)、Vulkan 用 **SPIR-V**
279
+ (`runtime/ext_gui/spirv/*.{vert,frag}`,由 `glslc` 编译)、Direct3D 12 用
280
+ **DXIL**(`shaders.hlsl`,由 `dxc` 编译)。
281
+ `scripts/build-gui-shaders.mjs` 重新生成内嵌二进制块,产物已提交,因此构建引擎
282
+ 无需额外工具。各格式的描述符绑定不同——SPIR-V 遵循 `SDL_gpu_vulkan.c` 的布局
283
+ (uniform 在 `set=1, binding=0`,采样器在 `set=2, binding=0`,纹理在
284
+ `set=2, binding=1`)——且 glslc 会把所有入口点命名为 `main`,而 MSL/DXIL 保留
285
+ 各自的描述性名称。
286
+ 需注意:SDL 3.2.10 的 Direct3D 12 后端无法创建着色器中声明了 uniform buffer 的
287
+ 图形管线(返回 `E_INVALIDARG`);引擎正是用这种方式传视口,因此 DXIL 这条路已
288
+ 构建但在上游修复前不可用。
289
+ - 图形片元着色器中的**圆角矩形距离场**提供抗锯齿填充;顶点携带
290
+ `position`、`local`、`half extents`、`radius` 与颜色,一个视口尺寸的
291
+ push constant 负责投影。启用 alpha 混合。
292
+ - 文本管线采样一张**单一共享的灰度字形图集**(`R8_UNORM`,2048²,
293
+ shelf 打包,LINEAR 过滤),把每个字形绘制为带纹理的四边形
294
+ (`position`、`uv`、颜色),按覆盖率调制 alpha。
295
+ - 只有在文档或视口变化(`geometry.dirty`)时才上传几何数据,因此稳态帧
296
+ 只需绑定并绘制。
297
+
298
+ 窗口背景(`setBackground`)是渲染通道的清除色。
299
+
300
+ M4 仍需完成:渐变。
301
+
302
+ ### 已实现的输入(M5)
303
+
304
+ `LayoutTree::hitTest` 返回包含某点的最深盒(先探测靠后的兄弟,使最上层元素
305
+ 胜出),`xt_dom_describe` 把元素转换为事件负载携带的 `div#id.class` 描述符。
306
+
307
+ - SDL 指针/滚轮/键盘事件会被路由到所属窗口,进行命中测试,并投递给用 `on`
308
+ 注册的处理器。`mousedown` 还会移动焦点。
309
+ - `:hover` 匹配悬停元素**及其祖先**(因此悬停子元素会点亮其父元素);
310
+ `:focus` 匹配聚焦元素。当任一改变时,`XtDocument::setHover`/`setFocus`
311
+ 重新计算样式与布局,并把窗口几何标记为脏,因此改动会在下一帧绘制。
312
+ - `win.hitTest(x, y)` 与 `win.sendEvent(type, options)` 暴露命中测试与合成
313
+ 输入,使整条路径可无头测试(e2e 套件在没有真实鼠标的情况下驱动点击、滚轮
314
+ 与按键)。
315
+
316
+ 完整输入仍需完成:文本选择、拖拽、IME 与剪贴板。
317
+
318
+ ### 已实现的图像(M6a)
319
+
320
+ `runtime/ext_gui/image.{h,cpp}` 封装 vendored **stb_image** 头文件,把
321
+ PNG/JPEG/BMP/GIF/TGA 解码为 RGBA8,并带有解码缓存与仅头文件的尺寸缓存
322
+ (`stbi_info`)。路径接受 `file://` 前缀与百分号编码。
323
+
324
+ - `<img>` 是一个替换元素(`display: inline-block`):设置了 CSS 尺寸时布局
325
+ 采用该尺寸,否则采用固有像素尺寸;当只约束一个轴时保持宽高比。构建盒树时
326
+ 从文件头(无需完整解码)读取固有尺寸。
327
+ - 绘制时为每个 `<img>` 生成一个 `PaintImage`;渲染器把每个唯一 `src` 解码/
328
+ 上传到 RGBA 纹理(按路径缓存)并绘制带纹理的四边形,把共享同一纹理的连续
329
+ 四边形批处理。
330
+
331
+ 图像仍需完成:CSS `background-image: url(...)`、`data:` URI、`object-fit`
332
+ 与九宫格边框。
333
+
334
+ ### 已实现的 transition(M6b)
335
+
336
+ `XtDocument` 在*目标*计算样式(当前 `:hover`/`:focus` 状态下的层叠结果)与
337
+ 用于布局和绘制的*显示*样式之间运行 CSS transition。当状态变化改变了某个参与
338
+ transition 的属性时,会记录一个正在运行的 transition,并在每帧重新应用直到
339
+ 结束;引擎用真实帧间隔推进时钟(`XtDocument::advance`),`win.advance(ms)`
340
+ 让测试可以确定性地推进。
341
+
342
+ - 支持的属性:`background-color`、`color`、`border-color`、
343
+ `border-radius`;`transition: all` 覆盖它们。影响结构/布局的属性暂不做
344
+ 动画(那会每帧重新布局)。
345
+ - `transition` 简写与 `transition-property` / `-duration` / `-delay` /
346
+ `-timing-function` 长写都会被解析;时间值接受 `s` 与 `ms`;缓动函数为
347
+ `linear`、`ease`、`ease-in`、`ease-out` 与 `ease-in-out`(`ease` 是
348
+ smoothstep 近似)。
349
+ - 在动画中途重定向会从当前插值启动一个新 transition,因此反转悬停会从它
350
+ 当前所在位置平滑动画。
351
+ - `computedStyle()` 报告显示(已插值)的值,因此 transition 在测试中可直接
352
+ 观察。
353
+
354
+ 动画仍需完成:`@keyframes` 动画与 `cubic-bezier(...)`。
355
+
356
+ ### 已实现的文本技术栈(M4b)
357
+
358
+ `runtime/ext_gui/text.{h,cpp}` 封装 **HarfBuzz**(整形)与 **FreeType**(度量
359
+ 与最终光栅化)。两者都静态构建,并由 `scripts/build-gui.ts` 链接进 `gui.a`。
360
+
361
+ - 字体从常见系统路径解析(macOS 上的 Helvetica/Arial、Linux 上的
362
+ DejaVu/Liberation、Windows 上的 Segoe UI/Arial),可用 `XT_GUI_FONT`
363
+ (以及 `XT_GUI_FONT_MONO`)覆盖,并按 `(字体类别, 字号)` 缓存。目前只使用
364
+ 常规直立字面;字重/斜体选择是后续细化。
365
+ - `xt_text_measure_width` 用 HarfBuzz 对 run 整形(因此字距调整与连字会生效),
366
+ `xt_text_metrics` 返回 FreeType 的 ascent / descent / 正常行高。当找不到
367
+ 字体文件时,模块回退到确定性的逐字节近似,因此布局仍可工作。
368
+ - `xt_text_shape_run` 返回定位后的字形,`xt_text_rasterize` 渲染 8 位位图;
369
+ 渲染器把它们打包进图集。在 HiDPI 显示器上,字形以
370
+ `font_size * SDL_GetWindowPixelDensity` 光栅化,而四边形以逻辑像素定位,
371
+ 因此文本保持清晰。
372
+ - 布局使用这些真实度量进行文本宽度、断行与 `line-height: normal` 计算;
373
+ `measureText`/`fontMetrics` 把它们暴露给测试。
374
+
375
+ 多窗口由对象模型自然得出:`createWindow` 返回一个原生对象句柄;每个句柄
376
+ 拥有自己的 `SDL_Window`/GPU 表面与自己的 DOM 树。`run()` 驱动一个共享主循环,
377
+ 它每帧 tick 所有窗口,并在最后一个窗口关闭时退出。
378
+
379
+ ## 事件循环集成
380
+
381
+ 运行时改动(已落地):
382
+
383
+ - `int xt_loop_poll(int timeout_ms)` —— 一次 reactor 迭代;`0` 非阻塞轮询,
384
+ `< 0` 阻塞。
385
+ - `void xt_loop_set_main(xt_main_loop_fn fn)` —— 宿主可以接管主循环。
386
+ `xt_run_event_loop()` 会委托给它;生成的 `main` 不变。
387
+ - `xt_loop_set_main(NULL)` 恢复默认的 `select(2)` 循环。
388
+
389
+ GUI 引擎既可以通过 `xt_loop_set_main` 注册自己的循环,也可以暴露显式的
390
+ `run()`;**M2 使用显式的 `run()`**,因此窗口是 TypeScript 程序可以控制的
391
+ 普通原生调用。每次 tick 它都会:
392
+
393
+ 1. 为每个窗口泵送 SDL 窗口/输入事件,
394
+ 2. 当显示列表变化时构建/上传它,并渲染每个打开的窗口(清除通道 + 图形几何
395
+ + 文本几何),
396
+ 3. 调用 `xt_loop_poll(0)` 与 `xt_drain_microtasks()`,使 socket/定时器与
397
+ `await` 续体持续取得进展,
398
+ 4. 重复直到所有窗口关闭或调用 `quit()`。
399
+
400
+ `xt_loop_set_main` 钩子仍保留给希望自行掌控循环的宿主。
401
+
402
+ 这使网络 I/O、定时器与 `await` 在 GUI 程序内部继续工作。
403
+
404
+ ## 原生 ↔ TS 桥接
405
+
406
+ - **TS → 引擎**:直接的 `xt_gui_*` 调用 / 窗口方法。
407
+ - **引擎 → TS**:`xt_call_with_this(fn, thisValue, argc, argv)`,其中函数值
408
+ 从 TS 捕获(例如用 `win.on(...)` 注册的事件处理器)。
409
+ - **未来的页面→原生 RPC**:在没有页面 JS 时不需要;原生 TS 就是控制器。
410
+ 如果日后加入声明式层,它将使用相同的 `xt_call_*` 入口。
411
+
412
+ ## 打包与构建
413
+
414
+ - 源码位于 `runtime/ext_gui/`(C/C++),外加 `vendor/` 下的 vendored 库
415
+ (被 gitignore;按需获取)。
416
+ - `npm run gui`(`scripts/build-gui.ts`)获取固定版本的 SDL3(`SDL3_TAG`,
417
+ 默认 `release-3.2.10`),构建静态 SDL3,获取并构建静态 FreeType
418
+ (`FREETYPE_VERSION`)与 HarfBuzz(`HARFBUZZ_VERSION`),编译引擎,并把
419
+ 所有内容合并到 `runtime/lib/<os>-<arch>/gui.a`(Windows 上 MSVC ABI 则为
420
+ `gui.lib`),与 `core.a` 采用相同约定,静态打包三者。合并使用 macOS 上的
421
+ `libtool` 与其他平台上的 `ar -M`(GNU/LLVM);CMake 归档会在构建根目录或
422
+ `Release/` 下按任一生成器风格被发现。
423
+ - `src/extensions/gui/index.ts` 通过 `nativeObjects()` 暴露该归档(经由
424
+ `findRuntimeLibrary`,因此 `.a`/`.lib` 都可用),并通过 `linkerFlags()`
425
+ 暴露操作系统框架。
426
+ - CI 在组装发布归档之前构建 `gui.a`,因此它会随 `runtime/lib/<slug>/` 一起
427
+ 发布(发布 tarball 会复制整个 `runtime/` 树)。归档在 Linux(Xvfb 下,
428
+ 使用 lavapipe 软件 Vulkan 驱动)与 macOS 上构建并运行示例;Windows 为
429
+ 试验性。
430
+ - 现有的缓存指纹已经会对 `nativeObjects()` 的内容做哈希,因此重建 `gui.a`
431
+ 会自动使缓存二进制失效。
432
+
433
+ ### 运行示例
434
+
435
+ ```sh
436
+ npm run runtime # runtime 改动后重建 core.a
437
+ npm run gui # 构建 runtime/lib/<os>-<arch>/gui.a(首次会获取 SDL3)
438
+ xbintsc run examples/gui/hello.ts --ext gui
439
+ ```
440
+
441
+ 设置 `XT_GUI_AUTOCLOSE_MS=<n>` 可在 `n` 毫秒后关闭所有窗口,e2e 测试
442
+ (`tests/e2e/gui-*.test.ts`)用它来无头运行。
443
+
444
+ 在无头 Linux 机器上,安装 SDL3 构建头文件,并在 Xvfb 与软件 Vulkan 驱动下
445
+ 运行:
446
+
447
+ ```sh
448
+ sudo apt-get install -y clang cmake libx11-dev libxext-dev libxrandr-dev \
449
+ libxcursor-dev libxi-dev libxinerama-dev libxfixes-dev libxkbcommon-dev \
450
+ libwayland-dev wayland-protocols libdecor-0-dev libasound2-dev libpulse-dev \
451
+ libdbus-1-dev libudev-dev libdrm-dev libgbm-dev libgl1-mesa-dev \
452
+ libegl1-mesa-dev libvulkan-dev mesa-vulkan-drivers xvfb
453
+ npm run runtime && npm run gui
454
+ xvfb-run -a --server-args="-screen 0 1280x720x24" \
455
+ npx tsx src/cli/main.ts run examples/gui/hello.ts --ext gui
456
+ ```
457
+
458
+ 这些就是 CI 的 `compile-examples` job 安装的包。默认使用 X11 后端;Wayland
459
+ 也已启用,但尚未实际演练。
460
+
461
+ ## 里程碑
462
+
463
+ 1. **M1 — 基础** ✅
464
+ - 运行时中的通用 `xt_loop_poll` / `xt_loop_set_main`。
465
+ - `gui` 扩展骨架 + 通用 CLI 扩展注册。
466
+ 2. **M2 — 窗口 + GPU 清除** ✅
467
+ - SDL3 窗口、SDL_GPU swapchain、多窗口、主循环集成。
468
+ - `createWindow` / `run` / `quit` + `on`/`off` 窗口方法端到端可用
469
+ (`runtime/ext_gui/`、`scripts/build-gui.ts`)。
470
+ 3. **M3 — HTML/CSS 子集** ✅
471
+ - **M3a — 解析 + 层叠** ✅ HTML 解析器、DOM 树、CSS 解析器、选择器匹配、
472
+ UA/作者/内联层叠、继承、计算样式
473
+ (`dom.*`、`css.*`、`style.*`、`document.*`)。
474
+ - **M3b — 布局** ✅ 块级/行内流 + Flexbox(`layout.*`)、
475
+ `getBoundingClientRect`/`layoutTree`。
476
+ 4. **M4 — 绘制 + 文本 + 显示列表**
477
+ - **M4a — 显示列表 + GPU 图形** ✅ 背景/边框显示列表、圆角矩形 SDL_GPU
478
+ 管线(`paint.*`、`renderer.*`)。
479
+ - **M4b-1 — 文本技术栈 + 度量** ✅ HarfBuzz + FreeType 链接进 `gui.a`、
480
+ 字体解析/缓存、供布局使用的基于整形的文本度量
481
+ (`text.*`、`measureText`/`fontMetrics`)。
482
+ - **M4b-2 — 字形渲染** ✅ FreeType 光栅化、共享的 shelf 打包字形图集、
483
+ 显示列表中的带纹理文本四边形与感知 HiDPI 的光栅缩放。渐变仍待完成。
484
+ 5. **M5 — 输入 + 事件** ✅
485
+ - 命中测试(`LayoutTree::hitTest`)、带负载投递给 TS 处理器的指针/滚轮/
486
+ 键盘事件、`:hover`/`:focus` 有状态匹配与重算样式
487
+ (`css.*`、`style.*`、`document.*`、`gui.cpp`),以及
488
+ `hitTest`/`sendEvent` 测试钩子。
489
+ 6. **M6 — 图像,然后是 CSS transition/动画**
490
+ - **M6a — 图像** ✅ stb_image 解码、`<img>` 替换元素布局、按文件 GPU 纹理
491
+ 与带纹理四边形(`image.*`、`paint.*`、`renderer.*`)。
492
+ - **M6b — transition/动画** ✅ `transition` 简写 + 长写、对
493
+ `background-color`/`color`/`border-color`/`border-radius` 的动画、重定向
494
+ 与 `win.advance(ms)`。`@keyframes` 仍待完成。
495
+ 7. **M7 — CI 与发布** ✅(Linux/macOS 构建) / 🚧(运行 + Windows)
496
+ - `compile-examples` 在 Linux 与 macOS 上构建 `gui.a`(必需),然后在
497
+ Linux 上的 Xvfb + lavapipe 下运行示例与 `tests/e2e/gui-*.test.ts`(必需)。
498
+ macOS 运行在确认 WindowServer 之前为试验性;它被保护起来,失败只记录
499
+ 日志而不标注运行。
500
+ - `package` job 在组装发布之前构建 `gui.a`,因此它会随现有 runtime 归档
501
+ 一起发布(`package-release` 复制整个 `runtime/`)。
502
+ - `vendor/`(SDL3/FreeType/HarfBuzz,缓慢的部分)按 OS/arch 缓存,以
503
+ `scripts/build-gui.ts` 为键。
504
+ - Windows(构建 + 运行)保持试验性,直到验证 MSVC 兼容的 `gui.lib` 与
505
+ D3D12/DXIL 着色器路径;失败只记录日志而不标注运行(见 *待决问题*)。
506
+ 8. **M8 — 交互式 DOM** ✅
507
+ - 具有稳定标识的元素/文档句柄、通过运行时访问器读写属性、遍历/特性/查询、
508
+ 带有延迟重算样式/重新布局的变更
509
+ (`dom_api.*`、`document.*`、`dom.*`、`gui.cpp`、`window.cpp`)、具有
510
+ 捕获 + 冒泡阶段与 `stopPropagation` 的元素事件,以及窗口级 `e.target`
511
+ 兼容性。无编译器改动。参见 `gui-scripts.md`。
512
+ 9. **M9 — AOT `<script>`** ✅
513
+ - **M9a** ✅ —— 通用 `Extension.assetLoaders` 钩子 + bundler 集成。
514
+ - **M9b** ✅ —— gui `.html` 资产加载器(`src/extensions/gui/html.ts`):
515
+ 内联主体被包装进 `__xt_script_<hash>(window, document)` 函数,通过
516
+ `__registerScript` 内置函数注册,并替换为
517
+ `<script data-xt-id="<hash>">` 标记;`win.loadHTML` 在解析后运行匹配的
518
+ 函数并触发 `DOMContentLoaded` 然后是 `load`。
519
+ - **M9c** ✅ —— `<script src>` 文件相对 HTML 读取,其顶层 import 被提升
520
+ (说明符从 HTML 目录重写),其主体被包装/注册;缺失文件与 import 绑定
521
+ 冲突会成为诊断。一切都按文档顺序运行。
522
+ 10. **M10 — 打磨** ✅
523
+ - `win.requestAnimationFrame(fn)` / `win.cancelAnimationFrame(id)`;回调
524
+ 在每帧顶部以帧时间戳运行,并可修改 DOM
525
+ (`gui.cpp`、`window.cpp`、`gui_engine.h`)。
526
+ - `Element.offsetWidth` / `offsetHeight`(取整后的边框盒,会冲刷待处理
527
+ 的变更)与 `Element.contains(other)`(`dom_api.cpp`)。
528
+
529
+ ## 进度日志
530
+
531
+ - **M1** ✅ 通用 `xt_loop_poll`/`xt_loop_set_main`;`gui` 扩展骨架。
532
+ - **M2** ✅ SDL3 窗口 + SDL_GPU 清除、多窗口、`createWindow`/`run`。
533
+ - **M3a** ✅ HTML 解析器(`dom.*`)、CSS 解析器/匹配器(`css.*`)、层叠与
534
+ 计算样式(`style.*`)、文档模型(`document.*`)、
535
+ `computedStyle`/`queryCount`/`documentTree`、e2e 覆盖。
536
+ - **M3b** ✅ 布局(`layout.*`):块级流、带断行的行内格式化上下文、单行
537
+ Flexbox、`getBoundingClientRect`/`layoutTree`、e2e 覆盖。
538
+ - **M4a** ✅ 显示列表(`paint.*`)与带 MSL 圆角矩形管线的 SDL_GPU 2D 渲染器
539
+ (`renderer.*`)、`paintList`/`paintCount`、e2e 覆盖。
540
+ - **M4b-1** ✅ FreeType + HarfBuzz 获取/构建/合并进 `gui.a`、带字体解析的文本
541
+ 模块(`text.*`)、HarfBuzz 整形与 FreeType 度量、布局中的真实文本度量、
542
+ `measureText`/`fontMetrics`、e2e 覆盖。
543
+ - **M4b-2** ✅ `renderer.*` 中的字形图集 + 带纹理文本管线、`paint.*` 中的
544
+ 整形文本 run、`text.*` 中的 `xt_text_shape_run`/`xt_text_rasterize`、
545
+ HiDPI 光栅缩放、e2e 覆盖。
546
+ - **M5** ✅ 命中测试 + 输入事件(`LayoutTree::hitTest`、`xt_dom_describe`、
547
+ `xt_gui_dispatch_*`)、匹配器中的 `:hover`/`:focus` 与动态重算样式、
548
+ `hitTest`/`sendEvent` 测试钩子、e2e 覆盖。
549
+ - **M6a** ✅ 图像解码(`image.*`、vendored stb_image)、布局中的 `<img>`
550
+ 固有尺寸、显示列表中的 `PaintImage`、按文件 RGBA 纹理与 `renderer.*` 中的
551
+ 图像管线、e2e 覆盖。
552
+ - **M6b** ✅ CSS transition(在 `style.*` 中解析、在 `document.*` 中维护动画
553
+ 时钟与 transition 状态、`advance`/`advance(ms)` 钩子、e2e 覆盖)。
554
+ - **M7** ✅ 在 CI 中于 Linux 与 macOS 上构建 `gui.a`(必需),并在 Linux 的
555
+ Xvfb 下运行示例与 GUI e2e 套件;`package` 发布 `gui.a`,`vendor/` 被缓存,
556
+ `ar -M` 合并在 GNU/Linux 与 macOS 上可用。macOS 运行与所有 Windows 在 CI
557
+ 中仍为试验性。
558
+ - **M8** ✅ 元素/文档句柄(`dom_api.*`)、带延迟重算样式/重新布局的 DOM 变更
559
+ (`document.*`、`xt_gui_flush_dom`)、带捕获/冒泡与 `stopPropagation` 的
560
+ 元素事件分发(`dom_api.*`、`gui.cpp`),以及
561
+ `tests/e2e/gui-*.test.ts` 中的 e2e 覆盖。
562
+ - **M9a** ✅ 扩展可以按文件扩展名注册资产加载器;
563
+ `bundleModules`/`loadGraph` 在读取文件后查询它们。
564
+ `tests/driver/modules.test.ts` 中的单元测试。
565
+ - **M9b** ✅ `import page from "./page.html"` 把内联 `<script lang="ts">` 主体
566
+ 编译为启动时注册、由 `win.loadHTML` 运行的 AOT 函数(在首次布局之前;
567
+ `DOMContentLoaded` 然后是 `load`)。`tests/extensions/gui.test.ts` 中的单元
568
+ 测试,`tests/e2e/gui-*.test.ts` 中的 e2e。
569
+ - **M9c** ✅ 外部 `<script src>` 文件被读取,其 import 被提升(说明符从 HTML
570
+ 目录重写),其主体被包装/注册;缺失文件与 import 绑定冲突会成为诊断。
571
+ 加载器错误被 `loadGraph` 捕获并报告为构建错误。
572
+ - **M10** ✅ 窗口句柄上的 `requestAnimationFrame`/`cancelAnimationFrame`
573
+ (回调在每帧布局之前以帧时间戳运行),以及元素句柄上的
574
+ `offsetWidth`/`offsetHeight`/`contains`;e2e 覆盖在
575
+ `tests/e2e/gui-*.test.ts` 中。
576
+
577
+ ## 待决问题
578
+
579
+ - Linux 首发是只发布 X11、Wayland,还是两者都发布。(决定:先 X11,
580
+ Wayland 随后。)
581
+ - Windows:`scripts/build-gui.ts` 会生成 MSVC 兼容的 `gui.lib`
582
+ (COFF 对象 + `ar -M`/`llvm-ar`),但尚未在 CI 中验证,因此 Windows 步骤
583
+ 为试验性(job 保持绿色),`package` 会跳过它。它还需要一个带 D3D12/DXIL
584
+ 后端的 SDL3 构建(DXIL 需要 `dxc`)。
585
+ - **D3D12 着色器:** SPIR-V(Vulkan)与 DXIL(Direct3D 12)二进制块均已构建并
586
+ 内嵌,非 Metal 路径不再跳过几何绘制。但在 **SDL 3.2.10** 上 DXIL 仍不可用:
587
+ 其 D3D12 后端会拒绝任何着色器中声明了 uniform buffer 的图形管线,而引擎正是
588
+ 用这种方式传视口。SDL 升级后需重新确认。