用 AI 编程助手构建应用
安装 Viva skill,让 AI 编程助手遵守框架的规则,并在不打开窗口的情况下检查界面。
Viva 自己绘制界面,只在被请求时重绘,也没有布局缓存,所以按网页或保留式工具包的写法来写,产出的代码会被评审拒绝。building-with-viva skill 是一份简短的说明,Claude Code、Codex 这类 AI 编程助手在改动 Viva 应用之前加载它。它只是一个 Markdown 文件,没有需要运行的代码。
skill 涵盖什么
- 如何判断一个需求属于应用行为,还是应当放进 Viva 而不是应用里的可复用桌面能力。
- 评审会拒绝的规则:绘制路径中不用反射或属性包,空闲时不做任何工作,每次绘制不分配内存,不手工给命中目标编号,不做未经测量的性能声明。
- 一个界面如何编写:
ui.Tree中的标识,在声明控件的地方处理点击,用flex布局。 - 如何在不打开窗口的情况下验证改动,顺序见无需窗口的测试,以及对没能检查的部分该如何如实说明。
- 指南、示例和测量数据在哪里。
它指向各篇指南而不重复其内容,所以足够短,可以整篇读完,也不会因为指南变化而过时。完整内容就在本页下方。
安装
把这个文件下载到项目的 skills 目录。Claude Code 读取 .claude/skills;遵循同一约定的其他 AI 编程助手读取 .agents/skills。
mkdir -p .claude/skills/building-with-viva
curl -fsSL https://vivaui.dev/skills/building-with-viva/SKILL.md \
-o .claude/skills/building-with-viva/SKILL.md
在 Viva 自己的仓库副本中,请改为复制或软链 docs/skills/building-with-viva。在那里,仓库的 AGENTS.md 优先于这个 skill。
如果你的 AI 编程助手不加载 skill,就把本页下方的完整内容复制进它读取的指令文件,例如 AGENTS.md 或 CLAUDE.md,或者直接让它阅读这个页面。
保持最新
网站提供的就是仓库 docs/skills 中的那个文件,每次构建站点时都会重新生成,所以下载到的内容、本页展示的内容与旁边的指南始终一致。升级 Viva 之后再运行一次同样的命令,并把文件随项目一起提交,这样参与项目的人和 AI 助手读到的是同一份说明。想了解这些规则背后的原因,请阅读编程模型指南。
完整的 skill 内容
与安装的文件内容相同(英文原文),所以不加载 skill 的代理可以直接在这里阅读,也可以把它粘贴进自己的指令文件,例如 AGENTS.md 或 CLAUDE.md。
Viva is a Go-first, GPU-native desktop framework. It draws its own UI: there is no WebView, HTML, CSS or JavaScript, and nothing is redrawn while the app is idle. Most mistakes come from writing it like a web or retained-mode toolkit. Read this before writing code, and read the guides it points to before guessing an API.
1. Decide whose code it is
Before building, sort each need into one of two kinds.
- Application behavior: your protocols, storage, workflows, product screens. It stays in your app.
- A reusable desktop capability: windows, input, text and IME, focus, layout, components, rendering, accessibility, lifecycle, headless control. If a second Viva app would copy the same helper or wiring, it belongs in Viva, not in your app. Stop and design the smallest self-contained API for it instead of working around the gap with platform calls, synthetic input or hand-written hit testing.
If you are in a checkout of Viva itself, its AGENTS.md is the authority and overrides this file.
2. Rules that review rejects
- No reflection,
map[string]anyor dynamic property bags in layout, input, scene or render paths. - No goroutines, timers or polling loops that run while the app is idle. Animation is a scheduled, temporary reason to draw, never a heartbeat. An idle window draws nothing.
- Declaring components and layout must not allocate per paint. State that is expensive to compute (shaped text, line breaks) is cached by whoever owns it and invalidated explicitly.
- Never number hit targets by hand. Identities are names in a
ui.Tree. - Do not add a dependency to the code that ships in the app without measuring its stripped binary size, startup and idle memory.
- Do not claim “zero allocation”, “fast” or “light” without a reproducible measurement.
3. How a screen is written
The painting function is the UI. It runs again on every redraw, declares what is on screen with value types, and keeps no layout cache. The smallest complete program is hello/main.go; the others in examples/guides show one concept each (layout, input, text field, overlays, windows, theming, drawing, background).
- Declare identities once:
tree = ui.Tree{}, atree.Feature("name")per area,tree.ID(feature, "control")per control. Keep them stable between frames; focus, hover and press are tracked by ID, andCanvas.Focused,Canvas.FocusVisibleandCanvas.Hoveredreport them back. - Handle a click where the control is declared:
f.Button(id, label).Clicked()is true for the one paint that delivers the click, and only then may state change. - Lay out with
flexinsideflex.View.Draw. Components are source-distributed, typed Go, one file per component; read the file rather than looking for a hidden style engine. - Prefer a few intention-revealing parameters. When same-typed positional arguments pile up, use a small struct or chainable options.
4. Verify without opening a window
Do not open a real window unless the user agrees; it interrupts them and depends on which display it lands on. Use these in order of cost.
app.Driverin a Go test: runs your real painter, router and focus rules from explicit calls (Click,Key,Type,Scroll,Advance) and returns aSnapshotof labels, hit regions and focus. Seehello/main_test.go. Test behavior a user depends on, not pixel offsets or exact primitive counts.- Headless build and line protocol: import
github.com/vivaui/viva/headless/autofrom a file guarded by//go:build vivahead, so release builds never contain it. Build with-tags vivaheadand start the program withVIVA_HEADLESS=1and a throwawayHOME:RunCanvasthen reads one command per line from stdin instead of opening a window (click "Label",type,key,waitfor,shot,texts,layout…). Each reply ends in oneokorerrorline. The complete command list is the comment aboveServeinheadless/control.go; do not copy it elsewhere, it would go stale. layoutlists text that is clipped, spills out of its control or lies over other text. It is faster to read than a screenshot; useshotwhen the look itself matters.- Real windows on each target platform for anything the above cannot see: IME, focus loss, window-manager close, physical keyboard, high-DPI. Say plainly what you did not test.
Before finishing, run gofmt, go vet ./... and go test ./... -count=1. For painting or input paths, report allocations per paint and steady-state time with go test -bench; for a change that affects linking, report the stripped binary size before and after.
5. Where to look
- Guides: https://vivaui.dev/en/docs/ (programming model, declarative layout, handling input, text input, overlays, theming, performance, testing, platforms).
- Platform behavior and what still needs a real window:
docs/conformance.md. - Measurements and the machines they were taken on:
docs/benchmarks/.