building-with-viva
GitHub针对 Viva 框架的 Go 桌面应用开发技能,涵盖 UI 构建、布局、输入处理及无头测试。指导代码编写、审查与调试,遵循 GPU 原生绘制规则,避免常见 Web 模式错误。
Trigger Scenarios
Install
npx skills add vivaui/viva --skill building-with-viva -g -y
SKILL.md
Frontmatter
{
"name": "building-with-viva",
"description": "Write, change or debug a desktop application built on the Viva framework (github.com\/vivaui\/viva), or check its UI without opening a window. Use when the code imports github.com\/vivaui\/viva\/app, ui, flex or components, or when asked to add a window, control, layout, input handling or a headless test to a Viva app."
}
Building with Viva
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.treelists every control with its role, identity, accessible name, value and state (focused, disabled, checked, covered ...); read it first to learn what the frame can do. A control with no name is reached by identity or role:click id:home/auto,click checkbox:,click "button:Save".expectturns a look into a check that fails with what the frame has instead:expect text "Connected" within 5000,expect state id:home/auto checked,expect layout clean,expect named. Prefer a script that ends inexpectlines to one you read by eye, and putexpectlines in the Go test for anything a user depends on (Session.Expecttakes the same words). When a step fails: not found, runtree; text cut off,layout; focus wrong,focus; a press does nothing, look forcoveredintree.- 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/.
Version History
- 4b1a04a Current 2026-11-07 08:54


