weifuwu 0.88.0 → 0.89.1

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 (389) hide show
  1. package/README.md +186 -219
  2. package/dist/client/components/ActionSheet/ActionSheet.d.ts +1 -1
  3. package/dist/client/components/AiChat/AiChat.d.ts +3 -4
  4. package/dist/client/components/ChatInput/ChatInput.d.ts +5 -0
  5. package/dist/client/components/CitationCard/CitationCard.d.ts +1 -1
  6. package/dist/client/components/Command/Command.d.ts +1 -1
  7. package/dist/client/components/ContextMenu/ContextMenu.d.ts +1 -1
  8. package/dist/client/components/DatePicker/DatePicker.d.ts +1 -1
  9. package/dist/client/components/Dropdown/Dropdown.d.ts +1 -1
  10. package/dist/client/components/Editor/model/html.d.ts +10 -0
  11. package/dist/client/components/ImageCropper/ImageCropper.d.ts +6 -2
  12. package/dist/client/components/Menu/Menu.d.ts +1 -1
  13. package/dist/client/components/NavMenu/NavMenu.d.ts +1 -1
  14. package/dist/client/components/Popconfirm/Popconfirm.d.ts +1 -1
  15. package/dist/client/components/SlideCanvas/SlideCanvas.d.ts +1 -1
  16. package/dist/client/components/Tour/Tour.d.ts +1 -1
  17. package/dist/client/components/TreeSelect/TreeSelect.d.ts +1 -1
  18. package/dist/client/components/index.js +41 -41
  19. package/dist/client/components/style.css +298 -644
  20. package/dist/client/layout/weifuwu-layout.css +286 -640
  21. package/dist/client/vdom/commands.d.ts +4 -1
  22. package/dist/client/vdom/core/diff/attrs.d.ts +10 -2
  23. package/dist/client/vdom/core/field/attributes.d.ts +12 -0
  24. package/dist/client/vdom/core/field/events.d.ts +8 -0
  25. package/dist/client/vdom/core/field/input-sync.d.ts +14 -0
  26. package/dist/client/vdom/core/field/props.d.ts +13 -1
  27. package/dist/client/vdom/core/field/ref.d.ts +6 -0
  28. package/dist/client/vdom/core/field/style.d.ts +7 -1
  29. package/dist/client/vdom/core/node/component.d.ts +3 -4
  30. package/dist/client/vdom/core/node/native.d.ts +6 -1
  31. package/dist/client/vdom/core/patch/index.d.ts +24 -1
  32. package/dist/client/vdom/core/patch/processors.d.ts +8 -1
  33. package/dist/client/vdom/core/protocol.d.ts +45 -0
  34. package/dist/client/vdom/core/router.d.ts +7 -0
  35. package/dist/client/vdom/core/ssr/absorb.d.ts +31 -4
  36. package/dist/client/vdom/core/transform/index.d.ts +6 -6
  37. package/dist/client/vdom/core/v2/cycle.d.ts +54 -0
  38. package/dist/client/vdom/core/v2/diff.d.ts +80 -0
  39. package/dist/client/vdom/core/v2/integrate.d.ts +24 -0
  40. package/dist/client/vdom/core/v2/render.d.ts +28 -0
  41. package/dist/client/vdom/core/v2/schedule.d.ts +33 -0
  42. package/dist/client/vdom/core/v2/serve.d.ts +4 -0
  43. package/dist/client/vdom/core/v2/spy.d.ts +21 -0
  44. package/dist/client/vdom/core/v2/ssr.d.ts +14 -0
  45. package/dist/client/vdom/core/vnode.d.ts +9 -6
  46. package/dist/client/vdom/dev/error-counter.d.ts +27 -0
  47. package/dist/client/vdom/dev/render-health.d.ts +60 -0
  48. package/dist/client/vdom/hooks/chat.d.ts +32 -5
  49. package/dist/client/vdom/hooks/drag-media.d.ts +3 -1
  50. package/dist/client/vdom/hooks/env.d.ts +32 -0
  51. package/dist/client/vdom/hooks/popup-manager.d.ts +21 -0
  52. package/dist/client/vdom/hooks/popup.d.ts +4 -1
  53. package/dist/client/vdom/hooks/stable.d.ts +4 -0
  54. package/dist/client/vdom/hooks/use-observable.d.ts +13 -0
  55. package/dist/client/vdom/index.d.ts +14 -6
  56. package/dist/client/vdom/index.js +4 -7
  57. package/dist/client/vdom/jsx-runtime.js +1 -1
  58. package/dist/client/vdom/middlewares/api.d.ts +8 -0
  59. package/dist/client/vdom/middlewares/auth-i18n.d.ts +7 -0
  60. package/dist/client/vdom/middlewares/ws.d.ts +24 -0
  61. package/dist/client/vdom/observable/index.d.ts +13 -0
  62. package/dist/client/vdom/observable/observable.d.ts +26 -0
  63. package/dist/client/vdom/observable/operators.d.ts +76 -0
  64. package/dist/client/vdom/observable/sources.d.ts +54 -0
  65. package/dist/client/vdom/observable/types.d.ts +45 -0
  66. package/dist/client/vdom/store.d.ts +17 -2
  67. package/dist/client/vdom/testing.js +2 -2
  68. package/dist/server/ai/agent.d.ts +18 -0
  69. package/dist/server/ai/client.d.ts +14 -2
  70. package/dist/server/ai/contracts.d.ts +2 -2
  71. package/dist/server/ai/sse.d.ts +6 -0
  72. package/dist/server/core/collect.d.ts +31 -0
  73. package/dist/server/core/error-counter.d.ts +11 -0
  74. package/dist/server/core/hub.d.ts +2 -0
  75. package/dist/server/core/router.d.ts +12 -9
  76. package/dist/server/db/contracts.d.ts +24 -0
  77. package/dist/server/db/memory-redis.d.ts +3 -4
  78. package/dist/server/db/memory-sql.d.ts +20 -4
  79. package/dist/server/db/postgres-server.d.ts +2 -0
  80. package/dist/server/db/query.d.ts +21 -0
  81. package/dist/server/db/redis-server.d.ts +4 -2
  82. package/dist/server/db/sql-parser.d.ts +2 -1
  83. package/dist/server/email/index.d.ts +2 -0
  84. package/dist/server/index.d.ts +4 -1
  85. package/dist/server/index.js +3927 -1984
  86. package/dist/server/messager/index.d.ts +21 -2
  87. package/dist/server/middleware/compress.d.ts +25 -0
  88. package/dist/server/ui/index.d.ts +8 -1
  89. package/dist/server/user/index.d.ts +4 -0
  90. package/dist/shared/router/chain.d.ts +9 -0
  91. package/dist/shared/router/context.d.ts +35 -0
  92. package/dist/shared/router/ctx-fields.d.ts +20 -0
  93. package/dist/shared/router/pipeline.d.ts +70 -0
  94. package/dist/shared/router/trie.d.ts +11 -3
  95. package/dist/shared/router/types.d.ts +23 -0
  96. package/docs/style-guide.md +44 -41
  97. package/package.json +12 -10
  98. package/content/apps/admin.md +0 -55
  99. package/content/apps/agent-platform.md +0 -35
  100. package/content/apps/auth.md +0 -49
  101. package/content/apps/multi.md +0 -44
  102. package/content/apps/todo.md +0 -51
  103. package/content/backend/ai.md +0 -28
  104. package/content/backend/auth.md +0 -20
  105. package/content/backend/email.md +0 -23
  106. package/content/backend/graphql.md +0 -23
  107. package/content/backend/http.md +0 -23
  108. package/content/backend/limit.md +0 -23
  109. package/content/backend/middleware.md +0 -17
  110. package/content/backend/queue.md +0 -23
  111. package/content/backend/redis.md +0 -26
  112. package/content/backend/router.md +0 -23
  113. package/content/backend/schedule.md +0 -23
  114. package/content/backend/sql.md +0 -27
  115. package/content/backend/sse.md +0 -26
  116. package/content/backend/ws.md +0 -26
  117. package/content/capabilities/app-node.md +0 -21
  118. package/content/capabilities/data.md +0 -21
  119. package/content/capabilities/events.md +0 -21
  120. package/content/capabilities/hooks.md +0 -22
  121. package/content/capabilities/i18n.md +0 -21
  122. package/content/capabilities/popup.md +0 -21
  123. package/content/capabilities/render-only.md +0 -23
  124. package/content/capabilities/router.md +0 -22
  125. package/content/capabilities/self-id.md +0 -21
  126. package/content/capabilities/store.md +0 -21
  127. package/content/capabilities/theme.md +0 -21
  128. package/content/capabilities/two-phase.md +0 -21
  129. package/content/components/accordion.md +0 -50
  130. package/content/components/actionsheet.md +0 -46
  131. package/content/components/affix.md +0 -51
  132. package/content/components/aichat.md +0 -57
  133. package/content/components/alert.md +0 -52
  134. package/content/components/alertgroup.md +0 -45
  135. package/content/components/anchor.md +0 -52
  136. package/content/components/app.md +0 -37
  137. package/content/components/approvalcard.md +0 -56
  138. package/content/components/appshell.md +0 -50
  139. package/content/components/aspectratio.md +0 -47
  140. package/content/components/authpage.md +0 -61
  141. package/content/components/autocomplete-v2.md +0 -40
  142. package/content/components/autocomplete.md +0 -56
  143. package/content/components/avatar.md +0 -51
  144. package/content/components/avatargroup.md +0 -47
  145. package/content/components/backtop.md +0 -53
  146. package/content/components/badge.md +0 -55
  147. package/content/components/breadcrumb.md +0 -49
  148. package/content/components/button.md +0 -63
  149. package/content/components/calendar-v2.md +0 -40
  150. package/content/components/calendar.md +0 -50
  151. package/content/components/card.md +0 -58
  152. package/content/components/carousel.md +0 -52
  153. package/content/components/cascader-v2.md +0 -39
  154. package/content/components/cascader.md +0 -54
  155. package/content/components/chart.md +0 -53
  156. package/content/components/chatinput.md +0 -63
  157. package/content/components/checkbox.md +0 -51
  158. package/content/components/checkboxgroup.md +0 -52
  159. package/content/components/citationcard.md +0 -48
  160. package/content/components/codeblock.md +0 -48
  161. package/content/components/codeeditor.md +0 -47
  162. package/content/components/collapse.md +0 -49
  163. package/content/components/colorpicker.md +0 -50
  164. package/content/components/command.md +0 -50
  165. package/content/components/confirm.md +0 -57
  166. package/content/components/contextmenu.md +0 -48
  167. package/content/components/copybutton.md +0 -51
  168. package/content/components/datepicker.md +0 -52
  169. package/content/components/descriptions-v2.md +0 -39
  170. package/content/components/descriptions.md +0 -52
  171. package/content/components/diffview-v2.md +0 -39
  172. package/content/components/diffview.md +0 -50
  173. package/content/components/divider.md +0 -48
  174. package/content/components/drawer.md +0 -55
  175. package/content/components/dropdown.md +0 -53
  176. package/content/components/editor.md +0 -49
  177. package/content/components/emptystate.md +0 -51
  178. package/content/components/exportcsv.md +0 -38
  179. package/content/components/field.md +0 -54
  180. package/content/components/filepreview-office.md +0 -37
  181. package/content/components/filepreview.md +0 -49
  182. package/content/components/filetree.md +0 -56
  183. package/content/components/fileupload-v2.md +0 -39
  184. package/content/components/fileupload.md +0 -57
  185. package/content/components/floatbutton.md +0 -52
  186. package/content/components/form-v2.md +0 -41
  187. package/content/components/form.md +0 -60
  188. package/content/components/grid.md +0 -51
  189. package/content/components/highlight-v2.md +0 -39
  190. package/content/components/highlight.md +0 -46
  191. package/content/components/hovercard.md +0 -51
  192. package/content/components/icon.md +0 -50
  193. package/content/components/imagecropper.md +0 -45
  194. package/content/components/img.md +0 -56
  195. package/content/components/infinitescroll-v2.md +0 -40
  196. package/content/components/infinitescroll.md +0 -54
  197. package/content/components/input.md +0 -65
  198. package/content/components/inputnumber.md +0 -58
  199. package/content/components/inview.md +0 -56
  200. package/content/components/jsonschemaform.md +0 -53
  201. package/content/components/jsonviewer-v2.md +0 -39
  202. package/content/components/jsonviewer.md +0 -49
  203. package/content/components/kanban.md +0 -46
  204. package/content/components/label.md +0 -48
  205. package/content/components/layout.md +0 -54
  206. package/content/components/layoutcontent.md +0 -43
  207. package/content/components/layoutheader.md +0 -43
  208. package/content/components/layoutsider.md +0 -43
  209. package/content/components/link.md +0 -52
  210. package/content/components/list.md +0 -44
  211. package/content/components/loading.md +0 -45
  212. package/content/components/logviewer-v2.md +0 -39
  213. package/content/components/logviewer.md +0 -53
  214. package/content/components/markdown.md +0 -51
  215. package/content/components/markdowneditor.md +0 -47
  216. package/content/components/math.md +0 -42
  217. package/content/components/mentions-v2.md +0 -39
  218. package/content/components/mentions.md +0 -52
  219. package/content/components/menu.md +0 -56
  220. package/content/components/menubar.md +0 -44
  221. package/content/components/messagebubble.md +0 -50
  222. package/content/components/modal.md +0 -59
  223. package/content/components/navmenu.md +0 -47
  224. package/content/components/notification.md +0 -51
  225. package/content/components/pageheader.md +0 -53
  226. package/content/components/pagination.md +0 -49
  227. package/content/components/paragraph.md +0 -45
  228. package/content/components/passwordinput.md +0 -57
  229. package/content/components/pininput-v2.md +0 -39
  230. package/content/components/pininput.md +0 -50
  231. package/content/components/pipeline.md +0 -49
  232. package/content/components/popconfirm.md +0 -58
  233. package/content/components/popover.md +0 -61
  234. package/content/components/progressbar.md +0 -50
  235. package/content/components/prompttemplate.md +0 -49
  236. package/content/components/qrcode.md +0 -50
  237. package/content/components/radiogroup.md +0 -53
  238. package/content/components/rate.md +0 -53
  239. package/content/components/reasoningblock.md +0 -47
  240. package/content/components/relationgraph.md +0 -52
  241. package/content/components/resizable.md +0 -53
  242. package/content/components/result.md +0 -49
  243. package/content/components/scrollbar.md +0 -49
  244. package/content/components/searchinput.md +0 -51
  245. package/content/components/segmentedcontrol.md +0 -59
  246. package/content/components/select-searchable.md +0 -47
  247. package/content/components/select.md +0 -66
  248. package/content/components/sessionlist.md +0 -52
  249. package/content/components/sheetgrid.md +0 -44
  250. package/content/components/skeleton.md +0 -55
  251. package/content/components/slidecanvas.md +0 -44
  252. package/content/components/slider.md +0 -62
  253. package/content/components/sortablelist.md +0 -39
  254. package/content/components/space.md +0 -52
  255. package/content/components/sparkline.md +0 -52
  256. package/content/components/statcard-countdown.md +0 -39
  257. package/content/components/statcard.md +0 -56
  258. package/content/components/steps.md +0 -48
  259. package/content/components/switch.md +0 -50
  260. package/content/components/tabbar.md +0 -45
  261. package/content/components/table-v2.md +0 -40
  262. package/content/components/table.md +0 -66
  263. package/content/components/tabs.md +0 -56
  264. package/content/components/tag.md +0 -51
  265. package/content/components/tagsinput-v2.md +0 -39
  266. package/content/components/tagsinput.md +0 -54
  267. package/content/components/text.md +0 -45
  268. package/content/components/textarea.md +0 -57
  269. package/content/components/themeswitch.md +0 -69
  270. package/content/components/timeline.md +0 -50
  271. package/content/components/title.md +0 -45
  272. package/content/components/toast.md +0 -52
  273. package/content/components/toggle-togglegroup.md +0 -52
  274. package/content/components/togglegroup.md +0 -47
  275. package/content/components/toolcallcard.md +0 -51
  276. package/content/components/tooltip.md +0 -50
  277. package/content/components/tour.md +0 -50
  278. package/content/components/transfer.md +0 -52
  279. package/content/components/tree-v2.md +0 -39
  280. package/content/components/tree.md +0 -59
  281. package/content/components/treeselect.md +0 -54
  282. package/content/components/typography.md +0 -51
  283. package/content/components/videoplayer.md +0 -52
  284. package/content/components/virtuallist.md +0 -52
  285. package/content/components/virtualtable-v2.md +0 -39
  286. package/content/components/virtualtable.md +0 -55
  287. package/content/components/watermark.md +0 -53
  288. package/content/components/wave.md +0 -43
  289. package/content/guides/ai-chat-family.md +0 -24
  290. package/content/guides/choose.md +0 -67
  291. package/content/guides/component-model.md +0 -32
  292. package/content/guides/component-standards.md +0 -123
  293. package/content/guides/components-guide.md +0 -813
  294. package/content/guides/custom-component.md +0 -321
  295. package/content/guides/data-guide.md +0 -281
  296. package/content/guides/file-preview-family.md +0 -18
  297. package/content/guides/frontend.md +0 -968
  298. package/content/guides/layout-choice.md +0 -64
  299. package/content/guides/layout-guide.md +0 -241
  300. package/content/guides/middleware.md +0 -459
  301. package/content/guides/mobile-guide.md +0 -105
  302. package/content/guides/page-building.md +0 -98
  303. package/content/guides/production.md +0 -37
  304. package/content/guides/quality.md +0 -60
  305. package/content/guides/realtime-guide.md +0 -296
  306. package/content/guides/render-only.md +0 -44
  307. package/content/guides/saas-guide.md +0 -253
  308. package/content/guides/server-guide.md +0 -359
  309. package/content/guides/start.md +0 -41
  310. package/content/guides/styling.md +0 -154
  311. package/content/guides/ui-dom-guide.md +0 -157
  312. package/content/index.json +0 -4344
  313. package/content/index.md +0 -181
  314. package/content/layout/align.md +0 -20
  315. package/content/layout/anchor.md +0 -19
  316. package/content/layout/app-shell.md +0 -23
  317. package/content/layout/border.md +0 -20
  318. package/content/layout/center.md +0 -19
  319. package/content/layout/cluster.md +0 -19
  320. package/content/layout/container.md +0 -19
  321. package/content/layout/fill.md +0 -19
  322. package/content/layout/grid.md +0 -19
  323. package/content/layout/hidden.md +0 -22
  324. package/content/layout/layer.md +0 -19
  325. package/content/layout/position.md +0 -22
  326. package/content/layout/row.md +0 -23
  327. package/content/layout/safe-area.md +0 -20
  328. package/content/layout/scroll.md +0 -21
  329. package/content/layout/spacing.md +0 -22
  330. package/content/layout/split.md +0 -19
  331. package/content/layout/stack.md +0 -19
  332. package/content/layout/surface.md +0 -23
  333. package/content/layout/text.md +0 -22
  334. package/content/patterns/app-shell.md +0 -34
  335. package/content/patterns/dashboard.md +0 -36
  336. package/content/patterns/data-screen.md +0 -30
  337. package/content/patterns/detail-page.md +0 -31
  338. package/content/patterns/docs.md +0 -34
  339. package/content/patterns/focus-task.md +0 -34
  340. package/content/patterns/landing.md +0 -35
  341. package/content/patterns/list-page.md +0 -32
  342. package/content/patterns/mobile.md +0 -31
  343. package/content/patterns/settings-page.md +0 -33
  344. package/content/patterns/workspace.md +0 -32
  345. package/dist/cli/content-sync.test.d.ts +0 -1
  346. package/dist/cli/docs-cli.test.d.ts +0 -1
  347. package/dist/cli/docs.d.ts +0 -2
  348. package/dist/cli/docs.mjs +0 -10966
  349. package/dist/client/vdom/core/build.d.ts +0 -36
  350. package/dist/client/vdom/core/diff/children.d.ts +0 -29
  351. package/dist/client/vdom/core/diff/index.d.ts +0 -22
  352. package/dist/client/vdom/core/diff/output.d.ts +0 -30
  353. package/dist/client/vdom/core/diff/same.d.ts +0 -22
  354. package/dist/client/vdom/core/serve.d.ts +0 -75
  355. package/dist/client/vdom/core/ssr/index.d.ts +0 -25
  356. package/examples/apps/admin/README.md +0 -23
  357. package/examples/apps/admin/api.ts +0 -27
  358. package/examples/apps/admin/app.tsx +0 -144
  359. package/examples/apps/admin/main.tsx +0 -8
  360. package/examples/apps/admin/server.ts +0 -37
  361. package/examples/apps/auth/README.md +0 -23
  362. package/examples/apps/auth/api.ts +0 -49
  363. package/examples/apps/auth/app.tsx +0 -137
  364. package/examples/apps/auth/main.tsx +0 -8
  365. package/examples/apps/auth/server.ts +0 -37
  366. package/examples/apps/multi/README.md +0 -23
  367. package/examples/apps/multi/app.tsx +0 -83
  368. package/examples/apps/multi/main.tsx +0 -8
  369. package/examples/apps/multi/server.ts +0 -33
  370. package/examples/apps/todo/README.md +0 -23
  371. package/examples/apps/todo/api.ts +0 -26
  372. package/examples/apps/todo/app.tsx +0 -128
  373. package/examples/apps/todo/main.tsx +0 -9
  374. package/examples/apps/todo/server.ts +0 -39
  375. package/examples/hello-world/README.md +0 -9
  376. package/examples/hello-world/client.ts +0 -14
  377. package/examples/hello-world/routes.tsx +0 -26
  378. package/examples/hello-world/server.ts +0 -46
  379. package/examples/patterns/AppShell.tsx +0 -224
  380. package/examples/patterns/Dashboard.tsx +0 -153
  381. package/examples/patterns/DataScreen.tsx +0 -104
  382. package/examples/patterns/DetailPage.tsx +0 -79
  383. package/examples/patterns/Docs.tsx +0 -105
  384. package/examples/patterns/FocusTask.tsx +0 -67
  385. package/examples/patterns/Landing.tsx +0 -90
  386. package/examples/patterns/ListPage.tsx +0 -77
  387. package/examples/patterns/Mobile.tsx +0 -154
  388. package/examples/patterns/SettingsPage.tsx +0 -98
  389. package/examples/patterns/SplitWorkspace.tsx +0 -113
package/README.md CHANGED
@@ -7,7 +7,7 @@ npm install weifuwu # 一个依赖,完整应用栈
7
7
  ```
8
8
 
9
9
  **本地开发(apps/ 下的应用)**:dev 模式经 `node_modules/weifuwu/*` 软链解析到源码
10
- (`import 'weifuwu/ui-dom'` 等——esbuild bundle 的模块解析;tsconfig paths 只管类型)。
10
+ (`import 'weifuwu/vdom'` 等——esbuild bundle 的模块解析;tsconfig paths 只管类型)。
11
11
  新环境 `npm install` 后执行 `node scripts/dev-links.mjs` 创建软链(apps 运行前提)。
12
12
  发布时才编译 dist(`node scripts/release.mjs`——dev 零构建)。
13
13
 
@@ -19,7 +19,7 @@ npm install weifuwu # 一个依赖,完整应用栈
19
19
  |------|-----------|
20
20
  | **一个包,零构建** | 服务端 `node --import weifuwu/dev` 直跑 `.tsx`;浏览器 CDN import map 即用;CSS 一条 link 即得完整设计系统——没有构建步骤、没有脚手架 |
21
21
  | **协议层全自研** | PG v3 / RESP2 / GraphQL schema / OpenAI 流式协议全部自研——确定性、可预测、错误模型统一;诚实裁剪:**不支持的能力明确报错,绝不静默降级** |
22
- | **消灭样板** | 动态编译免构建、`ctx.data.get` 一个 API 覆盖 SSR 预取/hydration/SPA、自研 DB 客户端免双重编码与 parseRow 样板 |
22
+ | **消灭样板** | 动态编译免构建、`useAsyncData` 一个 hook 覆盖 SSR 预取/hydration/SPA、自研 DB 客户端免双重编码与 parseRow 样板 |
23
23
  | **SaaS 地基随包内置** | rateLimit / email / userSystem / messager / queue / ai 六个中间件**互相咬合**(身份是消息的路由)——从「自建基础设施」变「声明业务」 |
24
24
  | **自托管友好** | 运行时仅 esbuild + graphql + ws;部署 = 一个 Node 进程 + Postgres + Redis;数据、代码、模型全部在自己手里 |
25
25
 
@@ -28,20 +28,21 @@ npm install weifuwu # 一个依赖,完整应用栈
28
28
  | 层 | 入口 | 能力 |
29
29
  |----|------|------|
30
30
  | 后端 | `weifuwu` | Trie 路由 / 中间件链 / serve / 自研 PG+Redis / SSR / GraphQL / WebSocket |
31
- | 前端 | `weifuwu/ui-dom` | **vdom3 精准事件流引擎**——createRouter(路由)+ createRoot(挂载)+ 事件流(渲染本体:`entity:action` 统一命名、DOM = fold、可回放可断言)+ SSR(事件流序列化);组件=两阶段异步组件 / ctx.params 对齐后端;**weifuwu/components 直接复用**(VNode 契约唯一来源 ui-dom,见 `content/guides/ui-dom-guide.md`) |
31
+ | 前端 | `weifuwu/vdom` | **vdom 引擎**——UIRouter(路由唯一入口:get/notFound/has/resolve——`(Request, ctx) => Response` 签名对齐后端)+ uiServe(浏览器 boot)+ uiSsr(服务端渲染——SSR SPA 首帧结构吸收)+ 命令流渲染(NDJSON 命令纯数据可回放);**weifuwu/components 直接复用**(VNode 契约唯一来源 vdom) |
32
32
  | 组件 | `weifuwu/components` | 129 个 HTML 原语组件(表单/表格/弹层/AiChat…),引用 `--wf-*` 主题变量 |
33
- | 样式 | `weifuwu/layout` | 66 布局原语 + 157 工具类 + 182 主题 Token,零自定义 CSS 文件 |
33
+ | 样式 | `weifuwu/layout` | 48 布局原语 + 90 工具类 + 183 主题 Token,零自定义 CSS 文件 |
34
34
  | SaaS 地基 | 随包内置 | rateLimit / email / userSystem / messager / queue / ai → `ctx.*` 一行接入 |
35
35
 
36
36
  > ⚠️ **注意:前后端都有 `ctx.ui`,但用途完全不同**
37
- > - **后端** `ctx.ui`(SSR/编译):`ctx.ui.html`(HTML 模板)、`ctx.ui.js`(TSX→JS 动态编译)、`ctx.ui.css`(CSS 编译)、`ctx.ui.ssr`(组件 SSR)、`ctx.ui.ssrData`(数据序列化)
38
- > - **前端** `ctx.ui`(渲染引擎,20+ hooks):
39
- > - 渲染:`render()`(唯一触发——render-only)/ `selfId()`(跨组件精准刷新)
40
- > - 状态:`useControlled()`(受控/非受控)/ `useStableRef()`(稳定 ref)/ `useExternal()`(共享状态订阅)
37
+ > - **后端** `ctx.ui`(SSR/编译):`ctx.ui.html`(HTML 模板)、`ctx.ui.js`(TSX→JS 动态编译)、`ctx.ui.css`(CSS 编译)、`ctx.ui.ssr`(组件 SSR
38
+ > - **前端** `ctx.ui`(渲染引擎,20+ hooks——**一切会变化的值都是 getter**):
39
+ > - 数据:`useAsyncData(fetcher, key)`(唯一异步边界——同 key 并发合并/竞态取消/缓存保留/SSR 种子预热——**作者零退订零竞态代码**)
40
+ > - 状态:`signal(n)`(getter 读 + set/update 写——变化自动重渲染)/ `useObservable(obs$)`(任何 Observable → getter)/ `useExternal()`(共享状态)
41
+ > - 受控:`useControlled()`(受控/非受控)/ `useControlledInput()`(逐键回流)
41
42
  > - 弹层:`usePopup()`(统一能力层——锚定浮层 + 会话级模态)/ `usePopupPosition()`(定位)
42
43
  > - 事件:`useInView()` / `useScrollPosition()` / `useMedia()` / `useBreakpoint()` / `useGlobalKey()` / `useDrag()` / `useDragDrop()` / `useHoverCapable()` / `useLongPress()` / `useVisualViewport()`
43
- > - 动画:`useAnimationEnd()`(完成回调)/ `usePresence()`(显隐状态机)/ `useTween()`(数值补间)/ `useReducedMotion()`(偏好感知)
44
- > - AI/数据:`useChat()`(AI 会话)/ `useAsync()`(异步取数)
44
+ > - 动画:`useAnimationEnd()` / `usePresence()`(显隐状态机)/ `useTween()` / `useReducedMotion()`
45
+ > - AI/生命周期:`useChat()`(AI 会话)/ `hold(fn)`(卸载清理——等价 onUnmount)
45
46
  > 后端的是「把页面和代码交给浏览器」,前端的是「在浏览器里驱动 UI」。
46
47
 
47
48
  ### 与主流方案的关系
@@ -55,14 +56,13 @@ npm install weifuwu # 一个依赖,完整应用栈
55
56
  | 认证 / 消息 / 队列 | **随包内置、互相咬合** | 自选 + 自缝 | 自选 + 自缝 |
56
57
  | 部署 | 一个 Node 进程 + PG + Redis | 各组件自理 | 平台绑定 |
57
58
 
58
- > 定位不是「替代某个框架」,而是**包换包**:用 weifuwu 一个依赖替换你原本要缝合的整套栈。心智模型有借鉴(两阶段组件接近 React、中间件接近 Express),但每一层都是自研的确定性实现——组件模型见[核心概念](#核心概念),与 antd/Element Plus/shadcn 的对应见 [docs/components-map.md](docs/components-map.md)。
59
+ > 定位不是「替代某个框架」,而是**包换包**:用 weifuwu 一个依赖替换你原本要缝合的整套栈。心智模型有借鉴(组件模型接近 React、中间件接近 Express),但每一层都是自研的确定性实现——组件模型见[核心概念](#核心概念),与 antd/Element Plus/shadcn 的对应见 [docs/components-map.md](docs/components-map.md)。
59
60
 
60
61
  ### 从这里开始
61
62
 
62
63
  | 你想… | 去哪 |
63
64
  |--------|------|
64
65
  | 10 分钟跑通 SPA + SSR | [快速开始](#快速开始) |
65
- | 立刻体验(跑现成 demo) | 快速开始的「30 秒体验」 |
66
66
  | 零后端原型(一个 HTML 文件) | [CDN 快速原型](#cdn-快速原型零构建纯-html) |
67
67
  | 按任务找 API(认证/消息/AI/移动端…) | [能力速查](#能力速查任务--api) |
68
68
  | 读完整 API 参考 | [文档导航](#文档导航) |
@@ -71,13 +71,13 @@ npm install weifuwu # 一个依赖,完整应用栈
71
71
 
72
72
  ## 设计理念
73
73
 
74
- > 顶部「定位」回答了**是什么 / 为什么**;以下是**哲学展开**——四条核心哲学与十一条技术原则。
74
+ > 顶部「定位」回答了**是什么 / 为什么**;以下是**哲学展开**——五条核心哲学与技术原则。
75
75
 
76
76
  ### 核心哲学
77
77
 
78
78
  **① 一个包,全栈一体。** 后端、前端、组件、样式装在一个 npm 包里,零配置、零构建、纯 link 可用:服务端 `--import weifuwu/dev` 直接跑 `.tsx`(Node loader + esbuild 同步编译);浏览器 CDN import map 直接跑;CSS 一条 link 即得完整设计系统。
79
79
 
80
- **② 全自研,诚实裁剪。** VDOM、PG v3 / RESP2 协议、GraphQL schema、OpenAI 兼容流式协议——全部自研而非包装他人。动机不是炫技而是**确定性**:自研客户端输出确定、行为可预测、错误模型统一。配套纪律是诚实裁剪:**不支持的能力明确抛 `ProtocolError('unsupported')`,绝不静默降级或"尽量支持"**(已裁剪清单见组件裁剪登记)。
80
+ **② 全自研,诚实裁剪。** VDOM、PG v3 / RESP2 协议、GraphQL schema、OpenAI 兼容流式协议——全部自研而非包装他人。动机不是炫技而是**确定性**:自研客户端输出确定、行为可预测、错误模型统一。配套纪律是诚实裁剪:**不支持的能力明确抛 `ProtocolError('unsupported')`,绝不静默降级或"尽量支持"**。
81
81
 
82
82
  **③ 消灭样板。** 框架的每一层都在消灭一类样板代码:
83
83
 
@@ -85,38 +85,28 @@ npm install weifuwu # 一个依赖,完整应用栈
85
85
  |---|---|
86
86
  | 构建样板 | 动态编译(`ctx.ui.js` / `weifuwu/dev`),改代码即刷即用,零构建步骤 |
87
87
  | 样式样板 | WUI 设计语言 + 语义原语 + 变量定制,零自定义 CSS 文件(`--wf-brand-seed` 改一个值全站换肤,暗色自动派生) |
88
- | 数据样板 | `ctx.data.get` 一个 API 覆盖 SSR 预取 / hydration 命中 / SPA fetch,写数据像写同步代码 |
88
+ | 异步样板 | `useAsyncData` 内建并发合并/竞态取消/缓存保留/SSR 种子——**作者零退订、零竞态、零 loading 样板** |
89
89
  | 协议样板 | 自研 PG/Redis 客户端消灭双重编码、parseRow 样板、`'EX'` 参数顺序陷阱 |
90
90
 
91
- **④ SaaS 地基,应用必须的一等能力。** 不只是库——rateLimit / email / userSystem / messager / queue 五个中间件随包内置,且互相咬合:**身份是消息的路由,消息是身份的交互**(`sendTo(ctx.user.id)` 按身份路由、`createConversation(ctx.user.id)` 创建者即身份、成员校验自动对齐),AI 对话走同一协议。开发者从「自建基础设施」变「声明业务」——`app.use(...)` 一行接入,一个多租户 AI 平台(agent-platform)已完整消费这层地基(auth / AI / 消息 / UI / 数据管道全部框架能力)。
91
+ **④ SaaS 地基,应用必须的一等能力。** 不只是库——rateLimit / email / userSystem / messager / queue 五个中间件随包内置,且互相咬合:**身份是消息的路由,消息是身份的交互**(`sendTo(ctx.user.id)` 按身份路由、`createConversation(ctx.user.id)` 创建者即身份、成员校验自动对齐),AI 对话走同一协议。开发者从「自建基础设施」变「声明业务」——一个多租户 AI 平台(agent-platform)已完整消费这层地基。
92
92
 
93
- ### 技术原则(哲学的展开)
94
-
95
- **零运行时依赖** — 前端无 npm 运行时依赖(自研 VDOM,不引入 Virtual DOM 库、rxjs、immer 等)。后端仅依赖 `esbuild`(TSX→JS 编译)+ `graphql` + `ws`(语言/协议本身)——**数据库客户端(PostgreSQL/Redis 协议)、GraphQL schema 工具全部自研**。esbuild 作为运行时依赖随 `npm install weifuwu` 自动安装,`ctx.ui.js()` 开箱即用。
96
-
97
- **两阶段组件模型** — 组件 = `async (initProps, ctx) => (props) => Promise<VNode>`。外层工厂只执行一次(mount,可 await 数据),内层 renderFn 每次状态/props 变化时执行(强制异步)。无 class、无 `this`、无 Hook——**位置即语义**:外层天生只跑一次,没有 hooks 规则、没有依赖数组、没有闭包陷阱(详解见[核心概念](#核心概念))。
98
-
99
- **render-only 确定性渲染** — 渲染唯一触发 `ctx.ui.render()`(闭包绑定组件),状态是普通对象(`let` + `render()`);跨组件共享用 `createStore` + `ctx.ui.useExternal()`。行为可静态推导,无隐式触发(详见[组件库](content/guides/components-guide.md))。
100
-
101
- **VDOM 输出透明(写 JSX,看 DOM 即真相)** — VDOM 对用户输入零 magic:条件渲染的 false 在 DOM 里是诊断占位注释(`<!--wf-hole: false-->`),数组项 key 与组件实例 id 直接落 DOM(`data-wf-key` / `data-wf-id`)——devtools 看到的 DOM 就是引擎决策的可读输出;非法输入占位 + warn,不崩溃不静默。转化契约唯一清晰:用户写什么,vnode 就是什么,DOM 就长什么样(规则表为内部开发契约)。
93
+ **⑤ 错误现形,自愈不可消音。** 渲染错误** hole 降级 + 下一拍重试**是容错不是静默——错误必须计数现形(`error-counter` 同错去重不刷屏、恢复清出再报);路由错误同样去重计数。dev 仪表 `window.__wfRenderHealth` 四轴读数(频率/规模/复用/错误)——**问题出现即读数**,不是问题出现后翻日志。
102
94
 
103
- **中间件注入一切** — 后端和前端共用同一理念:中间件向 `ctx` 注入能力(`ctx.sql` / `ctx.redis` / `ctx.api` / `ctx.auth` / `ctx.i18n` / `ctx.limit` / `ctx.email` / `ctx.queue` / `ctx.ai` / `ctx.msg` 等),Handler/组件从 `ctx` 读取。
104
-
105
- **async 工厂组件** — `async (initProps, ctx) => (props) => Promise<VNode>`(weifuwu **唯一组件形态**——同步组件已不支持):工厂层声明数据(`await ctx.data.get`)、mount 初始化状态(`let` + `render()`)、render 输出视图。异步在工厂边界与 renderFn,数据经闭包注入,写数据像写同步代码。三条纪律见[核心概念 · async 组件](#核心概念)。
95
+ ### 技术原则(哲学的展开)
106
96
 
107
- **SPA/SSR/Hydration 统一透明** 同一份路由定义(`createRouter`)一个组件三场景自动适配:后端将 vnode 构建为**事件流**(`renderToEvents`)→ `eventsToHtml` 输出完整 HTML + `serializeEvents` 序列化进 `__DATA__`,客户端 `deserializeEvents` + `replay` 收养(DOM = fold(事件流)——零 DOM 猜测、不重建、无闪跳)。`ctx.data.get` 一个 API:SSR 预取 / hydration 命中(不重复请求)/ SPA 触发 fetch。服务端直接用 `.tsx`(`weifuwu/dev` Node loader),前后端同一 JSX 运行时。
97
+ **零运行时依赖**前端无 npm 运行时依赖(自研 VDOM,不引入 Virtual DOM 库、rxjs、immer 等)。后端仅依赖 `esbuild`(TSX→JS 编译)+ `graphql` + `ws`(语言/协议本身)——**数据库客户端(PostgreSQL/Redis 协议)、GraphQL schema 工具全部自研**。
108
98
 
109
- **AI 是一等公民** 自研 OpenAI 兼容协议(`docs/ai-contract.md`)+ 零依赖流式客户端 + agent 工具循环 + HITL 人工审批 + embedding 向量化。后端 `ctx.ai` 一个入口:`chat()` / `stream()` / `agent()`(`stream(messages, { emit })` emitter 抽象——事件可接任意通道,`runToResult()` 结构化结果)/ `approve()` / `embed()` / `embedMany()`;前端 `ctx.ui.useChat()`(会话语义)+ `AiChat` 组件(标准对话界面)——流式 token / 工具调用卡 / 审批卡开箱即用,协议对页面完全透明,不用 ai-sdk。
99
+ **前后端同构,机制公用、实现不一样**handler 签名 `(req: Request, ctx) => Response` 双端字面同构;**路由内核五层单源**(`src/shared/router/`:trie 匹配 / pipeline 流程骨架 / context URL 解析与 ctx 注入 / chain 中间件链 / ctx-fields 扩展注册表)——`Router.handler()` `UIRouter.resolve()` 跑同一个 `dispatchRouter`,差异点(verb 表/404 形态/错误语义/ctx 扩展)钩子化,**serve(Request/Response 编解码边界)留各自域**。
110
100
 
111
- **SaaS 地基随包内置**rateLimit(限流)/ email(邮件)/ userSystem(用户认证)/ messager(消息系统)/ queue(可靠队列)以中间件形态随包提供,`app.use(...)` 一行接入(详见[SaaS 地基模块](content/guides/saas-guide.md))。互相咬合成协作基础:身份(userSystem)+ 消息(messager)的组合让「谁能跟谁说话、消息如何送达」天然对齐,不再需要第三套权限系统。
101
+ **工厂同步,异步边界全在 hooks,渲染纯同步**组件 = `(initProps, ctx) => (props) => VNode`(**同步工厂——async 即编译错**)。数据加载 `useAsyncData`、多源汇流 `useObservable`——异步全部收敛在 hooks 内部流管道(卸载自动退订——零泄漏);渲染纯同步(无 async 渲染、无中间态占位)。**一条规则易学易写易用**。
112
102
 
113
- **机制与策略分离**框架管**机制**(token 怎么签、消息怎么送达、agent 循环怎么跑),开发者管**策略**(谁能建群、租户隔离 SQL、技能注册表)。这是「诚实裁剪」的积极面:**框架不越界,应用层不被绑架**——agent-platform 迁移验证了边界:多租户隔离(`WHERE tenant_id`)、技能编排、聊天产品模型留在应用层,框架守住通用能力(auth / ai / messager / UI / 数据管道)。
103
+ **信号 getter 纪律** 一切会变化的值 = `() => T`——**任何位置调用返回最新值**——没有「必须在 render 内调用」的位置规则。`signal` getter + set/update 写,变化自动重渲染;hooks 全部 getter 化(`useScrollPosition()` 返回最新位置)。
114
104
 
115
- **零自定义 CSS 设计系统** 一个 CSS 文件 = 双层 Token + 布局原语 + 工具类 + 组件样式。业务页面不写 style.css:组件 + `wf-*` 原语写业务,主题改一个值(`--wf-brand-seed` 换肤 / `data-preset` 预设 / `--wf-btn-radius` 钩子),暗色自动(详见[布局系统](content/guides/layout-guide.md)与[主题配置](content/guides/styling.md))。
105
+ **VDOM 输出透明(命令流可回放)**渲染 = **命令流**(NDJSON 纯数据——13 种命令完整自足:create/insert/setText/…)——DOM = fold(命令流),可记录、可回放、可断言。VDOM 对用户输入零 magic:条件渲染的 false DOM 里是诊断占位注释,数组项 key 与组件实例 id 直接落 DOM——devtools 看到的 DOM 就是引擎决策的可读输出。
116
106
 
117
- **自研数据层**`ctx.sql`(PG v3 协议)与 `ctx.redis`(RESP2 协议)为**自研客户端**:确定性输出、行为可预测、统一错误模型。jsonb 自动解码、TTL 安全 API、schema 写前校验——高频痛点(双重编码/parseRow 样板/`'EX'` 参数顺序)从根上消除。
107
+ **对账防线(演绎保证——错必被抓)**双树终态等价对账器 + 命令流模拟器(Sim)+ 状态机迁移表(编译期穷尽)+ fuzz 生成器(1310 对多种子)——**状态机保证暴露机制完整,对账器保证错误必被抓**,两者互补缺一不可。四层验证体系见 [AGENTS.md](AGENTS.md)。
118
108
 
119
- > **实践验证**:多租户 AI 平台(`apps/agent-platform`——14 + 部门聊天 + 知识库 + HITL 审批)已完全运行在框架上:auth(userSystem)/ AI 引擎(ai)/ 实时消息(messager)/ UI(129 组件)/ 数据管道(ctx.api)零自研替代。框架哲学(中间件注入、诚实裁剪、机制与策略分离)经受住了真实复杂应用的检验——这也是我们确定「哪些进框架、哪些留应用层」的依据。
109
+ **中间件注入一切** 后端和前端共用同一理念:中间件向 `ctx` 注入能力(`ctx.sql` / `ctx.redis` / `ctx.api` / `ctx.auth` / `ctx.i18n` / `ctx.limit` / `ctx.email` / `ctx.queue` / `ctx.ai` / `ctx.msg` 等),Handler/组件从 `ctx` 读取——**注入声明(injects/depends)双端同一注册表机制**。
120
110
 
121
111
  ---
122
112
 
@@ -126,22 +116,30 @@ npm install weifuwu # 一个依赖,完整应用栈
126
116
 
127
117
  | 模式 | 适用场景 | 后端 | 客户端入口 |
128
118
  |------|---------|------|-----------|
129
- | **SPA** | 应用页(Dashboard、工具、后台) | HTML 外壳 | `createRouter(routes, root, { ctx })` |
130
- | **SSR + Hydration** | 内容页(博客、营销,需要 SEO/首屏) | `renderToEvents` `eventsToHtml` + `serializeEvents` | `deserializeEvents` + `replay`(事件流收养) |
119
+ | **SPA** | 应用页(Dashboard、工具、后台) | HTML 外壳 | `uiServe(router, { root })` |
120
+ | **SSR + Hydration** | 内容页(博客、营销,需要 SEO/首屏) | `uiSsr(router, url)`(HTML + `__DATA__` 种子) | `uiServe`(结构吸收——首帧复用服务端 DOM) |
131
121
 
132
122
  ### 先写共享部分(两种模式都一样)
133
123
 
134
124
  ```tsx
135
- // routes.tsx —— 路由声明(createRouter——vdom3 事件流引擎)
136
- import { createRouter, h } from 'weifuwu/ui-dom'
137
-
138
- // async 组件(原生):await 数据 返回视图(外层初始化,内层渲染)
139
- const Home = async (_init, ctx) => {
140
- const msg = await ctx.data.get('/api/hello') // 数据管道:一个 API 三场景
141
- return async (props) => <h1>{msg.msg}</h1>
125
+ // router.tsx —— 路由树(UIRouter——前后端同一棵树——单一实现源)
126
+ import { UIRouter, h, type UIContext } from 'weifuwu/vdom'
127
+
128
+ const Home = (_: unknown, ctx: UIContext) => {
129
+ // 数据管道:一个 API 三场景(SSR 预取 / hydration 命中 / SPA fetch)
130
+ const [getMsg] = ctx.ui.useAsyncData(() => fetch('/api/hello').then((r) => r.json()), 'hello')
131
+ const msg = getMsg()
132
+ if (!msg) return <p>加载中…</p>
133
+ return <h1>{msg.msg}</h1>
142
134
  }
143
135
 
144
- export const routes = [{ path: '/', render: () => h(Home, {}) }]
136
+ export function buildRouter(): UIRouter {
137
+ const router = new UIRouter()
138
+ router.get('/', () => h(Home))
139
+ router.get('/u/:id', (req, ctx) => h('h1', {}, `用户 ${ctx.params.id}`)) // :param 注入
140
+ router.notFound(() => h('h1', {}, '404'))
141
+ return router
142
+ }
145
143
  ```
146
144
 
147
145
  ### 模式 A:纯 SPA
@@ -169,11 +167,11 @@ serve(router, { port: 3000 })
169
167
  ```
170
168
 
171
169
  ```ts
172
- // src/client.ts —— 纯客户端渲染(vdom3 事件流引擎)
173
- import { createRouter } from 'weifuwu/ui-dom'
174
- import { routes } from './routes.tsx'
170
+ // src/client.ts —— 浏览器 boot(uiServe——UIRouter 唯一应用入口)
171
+ import { uiServe } from 'weifuwu/vdom'
172
+ import { buildRouter } from './router.tsx'
175
173
 
176
- createRouter(routes, document.querySelector('#root')!, { ctx }) // 监听 location → 匹配 → 事件流渲染
174
+ uiServe(buildRouter(), { root: '#root' }) // 监听导航 → 匹配 → 命令流渲染
177
175
  ```
178
176
 
179
177
  ### 模式 B:SSR + Hydration(内容页/SEO)
@@ -181,23 +179,22 @@ createRouter(routes, document.querySelector('#root')!, { ctx }) // 监听 loca
181
179
  同一份 `routes`、同一个组件,差异只在**后端构建事件流 → HTML + 序列化事件,客户端回放**:
182
180
 
183
181
  ```ts
184
- // server.ts —— SSR:vnode事件流 → 完整 HTML + __DATA__(DOM = fold 不变量)
182
+ // server.ts —— SSR:同一棵路由树 → 完整 HTML + __DATA__(SSR SPA 首帧)
185
183
  import { serve, Router, ui, cors } from 'weifuwu'
186
- import { renderToEvents, eventsToHtml, serializeEvents } from 'weifuwu/ui-dom'
187
- import { routes } from './routes.tsx'
184
+ import { uiSsr } from 'weifuwu/vdom'
185
+ import { buildRouter } from './router.tsx'
188
186
 
189
187
  const router = new Router()
190
188
  router.use(cors())
191
189
  router.use(ui())
192
190
 
193
- // 路由级 SSR:匹配共享 routes 构建 vnode 事件流 → HTML + __DATA__ 序列化
191
+ // SSR:路由树单源服务端渲染 → HTML + __DATA__ 种子(客户端结构吸收零闪跳)
194
192
  router.get('*', async (req, ctx) => {
195
- const def = routes.find((r) => r.path === (req.url ?? '/'))
196
- const events = await renderToEvents(def?.render({}) ?? null) // 事件流(服务端真 fetch 数据)
197
- const page = `<!doctype html><html><body><div id="root">${eventsToHtml(events)}</div>
198
- <script>window.__DATA__=${serializeEvents(events)}</script>
199
- <script src="/static/app.js"></script></body></html>`
200
- return ctx.ui.html.unsafe(page)
193
+ const html = await uiSsr(buildRouter(), new URL(req.url).pathname, {
194
+ title: '我的应用',
195
+ prefetch: async () => ({ hello: await (await fetch('http://localhost:3000/api/hello')).json() }),
196
+ })
197
+ return ctx.ui.html.unsafe(html)
201
198
  })
202
199
 
203
200
  router.get('/static/app.js', (req, ctx) => ctx.ui.js('./src/client.ts'))
@@ -207,13 +204,12 @@ serve(router, { port: 3000 })
207
204
  ```
208
205
 
209
206
  ```ts
210
- // src/client.ts —— 事件流回放收养(deserializeEvents + replay——零 DOM 猜测)
211
- import { createRouter, deserializeEvents, replay } from 'weifuwu/ui-dom'
212
- import { routes } from './routes.tsx'
207
+ // src/client.ts —— 结构吸收(uiServe 首帧复用服务端 DOM——零闪跳)
208
+ import { uiServe } from 'weifuwu/vdom'
209
+ import { buildRouter } from './router.tsx'
213
210
 
214
- const root = document.querySelector('#root')!
215
- replay(deserializeEvents((window as any).__DATA__), root) // 收养服务端 HTML(无闪跳)
216
- createRouter(routes, root) // 之后正常交互渲染
211
+ // 同一棵路由树:服务端 HTML 被吸收(焦点/输入值保持),之后正常交互渲染
212
+ uiServe(buildRouter(), { root: '#root' })
217
213
  ```
218
214
 
219
215
  ### 启动(两种模式都一样)
@@ -244,15 +240,6 @@ cd apps/agent-platform && npm run seed && npm run dev
244
240
 
245
241
  > 想**零后端、零构建**最快跑起来?直接跳到下面的「CDN 快速原型」——一个 `.html` 文件即可。
246
242
 
247
- ### 📦 安装后离线文档(content/ 随包发布)
248
-
249
- ```bash
250
- npm i weifuwu
251
- npx weifuwu docs # → http://localhost:4000 本地文档站(组件 API 表/纪律/示例/验证)
252
- ```
253
-
254
- `node_modules/weifuwu/content/` 是完整文档库(逐组件文档 + 页面/应用模板 + 后端能力 + 指南 + 选型决策树 + 人类质量标准)——版本永远匹配安装的代码;`examples/` 是可复制源码(页面模式 + 应用模板 todo/auth/admin/multi)。LLM 开发路径:`read node_modules/weifuwu/content/index.md` → 目标 .md → 复制 examples/ 源码。
255
-
256
243
  ---
257
244
 
258
245
  ## CDN 快速原型(零构建、纯 HTML)
@@ -268,7 +255,7 @@ npx weifuwu docs # → http://localhost:4000 本地文档站(组件 API 表
268
255
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
269
256
  <title>Weifuwu CDN 示例</title>
270
257
 
271
- <!-- 组件样式(可选,如只用 weifuwu/ui-dom 则不需要) -->
258
+ <!-- 组件样式(可选,如只用 weifuwu/vdom 则不需要) -->
272
259
  <link
273
260
  rel="stylesheet"
274
261
  href="https://unpkg.com/weifuwu@latest/dist/components/style.css"
@@ -281,57 +268,34 @@ npx weifuwu docs # → http://localhost:4000 本地文档站(组件 API 表
281
268
  <script type="importmap">
282
269
  {
283
270
  "imports": {
284
- "weifuwu/ui-dom": "https://unpkg.com/weifuwu@latest/dist/ui-dom/index.js",
271
+ "weifuwu/vdom": "https://unpkg.com/weifuwu@latest/dist/vdom/index.js",
285
272
  "weifuwu/components": "https://unpkg.com/weifuwu@latest/dist/components/index.js"
286
273
  }
287
274
  }
288
275
  </script>
289
276
 
290
277
  <script type="module">
291
- import { createRouter, h } from 'weifuwu/ui-dom'
292
- import { Card, Button, Badge } from 'weifuwu/components'
293
-
294
- // 组件 = async (initProps, ctx) => (props) => Promise<VNode>(render-only:改状态后 ctx.ui.render())
295
- const Counter = async (_init, ctx) => {
296
- let count = 0 // mount 初始化
297
-
298
- return () =>
299
- h(Card, { variant: 'default', padding: 'lg' },
300
- h('h2', { style: { textAlign: 'center', margin: 0 } }, '⚡ Weifuwu'),
301
- h('div', { style: { fontSize: '4rem', fontWeight: 600, textAlign: 'center' } },
302
- String(count)),
303
- h('div', { style: { textAlign: 'center', marginTop: '1rem' } },
304
- h(Badge, {
305
- variant: count % 2 === 0 ? 'success' : 'warning'
306
- }, count % 2 === 0 ? '偶数' : '奇数')),
307
- h('hr', { style: { margin: '1rem 0', border: 'none', borderTop: '1px solid #eee' } }),
308
- h('div', { style: { display: 'flex', gap: '0.5rem', justifyContent: 'center' } },
309
- h(Button, { variant: 'secondary', onClick: () => { count--; ctx.ui.render() } }, '➖ 减 1'),
310
- h(Button, { variant: 'danger', onClick: () => { count = 0; ctx.ui.render() } }, '↺ 重置'),
311
- h(Button, { variant: 'primary', onClick: () => { count++; ctx.ui.render() } }, '➕ 加 1'),
312
- ),
313
- )
314
- }
278
+ import { UIRouter, uiServe, h } from 'weifuwu/vdom'
315
279
 
316
- // 路由 + 渲染(vdom3 事件流引擎:createRouter——监听 location → 匹配 → 事件流落地)
317
- createRouter(
318
- [{ path: '/', render: () => h(Counter, {}) }],
319
- document.querySelector('#root')!,
320
- )
280
+ // 路由树(UIRouter——get/notFound——(req, ctx) => Response 同构后端)
281
+ const router = new UIRouter()
282
+ router.get('/', () => h('h1', {}, 'Hello weifuwu'))
283
+ router.get('/u/:id', (req, ctx) => h('h1', {}, `用户 ${ctx.params.id}`))
284
+ router.notFound(() => h('h1', {}, '404'))
285
+
286
+ // 浏览器 boot(uiServe——监听导航 → 匹配 → 命令流渲染)
287
+ uiServe(router, { root: '#root' })
321
288
  </script>
289
+ <!-- 对照服务端:new Router().get('/', () => new Response(...))——同一签名形状 -->
322
290
  </body>
323
291
  </html>
324
292
  ```
325
293
 
326
- 将此 HTML 保存到本地用浏览器打开即可运行。
327
-
328
- ### CDN 资源地址说明
329
-
330
- | 资源 | CDN 地址 | 说明 |
294
+ | 模块 | CDN 路径 | 说明 |
331
295
  |------|---------|------|
332
- | `weifuwu/ui-dom` | `https://unpkg.com/weifuwu@latest/dist/ui-dom/index.js` | 前端运行时(createRouter, createRoot, h, 事件流, 状态管理等) |
296
+ | `weifuwu/vdom` | `https://unpkg.com/weifuwu@latest/dist/vdom/index.js` | 前端运行时(UIRouter, uiServe, h/jsx, hooks 等) |
333
297
  | `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` | 129 个 UI 组件(Button, Card, Table, Modal, Icon 等) |
334
- | `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS + 182 个主题 Token + 66 个布局原语 + 157 个工具类 |
298
+ | `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS + 183 个主题 Token + 48 个布局原语 + 90 个工具类 |
335
299
  | 独立布局系统 | `https://unpkg.com/weifuwu@latest/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
336
300
 
337
301
 
@@ -342,6 +306,7 @@ npx weifuwu docs # → http://localhost:4000 本地文档站(组件 API 表
342
306
  | 导入路径 | 模块 | 用途 | 依赖 |
343
307
  |---------|------|------|------|
344
308
  | `weifuwu` | **Router** | Trie 路由 + 中间件链 + WebSocket + GraphQL | — |
309
+ | `src/shared/router` | **路由内核(前后端共享五层单源)** | trie(匹配)+ pipeline(流程骨架)+ context(URL 解析/ctx 注入)+ chain(中间件链)+ ctx-fields(ctx 扩展注册表)——**同一套机制双端复用**,serve(编解码边界)留各自域 | — |
345
310
  | `weifuwu` | **serve** | HTTP 服务器 | Router |
346
311
  | `weifuwu` | **cors** | CORS 跨域中间件 | Router |
347
312
  | `weifuwu` | **serveStatic** | 静态文件服务(ETag/304/目录索引) | Router |
@@ -349,8 +314,8 @@ npx weifuwu docs # → http://localhost:4000 本地文档站(组件 API 表
349
314
  | `weifuwu` | **redis** | Redis 客户端(自研 RESP2 协议)→ `ctx.redis` | Router, REDIS_URL |
350
315
  | `weifuwu/db` | **Memory 实现** | `createMemorySql()` / `MemoryRedis`——生产契约黑盒实现(开发/测试/单实例零数据库);`MemoryRedisServer`/`MemoryPostgresServer`——进程内线协议服务器(协议测试零 docker) | — |
351
316
  | `weifuwu` | **ui** | SSR 渲染 + esbuild JS/CSS 动态编译 → `ctx.ui` | Router |
352
- | `weifuwu/ui-dom` | **createRouter** | vdom3 路由:RouteDef[] + 中间件面 ctx 注入(对齐后端 `app.use`)——监听 location 匹配 事件流渲染 | — |
353
- | `weifuwu/ui-dom` | **SSR(事件流形态)** | `renderToEvents``eventsToHtml` + `serializeEvents`(DOM = fold——客户端 `replay` 收养) | Router, ui |
317
+ | `weifuwu/vdom` | **UIRouter** | 前端路由唯一入口:get/notFound/has/resolve——`(req, ctx) => Response` 签名对齐后端——params/query/route 注入(shared Trie 五层单源) | — |
318
+ | `weifuwu/vdom` | **uiServe / uiSsr** | uiServe 浏览器 boot(导航监听 命令流渲染);uiSsr 服务端渲染(HTML + `__DATA__` 种子——**结构吸收** SSR SPA 首帧) | Router, ui |
354
319
  | `weifuwu` | **rateLimit** | 限流中间件(fixed/sliding,redis 多实例原子)→ `ctx.limit` | Router, redis |
355
320
  | `weifuwu` | **email** | 邮件发送(Resend/SMTP 自研/自定义适配器)→ `ctx.email` | Router |
356
321
  | `weifuwu` | **userSystem** | 用户系统(scrypt 密码哈希 + 混合会话 + 多租户感知)→ `ctx.user` / `ctx.auth` / `ctx.tenantId` + `/api/auth/*` | Router, postgres |
@@ -364,17 +329,17 @@ npx weifuwu docs # → http://localhost:4000 本地文档站(组件 API 表
364
329
  | `weifuwu` | **ok / badRequest / …** | HTTP 响应辅助函数(ok/badRequest/... 等 12 个) | — |
365
330
  | `weifuwu` | **parseBody** | JSON 请求体安全解析 | — |
366
331
  | Router 方法 | **app.graphql()** | GraphQL 端点(支持 GraphiQL),Router 实例方法(无需单独 import) | Router |
367
- | `weifuwu/ui-dom` | **createRouter / createRoot** | vdom3 事件流引擎:路由(RouteDef[] + ctx 注入)/ 挂载(组件树)——渲染全链路 `entity:action` 事件流(DOM = fold——可记录/回放/断言);**事件代理**(监听 O(1)——零重绑——统一注册表);**app 节点**(多应用加载——registerApp) | — |
368
- | `weifuwu/ui-dom` | **事件流原语** | `stream` / `ev` / `evKey` / `replay` / `eventsOf` / `expectEventSequence`——渲染可观测/可断言 | — |
369
- | `weifuwu/ui-dom` | **async 组件** | async 函数即组件(与同步同签名);数据走 ctx.data 三场景(三条纪律见[核心概念](#核心概念)) | — |
370
- | `weifuwu/ui-dom` | **ctx.data** | 数据管道:SSR 预取 / hydration 命中 / SPA fetch(`ctx.data.get`) | — |
371
- | `weifuwu/ui-dom` | **api / auth / ws** | HTTP 客户端 / 认证 / WebSocket 中间件 | — |
372
- | `weifuwu/ui-dom` | **i18n** | 国际化中间件(运行时切换语言) | — |
373
- | `weifuwu/ui-dom` | **renderToEvents / eventsToHtml / serializeEvents** | 服务端渲染:vnode → 事件流 → HTML + `__DATA__` 序列化(客户端 `replay` 收养) | — |
374
- | `weifuwu/ui-dom` | **useChat / AiChat 原语** | AI 会话(流式/工具调用/HITL) | — |
375
- | `weifuwu/ui-dom` | **事件原语** | `usePopup`(统一弹窗能力层)/ `usePresence` / `useInView` / `useScrollPosition` / `useGlobalKey` / `useDrag` / `useDragDrop` / `useAnimationEnd` / `useTween` / `useReducedMotion`(浏览器事件/动画统一入口,见 [content/guides/mobile-guide.md](content/guides/mobile-guide.md)) | — |
376
- | `weifuwu/components` | **129 个组件** | Button/Table/Modal/Confirm/Toast/... + `confirm()` / `toast()` 命令式中间件 | weifuwu/ui-dom |
377
- | `weifuwu/layout` | **CSS 布局** | 66 个布局原语 + 157 个工具类 + 182 个主题 Token(也支持 `weifuwu/layout/style.css`) | — |
332
+ | `weifuwu/vdom` | **命令流渲染引擎** | 渲染 = 命令流(NDJSON 纯数据——13 命令完整自足——可回放可断言);**事件代理**(监听 O(1)——零重绑——统一注册表);三状态机 + 对账器(终态等价 fuzz 1310 对防线) | — |
333
+ | `weifuwu/vdom` | **渲染健康仪表** | `window.__wfRenderHealth`(频率/规模/复用/错误四轴——dev 模式——渲染问题出现即读数) | — |
334
+ | `weifuwu/vdom` | **组件契约** | 工厂同步(`(initProps, ctx) => RenderFn`)——异步边界全在 hooks(useAsyncData/useObservable)——渲染纯同步(见[核心概念](#核心概念)) | — |
335
+ | `weifuwu/vdom` | **useAsyncData** | 数据管道:同 key 并发合并 / 竞态取消 / 缓存保留 / SSR 种子预热(`ctx.ui.useAsyncData(fetcher, key)`) | — |
336
+ | `weifuwu/vdom` | **api / auth / ws / i18n** | HTTP 客户端 / 认证 / WebSocket / 国际化中间件(ctx 注入) | — |
337
+
338
+ | `weifuwu/vdom` | **hooks 全家** | usePopup/useControlled/useExternal/useInView/useScrollPosition/useTween/useBreakpoint/... (getter 纪律——任意位置读最新) | — |
339
+ | `weifuwu/vdom` | **useChat / AiChat 原语** | AI 会话(流式/工具调用/HITL) | — |
340
+ | `weifuwu/vdom` | **事件原语** | `usePopup`(统一弹窗能力层)/ `usePresence` / `useInView` / `useScrollPosition` / `useGlobalKey` / `useDrag` / `useDragDrop` / `useAnimationEnd` / `useTween` / `useReducedMotion`(浏览器事件/动画统一入口,见 —) | — |
341
+ | `weifuwu/components` | **129 个组件** | Button/Table/Modal/Confirm/Toast/... + `confirm()` / `toast()` 命令式中间件 | weifuwu/vdom |
342
+ | `weifuwu/layout` | **CSS 布局** | 48 个布局原语 + 90 个工具类 + 183 个主题 Token(也支持 `weifuwu/layout/style.css`) | — |
378
343
 
379
344
  ---
380
345
 
@@ -384,106 +349,102 @@ npx weifuwu docs # → http://localhost:4000 本地文档站(组件 API 表
384
349
 
385
350
  | 任务 | 用 | 位置 |
386
351
  |------|-----|------|
387
- | 起 HTTP 服务 + 路由 | `serve(app)` + `new Router()` + `app.get/post/...` | [content/guides/server-guide.md](content/guides/server-guide.md) |
388
- | 渲染页面(SPA / SSR) | `createRouter(routes, root, { ctx })`;SSR = `renderToEvents` `eventsToHtml` + `replay`(事件流形态) | [content/guides/ui-dom-guide.md](content/guides/ui-dom-guide.md) · [content/guides/frontend.md](content/guides/frontend.md) |
389
- | 数据持久化 | `postgres()` → `` ctx.sql`SELECT *` `` · `redis()` → `ctx.redis` · **`sql.query`**(Query Language AST 双后端) | [content/guides/data-guide.md](content/guides/data-guide.md) |
390
- | 零数据库开发/测试 | `createMemorySql()` / `MemoryRedis`——契约同真库、替换成本为零 | [content/guides/data-guide.md](content/guides/data-guide.md) |
391
- | 数据管道(SSR 预取/hydration/SPA) | `ctx.data.get(key)` + async 组件 | [content/guides/frontend.md](content/guides/frontend.md) |
392
- | 用户注册/登录/会话/多租户 | `userSystem()` → `ctx.auth` + `/api/auth/*` | [content/guides/saas-guide.md](content/guides/saas-guide.md) |
393
- | 限流防爆破 | `rateLimit()` + `ctx.limit()` | [content/guides/saas-guide.md](content/guides/saas-guide.md) |
394
- | 发邮件 | `email()` → `ctx.email`(Resend/SMTP) | [content/guides/saas-guide.md](content/guides/saas-guide.md) |
395
- | 实时消息/聊天/通知 | `messager()` → `ctx.msg` + `app.ws` | [content/guides/saas-guide.md](content/guides/saas-guide.md) |
396
- | 后台任务/定时 | `queue()` → `ctx.queue` · `scheduler()` → `ctx.schedule/cron` | [content/guides/saas-guide.md](content/guides/saas-guide.md) |
397
- | AI 对话 / Agent / HITL 审批 | `ai()` → `ctx.ai` + `ctx.ui.useChat()` + `AiChat` | [content/guides/saas-guide.md](content/guides/saas-guide.md) |
398
- | GraphQL / WebSocket | `app.graphql(handler)` · `app.ws(path, handler)` | [content/guides/realtime-guide.md](content/guides/realtime-guide.md) |
399
- | 前端 UI 组件 | `weifuwu/components`(129 个:Button/Table/Modal/AiChat/...) | [content/guides/components-guide.md](content/guides/components-guide.md) |
400
- | 布局/主题/暗色 | `weifuwu/layout`(58 原语 + 157 工具类 + 182 Token) | [content/guides/layout-guide.md](content/guides/layout-guide.md) |
401
- | 样式定制(零自定义 CSS) | `--wf-*` 变量覆盖 + 组件定制钩子 | [content/guides/styling.md](content/guides/styling.md) |
402
- | 移动端适配(tap/长按/键盘/弹层) | `usePopup` / `useHoverCapable` / `useLongPress` / `useVisualViewport` | [content/guides/mobile-guide.md](content/guides/mobile-guide.md) |
403
- | 前后端类型安全中间件 | `createMiddleware`(声明注入即类型化) | [content/guides/server-guide.md](content/guides/server-guide.md) |
352
+ | 起 HTTP 服务 + 路由 | `serve(app)` + `new Router()` + `app.get/post/...` | |
353
+ | 渲染页面(SPA / SSR) | `UIRouter` + `uiServe(router, { root })`;SSR = `uiSsr(router, url)`(结构吸收——首帧零差异) | · |
354
+ | 数据持久化 | `postgres()` → `` ctx.sql`SELECT *` `` · `redis()` → `ctx.redis` · **`sql.query`**(Query Language AST 双后端) | |
355
+ | 零数据库开发/测试 | `createMemorySql()` / `MemoryRedis`——契约同真库、替换成本为零 | |
356
+ | 数据管道(SSR 预取/hydration/SPA) | `ctx.ui.useAsyncData(fetcher, key)`(并发合并/竞态取消/SSR 种子) | |
357
+ | 用户注册/登录/会话/多租户 | `userSystem()` → `ctx.auth` + `/api/auth/*` | |
358
+ | 限流防爆破 | `rateLimit()` + `ctx.limit()` | |
359
+ | 发邮件 | `email()` → `ctx.email`(Resend/SMTP) | |
360
+ | 实时消息/聊天/通知 | `messager()` → `ctx.msg` + `app.ws` | |
361
+ | 后台任务/定时 | `queue()` → `ctx.queue` · `scheduler()` → `ctx.schedule/cron` | |
362
+ | AI 对话 / Agent / HITL 审批 | `ai()` → `ctx.ai` + `ctx.ui.useChat()` + `AiChat` | |
363
+ | GraphQL / WebSocket | `app.graphql(handler)` · `app.ws(path, handler)` | |
364
+ | 前端 UI 组件 | `weifuwu/components`(129 个:Button/Table/Modal/AiChat/...) | |
365
+ | 布局/主题/暗色 | `weifuwu/layout`(49 原语 + 90 工具类 + 183 Token) | |
366
+ | 样式定制(零自定义 CSS) | `--wf-*` 变量覆盖 + 组件定制钩子 | |
367
+ | 移动端适配(tap/长按/键盘/弹层) | `usePopup` / `useHoverCapable` / `useLongPress` / `useVisualViewport` | |
368
+ | 前后端类型安全中间件 | `createMiddleware`(声明注入即类型化) | |
404
369
 
405
370
  ---
406
371
 
407
372
  ## 核心概念
408
373
 
409
- ### 三层形态(路由 / 组件 / async 组件工厂)
410
-
411
- | 层 | 签名 | 异步 | 生命周期 |
412
- |----|------|------|---------|
413
- | **UIHandler**(路由) | `async (location, ctx) => VNode` | ✅ 整体 | 每次路由变化执行 |
414
- | **Component**(唯一形态) | `async (initProps, ctx) => (props) => Promise<VNode>` | ✅ 工厂 + renderFn | mount 一次 + render 每次;同步组件已不支持(类型强制 Promise) |
374
+ ### 一条规则:工厂同步、异步在 hooks、渲染纯同步
415
375
 
416
- 异步只在两个边界——路由 handler(整页)和组件工厂(数据声明)+ renderFn(强制异步)。渲染器按「返回值 instanceof Promise」统一判别:主路径 `buildVNode` async 预构建(await 全部工厂,兄弟并行)→ 原子落地(无**中间态**占位、无补全回调——注意:数组内 false/null 的静态诊断占位 `<!--wf-hole-->` 是另一回事,见「VDOM 输出透明」);运行时首次挂载的 async 组件同样在 buildVNode 阶段 await;骨架屏:预置骨架屏 HTML → 首帧原子替换 + `handle.ready`。
417
- ### 两阶段组件(新手必读:为什么是两层)
418
-
419
- 组件 = `async (initProps, ctx) => (props) => Promise<VNode>`——**外层 = 初始化(只执行一次,可 await 数据),内层 = 渲染(每次状态/props 变化时执行,强制异步)**。类比:外层是对象的构造函数,内层是它的 render 方法。
376
+ ```tsx
377
+ type Component<P, C> = (initProps: P, ctx: C) => RenderFn<P> // 工厂同步(毫秒即挂载完)
378
+ type RenderFn<P> = (props: P) => VNode | null | (VNode | null)[] // 渲染纯同步
379
+ ```
420
380
 
421
381
  ```tsx
422
- const Counter = async (_init, ctx) => {
423
- // 外层(mount):只跑一次——初始化状态、订阅、定时器(可 await 数据)
424
- let count = 0
425
- return async (props) =>
426
- // 内层(render):每次变化执行(强制异步)——读状态输出视图(render-only:改状态后显式 render())
427
- <button onClick={() => { count++; ctx.ui.render() }}>{count}</button>
382
+ const Counter = (_init, ctx) => {
383
+ // 工厂(mount 一次):建 signal、订阅、定时器——同步执行
384
+ const count = ctx.ui.signal(0)
385
+ return () => (
386
+ // 渲染(每次变化自动执行):读 getter——最新值
387
+ <button onClick={() => count.set((c) => c + 1)}>{count()}</button>
388
+ )
428
389
  }
429
390
  ```
430
391
 
431
- > 为什么不是单层函数(React 风格)?单层函数每次渲染都执行整个函数体,需要 hooks 记忆机制来区分"初始化"和"渲染";两阶段用**位置即语义**——外层天生只跑一次,没有 hooks 规则、没有依赖数组、没有闭包陷阱。
432
- > 异步数据用原生 async 组件(见下文):`async (initProps, ctx) => await ctx.data.get(...)` → 返回 renderFn,数据经闭包注入。
392
+ > **为什么工厂必须同步?** 同步工厂 = 毫秒级挂载(无 mounting 窗口、无 async
393
+ > 竞态、SSR/客户端行为完全一致)。异步需求全部收敛到 hooks 内部流管道——
394
+ > 作者层面**没有 await 渲染**这件事。事件回调内 `await` 合法(非渲染路径)。
433
395
 
434
- ### 中间件模式(前后端一致)
396
+ ### 数据:useAsyncData(唯一异步边界)
435
397
 
436
- ```
437
- 后端: app.use(cors())
438
- app.use(postgres())
439
- app.get('/users', (req, ctx) => { ctx.sql`SELECT *` })
440
- // ctx 已注入 ctx.sql
441
-
442
- 前端: const router = createRouter(routes, root, { ctx })
443
- router.use(api({ baseURL: '/api' }))
444
- router.use(auth())
445
- router.get('/users', async (location, ctx) => h(UsersPage, {}))
446
- // createRouter = 监听 location → 匹配 → 事件流渲染落地
447
- // ctx 已注入 ctx.api, ctx.auth
398
+ ```tsx
399
+ const Home = (_: unknown, ctx: UIContext) => {
400
+ const [getMsg, reload] = ctx.ui.useAsyncData(
401
+ () => fetch('/api/hello').then((r) => r.json()), 'hello')
402
+ const msg = getMsg() // getter——最新值(null = loading/降级)
403
+ if (!msg) return <p>加载中…</p>
404
+ return <h1>{msg.msg} <button onClick={reload}>刷新</button></h1>
405
+ }
448
406
  ```
449
407
 
450
- 前端 req = `window.location`,res = `VNode`,`createRouter` = 事件流渲染落地——与后端 `Request → Response`、`serve(router)` 完全同构。
408
+ **内建语义(作者零代码)**:同 key 并发合并(N 组件 fetch 1 次)· 竞态取消
409
+ (旧请求作废)· 缓存保留(重挂载零请求)· 卸载自动退订(零泄漏)· SSR 种子
410
+ 预热(首帧零请求)。多源汇流:`ctx.ui.useObservable(obs$)` + `combineLatest`。
451
411
 
452
- ### 状态管理
412
+ ### 路由:前后端同一内核
453
413
 
454
- | 模式 | 后端 | 前端 |
455
- |------|------|------|
456
- | 注入 | 中间件注入 ctx.field | 中间件注入 ctx.field |
457
- | 读取 | handler 读取 ctx | 组件读取 ctx |
458
- | 渲染 | 返回 Response | `ctx.ui.render()` 触发局部 VDOM patch(render-only);共享状态 `createStore` + `useExternal` |
414
+ ```
415
+ 后端: const app = new Router()
416
+ app.get('/u/:id', (req, ctx) => Response.json(ctx.params))
417
+ serve(app, { port: 3000 })
459
418
 
460
- ### async 组件(三条纪律)
419
+ 前端: const router = new UIRouter()
420
+ router.get('/u/:id', (req, ctx) => h('h1', {}, ctx.params.id))
421
+ uiServe(router, { root: '#root' })
422
+ ```
461
423
 
462
- async 组件让"拿数据渲染页面"像写同步代码——签名与同步组件一致,唯一差别是 `async` 关键字:`async (initProps, ctx) => renderFn`。异步只在工厂边界:
424
+ **`(req: Request, ctx) => Response` 字面同构**——两者内部跑同一个
425
+ `dispatchRouter`(`src/shared/router/` 五层单源:trie 匹配 / pipeline 骨架 /
426
+ context 解析与注入 / chain 中间件链 / ctx-fields 注册表)。差异点钩子化:
427
+ verb 表(server)/ctx.route 注入(client)/404 形态——**serve(编解码边界)留各自域**。
428
+
429
+ ### 中间件注入一切(前后端一致)
463
430
 
464
- ```tsx
465
- const UserProfile = async (_init, ctx) => {
466
- const user = await ctx.data.get(`/api/user/${ctx.params.id}`) // ① 工厂层:声明数据(三场景自动)
467
- let liked = false // ② mount:客户端状态(render-only)
468
- return async (props) =>
469
- h('div', {},
470
- h('p', {}, user.name), // 服务端状态(闭包,SSR 进 HTML)
471
- h('button', { onClick: () => { liked = !liked; ctx.ui.render() } }, liked ? '❤️' : '🤍'))
472
- }
473
431
  ```
432
+ 后端: app.use(cors()); app.use(postgres())
433
+ app.get('/users', (req, ctx) => ctx.sql`SELECT *`)
434
+ // ctx 已注入 ctx.sql——injects/depends 声明受检
474
435
 
475
- **三条纪律**(不遵守就是隐性 bug):
436
+ 前端: ctx.ui.useAsyncData / useObservable / useChat / hold
437
+ // ctx.ui 渲染原语面——信号 getter 纪律
438
+ ```
476
439
 
477
- | 纪律 | 反例 | 正确 |
478
- |---|---|---|
479
- | ① 数据 key 必须含维度 | `ctx.data.get('/api/user')`——`/users/1 → /users/2` 导航命中旧缓存 | `ctx.data.get(\`/api/user/${ctx.params.id}\`)` |
480
- | ② 会变的数据放组件状态 | `const count = data.count`——点击永不更新 | `let count = data.count` + 交互后 `ctx.ui.render()`(初始值 seed 自服务端数据) |
481
- | ③ 初始状态必须确定性 | `let w = window.innerWidth`——SSR/hydration mismatch | 用服务端数据 seed,交互后再测 |
440
+ ### 状态管理
482
441
 
483
- **常见坑**:
484
- - 工厂按**实例**执行(N 处实例 = N 次工厂调用)——数据必须走 `ctx.data`(自带缓存 + 并发合并,重复执行零成本);禁止副作用/昂贵操作裸写工厂
485
- - 闭包数据是页面加载时的**快照**——路由参数变化靠工厂重跑刷新(key 缓存 miss 重新取数)
486
- - **个性化数据不进 `ctx.data`**——SSR 会把工厂取数结果序列化给所有客户端,会话/用户相关数据留在客户端 `let` + fetch + `render()`
442
+ | 场景 | API | 语义 |
443
+ |------|-----|------|
444
+ | 组件内状态 | `ctx.ui.signal(n)` | getter + set/update 写——变化自动重渲染 |
445
+ | 跨组件共享 | `createStore` + `ctx.ui.useExternal(store)` | 共享状态订阅——getter 最新 |
446
+ | 任意 Observable | `ctx.ui.useObservable(obs$, initial)` | rxjs 风格流 → getter(卸载自动退订) |
447
+ | 服务端状态 | handler 返回 Response | 每请求独立 |
487
448
 
488
449
  ### 渲染策略:SPA 还是 SSR?
489
450
 
@@ -492,17 +453,23 @@ const UserProfile = async (_init, ctx) => {
492
453
  | | SPA | SSR + Hydration |
493
454
  |---|---|---|
494
455
  | 适用 | 应用页(后台、工具、Dashboard) | 内容页(博客、营销,需要 SEO/首屏) |
495
- | 后端 | HTML 外壳 | `renderToEvents` → `eventsToHtml` + `serializeEvents`(完整 HTML + `__DATA__`) |
496
- | 客户端 | `createRouter(routes, root, { ctx })` | `deserializeEvents` + `replay`(事件流收养——DOM = fold) |
456
+ | 后端 | HTML 外壳 | `uiSsr(router, url)`(完整 HTML + `__DATA__` 种子——同一棵路由树) |
457
+ | 客户端 | `uiServe(router, { root })` | **结构吸收**(首帧复用服务端 DOM——焦点/输入值保持——失败原子回退重建) |
497
458
 
498
- **怎么选**:默认 SPA;需要 SEO 或首屏即内容时用 SSR。两种模式可混合——SSR 走共享 routes,未匹配走普通 handler。
459
+ **怎么选**:默认 SPA;需要 SEO 或首屏即内容时用 SSR。两种模式可混合。
499
460
 
500
- ### Closeable 接口
461
+ ### VDOM 输出透明
501
462
 
502
- 所有有状态模块(postgres、redis)实现 `close(): Promise<void>`,serve 关闭时自动调用。
463
+ - **命令流可回放**:渲染 = NDJSON 命令流(纯数据)——DOM = fold(命令流)——
464
+ 可记录/回放/断言(测试基础设施与渲染本体同源)
465
+ - **诊断占位**:条件渲染 false → `<!--wf-hole-->` 注释锚(兄弟不误删)
466
+ - **id 落 DOM**:`data-wf-id` / `data-wf-key`——devtools 即引擎决策日志
467
+ - **非法输入显式化**:重复 key warn(不静默)、事件非函数 warn、受控缺回调
468
+ warn——**A 级检测兜底**
503
469
 
504
- ---
470
+ ### Closeable 接口
505
471
 
472
+ 所有有状态模块(postgres、redis)实现 `close(): Promise<void>`,serve 关闭时自动调用(WS 1001 握手先行 + 重复 close 幂等)。
506
473
 
507
474
  ---
508
475
 
@@ -514,23 +481,23 @@ README 只保留入门内容(设计理念 / 快速开始 / 核心概念 / 模
514
481
 
515
482
  | 文档 | 内容 |
516
483
  |------|------|
517
- | [content/guides/server-guide.md](content/guides/server-guide.md) | HTTP 服务层:Router / serve / cors / serveStatic / HttpError / 响应辅助 / parseBody |
518
- | [content/guides/data-guide.md](content/guides/data-guide.md) | 数据层:postgres(PG v3 自研协议)/ redis(RESP2 自研协议)/ Query Language(AST 双后端)/ Memory 实现(零数据库)/ 测试零外部依赖 |
519
- | [content/guides/realtime-guide.md](content/guides/realtime-guide.md) | 实时与渲染:scheduler / ui(SSR + JS/CSS 编译)/ graphql / WebSocket |
520
- | [content/guides/saas-guide.md](content/guides/saas-guide.md) | SaaS 地基:rateLimit / email / userSystem / messager / queue / ai |
484
+ | | HTTP 服务层:Router / serve / cors / serveStatic / HttpError / 响应辅助 / parseBody |
485
+ | | 数据层:postgres(PG v3 自研协议)/ redis(RESP2 自研协议)/ Query Language(AST 双后端)/ Memory 实现(零数据库)/ 测试零外部依赖 |
486
+ | | 实时与渲染:scheduler / ui(SSR + JS/CSS 编译)/ graphql / WebSocket |
487
+ | | SaaS 地基:rateLimit / email / userSystem / messager / queue / ai |
521
488
  | [docs/ai-contract.md](docs/ai-contract.md) | AI Stream Protocol:wf: 事件(SSE 下行 + POST 上行)——流式/工具/审批 |
522
489
 
523
490
  ### 前端开发者
524
491
 
525
492
  | 文档 | 内容 |
526
493
  |------|------|
527
- | [content/guides/frontend.md](content/guides/frontend.md) | 前端核心:应用引导(createRouter/createRoot——vdom3 事件流)/ 组件模型 / 异步组件 / 状态管理 / 条件与列表 / ref / 类型(weifuwu/client 已并入 ui-dom) |
528
- | [content/guides/ui-dom-guide.md](content/guides/ui-dom-guide.md) | **ui-dom**:vdom3 精准事件流引擎(createRouter/createRoot + 事件流)——components 复用 + SSR(事件流形态)(前端唯一运行时) |
529
- | [content/guides/middleware.md](content/guides/middleware.md) | 前端中间件:router / api / auth / ws / i18n / ErrorBoundary / confirm / toast / ScrollLock / extendCtx |
530
- | [content/guides/components-guide.md](content/guides/components-guide.md) | 组件库(129 个组件 + 使用示例 + 组件列表) |
531
- | [content/guides/layout-guide.md](content/guides/layout-guide.md) | 布局系统:66 个布局原语 + 157 个工具类 + 182 个主题 Token |
494
+ | | 前端核心:应用引导(UIRouter/uiServe)/ 组件模型(工厂同步 + hooks 异步边界)/ 状态管理 / 条件与列表 / ref |
495
+ | | **vdom**:命令流渲染引擎(UIRouter + uiServe/uiSsr + hooks)——components 复用 + SSR 结构吸收(前端唯一运行时) |
496
+ | | 前端中间件:router / api / auth / ws / i18n / ErrorBoundary / confirm / toast / ScrollLock / extendCtx |
497
+ | | 组件库(129 个组件 + 使用示例 + 组件列表) |
498
+ | | 布局系统:48 个布局原语 + 90 个工具类 + 183 个主题 Token |
532
499
  | [docs/style-guide.md](docs/style-guide.md) | 样式学习路径与命名规范:三档学习(组件 → 原语 → 速查)|
533
- | [content/guides/styling.md](content/guides/styling.md) | 样式定制指南:零自定义 CSS 模式 / 暗色 / 组件级覆盖 / 作用域主题 |
500
+ | | 样式定制指南:零自定义 CSS 模式 / 暗色 / 组件级覆盖 / 作用域主题 |
534
501
 
535
502
  ### 通用
536
503
 
@@ -538,9 +505,9 @@ README 只保留入门内容(设计理念 / 快速开始 / 核心概念 / 模
538
505
  |------|------|
539
506
  | [docs/examples.md](docs/examples.md) | 组合场景示例:登录表单 / 数据列表 + 搜索 / 消息提示 |
540
507
  | [docs/environment.md](docs/environment.md) | 环境变量与开发命令 |
541
- | [content/guides/mobile-guide.md](content/guides/mobile-guide.md) | 移动端开发指南:断点 / 44px 命中区 / usePopup / 手势 / safe-area |
508
+ | | 移动端开发指南:断点 / 44px 命中区 / usePopup / 手势 / safe-area |
542
509
  | [docs/components-map.md](docs/components-map.md) | 组件速查:weifuwu ↔ antd / Element Plus / shadcn 对应 + 迁移路径 |
543
- | [content/guides/custom-component.md](content/guides/custom-component.md) | 自定义组件开发指南:usePopup / useControlled / 动画 / AI 组件 / 类型纪律 |
510
+ | | 自定义组件开发指南:usePopup / useControlled / 动画 / AI 组件 / 类型纪律 |
544
511
 
545
512
  > **贡献**:见 [CONTRIBUTING.md](CONTRIBUTING.md)——四条路径(文档/测试/demo/组件)+ 质量防线。
546
513
  >