你的 AI 寫應用程式, Hozu 負責檢查。
說出你想要的 app,AI 幫你做出來;AI 每改一次,Hozu 就先檢查一次,不讓東西在你沒注意時壞掉。
↓ 按下「AI 修改」,看 AI 改這個 app,Hozu 同時檢查它的成果。
買牛奶
✔ types ok · 0 errors · lock current

影片 · 2 分鐘
兩分鐘,不用寫程式。
你負責想像,AI 負責打造,而 Peg(那根鎖住榫接的紅色木釘)會在每次修改送到任何人面前之前先檢查。給 vibe coder 與設計師。
英文字幕 · 語音由 elevenlabs.io 生成
帳單
你的 AI 從沒看過 Hozu。等它熟悉之後,成本和 Nuxt 差不多。
你付出的
你得到的
每個模型都早已熟悉 Nuxt;Hozu 則要在每個 session 中從我們的指南學起。即便如此,一次修改花費的是 Nuxt 的 1.32–1.42× token;熟悉指南之後則是 1.02–1.06×:差不多。剩下的差距來自學習,而不是框架本身。
換來的是:在 29 次修改中,Hozu 通過了每一步的所有檢查。Nuxt 則在 3/29 次修改中默默弄壞了原本正常的匯出功能,而它的型別檢查與建置仍然全綠。你的 agent 會先執行檢查器、修好發現的問題,才告訴你「完成了」。
以上是實測 0024 的數據:Hozu 0.14 對 Nuxt 4.5.2,同一個應用程式、同一個模型(Claude Opus)。之後的版本尚未用這種方式測量。更早的版本成本更高:Hozu 0.7 為 2.64–2.73×、0.8 為 1.34–1.72×。
速度
經過檢查,依然是最輕的頁面。
同一個頁面(100 件商品加一個購物車計數器),分別用各個框架打造,並在各自的正式環境伺服器上執行。每次請求都重新渲染。只有會回應點擊的部分才會送出 JavaScript。
同一個頁面、四個框架,每次請求都渲染
Hozu 0.25.0
adapter-node
- 請求/秒
- 15,818
- JS(gzip)
- 9.7 KB
- 可互動
- 60 ms
SvelteKit 3.0.0
Svelte 5.57.1, adapter-node 6.0.0
- 請求/秒
- 6,581
- JS(gzip)
- 33.0 KB
- 可互動
- 101 ms
Nuxt 4.5.2
Vue 3.5.43, Nitro 2.13.4
- 請求/秒
- 2,953
- JS(gzip)
- 75.8 KB
- 可互動
- 91 ms
Next.js 16.3.8
React 19.3.0, App Router
- 請求/秒
- 1,616
- JS(gzip)
- 130.9 KB
- 可互動
- 164 ms
| 框架 | 每秒請求數 | JavaScript(gzip) | 可互動時間 |
|---|---|---|---|
| Hozu 0.25.0adapter-node | 15,818 | 9.7 KB | 60 ms |
| SvelteKit 3.0.0Svelte 5.57.1, adapter-node 6.0.0 | 6,581 | 33.0 KB | 101 ms |
| Nuxt 4.5.2Vue 3.5.43, Nitro 2.13.4 | 2,953 | 75.8 KB | 91 ms |
| Next.js 16.3.8React 19.3.0, App Router | 1,616 | 130.9 KB | 164 ms |
於 2026-10-08 以 Hozu 0.25.0 測量:在 Apple M4 Pro、Node 22.22.2、Chrome 154 上執行一次;瀏覽器計時時將 CPU 降速四倍。Next.js 以預先渲染的方式提供頁面時,可達每秒 6,664 次請求。接受 gzip 時,Hozu 為 11,454 次、Next.js 為 1,502 次;Nuxt 與 SvelteKit 傳送未壓縮的頁面。JavaScript 大小皆以相同方式 gzip 計算。測量方式 · 只比較函式庫本身(React、Vue、Preact、Svelte)
工作方式
三個步驟,打字交給你的 agent。
1 · 建立
一個指令建立應用程式,並把 Hozu 指南放在旁邊。
2 · 告訴你的 agent
「幫我的筆記加上分享功能。」或用 Hozu DevTools 直接指著畫面:它會把檔案與行號交給你的 agent。
3 · 它自己檢查
它會執行 hozu check、修好發現的問題,再告訴你完成了。
npm create hozu@latest my-app -- --agent claude
cd my-app
npm install
npx hozu add feature tasks --page /tasks
npx hozu check由 agent 檢查
你的 agent 會檢查自己的成果,在真正的瀏覽器裡。
hozu check 會在任何東西執行之前讀過整個程式。接著 hozu browse 在 Chrome 裡像真人一樣操作頁面,並回報每一步改變了什麼。你的 agent 會在告訴你完成之前執行這兩者,你也可以自己執行。
hozu check
型別、每條規則及其建議修正、每個決策的 contract,以及記錄每個部分行為的 lock。
hozu browse
填寫、點擊、送出、在頁面間移動;JavaScript 可開、可關或兩者都測,還能同時扮演 Ada 與 Bob。
hold,然後 release
讓一次儲存保持等待,讀取忙碌中的頁面。每一步都會回報重新載入、閃爍與版面位移。
在某次實測中,一張商店卡片看起來沒問題,連結卻點不到:標題的 ::after 蓋在它上面。從 0.25 起,hozu browse 會讓這次點擊失敗,並指出蓋在上面的是什麼。0.25 的變更
誰能看到
你的筆記只屬於你,Hozu 會檢查。
每個涉及訪客資料的 query 與修改,都要宣告誰可以執行。忘了宣告,應用程式就無法通過型別檢查。Hozu 會在你的程式碼執行前拒絕其他所有人,並檢查清單裡只有該訪客自己的資料列。
access: 'signedIn'
或是資料列的擁有者,或像管理員角色這樣的條件。在 query 旁邊寫一行即可。
Forbidden
其他人都會被拒絕,頁面回應 403 或請他們先登入。
以 Bob 的身分試試
一個 hozu browse 指令就能以 Ada 與 Bob 登入,並以 Bob 的身分開啟 Ada 的頁面。
資料
你的 API,從該呼叫的地方呼叫。
每個 query 與 mutation 宣告它需要什麼,由 Hozu 決定它在哪裡執行。公開 API 先在伺服器上渲染,之後直接從瀏覽器呼叫:不多繞一手,也沒有雙倍流量。存在瀏覽器裡的 token 永遠不會傳到你的伺服器,而沒有伺服器的應用程式可以匯出到 GitHub Pages。
runs: 'server'
資料庫、機密或 session。resolver 照舊寫在 app.ts。
runs: 'either'
公開 API,或你自己開了 CORS 的 API。首次繪製時就在 HTML 裡,之後由瀏覽器呼叫。
runs: 'browser'
訪客自己的 token。伺服器上呈現載入狀態,API 呼叫在瀏覽器中進行。
後端
你的後端可以是 Go。
resolver 只是具型別宣告背後的一般後端程式碼,所以不一定要用 TypeScript。在 remote() 中列出 effect,執行 hozu gen,再實作它產生的 Go interface。誰可以呼叫、快取、tag,以及依 output schema 檢查每個回應,這些都留在 Hozu 伺服器中,因此回應錯誤的服務會直接被拒絕(fail closed)。
remote()
在 app.ts 中寫一個項目,列出服務、它的 secret 與它實作的 effect。其餘部分仍是 TypeScript。
hozu gen
依你的宣告產生 Go contract。改了宣告卻忘了重新產生時,hozu check 會指出哪些 effect 已經過期。
由 agent 實測
一個 agent 把商店後台的訂單流程、庫存與儀表板搬到了 Go。筆記應用程式的每個 resolver 都改用 Go 後,仍通過了隱藏的瀏覽器檢查。
當 resolver 確實有吃重的工作時,Go 才划算;若只是薄薄一層,多繞一手的成本比省下的還多。TypeScript 仍是預設。
DevTools
指著它,你的 agent 就拿到那一行。
執行 npm run dev,選擇 Select,點擊有問題的地方。說明該怎麼改,直接在頁面上試試尺寸、顏色或其他文字,然後交出去。
這份請求會指出檔案、行號,以及用 Hozu 的方式該怎麼改:只改這一個按鈕還是全部按鈕、兩處共用的訊息、需要 contract 的狀態。你的 agent 不用再找,直接開始修。
它也會回頭指給你看:每個改過的部分都會在你的頁面上加上編號框,並附上用你的話寫的說明。
給設計師
用起來就像 Figma。
DevTools 沿用設計師已經熟悉的快捷鍵、測量方式與用語:Shift+Enter 往上一層,Alt 測量,Design 面板讀起來就像 Figma 的,而且你的 token 排在最前面。
Assets 在同一頁呈現每個元件與 variant,以及你在 previews.ts 中命名的畫面。在頁面上試著修改,然後交給你的 agent。程式碼由它寫,你完全不用打開檔案。
Shift+Enter · Enter · Tab
選取外層、內層與相鄰的元素。
Alt
按住並指向:顯示 px 距離。
W × H
每個選取都會顯示尺寸。
Design 面板
Frame、Auto layout、Layer、Fill、Stroke、Effects、Text。
Variables
token 優先:red · #fb3a0e、2xl · 24px。
Comments
你的 agent 留下的說明是編號圖釘:可以回覆或標為已解決。
它抓得到什麼
看起來沒問題,實際上會壞的錯誤。
這些錯誤都能通過型別檢查與建置。Hozu 還是會擋下來,並告訴你該怎麼做。點擊、滑過或用 Tab 移到卡片上,就能看到真正的診斷訊息。
✘ HZ049
你的筆記,出現在別人的螢幕上。
✘ HZ049 cached-user-data
A user-scoped query is cached across requests.
fix: freshness: 'request'
✘ HZ054
你勾了三個方塊,應用程式只看到一個。
✘ HZ054 single-value-form-read
A multi-value field is read with ui.dom.form.
fix: ui.dom.formAll(name)
✘ HZ091
Ada 的清單裡出現了 Bob 的筆記。
✘ HZ091 rows-outside-owner
A query with owner access returned rows the visitor does not own.
fix: read only the visitor’s rows in the resolver
✘ HZ057
行為變了,卻沒有人審查。
✘ HZ057 lock-out-of-date
The lock differs from the computed lock.
fix: hozu check --update-lock, then read each now: line
元件
宣告式 UI,逐一檢查每個 class。
按鈕是 kit 裡的一個宣告,而不是會消失的 helper。選一個 variant:預覽、它的原始碼,以及 hozu render 印出的內容,全都來自同一個元件。本站也是用同一套 kit 打造的。
import { ui } from '@hozu/core'
import { z } from 'zod'
import { tv } from './tv.ts'
const styles = tv({
base: 'inline-block border-4 px-4 py-2.5 text-sm font-extrabold uppercase tracking-wide transition-colors duration-150',
variants: {
intent: {
solid: 'border-ink bg-ink text-paper hover:bg-paper hover:text-ink',
outline: 'border-ink text-ink hover:bg-ink hover:text-paper',
light: 'border-paper bg-paper text-ink hover:bg-transparent hover:text-paper',
lightOutline: 'border-paper text-paper hover:bg-paper hover:text-ink',
},
},
defaultVariants: { intent: 'solid' },
})
export const Button = ui.component({
tag: 'a',
styles,
props: z.object({ href: z.string() }),
children: true,
render: ({ props, children }) => ui.a({ href: props.href, 'data-button': '' }, children),
})
hozu render site.Button --variant intent=solid
{
"html": "<a class=\"inline-block border-4 px-4 py-2.5 text-sm font-extrabold uppercase tracking-wide transition-colors duration-150 border-ink bg-ink text-paper hover:bg-paper hover:text-ink\" href=\"#\" data-button></a>",
"class": "inline-block border-4 px-4 py-2.5 text-sm font-extrabold uppercase tracking-wide transition-colors duration-150 border-ink bg-ink text-paper hover:bg-paper hover:text-ink",
"owned": [
"background-color",
"border-color",
"border-style",
"border-width",
"color",
"display",
"font-size",
"font-weight",
"letter-spacing",
"line-height",
"padding-block",
"padding-inline",
"text-transform",
"transition-duration",
"transition-property",
"transition-timing-function"
]
}同一個元素上有兩個 class 設定同一個屬性時,會回報為 HZ079,所以覆寫永遠不會意外生效。閱讀元件說明
頂端的 3D 榫接也是一個元件。
由 Blender 腳本建立模型,three.js 在 client component 中渲染,而執行示範的同一個 machine 會告訴它何時分開。沒有 JavaScript 時,它是一張靜態圖片。
import { ui } from '@hozu/core'
import { z } from 'zod'
import { tv } from './tv.ts'
const poster = ui.asset(new URL('../assets/joint-poster.webp', import.meta.url))
const model = ui.asset(new URL('../assets/joint.glb', import.meta.url))
export const Joint = ui.component({
tag: 'div',
styles: tv({ base: 'relative aspect-square w-full max-w-[34rem] select-none' }),
props: z.object({ split: z.boolean() }),
client: new URL('./joint.client.ts', import.meta.url),
load: 'visible',
render: () =>
ui.div({}, [
ui.img({
src: poster,
'data-model': model,
width: 900,
height: 900,
alt: 'A tenon joint: two posts, a beam with two tenons and two red pegs',
class: 'h-full w-full',
}),
]),
})
// site/joint.client.ts
export default implement<typeof Joint>(({ el, props, signal }) => {
// … GLTFLoader, toon materials, ink hulls, idle turn, drag …
return {
update(next) {
const changed = next.split !== split
split = next.split
if (changed) pose(split, !reduce)
},
destroy() { … },
}
})底層原理
feature() → IR → validator → compiler → runtime
每個頁面都依其資料的宣告來規劃:誰能看到、需要多新。只有綁定 machine 的節點會送出 JavaScript;這個頁面上其餘的一切都是純 HTML。
實測
實際測量,連不完美的地方也一併呈現。
實測 0024:同一個筆記應用程式被建立並修改 28 次,分別是從指南學習 Hozu、已經熟悉 Hozu,以及使用 Nuxt。最後八次修改由一個從未看過 Hozu 的 session 撰寫。保留驗證的結果:學習期間 Nuxt 的 1.32–1.42× token,熟悉之後 1.02–1.06×(各跑兩次)。
| Hozu 0.14 | Nuxt 4.5.2 | |
|---|---|---|
| 所有檢查都通過的修改 | 29 | 26 |
| 靜默失敗(建置通過,功能卻壞了) | 0 | 3 |
| 每次全新修改的 token,相對 Nuxt | 1.32–1.42× | 基準 |
| 同上,熟悉 Hozu 之後 | 1.02–1.06× | 基準 |
在那之前是實測 0021(Hozu 0.8)。成本:每次修改為 Nuxt 的 1.34–1.72× token(第 13–20 步,每個框架跑一次),對比 Hozu 0.7 為 2.64–2.73×。結果:16 次修改中有 0 次迴歸、0 次靜默失敗;對比 Hozu 0.7 的 8 次迴歸失敗,以及 5 個步驟出現靜默失敗。未達成:成本比例在整個過程中仍略微上升。
| Hozu 0.7 | Hozu 0.8 | |
|---|---|---|
| 迴歸失敗 | 8 | 0 |
| 出現靜默失敗的步驟 | 5 | 0 |
| 相對 Nuxt 的成本(幾何平均) | 2.64–2.73× | 1.34–1.72× |
原始紀錄;其中 Nuxt 第 14 步少算了,報告中說明了修正方式。閱讀實測 0021 · 閱讀實測 0024 · 所有實測