xbintsc 0.5.20 → 0.5.21

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 (100) hide show
  1. package/AGENTS.md +5 -3
  2. package/dist/src/driver/compiler.js +1 -1
  3. package/dist/src/driver/compiler.js.map +1 -1
  4. package/dist/src/driver/runtime-objects.d.ts +1 -1
  5. package/dist/src/driver/runtime-objects.js +1 -0
  6. package/dist/src/driver/runtime-objects.js.map +1 -1
  7. package/dist/src/driver/version.d.ts +1 -1
  8. package/dist/src/driver/version.js +1 -1
  9. package/dist/src/extensions/node/fs/index.d.ts +4 -3
  10. package/dist/src/extensions/node/fs/index.js +12 -3
  11. package/dist/src/extensions/node/fs/index.js.map +1 -1
  12. package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
  13. package/dist/src/extensions/node/fs-promises/index.js +15 -6
  14. package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
  15. package/dist/tests/e2e/async-await.test.js +1 -1
  16. package/dist/tests/e2e/async-fs.test.d.ts +9 -0
  17. package/dist/tests/e2e/async-fs.test.js +119 -0
  18. package/dist/tests/e2e/async-fs.test.js.map +1 -0
  19. package/dist/tests/e2e/fetch.test.js +24 -0
  20. package/dist/tests/e2e/fetch.test.js.map +1 -1
  21. package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
  22. package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
  23. package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
  24. package/doc/ai/contributing.md +2 -1
  25. package/doc/ai/extensions.md +4 -3
  26. package/doc/ai/language-support.md +2 -1
  27. package/doc/ai/zh-CN/contributing.md +1 -1
  28. package/doc/ai/zh-CN/extensions.md +4 -3
  29. package/doc/ai/zh-CN/language-support.md +2 -1
  30. package/doc/gui/api.md +8 -0
  31. package/doc/gui/known-issues.md +10 -9
  32. package/doc/gui/media.md +32 -3
  33. package/doc/gui.md +24 -2
  34. package/doc/implemented/async.md +16 -3
  35. package/doc/implemented/fetch.md +38 -6
  36. package/doc/implemented/node/fs-promises.md +19 -8
  37. package/doc/implemented/node.md +1 -1
  38. package/doc/implemented.md +1 -1
  39. package/doc/unimplemented/deviations.md +1 -1
  40. package/doc/unimplemented/node/async.md +6 -3
  41. package/doc/unimplemented/node/fs-promises.md +10 -6
  42. package/doc/unimplemented/node/fs.md +7 -8
  43. package/doc/unimplemented/node.md +2 -2
  44. package/doc/unimplemented/toolchain.md +1 -1
  45. package/doc/zh-CN/gui/api.md +7 -0
  46. package/doc/zh-CN/gui/known-issues.md +8 -7
  47. package/doc/zh-CN/gui/media.md +26 -2
  48. package/doc/zh-CN/gui.md +18 -1
  49. package/doc/zh-CN/implemented/async.md +13 -2
  50. package/doc/zh-CN/implemented/fetch.md +23 -4
  51. package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
  52. package/doc/zh-CN/implemented/node.md +1 -1
  53. package/doc/zh-CN/implemented.md +1 -1
  54. package/doc/zh-CN/unimplemented/deviations.md +1 -1
  55. package/doc/zh-CN/unimplemented/node/async.md +6 -3
  56. package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
  57. package/doc/zh-CN/unimplemented/node/fs.md +2 -2
  58. package/doc/zh-CN/unimplemented/node.md +2 -2
  59. package/doc/zh-CN/unimplemented/toolchain.md +1 -1
  60. package/package.json +1 -1
  61. package/runtime/ext_gui/inline_svg.cpp +219 -0
  62. package/runtime/ext_gui/inline_svg.h +41 -0
  63. package/runtime/ext_gui/layout.cpp +18 -0
  64. package/runtime/ext_gui/layout.h +7 -0
  65. package/runtime/ext_gui/layout_flow.cpp +4 -3
  66. package/runtime/ext_gui/paint.cpp +14 -0
  67. package/runtime/ext_gui/paint.h +5 -1
  68. package/runtime/ext_gui/renderer_image.cpp +53 -25
  69. package/runtime/ext_gui/style_ua.cpp +3 -0
  70. package/runtime/ext_gui/svg.cpp +122 -39
  71. package/runtime/ext_gui/svg.h +14 -3
  72. package/runtime/ext_gui/window.cpp +28 -0
  73. package/runtime/ext_node/fs/async_api.c +314 -0
  74. package/runtime/ext_node/fs/async_complete.c +99 -0
  75. package/runtime/ext_node/fs/async_fh.c +206 -0
  76. package/runtime/ext_node/fs/async_job.c +81 -0
  77. package/runtime/ext_node/fs/async_job.h +132 -0
  78. package/runtime/ext_node/fs/async_worker.c +534 -0
  79. package/runtime/ext_node/fs/async_worker_dir.c +439 -0
  80. package/runtime/ext_node/fs/async_worker_dir.h +33 -0
  81. package/runtime/ext_node/fs/async_worker_fd.c +74 -0
  82. package/runtime/ext_node/fs/dir.c +60 -15
  83. package/runtime/ext_node/fs/fs_common.h +9 -0
  84. package/runtime/ext_node/fs/fs_ops.c +18 -29
  85. package/runtime/ext_node/fs/parts/builtins.inc +34 -0
  86. package/runtime/ext_node/fs/parts/platform.inc +66 -0
  87. package/runtime/ext_node/fs/promises.c +44 -234
  88. package/runtime/rt_internal.h +2 -0
  89. package/runtime/xt_async_io.c +333 -0
  90. package/runtime/xt_async_io.h +47 -0
  91. package/runtime/xt_fetch/async.inc +295 -0
  92. package/runtime/xt_fetch/request.inc +6 -183
  93. package/runtime/xt_fetch/socket.inc +5 -4
  94. package/runtime/xt_fetch.c +6 -4
  95. package/runtime/xt_loop.c +44 -3
  96. package/src/driver/compiler.ts +1 -1
  97. package/src/driver/runtime-objects.ts +1 -0
  98. package/src/driver/version.ts +1 -1
  99. package/src/extensions/node/fs/index.ts +12 -3
  100. package/src/extensions/node/fs-promises/index.ts +15 -6
@@ -0,0 +1,160 @@
1
+ /**
2
+ * End-to-end tests for the `gui` extension — inline `<svg>` elements.
3
+ *
4
+ * An inline `<svg>` tree is not an SVG *file*: the document already holds the
5
+ * nodes, so the engine serializes the subtree back into an SVG document and
6
+ * rasterises it exactly like `<img src="logo.svg">`. These tests pin the
7
+ * replaced-element sizing, the display-list entry, the serializer's canonical
8
+ * SVG names (the HTML parser lowercases `viewBox`/`linearGradient`) and the
9
+ * fact that a DOM mutation re-serializes the artwork.
10
+ */
11
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
12
+ import { createGuiHarness, guiAvailable } from "./gui-helpers.js";
13
+ const harness = createGuiHarness();
14
+ describe.skipIf(!guiAvailable)("gui extension — inline svg", () => {
15
+ beforeAll(() => harness.setup());
16
+ afterAll(() => harness.cleanup());
17
+ it("sizes and paints an inline svg as a replaced element", () => {
18
+ const { stdout, stderr } = harness.compileAndRun("inline-svg", `
19
+ import { createWindow, run } from "gui";
20
+
21
+ const HTML = \`
22
+ <html><head><style>
23
+ body { margin: 0; }
24
+ #scaled { width: 64px; height: 48px; }
25
+ </style></head><body>
26
+ <svg id="natural" width="8" height="6" viewBox="0 0 8 6">
27
+ <rect width="8" height="6" fill="#ff0000"/>
28
+ <circle cx="4" cy="3" r="2" fill="#0000ff"/>
29
+ </svg>
30
+ <svg id="viewbox" viewBox="0 0 20 10">
31
+ <rect width="20" height="10" fill="#00ff00"/>
32
+ </svg>
33
+ <svg id="scaled" width="8" height="6" viewBox="0 0 8 6">
34
+ <rect width="8" height="6" fill="#ff0000"/>
35
+ </svg>
36
+ </body></html>
37
+ \`;
38
+
39
+ const win = createWindow({ title: "inline-svg", width: 400, height: 300 });
40
+ win.on("ready", () => {
41
+ const natural = win.getBoundingClientRect("#natural");
42
+ console.log("natural=" + natural.width + "x" + natural.height);
43
+ const viewbox = win.getBoundingClientRect("#viewbox");
44
+ console.log("viewbox=" + viewbox.width + "x" + viewbox.height);
45
+ const scaled = win.getBoundingClientRect("#scaled");
46
+ console.log("scaled=" + scaled.width + "x" + scaled.height);
47
+ console.log("display=" + win.computedStyle("#natural", "display"));
48
+ console.log("hit=" + win.hitTest(4, 3));
49
+ console.log("no-rects=" + (win.layoutTree().indexOf("<rect>") < 0));
50
+ console.log("paint-list-start");
51
+ console.log(win.paintList());
52
+ console.log("paint-list-end");
53
+ const info = win.inlineSvg("#natural");
54
+ console.log("markup=" + info.markup);
55
+ console.log("parsed=" + info.width + "x" + info.height + " shapes=" + info.shapes);
56
+ });
57
+ win.loadHTML(HTML);
58
+ run();
59
+ `);
60
+ // width/height attributes give the intrinsic size; a bare viewBox is the
61
+ // fallback; an explicit CSS size wins over both.
62
+ expect(stdout).toContain("natural=8x6");
63
+ expect(stdout).toContain("viewbox=20x10");
64
+ expect(stdout).toContain("scaled=64x48");
65
+ // The `<svg>` box itself is the hit-test target; its children are vector
66
+ // art, not HTML boxes, so the walk stays at the svg element.
67
+ expect(stdout).toContain("hit=svg#natural");
68
+ expect(stdout).toContain("display=inline-block");
69
+ // The svg subtree produces no HTML boxes: only the replaced box is laid out.
70
+ expect(stdout).toContain("no-rects=true");
71
+ // A source newline between two inline elements is a hard line break in this
72
+ // engine (like `<br>`), so the three boxes stack in document order.
73
+ const list = stdout.slice(stdout.indexOf("paint-list-start"), stdout.indexOf("paint-list-end"));
74
+ expect(list).toContain("image x=0.0 y=0.0 w=8.0 h=6.0 inline-svg=");
75
+ expect(list).toContain("image x=0.0 y=6.0 w=20.0 h=10.0 inline-svg=");
76
+ expect(list).toContain("image x=0.0 y=16.0 w=64.0 h=48.0 inline-svg=");
77
+ // The serialized document restores the case-sensitive SVG names the HTML
78
+ // parser lowercased, and only the svg subtree is included.
79
+ const markup = stdout
80
+ .split("\n")
81
+ .find((line) => line.startsWith("markup="))
82
+ .slice("markup=".length);
83
+ expect(markup.startsWith('<svg xmlns="http://www.w3.org/2000/svg" id="natural" width="8" height="6" viewBox="0 0 8 6">')).toBe(true);
84
+ expect(markup).toContain('<rect width="8" height="6" fill="#ff0000"></rect>');
85
+ expect(markup).toContain('<circle cx="4" cy="3" r="2" fill="#0000ff"></circle>');
86
+ expect(markup).not.toContain("viewbox=");
87
+ expect(stdout).toContain("parsed=8x6 shapes=2");
88
+ // Rasterising a parsed document must actually succeed.
89
+ expect(stderr).not.toContain("could not rasterise inline SVG");
90
+ });
91
+ it("parses gradients, re-serializes on mutation and restyles the box", () => {
92
+ const { stdout, stderr } = harness.compileAndRun("inline-svg-mutate", `
93
+ import { createWindow, run } from "gui";
94
+
95
+ const HTML = \`
96
+ <html><head><style>
97
+ body { margin: 0; }
98
+ #icon { background: #123456; border: 2px solid #00ff00; }
99
+ </style></head><body>
100
+ <svg id="icon" width="24" height="24" viewBox="0 0 24 24">
101
+ <defs>
102
+ <linearGradient id="grad" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="24" y2="24">
103
+ <stop offset="0%" stop-color="#ff0000"/>
104
+ <stop offset="100%" stop-color="#0000ff"/>
105
+ </linearGradient>
106
+ </defs>
107
+ <rect id="box" width="24" height="24" fill="url(#grad)"/>
108
+ </svg>
109
+ </body></html>
110
+ \`;
111
+
112
+ const win = createWindow({ title: "inline-svg-mutate", width: 400, height: 300 });
113
+ win.on("ready", () => {
114
+ const first = win.inlineSvg("#icon");
115
+ console.log("shapes=" + first.shapes);
116
+ console.log("gradient=" + (first.markup.indexOf("linearGradient") >= 0));
117
+ console.log("units=" + (first.markup.indexOf('gradientUnits="userSpaceOnUse"') >= 0));
118
+ console.log("stops=" + first.markup.split("<stop").length);
119
+
120
+ const icon = win.document.getElementById("icon");
121
+ icon.setAttribute("width", "48");
122
+ const rect = win.document.querySelector("rect");
123
+ rect.setAttribute("fill", "#00ff00");
124
+ const grown = win.getBoundingClientRect("#icon");
125
+ const second = win.inlineSvg("#icon");
126
+ console.log("grown=" + grown.width + "x" + grown.height);
127
+ console.log("moved=" + (second.markup.indexOf('fill="#00ff00"') >= 0));
128
+
129
+ icon.innerHTML = '<circle cx="12" cy="12" r="10" fill="#ffff00"/>';
130
+ const third = win.inlineSvg("#icon");
131
+ console.log("replaced=" + (third.markup.indexOf("<circle") >= 0) + " shapes=" + third.shapes);
132
+ console.log("paint-list-start");
133
+ console.log(win.paintList());
134
+ console.log("paint-list-end");
135
+ });
136
+ win.loadHTML(HTML);
137
+ run();
138
+ `);
139
+ expect(stdout).toContain("shapes=1");
140
+ // The HTML parser lowercases `linearGradient`; the serializer restores it,
141
+ // or NanoSVG would silently drop the gradient definition.
142
+ expect(stdout).toContain("gradient=true");
143
+ expect(stdout).toContain("units=true");
144
+ // start + two stops = three occurrences of "<stop".
145
+ expect(stdout).toContain("stops=3");
146
+ // A presentation-attribute mutation changes the intrinsic size, and a paint
147
+ // mutation changes the serialized document the renderer rasterises.
148
+ expect(stdout).toContain("grown=52x28");
149
+ expect(stdout).toContain("moved=true");
150
+ // `innerHTML` swaps the artwork for a new subtree.
151
+ expect(stdout).toContain("replaced=true shapes=1");
152
+ const list = stdout.slice(stdout.indexOf("paint-list-start"), stdout.indexOf("paint-list-end"));
153
+ // The svg element is still an ordinary styled box: background + 4 borders.
154
+ expect(list).toContain("color=#123456ff");
155
+ expect((list.match(/#00ff00ff/g) ?? []).length).toBe(4);
156
+ expect(list).toContain("w=48.0 h=24.0 inline-svg=");
157
+ expect(stderr).not.toContain("could not rasterise inline SVG");
158
+ });
159
+ });
160
+ //# sourceMappingURL=gui-inline-svg.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gui-inline-svg.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-inline-svg.test.ts"],"names":[],"mappings":"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"}
@@ -110,7 +110,8 @@ diff scratch/ref.ll scratch/gen2.ll
110
110
  therefore never allocate per access.
111
111
  - The GC is a non-moving mark-sweep collector: new heap objects go through
112
112
  `xt_alloc`, and a value must be reachable from an explicit root slot, a
113
- registered root provider (the event loop, the microtask queue) or the
113
+ registered root provider (the event loop, the microtask queue, the async I/O
114
+ job roots) or the
114
115
  conservative C-stack scan while it is live. A value held only in a C local that
115
116
  is not on the scanned stack is a bug.
116
117
 
@@ -36,7 +36,7 @@ resolve to the same implementation.
36
36
  | Module | Notes |
37
37
  | --- | --- |
38
38
  | `fs` | **synchronous API only** (`readFileSync`, `writeFileSync`, …); `fs/promises` is separate |
39
- | `fs/promises` | promise-based file APIs |
39
+ | `fs/promises` | promise-based file APIs; the blocking I/O runs on the async I/O worker pool and settles on the reactor |
40
40
  | `path` | also hooks namespace dispatch, so `path.join(...)` works |
41
41
  | `os` | |
42
42
  | `process` | `process.cwd()`, `argv`, `env`, …; namespace dispatch too |
@@ -54,8 +54,9 @@ Coverage per module (exact functions and options) is in
54
54
  [../unimplemented/node.md](../unimplemented/node.md).
55
55
 
56
56
  Remember the runtime model before building on `net`/`http`: the event loop runs
57
- **after** the program body, but `async`/`await` now suspends on pending promises,
58
- so a promise settled from a socket callback can be awaited. See
57
+ **after** the program body, but `async`/`await` suspends on pending promises, so a
58
+ promise settled from a socket callback, a timer or an offloaded file/HTTP job can
59
+ be awaited. `net.connect` itself is still blocking. See
59
60
  [language-support.md](./language-support.md).
60
61
 
61
62
  ### `gui` — HTML/CSS window
@@ -75,7 +75,8 @@ Canonical detail (always current, much longer):
75
75
  | `setTimeout` and callbacks | ✅ | timers run on the core event loop; `await new Promise(r => setTimeout(r, ms))` works |
76
76
  | `queueMicrotask` / `setImmediate` / `performance.now` | ✅ | microtasks share the promise FIFO queue; `setImmediate` is a zero-delay timer with numeric ids; `performance.now` is monotonic fractional milliseconds |
77
77
  | Sockets / servers (`http`, `net`, `dgram`) | ✅ | callbacks run on the event loop and can settle a promise you await (`net` / `http` examples included in the tests) |
78
- | `fs/promises` | ⚠️ | awaitable, but each call performs its I/O synchronously before settling, so it does not yield while the I/O runs |
78
+ | `fs/promises` | ✅ | file, directory and `FileHandle` operations run on the async I/O worker pool and settle on the reactor, so timers and sockets keep running while they wait |
79
+ | Global `fetch` | ✅ | HTTP/1.1 client whose connect/send/receive runs on the same worker pool; `await fetch(...)` yields and redirects are followed on the reactor. `http://` only (no TLS) and the body is buffered before resolving |
79
80
  | Worker threads | 🚫 | workers are child processes |
80
81
  | `async` generators | ✅ | suspend at `await` and `yield`; every `.next()` returns a promise; `for await...of` supported |
81
82
 
@@ -93,7 +93,7 @@ diff scratch/ref.ll scratch/gen2.ll
93
93
  - 字符串字面量会被驻留:每个不同字面量只在 `main` 中创建一次,存入 GC 根全局,
94
94
  每次使用只做一次 load;属性名因此不再逐次分配。
95
95
  - GC 是非移动标记清扫:新堆对象走 `xt_alloc`;值存活期间必须能从显式根槽、已注册的
96
- 根提供者(事件循环、微任务队列)或 C 栈保守扫描到达。只存在于未被扫描的 C 局部
96
+ 根提供者(事件循环、微任务队列、异步 I/O job 根)或 C 栈保守扫描到达。只存在于未被扫描的 C 局部
97
97
  变量里的值就是 bug。
98
98
 
99
99
  ## 修改运行时
@@ -35,7 +35,7 @@ xbintsc run app.ts --ext node
35
35
  | 模块 | 说明 |
36
36
  | --- | --- |
37
37
  | `fs` | **仅同步 API**(`readFileSync`、`writeFileSync` 等);`fs/promises` 是独立模块 |
38
- | `fs/promises` | 基于 Promise 的文件 API |
38
+ | `fs/promises` | 基于 Promise 的文件 API;阻塞 I/O 跑在异步 I/O 线程池上并在 reactor 上 settle |
39
39
  | `path` | 同时接入命名空间分发,`path.join(...)` 可用 |
40
40
  | `os` | |
41
41
  | `process` | `process.cwd()`、`argv`、`env` 等;也接入命名空间分发 |
@@ -53,8 +53,9 @@ xbintsc run app.ts --ext node
53
53
  [../../zh-CN/unimplemented/node.md](../../zh-CN/unimplemented/node.md)。
54
54
 
55
55
  在 `net`/`http` 上搭东西之前,先记住运行时模型:事件循环在**程序主体之后**才运行,
56
- 但 `async`/`await` 现在会在 pending Promise 上挂起,因此由套接字回调 settle 的 Promise
57
- 可以被 await。见 [language-support.md](./language-support.md)。
56
+ 但 `async`/`await` 会在 pending Promise 上挂起,因此由套接字回调、定时器或 offload 的
57
+ 文件/HTTP job settle 的 Promise 都可以 await。`net.connect` 本身仍是阻塞的。见
58
+ [language-support.md](./language-support.md)。
58
59
 
59
60
  ### `gui` —— HTML/CSS 窗口
60
61
 
@@ -72,7 +72,8 @@ xbintsc 编译 TypeScript 的一个**实用子集**。本页用来判断你能
72
72
  | `setTimeout` 与回调 | ✅ | 定时器运行在核心事件循环上;`await new Promise(r => setTimeout(r, ms))` 可用 |
73
73
  | `queueMicrotask` / `setImmediate` / `performance.now` | ✅ | 微任务与 Promise 共用 FIFO 队列;`setImmediate` 是零延迟定时器,返回数字 id;`performance.now` 返回单调递增的毫秒小数 |
74
74
  | 套接字/服务端(`http`、`net`、`dgram`) | ✅ | 回调在事件循环上执行,可以 settle 你正在 await 的 Promise(测试含 `net`/`http` 示例) |
75
- | `fs/promises` | ⚠️ | 可以 await,但每次调用仍先同步完成 I/O 再 settle,I/O 期间不会让出 |
75
+ | `fs/promises` | ✅ | 文件、目录与 `FileHandle` 操作跑在异步 I/O 线程池上,并在 reactor 上 settle,等待期间定时器与套接字继续运行 |
76
+ | 全局 `fetch` | ✅ | HTTP/1.1 客户端,connect/send/receive 跑在同一线程池;`await fetch(...)` 会让出,重定向在 reactor 上处理。仅 `http://`(无 TLS),resolve 前缓冲整个响应体 |
76
77
  | worker 线程 | 🚫 | worker 是子进程 |
77
78
  | `async` 生成器 | ✅ | 可在 `await` 与 `yield` 处挂起;每次 `.next()` 返回 Promise;支持 `for await...of` |
78
79
 
package/doc/gui/api.md CHANGED
@@ -65,6 +65,7 @@ win.documentTree() // serialized DOM (debugging)
65
65
  win.layoutTree() // serialized layout boxes (debugging)
66
66
  win.paintCount() // number of shapes in the display list
67
67
  win.paintList() // serialized display list (debugging)
68
+ win.inlineSvg(selector) // serialized inline <svg>: { markup, width, height, shapes }
68
69
  win.measureText(text, fontSize?, family?) // shaped advance width in pixels
69
70
  win.fontMetrics(fontSize?, family?) // { ascent, descent, lineHeight, ready }
70
71
  win.hitTest(x, y) // deepest element descriptor, or ""
@@ -73,6 +74,13 @@ win.advance(ms) // step the CSS transition clock (testing
73
74
  win.stats() // engine/runtime counters (memory debugging)
74
75
  ```
75
76
 
77
+ `win.inlineSvg(selector)` returns the standalone SVG document the engine builds
78
+ from the first `<svg>` element matching `selector` (`markup`), together with the
79
+ intrinsic size and shape count NanoSVG parses out of it; it is `undefined` when
80
+ nothing matches or the element is not an `<svg>`. It makes the serializer's
81
+ canonical casing (`viewBox`, `linearGradient`, `gradientUnits`, …) testable
82
+ without a GPU — the HTML parser delivers those names lowercased.
83
+
76
84
  `win.stats()` returns `{ heapBytes, heapAllocations, treeNodes, detachedRoots,`
77
85
  `detachedNodes, handles, handleSlots, listeners }`: the live runtime heap, the
78
86
  size of the DOM tree, the detached-node pool, and the element-handle tables. In a
@@ -34,20 +34,21 @@
34
34
  *class change* on a flex container has also been observed to crash the same
35
35
  way; `examples/gui/pelican-bike` reveals its game-over overlay with `opacity`
36
36
  instead.
37
+ - **SVG is NanoSVG's subset.** SVG *files* (`<img src="logo.svg">`) and inline
38
+ `<svg>` trees share the vendored NanoSVG parser/rasteriser
39
+ (`runtime/ext_gui/svg.cpp`, `inline_svg.cpp`), which renders
40
+ `path`/`rect`/`circle`/`ellipse`/`line`/`polyline`/`polygon`, groups, `defs`
41
+ gradients and class-based `<style>` rules. `<text>`, `<use>`, filters, masks
42
+ and clip paths are not rendered, and CSS applied by the document cascade does
43
+ not reach inside the artwork (fill/stroke are SVG presentation attributes,
44
+ not CSS properties here). Vector art that needs those has to be rasterised
45
+ beforehand, drawn from boxes, or restricted to the supported syntax. See
46
+ [Media](./media.md#images-m6a).
37
47
 
38
48
  ## Open questions
39
49
 
40
50
  - Whether Linux ships X11, Wayland, or both in the first cut. (Decision:
41
51
  X11 first, Wayland later.)
42
- - **SVG:** SVG *files* are supported through the image loader
43
- (`<img src="logo.svg">`): `runtime/ext_gui/svg.cpp` wraps the vendored
44
- **NanoSVG** parser/rasteriser and `image.{h,cpp}` treats an SVG as a vector
45
- image, rasterised at the drawn pixel size. Inline `<svg>` **trees** in a
46
- document are still not supported: the HTML parser knows elements and
47
- attributes, the layout engine knows boxes, and paint knows
48
- rectangles/text/images, so `<svg>` subtrees would need a whole second
49
- geometry+paint path. Vector art therefore has to arrive as an SVG/PNG file or
50
- be drawn from boxes, as `examples/gui/pelican-bike` does.
51
52
  - **Windows: the C++ runtime must not be named on the link line.** `gui.lib` is
52
53
  built with the *static* CRT (`CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`) and
53
54
  every engine object records `libcpmt` as a dependent library, which is also
package/doc/gui/media.md CHANGED
@@ -12,7 +12,9 @@ size cache (`stbi_info`). Paths accept a `file://` prefix and percent-encoding.
12
12
  library (parse + anti-aliased scanline rasteriser), detects an SVG by extension
13
13
  or by its contents, and produces the same RGBA8 `Image` the pipeline already
14
14
  consumes. `stb_image` cannot decode SVG, so this is what makes
15
- `<img src="logo.svg">` work.
15
+ `<img src="logo.svg">` work. The same parser is used for an inline `<svg>`
16
+ subtree, whose DOM nodes are serialized into a document first
17
+ (`inline_svg.{h,cpp}`).
16
18
 
17
19
  - `<img>` is a replaced element (`display: inline-block`): layout gives it the
18
20
  CSS size when set, otherwise the intrinsic pixel size, and preserves the
@@ -27,12 +29,39 @@ consumes. `stb_image` cannot decode SVG, so this is what makes
27
29
  pixels (logical size × the window's pixel density), preserving the viewBox
28
30
  aspect ratio, and cached by that size, so scaling or a HiDPI display produces
29
31
  a crisp render instead of a magnified bitmap.
32
+ - An **inline** `<svg>` element is a replaced element like `<img>`, but its
33
+ source is the DOM subtree itself. `runtime/ext_gui/inline_svg.{h,cpp}`
34
+ serializes that subtree back into a standalone SVG document, restoring the
35
+ case-sensitive spellings NanoSVG matches (`viewBox`, `preserveAspectRatio`,
36
+ `linearGradient`, `gradientUnits`, …) that the HTML parser lowercased. Layout
37
+ sizes the box from the `width`/`height` attributes, else the `viewBox`, else
38
+ 300x150; paint carries the markup on its image op and the renderer parses and
39
+ rasterises it at the drawn device pixel size through the same NanoSVG cache as
40
+ an SVG file. A DOM mutation re-serializes the subtree on the next layout (so
41
+ `setAttribute` and `innerHTML` both repaint the artwork), and the markup cache
42
+ is bounded (least-recently-used) so an animated document cannot grow it
43
+ without limit.
30
44
  - The parse is cached per path; the parse and rasteriser state are released in
31
45
  `xt_svg_shutdown` (`xt_image_shutdown`).
32
46
 
47
+ Document CSS styles the `<svg>` **box** — size, margins, background, borders,
48
+ `transform`, opacity, `display` — exactly as it styles any other element. The
49
+ artwork inside it keeps SVG semantics: presentation attributes and the
50
+ `class`-based rules of an SVG `<style>` block are read by NanoSVG, not by the
51
+ HTML cascade, so `fill`/`stroke` are not CSS properties here. For layout, paint,
52
+ hit testing and events the whole `<svg>` is one replaced box: its children get no
53
+ DOM boxes, so a click's `e.target` is the `<svg>` element, not a `<rect>`. The
54
+ renderer caches one texture per markup-and-size pair, so a program that rewrites
55
+ its artwork every frame keeps a texture per variant; prefer animating the box
56
+ (`transform`, `opacity`) or swapping between a small set of documents.
57
+
33
58
  Still to do for images: CSS `background-image: url(...)`, `data:` URIs,
34
- `object-fit`, 9-slice borders and inline `<svg>` elements (only SVG *files* are
35
- supported; see [Open questions](./known-issues.md#open-questions)).
59
+ `object-fit` and 9-slice borders.
60
+
61
+ Both kinds of SVG share NanoSVG's subset: `path`, `rect`, `circle`, `ellipse`,
62
+ `line`, `polyline`, `polygon`, groups (`g`), `defs` with linear/radial gradients
63
+ and class-based `<style>` rules rasterise; `<text>`, `<use>`, filters, masks and
64
+ clip paths are not rendered (see [Known issues](./known-issues.md)).
36
65
 
37
66
  ## Video & audio (M13)
38
67
 
package/doc/gui.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xbintsc GUI extension (self-hosted HTML/CSS renderer)
2
2
 
3
- Status: **M15** — features (M1–M14) are complete: HTML parsing, CSS selector
3
+ Status: **M16** — features (M1–M15) are complete: HTML parsing, CSS selector
4
4
  matching, the cascade, computed styles and layout (block, inline, Flexbox and
5
5
  CSS positioning) are
6
6
  in place, and the engine *paints*: it builds a display list of rectangles, images
@@ -9,7 +9,9 @@ tested and delivered to native TS handlers, `:hover`/`:focus` are matched
9
9
  dynamically, `<img>` is sized from its intrinsic dimensions and drawn from a
10
10
  texture — PNG/JPEG/… bitmaps and SVG files alike, the latter rasterised at the
11
11
  drawn size so vector art stays crisp — and CSS transitions animate paint
12
- properties. **M8** adds an interactive
12
+ properties. **M16** gives inline `<svg>` trees the same treatment: the subtree is
13
+ serialized back into an SVG document and rasterised at the drawn device size, so
14
+ an icon needs no separate file. **M8** adds an interactive
13
15
  DOM: element handles with stable identity, mutation (`appendChild`, `textContent`,
14
16
  `classList`, `style`, …) and element-level events with capture/bubbling.
15
17
  **M9** compiles `<script>` bodies ahead of time (inline and `<script src>`) — see
@@ -257,6 +259,18 @@ engine were Rust.
257
259
  [Layout](./gui/layout.md#transforms-m15) and
258
260
  [Animation](./gui/animation.md#css-transforms-m15); e2e in
259
261
  `tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
262
+ 16. **M16 — inline `<svg>`** ✅
263
+ - `<svg>` is a replaced element (`layout.*`): its intrinsic size comes from
264
+ the `width`/`height` attributes, else the `viewBox`, else 300x150, and the
265
+ normal box walk does not descend into its children.
266
+ - `inline_svg.{h,cpp}` serializes the subtree into a standalone SVG document,
267
+ restoring the case-sensitive names the HTML parser lowercased (`viewBox`,
268
+ `preserveAspectRatio`, `linearGradient`, `gradientUnits`, …).
269
+ - Paint carries the markup on its image op (`PaintImage::svg`) and the
270
+ renderer parses/rasterises it at the drawn device size through the same
271
+ NanoSVG cache as an SVG file (`svg.{h,cpp}`); the markup cache is bounded
272
+ and a DOM mutation re-serializes on the next layout. e2e in
273
+ `tests/e2e/gui-inline-svg.test.ts`.
260
274
 
261
275
  ## Progress log
262
276
 
@@ -355,4 +369,12 @@ engine were Rust.
355
369
  through the inverse, and `getBoundingClientRect` returns the transformed AABB.
356
370
  `examples/gui/pelican-svg` animates its artwork with it; e2e in
357
371
  `tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
372
+ - **M16** ✅ inline `<svg>`. `buildBoxTree` treats an `<svg>` element as a
373
+ replaced box (intrinsic size from `width`/`height`, then `viewBox`, then
374
+ 300x150) and stops descending, `inline_svg.{h,cpp}` serializes the subtree with
375
+ canonical SVG casing, paint carries the markup on `PaintImage::svg`, and
376
+ `renderer_image.cpp` rasterises it at the drawn device pixel size through the
377
+ NanoSVG cache (`svg.{h,cpp}`, bounded LRU for markup). The `win.inlineSvg`
378
+ diagnostic exposes the serialized document, and `tests/e2e/gui-inline-svg.test.ts`
379
+ pins sizing, hit testing, gradients, CSS box styling and mutation.
358
380
 
@@ -3,7 +3,8 @@
3
3
  > Part of [Implemented syntax and features](../implemented.md) · [简体中文](../zh-CN/implemented/async.md)
4
4
 
5
5
  Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`,
6
- `runtime/xt_generator.c`, `runtime/xt_loop.c`, `runtime/xt_performance.c`.
6
+ `runtime/xt_generator.c`, `runtime/xt_loop.c`, `runtime/xt_async_io.c`,
7
+ `runtime/xt_performance.c`.
7
8
 
8
9
  ## async / await
9
10
 
@@ -11,7 +12,7 @@ Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`
11
12
  - The body runs synchronously until the first `await` of a pending promise, then suspends; settlement queues a microtask that resumes the coroutine.
12
13
  - An exception that escapes the body rejects that promise instead of throwing synchronously.
13
14
  - `await` on a non-promise or settled promise keeps the synchronous fast path and raises on rejection.
14
- - Each `async` call owns a private stack and swaps its `try` frame stack and GC stack region, so `await` survives `try`/`finally`, loops and closures. A promise settled from a timer, a socket callback or another coroutine resumes the suspended body on the microtask queue.
15
+ - Each `async` call owns a private stack and swaps its `try` frame stack and GC stack region, so `await` survives `try`/`finally`, loops and closures. A promise settled from a timer, a socket callback, an offloaded file operation or another coroutine resumes the suspended body on the microtask queue.
15
16
 
16
17
  ## async generators
17
18
 
@@ -35,4 +36,16 @@ Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`
35
36
  - `setTimeout` / `clearTimeout` / `setInterval` / `clearInterval` / `setImmediate` / `clearImmediate` / `queueMicrotask` and `performance.now` (monotonic fractional milliseconds).
36
37
  - Timers use numeric ids; `Timeout` / `Immediate` objects are not implemented.
37
38
  - A promise settled from a timer can be awaited: the async body suspends on the pending promise and resumes when the timer fires. Sockets and servers (`net`, `http`, `dgram`) deliver their callbacks on the same loop.
38
- - `fetch` is blocking and `fs/promises` performs its I/O synchronously before settling — see [Runtime deviations](../unimplemented/deviations.md). Async generator cleanup limits are listed on the same page.
39
+ - `fetch` and `fs/promises` are reactor-driven rather than blocking — see the next section.
40
+
41
+ ## Asynchronous I/O offload
42
+
43
+ - `runtime/xt_async_io.c` owns a small pool of worker threads (four by default, created lazily). Jobs are split in two halves: a `run` function on a worker that may only touch plain C memory, and a `complete` function on the main thread that builds values and settles the promise. The split is what keeps the collector and the value model single-threaded.
44
+ - A completed job wakes `select(2)` through a self-pipe (POSIX) or a loopback UDP socket (Windows); the reactor also polls the pool, so a failed wakeup degrades to a short wait instead of a hang.
45
+ - While a job is queued or running, up to four values per job (its promise, and for `fetch` the headers object) are kept in the job's root slots and scanned by the collector.
46
+ - `fetch` runs each request/redirect hop as a job; `fs/promises` runs every file and descriptor operation as a job. Neither blocks timers, sockets or other `await`s.
47
+ - The event loop stays alive while jobs are outstanding (`xt_loop_has_work`), and exits once the pool is idle again.
48
+
49
+ Known limits of the asynchronous surface (no TLS/streaming `fetch`, inert
50
+ `fs.watch`, four-worker pool) are listed in
51
+ [Runtime deviations](../unimplemented/deviations.md) and the Node pages.
@@ -2,11 +2,43 @@
2
2
 
3
3
  > Part of [Implemented syntax and features](../implemented.md) · [简体中文](../zh-CN/implemented/fetch.md)
4
4
 
5
- Location: `runtime/xt_fetch.c`.
5
+ Location: `runtime/xt_fetch.c`, `runtime/xt_fetch/async.inc`, `runtime/xt_async_io.c`.
6
6
 
7
- - Global `fetch(input, init)` is a blocking HTTP/1.1 client whose promise is already settled (the exchange happens synchronously; it is not scheduled on the reactor).
8
- - It accepts `http://` URLs only — `https://` rejects with a `TypeError` because no TLS backend is linked.
9
- - Options: `method`, `headers` (plain object or `Headers`), `body` and `redirect` (`follow` / `manual` / `error`); up to 20 redirects are followed, and a 301/302/303 switches POST to GET.
10
- - The resolved `Response`-shaped object exposes `ok` / `status` / `statusText` / `url` / `headers` plus `text()` / `json()` / `arrayBuffer()` / `bytes()` / `clone()`.
11
- - `Headers` is case-insensitive with `get` / `has` / `set` / `append` / `delete` / `keys` / `values` / `entries` / `forEach` / `getSetCookie`.
7
+ - Global `fetch(input, init)` is an HTTP/1.1 client. The blocking socket exchange
8
+ (`getaddrinfo`, connect, send, receive) runs on the asynchronous I/O worker
9
+ pool, so a request in flight does **not** stall the event loop: timers, sockets
10
+ and other `await`s keep running and the promise settles when the reactor
11
+ dispatches the completed exchange.
12
+ - Redirects are followed on the main thread: each hop is one offloaded
13
+ exchange, up to 20 redirects, and a 301/302/303 switches POST to GET.
14
+ - It accepts `http://` URLs only — `https://` rejects with a `TypeError` because
15
+ no TLS backend is linked.
16
+ - Options: `method`, `headers` (plain object or `Headers`), `body` and `redirect`
17
+ (`follow` / `manual` / `error`).
18
+ - The resolved `Response`-shaped object exposes `ok` / `status` / `statusText` /
19
+ `url` / `headers` plus `text()` / `json()` / `arrayBuffer()` / `bytes()` /
20
+ `clone()`. The body is buffered before the promise settles; streaming bodies
21
+ are not implemented.
22
+ - `Headers` is case-insensitive with `get` / `has` / `set` / `append` /
23
+ `delete` / `keys` / `values` / `entries` / `forEach` / `getSetCookie`.
12
24
  - Invalid URLs and network failures reject with a `TypeError`.
25
+
26
+ ```ts
27
+ async function main(): Promise<void> {
28
+ const response = await fetch("http://127.0.0.1:8080/status");
29
+ console.log(response.status, await response.text());
30
+ }
31
+ main();
32
+ ```
33
+
34
+ A timer scheduled before the request completes fires while the exchange is still
35
+ in flight:
36
+
37
+ ```ts
38
+ const pending = fetch("http://127.0.0.1:8080/slow");
39
+ setTimeout(() => console.log("timer"), 10); // runs before the 150ms response
40
+ await pending;
41
+ ```
42
+
43
+ Known limits (only `http://`, no TLS, buffered body) are listed in
44
+ [Runtime deviations](../unimplemented/deviations.md).
@@ -2,14 +2,20 @@
2
2
 
3
3
  > Part of [Node extension: implemented](../node.md) · [简体中文](../../zh-CN/implemented/node/fs-promises.md)
4
4
 
5
- Location: `src/extensions/node/fs-promises/index.ts`, `runtime/ext_node/fs/promises.c`
5
+ Location: `src/extensions/node/fs-promises/index.ts`, `runtime/ext_node/fs/async_*.c`,
6
+ `runtime/ext_node/fs/promises.c`.
6
7
 
7
- There is no asynchronous I/O scheduler, so each function wraps the corresponding
8
- synchronous `fs` implementation in an **already-settled Promise**. Failures
9
- **reject** with the same Node-shaped `Error` (with `code` / `errno` / `syscall` /
10
- `path`) that the synchronous form throws. `fs.promises` is also reachable from
11
- the `fs` module (`import { promises as fsp } from "fs"`), and both namespaces
12
- expose a `constants` object.
8
+ Every function submits a job to the runtime's asynchronous I/O pool
9
+ (`runtime/xt_async_io.c`): the blocking syscall runs on a worker thread and the
10
+ promise settles on the event loop. `await` therefore yields while a large read,
11
+ a slow network filesystem or a descriptor operation is in progress, and `then` /
12
+ `catch` keep the usual Promise semantics. Failures **reject** with the same
13
+ Node-shaped `Error` (with `code` / `errno` / `syscall` / `path`) that the
14
+ synchronous form throws.
15
+
16
+ `fs.promises` is also reachable from the `fs` module
17
+ (`import { promises as fsp } from "fs"`), and both namespaces expose a
18
+ `constants` object.
13
19
 
14
20
  Promise exports: `readFile`, `writeFile`, `appendFile`, `mkdir`, `readdir`,
15
21
  `rm`, `unlink`, `rmdir`, `rename`, `copyFile`, `cp`, `realpath`, `stat`,
@@ -19,7 +25,9 @@ Promise exports: `readFile`, `writeFile`, `appendFile`, `mkdir`, `readdir`,
19
25
 
20
26
  `open(...)` resolves to a **`FileHandle`** with:
21
27
  `read`, `write`, `readFile`, `writeFile`, `appendFile`, `close`, `stat`,
22
- `truncate`, `chmod`, `chown`, `utimes`, `sync`, `datasync`.
28
+ `truncate`, `chmod`, `chown`, `utimes`, `sync`, `datasync`. Those methods are
29
+ offloaded too; `read(buffer, …)` fills the caller's `Buffer` when the worker
30
+ completes.
23
31
 
24
32
  ```ts
25
33
  import { readFile, writeFile, open } from "fs/promises";
@@ -34,5 +42,8 @@ async function main(): Promise<void> {
34
42
  main();
35
43
  ```
36
44
 
45
+ Work runs on a small fixed pool (four workers) and completions are dispatched in
46
+ submission order per worker; independent operations can overlap. A job never
47
+ blocks another `await`, a timer or a socket callback.
37
48
 
38
49
  Known deviations are listed in [Unimplemented: `fs/promises`](../../unimplemented/node/fs-promises.md).
@@ -19,7 +19,7 @@ work on another. Missing or different behaviour is recorded under
19
19
  | Module | Page | Notes |
20
20
  | --- | --- | --- |
21
21
  | `fs` | [fs](./node/fs.md) | synchronous API only |
22
- | `fs/promises` | [fs/promises](./node/fs-promises.md) | wraps the synchronous API in settled promises |
22
+ | `fs/promises` | [fs/promises](./node/fs-promises.md) | reactor-driven file and `FileHandle` operations on the async I/O pool |
23
23
  | `path` | [path](./node/path.md) | `path.<name>(...)` namespace calls |
24
24
  | `os` | [os](./node/os.md) | `os.<name>(...)` namespace calls |
25
25
  | `process` | [process](./node/process.md) | object: methods and properties |
@@ -37,7 +37,7 @@ small file instead of a single monolithic document.
37
37
  - [Collections](./implemented/collections.md) — Map, Set, WeakMap, WeakSet, WeakRef, iteration
38
38
  - [Typed arrays and binary data](./implemented/typed-arrays.md) — typed arrays, ArrayBuffer, DataView
39
39
  - [Async, promises and generators](./implemented/async.md) — async/await, async generators, promises, timers
40
- - [fetch](./implemented/fetch.md) — blocking HTTP/1.1 client and Headers
40
+ - [fetch](./implemented/fetch.md) — reactor-driven HTTP/1.1 client and Headers
41
41
 
42
42
  ### Modules and tooling
43
43
 
@@ -14,7 +14,7 @@ These features **compile and run**, but the result does not fully match ECMAScri
14
14
  | Array out-of-bounds / sparse | Out-of-bounds access returns `undefined`; assigning `arr.length` truncates / extends, but sparse holes are not tracked distinctly |
15
15
  | Memory management | Non-moving mark-sweep collector behind `xt_alloc`: explicit roots, subsystem root providers and a conservative C-stack scan. Single-threaded and stop-the-world; no moving/compaction or generational mode |
16
16
  | Function `arity` / argument count | No argument count validation; `fn.length` reports the declared arity but calls are never checked against it |
17
- | `async` / `await` | Real suspension on a stackful coroutine; `await` always queues a microtask (non-promises and already-settled promises included), so interleaving with pending `.then` callbacks matches the specification. Top-level `await` blocks on the event loop rather than yielding the module body. `fs/promises` still performs the I/O synchronously before settling; `fetch` is blocking. Async generators suspend at both `await` and `yield`; `for await...of` awaits one step per iteration |
17
+ | `async` / `await` | Real suspension on a stackful coroutine; `await` always queues a microtask (non-promises and already-settled promises included), so interleaving with pending `.then` callbacks matches the specification. Top-level `await` blocks on the event loop rather than yielding the module body. `fs/promises` and `fetch` offload their blocking work to a four-worker pool and settle on the reactor. Async generators suspend at both `await` and `yield`; `for await...of` awaits one step per iteration |
18
18
  | Async generator cleanup | `for await...of` does not call the iterator's `return()` when the loop exits early. `yield*` inside an async generator delegates to synchronous iterables only. `.return()` at a suspended `yield` completes without running `finally` (the same limitation as synchronous generators) |
19
19
  | `super` | `super.x` / `super(...)` takes the prototype of `this`'s prototype; single-level inheritance is correct, but depth > 1 may be inaccurate |
20
20
  | `Error.stack` | Function-level frames only: each frame is `at name (file:line)` with the function's declaration line (updated to the construction line for the innermost frame); no column numbers and no frames for generator / `async` bodies (they switch stacks) |
@@ -5,13 +5,16 @@
5
5
  Implemented:
6
6
 
7
7
  - Core event loop `runtime/xt_loop.c` (a `select(2)` reactor); `net` / `dgram` / `http` are all built on it.
8
- - `Promise`, `async` / `await` are available (see the core documents); `fs/promises` returns Promises.
8
+ - Async I/O offload `runtime/xt_async_io.c`: blocking file and HTTP-client work runs on four worker threads and completes on the reactor, so `fs/promises` and `fetch` do not stall timers or sockets.
9
+ - `Promise`, `async` / `await` are available (see the core documents); `fs/promises` returns Promises that settle from the reactor.
9
10
  - Timers are available as globals: `setTimeout` / `clearTimeout` / `setInterval` / `clearInterval` run on the core reactor and keep the process alive until no timer remains.
10
11
  - A standalone `EventEmitter` is provided by the `events` module (also a global constructor); streams and sockets additionally carry internal emitter methods (`on` / `addListener` / `once` / `off` / `removeListener` / `emit`).
11
12
 
12
13
  Still unimplemented:
13
14
 
14
15
  - Node's `Timeout` object (`setTimeout` returns a numeric id; `setImmediate` and `queueMicrotask` exist as globals).
15
- - A real asynchronous I/O scheduler: `fs/promises` is essentially an immediate-settle wrapper around synchronous operations and does not yield while waiting for I/O.
16
+ - File watching: `fs.watch` / `fs/promises.watch` return inert objects; there is no inotify/kqueue/ReadDirectoryChangesW backend.
17
+ - `Dir.read` / `Dir.close` invoke their callbacks immediately, and callback-style `fs.readFile` / `fs.writeFile` / … are still unsupported (use `fs/promises` or the `*Sync` forms).
16
18
  - On streams and sockets the built-in `once` is equivalent to `on` (no "remove after firing once" semantics); the `events` module's `EventEmitter` does implement real `once`.
17
- - `net`'s `connect` is blocking; HTTP responses require `Connection: close`, with no keep-alive / chunked transfer / pipelining.
19
+ - `net`'s `connect` is still a blocking `connect(2)` performed before the socket joins the reactor; HTTP responses require `Connection: close`, with no keep-alive / chunked transfer / pipelining. `fetch` has no TLS and buffers the whole body before resolving.
20
+ - The offload pool is fixed at four workers; there is no thread-pool sizing option and no `AbortSignal` cancellation for queued jobs.