weifuwu 0.83.0 → 0.85.0

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 (73) hide show
  1. package/README.md +56 -51
  2. package/dist/components/ChatInput/ChatInput.d.ts +2 -2
  3. package/dist/components/Confirm/Confirm.d.ts +2 -2
  4. package/dist/components/Notification/Notification.d.ts +0 -2
  5. package/dist/components/Toast/Toast.d.ts +0 -2
  6. package/dist/components/index.d.ts +3 -3
  7. package/dist/components/index.js +12 -12
  8. package/dist/components/style.css +17 -0
  9. package/dist/index.js +2408 -341
  10. package/dist/ui-dom/Confirm.d.ts +1 -1
  11. package/dist/ui-dom/Notification.d.ts +1 -1
  12. package/dist/ui-dom/Toast.d.ts +1 -1
  13. package/dist/ui-dom/hooks/index.d.ts +0 -1
  14. package/dist/ui-dom/hooks/popup.d.ts +2 -0
  15. package/dist/ui-dom/hooks/stable.d.ts +2 -6
  16. package/dist/ui-dom/index.d.ts +24 -19
  17. package/dist/ui-dom/index.js +40 -22
  18. package/dist/ui-dom/jsx-runtime.js +1 -1
  19. package/dist/ui-dom/middleware/ws.d.ts +8 -0
  20. package/dist/ui-dom/popup-tracker.d.ts +1 -1
  21. package/dist/ui-dom/testing.d.ts +3 -9
  22. package/dist/ui-dom/testing.js +7 -1
  23. package/dist/ui-dom/types.d.ts +7 -37
  24. package/dist/ui-dom/vdom3/README.d.ts +6 -3
  25. package/dist/ui-dom/vdom3/app.d.ts +20 -0
  26. package/dist/ui-dom/vdom3/audit.d.ts +13 -0
  27. package/dist/ui-dom/vdom3/build.d.ts +7 -3
  28. package/dist/ui-dom/vdom3/commands.d.ts +13 -3
  29. package/dist/ui-dom/vdom3/comp-index.d.ts +18 -0
  30. package/dist/ui-dom/vdom3/delegate.d.ts +48 -0
  31. package/dist/ui-dom/vdom3/events.d.ts +8 -2
  32. package/dist/ui-dom/vdom3/index.d.ts +5 -3
  33. package/dist/ui-dom/vdom3/jsx.d.ts +26 -4
  34. package/dist/ui-dom/vdom3/record.d.ts +3 -0
  35. package/dist/ui-dom/vdom3/render.d.ts +19 -6
  36. package/dist/ui-dom/vdom3/replay.d.ts +11 -8
  37. package/dist/ui-dom/vdom3/root.d.ts +3 -7
  38. package/dist/ui-dom/vdom3/types.d.ts +128 -70
  39. package/dist/ui-dom/vdom3/ui.d.ts +3 -30
  40. package/dist/ui-dom/vdom3.js +12 -10
  41. package/dist/ui-dom/vnode.d.ts +9 -89
  42. package/docs/compat-three-library.md +1 -1
  43. package/docs/components-map.md +1 -1
  44. package/docs/frontend-middleware.md +59 -53
  45. package/docs/frontend-ui-dom.md +115 -132
  46. package/docs/frontend.md +28 -24
  47. package/docs/realtime.md +21 -21
  48. package/package.json +1 -1
  49. package/dist/test/client/setup.d.ts +0 -12
  50. package/dist/ui-dom/context.d.ts +0 -32
  51. package/dist/ui-dom/hooks/test/hooks.test.d.ts +0 -1
  52. package/dist/ui-dom/middleware/serve.d.ts +0 -27
  53. package/dist/ui-dom/router.d.ts +0 -73
  54. package/dist/ui-dom/vdom2/audit.d.ts +0 -28
  55. package/dist/ui-dom/vdom2/build.d.ts +0 -53
  56. package/dist/ui-dom/vdom2/ctx.d.ts +0 -32
  57. package/dist/ui-dom/vdom2/dom-trace.d.ts +0 -4
  58. package/dist/ui-dom/vdom2/events.d.ts +0 -65
  59. package/dist/ui-dom/vdom2/hydrate.d.ts +0 -21
  60. package/dist/ui-dom/vdom2/index.d.ts +0 -13
  61. package/dist/ui-dom/vdom2/kind.d.ts +0 -44
  62. package/dist/ui-dom/vdom2/lifecycle.d.ts +0 -54
  63. package/dist/ui-dom/vdom2/mount.d.ts +0 -77
  64. package/dist/ui-dom/vdom2/patch.d.ts +0 -116
  65. package/dist/ui-dom/vdom2/registry.d.ts +0 -27
  66. package/dist/ui-dom/vdom2/render.d.ts +0 -21
  67. package/dist/ui-dom/vdom2/route.d.ts +0 -29
  68. package/dist/ui-dom/vdom2/ssr.d.ts +0 -32
  69. package/dist/ui-dom/vdom2/trace.d.ts +0 -62
  70. package/dist/ui-dom/vdom2/transform.d.ts +0 -76
  71. package/dist/ui-dom/vdom2/transitions.d.ts +0 -24
  72. package/dist/ui-dom/vdom2/x2html.d.ts +0 -26
  73. package/dist/ui-dom/vdom3/compat.d.ts +0 -20
package/README.md CHANGED
@@ -6,6 +6,11 @@
6
6
  npm install weifuwu # 一个依赖,完整应用栈
7
7
  ```
8
8
 
9
+ **本地开发(apps/ 下的应用)**:dev 模式经 `node_modules/weifuwu/*` 软链解析到源码
10
+ (`import 'weifuwu/ui-dom'` 等——esbuild bundle 的模块解析;tsconfig paths 只管类型)。
11
+ 新环境 `npm install` 后执行 `node scripts/dev-links.mjs` 创建软链(apps 运行前提)。
12
+ 发布时才编译 dist(`node scripts/release.mjs`——dev 零构建)。
13
+
9
14
  **定位**:面向需要完整应用栈、又不想缝合多个框架/服务、且重视代码与数据所有权的开发者——独立开发者、小团队、自托管/私有化部署。尤其当应用包含 **认证 + 实时消息 + AI 对话 + 后台管理** 时,weifuwu 把这些「每个应用都要的地基」全部内置为一行 `app.use(...)`。
10
15
 
11
16
  ### 五个关键卖点
@@ -23,7 +28,7 @@ npm install weifuwu # 一个依赖,完整应用栈
23
28
  | 层 | 入口 | 能力 |
24
29
  |----|------|------|
25
30
  | 后端 | `weifuwu` | Trie 路由 / 中间件链 / serve / 自研 PG+Redis / SSR / GraphQL / WebSocket |
26
- | 前端 | `weifuwu/ui-dom` | **UIRouter(纯路由 + ctx 注入链)+ uiServe(渲染运行时)+ SSR/hydration**——handler=异步组件 / 中间件两阶段 / ctx.params 对齐后端;**weifuwu/components 直接复用**(VNode 契约唯一来源 ui-dom,见 `docs/frontend-ui-dom.md`) |
31
+ | 前端 | `weifuwu/ui-dom` | **vdom3 精准事件流引擎**——createRouter(路由)+ createRoot(挂载)+ 事件流(渲染本体:`entity:action` 统一命名、DOM = fold、可回放可断言)+ SSR(事件流序列化);组件=两阶段异步组件 / ctx.params 对齐后端;**weifuwu/components 直接复用**(VNode 契约唯一来源 ui-dom,见 `docs/frontend-ui-dom.md`) |
27
32
  | 组件 | `weifuwu/components` | 115 个 HTML 原语组件(表单/表格/弹层/AiChat…),引用 `--wf-*` 主题变量 |
28
33
  | 样式 | `weifuwu/layout` | 66 布局原语 + 156 工具类 + 177 主题 Token,零自定义 CSS 文件 |
29
34
  | SaaS 地基 | 随包内置 | rateLimit / email / userSystem / messager / queue / ai → `ctx.*` 一行接入 |
@@ -99,7 +104,7 @@ npm install weifuwu # 一个依赖,完整应用栈
99
104
 
100
105
  **async 工厂组件** — `async (initProps, ctx) => (props) => Promise<VNode>`(weifuwu **唯一组件形态**——同步组件已不支持):工厂层声明数据(`await ctx.data.get`)、mount 初始化状态(`let` + `render()`)、render 输出视图。异步在工厂边界与 renderFn,数据经闭包注入,写数据像写同步代码。三条纪律见[核心概念 · async 组件](#核心概念)。
101
106
 
102
- **SPA/SSR/Hydration 统一透明** — 同一份路由定义(`UIRouter`)一个组件三场景自动适配:后端 `ssrPage(router, { url })` 匹配即自动 SSR(完整 HTML + `__DATA__`),客户端 `uiServe(router, { root, hydrate: true })` URL 同源匹配并收养服务端 HTML(不重建、无闪跳)。`ctx.data.get` 一个 API:SSR 预取 / hydration 命中(不重复请求)/ SPA 触发 fetch。服务端直接用 `.tsx`(`weifuwu/dev` Node loader),前后端同一 JSX 运行时。
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 运行时。
103
108
 
104
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。
105
110
 
@@ -121,16 +126,14 @@ npm install weifuwu # 一个依赖,完整应用栈
121
126
 
122
127
  | 模式 | 适用场景 | 后端 | 客户端入口 |
123
128
  |------|---------|------|-----------|
124
- | **SPA** | 应用页(Dashboard、工具、后台) | HTML 外壳 | `uiServe(router, { root: '#root' })` |
125
- | **SSR + Hydration** | 内容页(博客、营销,需要 SEO/首屏) | `ssrPage(router)` 一行 | `uiServe(router, { root: '#root', hydrate: true })` |
129
+ | **SPA** | 应用页(Dashboard、工具、后台) | HTML 外壳 | `createRouter(routes, root, { ctx })` |
130
+ | **SSR + Hydration** | 内容页(博客、营销,需要 SEO/首屏) | `renderToEvents` `eventsToHtml` + `serializeEvents` | `deserializeEvents` + `replay`(事件流收养) |
126
131
 
127
132
  ### 先写共享部分(两种模式都一样)
128
133
 
129
134
  ```tsx
130
- // routes.tsx —— 页面声明(前后端共用 UIRouter)
131
- import { UIRouter } from 'weifuwu/ui-dom'
132
-
133
- const app = new UIRouter()
135
+ // routes.tsx —— 路由声明(createRouter——vdom3 事件流引擎)
136
+ import { createRouter, h } from 'weifuwu/ui-dom'
134
137
 
135
138
  // async 组件(原生):await 数据 → 返回视图(外层初始化,内层渲染)
136
139
  const Home = async (_init, ctx) => {
@@ -138,9 +141,7 @@ const Home = async (_init, ctx) => {
138
141
  return async (props) => <h1>{msg.msg}</h1>
139
142
  }
140
143
 
141
- app.get('/', async () => <Home />) // handler = 异步组件
142
-
143
- export { app }
144
+ export const routes = [{ path: '/', render: () => h(Home, {}) }]
144
145
  ```
145
146
 
146
147
  ### 模式 A:纯 SPA
@@ -168,47 +169,51 @@ serve(router, { port: 3000 })
168
169
  ```
169
170
 
170
171
  ```ts
171
- // src/client.ts —— 纯客户端渲染
172
- import { uiServe } from 'weifuwu/ui-dom'
173
- import { app } from './routes.tsx'
172
+ // src/client.ts —— 纯客户端渲染(vdom3 事件流引擎)
173
+ import { createRouter } from 'weifuwu/ui-dom'
174
+ import { routes } from './routes.tsx'
174
175
 
175
- uiServe(app, { root: '#root' }) // 监听 location → 执行路由VDOM 落地
176
+ createRouter(routes, document.querySelector('#root')!, { ctx }) // 监听 location → 匹配事件流渲染
176
177
  ```
177
178
 
178
179
  ### 模式 B:SSR + Hydration(内容页/SEO)
179
180
 
180
- 同一份 `router`、同一个组件,差异只在**后端加 `ssrPage` 一行、客户端加 `hydrate` 参数**:
181
+ 同一份 `routes`、同一个组件,差异只在**后端构建事件流 HTML + 序列化事件,客户端回放**:
181
182
 
182
183
  ```ts
183
- // server.ts —— 完整版(与模式 A 的差异:ssrPage + 一条样式路由)
184
+ // server.ts —— SSR:vnode 事件流 → 完整 HTML + __DATA__(DOM = fold 不变量)
184
185
  import { serve, Router, ui, cors } from 'weifuwu'
185
- import { ssrPage } from 'weifuwu/ui-dom'
186
- import { app } from './routes.tsx'
186
+ import { renderToEvents, eventsToHtml, serializeEvents } from 'weifuwu/ui-dom'
187
+ import { routes } from './routes.tsx'
187
188
 
188
189
  const router = new Router()
189
190
  router.use(cors())
190
191
  router.use(ui())
191
192
 
192
- // 路由级 SSR:GET 匹配共享 router 注入 ctx.paramsawait 组件工厂
193
- // → 完整 HTML + __DATA__ + bundle/styles 引用(无需手写页面 handler)
193
+ // 路由级 SSR:匹配共享 routes构建 vnode事件流 → HTML + __DATA__ 序列化
194
194
  router.get('*', async (req, ctx) => {
195
- const { page } = await ssrPage(app, { url: req.url ?? '/' })
196
- return ctx.ui.html.unsafe(page) // page 是完整 HTML(ssrPage 已序列化 __DATA__)——unsafe 防二次转义
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)
197
201
  })
198
202
 
199
203
  router.get('/static/app.js', (req, ctx) => ctx.ui.js('./src/client.ts'))
200
- router.get('/static/style.css', (req, ctx) => ctx.ui.css('./src/style.css'))
201
204
  router.get('/api/hello', () => Response.json({ msg: 'world' }))
202
205
 
203
206
  serve(router, { port: 3000 })
204
207
  ```
205
208
 
206
209
  ```ts
207
- // src/client.ts —— 与模式 A 的唯一差异:hydrate: true(收养服务端 HTML,无闪跳)
208
- import { uiServe } from 'weifuwu/ui-dom'
209
- import { app } from './routes.tsx'
210
+ // src/client.ts —— 事件流回放收养(deserializeEvents + replay——零 DOM 猜测)
211
+ import { createRouter, deserializeEvents, replay } from 'weifuwu/ui-dom'
212
+ import { routes } from './routes.tsx'
210
213
 
211
- uiServe(app, { root: '#root', hydrate: true })
214
+ const root = document.querySelector('#root')!
215
+ replay(deserializeEvents((window as any).__DATA__), root) // 收养服务端 HTML(无闪跳)
216
+ createRouter(routes, root) // 之后正常交互渲染
212
217
  ```
213
218
 
214
219
  ### 启动(两种模式都一样)
@@ -274,7 +279,7 @@ cd apps/agent-platform && npm run seed && npm run dev
274
279
  </script>
275
280
 
276
281
  <script type="module">
277
- import { UIRouter, uiServe, h } from 'weifuwu/ui-dom'
282
+ import { createRouter, h } from 'weifuwu/ui-dom'
278
283
  import { Card, Button, Badge } from 'weifuwu/components'
279
284
 
280
285
  // 组件 = async (initProps, ctx) => (props) => Promise<VNode>(render-only:改状态后 ctx.ui.render())
@@ -299,10 +304,11 @@ cd apps/agent-platform && npm run seed && npm run dev
299
304
  )
300
305
  }
301
306
 
302
- // 路由 + 渲染:监听 location → 执行 handler VDOM 落地
303
- const app = new UIRouter()
304
- app.get('/', () => h(Counter, {}))
305
- uiServe(app, { root: '#root' })
307
+ // 路由 + 渲染(vdom3 事件流引擎:createRouter——监听 location → 匹配事件流落地)
308
+ createRouter(
309
+ [{ path: '/', render: () => h(Counter, {}) }],
310
+ document.querySelector('#root')!,
311
+ )
306
312
  </script>
307
313
  </body>
308
314
  </html>
@@ -314,7 +320,7 @@ cd apps/agent-platform && npm run seed && npm run dev
314
320
 
315
321
  | 资源 | CDN 地址 | 说明 |
316
322
  |------|---------|------|
317
- | `weifuwu/ui-dom` | `https://unpkg.com/weifuwu@latest/dist/ui-dom/index.js` | 前端运行时(UIRouter, uiServe, h, 状态管理等) |
323
+ | `weifuwu/ui-dom` | `https://unpkg.com/weifuwu@latest/dist/ui-dom/index.js` | 前端运行时(createRouter, createRoot, h, 事件流, 状态管理等) |
318
324
  | `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` | 115 个 UI 组件(Button, Card, Table, Modal, Icon 等) |
319
325
  | `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS + 177 个主题 Token + 66 个布局原语 + 156 个工具类 |
320
326
  | 独立布局系统 | `https://unpkg.com/weifuwu@latest/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
@@ -334,8 +340,8 @@ cd apps/agent-platform && npm run seed && npm run dev
334
340
  | `weifuwu` | **redis** | Redis 客户端(自研 RESP2 协议)→ `ctx.redis` | Router, REDIS_URL |
335
341
  | `weifuwu/db` | **Memory 实现** | `createMemorySql()` / `MemoryRedis`——生产契约黑盒实现(开发/测试/单实例零数据库);`MemoryRedisServer`/`MemoryPostgresServer`——进程内线协议服务器(协议测试零 docker) | — |
336
342
  | `weifuwu` | **ui** | SSR 渲染 + esbuild JS/CSS 动态编译 → `ctx.ui` | Router |
337
- | `weifuwu/ui-dom` | **uiServe** | 渲染运行时:监听 location 执行路由 VDOM 落地(`hydrate: true` 收养 SSR HTML) | UIRouter |
338
- | `weifuwu/ui-dom` | **ssrPage** | 路由级 SSR:匹配共享 router 自动完整 HTML + `__DATA__` | Router, ui |
343
+ | `weifuwu/ui-dom` | **createRouter** | vdom3 路由:RouteDef[] + 中间件面 ctx 注入(对齐后端 `app.use`)——监听 location 匹配 → 事件流渲染 | |
344
+ | `weifuwu/ui-dom` | **SSR(事件流形态)** | `renderToEvents` `eventsToHtml` + `serializeEvents`(DOM = fold——客户端 `replay` 收养) | Router, ui |
339
345
  | `weifuwu` | **rateLimit** | 限流中间件(fixed/sliding,redis 多实例原子)→ `ctx.limit` | Router, redis |
340
346
  | `weifuwu` | **email** | 邮件发送(Resend/SMTP 自研/自定义适配器)→ `ctx.email` | Router |
341
347
  | `weifuwu` | **userSystem** | 用户系统(scrypt 密码哈希 + 混合会话 + 多租户感知)→ `ctx.user` / `ctx.auth` / `ctx.tenantId` + `/api/auth/*` | Router, postgres |
@@ -349,14 +355,13 @@ cd apps/agent-platform && npm run seed && npm run dev
349
355
  | `weifuwu` | **ok / badRequest / …** | HTTP 响应辅助函数(ok/badRequest/... 等 12 个) | — |
350
356
  | `weifuwu` | **parseBody** | JSON 请求体安全解析 | — |
351
357
  | Router 方法 | **app.graphql()** | GraphQL 端点(支持 GraphiQL),Router 实例方法(无需单独 import) | Router |
352
- | `weifuwu/ui-dom` | **UIRouter** | 纯路由 + ctx 注入链(`use` 中间件累积类型,对齐后端 `app.use`);handler=异步组件 | — |
353
- | `weifuwu/ui-dom` | **uiServe** | 渲染运行时:监听 location 执行路由 VDOM diff/patch;`hydrate: true` 收养 SSR HTML | UIRouter |
354
- | `weifuwu/ui-dom/vdom3` | **vdom3 引擎**(转正中) | 下一代渲染(vnode + stream——事件流可回放/可逆);`createRouter` + `createRoot` | vdom3 |
358
+ | `weifuwu/ui-dom` | **createRouter / createRoot** | vdom3 事件流引擎:路由(RouteDef[] + ctx 注入)/ 挂载(组件树)——渲染全链路 `entity:action` 事件流(DOM = fold——可记录/回放/断言);**事件代理**(监听 O(1)——零重绑——统一注册表);**app 节点**(多应用加载——registerApp) | — |
359
+ | `weifuwu/ui-dom` | **事件流原语** | `stream` / `ev` / `evKey` / `replay` / `eventsOf` / `expectEventSequence`——渲染可观测/可断言 | |
355
360
  | `weifuwu/ui-dom` | **async 组件** | async 函数即组件(与同步同签名);数据走 ctx.data 三场景(三条纪律见[核心概念](#核心概念)) | — |
356
- | `weifuwu/ui-dom` | **ctx.data** | 数据管道:SSR 预取 / hydration 命中 / SPA fetch(`ctx.data.get`) | uiServe |
361
+ | `weifuwu/ui-dom` | **ctx.data** | 数据管道:SSR 预取 / hydration 命中 / SPA fetch(`ctx.data.get`) | |
357
362
  | `weifuwu/ui-dom` | **api / auth / ws** | HTTP 客户端 / 认证 / WebSocket 中间件 | — |
358
363
  | `weifuwu/ui-dom` | **i18n** | 国际化中间件(运行时切换语言) | — |
359
- | `weifuwu/ui-dom` | **ssrPage / serializeData** | 服务端渲染:SSR HTML + `__DATA__` 序列化(`ctx.params` 两端同源) | — |
364
+ | `weifuwu/ui-dom` | **renderToEvents / eventsToHtml / serializeEvents** | 服务端渲染:vnode → 事件流 → HTML + `__DATA__` 序列化(客户端 `replay` 收养) | — |
360
365
  | `weifuwu/ui-dom` | **useChat / AiChat 原语** | AI 会话(流式/工具调用/HITL) | — |
361
366
  | `weifuwu/ui-dom` | **事件原语** | `usePopup`(统一弹窗能力层)/ `usePresence` / `useInView` / `useScrollPosition` / `useGlobalKey` / `useDrag` / `useDragDrop` / `useAnimationEnd` / `useTween` / `useReducedMotion`(浏览器事件/动画统一入口,见 [docs/mobile.md](docs/mobile.md)) | — |
362
367
  | `weifuwu/components` | **113 个组件** | Button/Table/Modal/Confirm/Toast/... + `confirm()` / `toast()` 命令式中间件 | weifuwu/ui-dom |
@@ -371,7 +376,7 @@ cd apps/agent-platform && npm run seed && npm run dev
371
376
  | 任务 | 用 | 位置 |
372
377
  |------|-----|------|
373
378
  | 起 HTTP 服务 + 路由 | `serve(app)` + `new Router()` + `app.get/post/...` | [docs/server.md](docs/server.md) |
374
- | 渲染页面(SPA / SSR+hydrate) | `ui()` + `ssrPage(router)`;`uiServe(router, { root, hydrate })` | [docs/frontend-ui-dom.md](docs/frontend-ui-dom.md) · [docs/frontend.md](docs/frontend.md) |
379
+ | 渲染页面(SPA / SSR) | `createRouter(routes, root, { ctx })`;SSR = `renderToEvents` → `eventsToHtml` + `replay`(事件流形态) | [docs/frontend-ui-dom.md](docs/frontend-ui-dom.md) · [docs/frontend.md](docs/frontend.md) |
375
380
  | 数据持久化 | `postgres()` → `` ctx.sql`SELECT *` `` · `redis()` → `ctx.redis` · **`sql.query`**(Query Language AST 双后端) | [docs/data.md](docs/data.md) |
376
381
  | 零数据库开发/测试 | `createMemorySql()` / `MemoryRedis`——契约同真库、替换成本为零 | [docs/data.md](docs/data.md) |
377
382
  | 数据管道(SSR 预取/hydration/SPA) | `ctx.data.get(key)` + async 组件 | [docs/frontend.md](docs/frontend.md) |
@@ -399,7 +404,7 @@ cd apps/agent-platform && npm run seed && npm run dev
399
404
  | **UIHandler**(路由) | `async (location, ctx) => VNode` | ✅ 整体 | 每次路由变化执行 |
400
405
  | **Component**(唯一形态) | `async (initProps, ctx) => (props) => Promise<VNode>` | ✅ 工厂 + renderFn | mount 一次 + render 每次;同步组件已不支持(类型强制 Promise) |
401
406
 
402
- 异步只在两个边界——路由 handler(整页)和组件工厂(数据声明)+ renderFn(强制异步)。渲染器按「返回值 instanceof Promise」统一判别:主路径 `buildVNode` async 预构建(await 全部工厂,兄弟并行)→ 原子落地(无**中间态**占位、无补全回调——注意:数组内 false/null 的静态诊断占位 `<!--wf-hole-->` 是另一回事,见「VDOM 输出透明」);运行时首次挂载的 async 组件同样在 buildVNode 阶段 await;骨架屏 `uiServe({ loading })` + `handle.ready`。
407
+ 异步只在两个边界——路由 handler(整页)和组件工厂(数据声明)+ renderFn(强制异步)。渲染器按「返回值 instanceof Promise」统一判别:主路径 `buildVNode` async 预构建(await 全部工厂,兄弟并行)→ 原子落地(无**中间态**占位、无补全回调——注意:数组内 false/null 的静态诊断占位 `<!--wf-hole-->` 是另一回事,见「VDOM 输出透明」);运行时首次挂载的 async 组件同样在 buildVNode 阶段 await;骨架屏:预置骨架屏 HTML 首帧原子替换 + `handle.ready`。
403
408
  ### 两阶段组件(新手必读:为什么是两层)
404
409
 
405
410
  组件 = `async (initProps, ctx) => (props) => Promise<VNode>`——**外层 = 初始化(只执行一次,可 await 数据),内层 = 渲染(每次状态/props 变化时执行,强制异步)**。类比:外层是对象的构造函数,内层是它的 render 方法。
@@ -425,15 +430,15 @@ const Counter = async (_init, ctx) => {
425
430
  app.get('/users', (req, ctx) => { ctx.sql`SELECT *` })
426
431
  // ctx 已注入 ctx.sql
427
432
 
428
- 前端: const router = new UIRouter()
433
+ 前端: const router = createRouter(routes, root, { ctx })
429
434
  router.use(api({ baseURL: '/api' }))
430
435
  router.use(auth())
431
436
  router.get('/users', async (location, ctx) => h(UsersPage, {}))
432
- uiServe(router, { root: '#root' })
437
+ // createRouter = 监听 location → 匹配 → 事件流渲染落地
433
438
  // ctx 已注入 ctx.api, ctx.auth
434
439
  ```
435
440
 
436
- 前端 req = `window.location`(原生对象,不包装),res = `VNode`,`uiServe` = VDOM 落地——与后端 `Request → Response`、`serve(router)` 完全同构。
441
+ 前端 req = `window.location`,res = `VNode`,`createRouter` = 事件流渲染落地——与后端 `Request → Response`、`serve(router)` 完全同构。
437
442
 
438
443
  ### 状态管理
439
444
 
@@ -478,10 +483,10 @@ const UserProfile = async (_init, ctx) => {
478
483
  | | SPA | SSR + Hydration |
479
484
  |---|---|---|
480
485
  | 适用 | 应用页(后台、工具、Dashboard) | 内容页(博客、营销,需要 SEO/首屏) |
481
- | 后端 | HTML 外壳 | `ssrPage(router, { url })` 一行(自动完整 HTML + `__DATA__`) |
482
- | 客户端 | `uiServe(router, { root: '#root' })` | `uiServe(router, { root: '#root', hydrate: true })` |
486
+ | 后端 | HTML 外壳 | `renderToEvents` `eventsToHtml` + `serializeEvents`(完整 HTML + `__DATA__`) |
487
+ | 客户端 | `createRouter(routes, root, { ctx })` | `deserializeEvents` + `replay`(事件流收养——DOM = fold) |
483
488
 
484
- **怎么选**:默认 SPA;需要 SEO 或首屏即内容时用 SSR。两种模式可混合——一个 app `ssrPage` 匹配共享 router,未匹配 `next()` 走普通 handler。
489
+ **怎么选**:默认 SPA;需要 SEO 或首屏即内容时用 SSR。两种模式可混合——SSR 走共享 routes,未匹配走普通 handler。
485
490
 
486
491
  ### Closeable 接口
487
492
 
@@ -510,8 +515,8 @@ README 只保留入门内容(设计理念 / 快速开始 / 核心概念 / 模
510
515
 
511
516
  | 文档 | 内容 |
512
517
  |------|------|
513
- | [docs/frontend.md](docs/frontend.md) | 前端核心:应用引导(UIRouter+uiServe)/ 组件模型 / 异步组件 / 状态管理 / 条件与列表 / ref / 类型(weifuwu/client 已并入 ui-dom) |
514
- | [docs/frontend-ui-dom.md](docs/frontend-ui-dom.md) | **ui-dom**:UIRouter 纯路由 + uiServe 渲染运行时 + ctx 注入链 + components 复用 + SSR/hydration(前端唯一运行时——weifuwu/client 已删除) |
518
+ | [docs/frontend.md](docs/frontend.md) | 前端核心:应用引导(createRouter/createRoot——vdom3 事件流)/ 组件模型 / 异步组件 / 状态管理 / 条件与列表 / ref / 类型(weifuwu/client 已并入 ui-dom) |
519
+ | [docs/frontend-ui-dom.md](docs/frontend-ui-dom.md) | **ui-dom**:vdom3 精准事件流引擎(createRouter/createRoot + 事件流)——components 复用 + SSR(事件流形态)(前端唯一运行时) |
515
520
  | [docs/frontend-middleware.md](docs/frontend-middleware.md) | 前端中间件:router / api / auth / ws / i18n / ErrorBoundary / confirm / toast / ScrollLock / extendCtx |
516
521
  | [docs/components.md](docs/components.md) | 组件库(113 个组件 + 使用示例 + 组件列表) |
517
522
  | [docs/layout.md](docs/layout.md) | 布局系统:66 个布局原语 + 156 个工具类 + 177 个主题 Token |
@@ -15,7 +15,7 @@
15
15
  * 使用两阶段模型 + render-only。状态:内部 keyword(useControlledInput Map 缓存)。
16
16
  */
17
17
  import type { Component, VNode } from '../../ui-dom/vnode.ts';
18
- import type { HookEnv } from '../../ui-dom/hooks/types.ts';
18
+ import type { V3Ui } from '../../ui-dom/vdom3/types.ts';
19
19
  export interface ChatInputLabels {
20
20
  send: string;
21
21
  stop: string;
@@ -57,5 +57,5 @@ export interface ChatInputControl {
57
57
  setValue: (v: string) => void;
58
58
  }
59
59
  export declare const ChatInput: Component<ChatInputProps, {
60
- ui: HookEnv;
60
+ ui: V3Ui;
61
61
  }>;
@@ -5,7 +5,6 @@
5
5
  * 命令式:confirm() 中间件注入 ctx.confirm(),返回 Promise<boolean>。
6
6
  */
7
7
  import type { Component } from '../../ui-dom/vnode.ts';
8
- import type { AppMiddleware } from '../../ui-dom/types.ts';
9
8
  export interface ConfirmProps {
10
9
  open?: boolean;
11
10
  title?: string;
@@ -20,6 +19,8 @@ export interface ConfirmProps {
20
19
  maskClosable?: boolean;
21
20
  onConfirm?: () => void;
22
21
  onCancel?: () => void;
22
+ /** Modal 关闭回调(Escape/遮罩——onCancel 缺省时兜底——命令式兼容) */
23
+ onClose?: () => void;
23
24
  }
24
25
  /** 命令式 ctx.confirm 的选项(ConfirmProps 的子集) */
25
26
  export interface ConfirmOptions {
@@ -40,5 +41,4 @@ export interface ConfirmInjected {
40
41
  * 每次调用挂载 Confirm 组件到独立容器(mountVNode),
41
42
  * resolve 后 callRefCleanup 清理(含 Modal 的 portal DOM)+ 移除容器。
42
43
  */
43
- export declare function confirm(): AppMiddleware<{}, ConfirmInjected>;
44
44
  export declare const Confirm: Component<ConfirmProps>;
@@ -1,5 +1,4 @@
1
1
  import type { Component } from '../../ui-dom/vnode.ts';
2
- import type { AppMiddleware } from '../../ui-dom/types.ts';
3
2
  export type NotificationType = 'success' | 'error' | 'info' | 'warning';
4
3
  export type NotificationPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
5
4
  export interface NotificationItem {
@@ -67,4 +66,3 @@ export interface NotificationInjected {
67
66
  }) => void;
68
67
  };
69
68
  }
70
- export declare function notification(opts?: NotificationOptions): AppMiddleware<{}, NotificationInjected>;
@@ -1,5 +1,4 @@
1
1
  import type { Component } from '../../ui-dom/vnode.ts';
2
- import type { AppMiddleware } from '../../ui-dom/types.ts';
3
2
  export type ToastType = 'success' | 'error' | 'info' | 'warning';
4
3
  export type ToastPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center';
5
4
  export interface ToastItem {
@@ -40,4 +39,3 @@ export interface ToastInjected {
40
39
  onClick: () => void;
41
40
  }) => void;
42
41
  }
43
- export declare function toast(opts?: ToastOptions): AppMiddleware<{}, ToastInjected>;
@@ -25,9 +25,9 @@ export { Table } from './Table/Table.ts';
25
25
  export type { TableProps, TableColumn } from './Table/Table.ts';
26
26
  export { Modal } from './Modal/Modal.ts';
27
27
  export type { ModalProps } from './Modal/Modal.ts';
28
- export { Confirm, confirm } from './Confirm/Confirm.ts';
28
+ export { Confirm } from './Confirm/Confirm.ts';
29
29
  export type { ConfirmProps, ConfirmOptions } from './Confirm/Confirm.ts';
30
- export { Toast, toast } from './Toast/Toast.ts';
30
+ export { Toast } from './Toast/Toast.ts';
31
31
  export type { ToastProps, ToastItem, ToastType, ToastPosition, ToastOptions, ToastInjected } from './Toast/Toast.ts';
32
32
  export { Alert } from './Alert/Alert.ts';
33
33
  export type { AlertProps, AlertVariant } from './Alert/Alert.ts';
@@ -157,7 +157,7 @@ export { Affix } from './Affix/Affix.ts';
157
157
  export type { AffixProps } from './Affix/Affix.ts';
158
158
  export { HoverCard } from './HoverCard/HoverCard.ts';
159
159
  export type { HoverCardProps, HoverCardPosition } from './HoverCard/HoverCard.ts';
160
- export { Notification, notification } from './Notification/Notification.ts';
160
+ export { Notification } from './Notification/Notification.ts';
161
161
  export type { NotificationProps, NotificationItem, NotificationType, NotificationPosition, NotificationOptions, NotificationInjected } from './Notification/Notification.ts';
162
162
  export { ContextMenu } from './ContextMenu/ContextMenu.ts';
163
163
  export type { ContextMenuProps, ContextMenuItem } from './ContextMenu/ContextMenu.ts';