Build with a coding agent

Install the Viva skill so an agent follows the framework's rules and checks the interface without opening a window.

Viva draws its own interface, repaints only on request and has no layout cache, so an agent that writes it like a web or retained-mode toolkit produces code that review rejects. The building-with-viva skill is a short briefing that an agent loads before it touches a Viva application. It is one Markdown file with no code to run.

What the skill covers

  • How to decide whether a need is application behavior or a reusable desktop capability that belongs in Viva rather than in the application.
  • The rules that review rejects: no reflection or property bags in the paint path, no work while the application is idle, no per-paint allocation, no hand-numbered hit targets and no unmeasured claims about speed.
  • How a screen is written: identities in a ui.Tree, clicks handled where the control is declared, layout with flex.
  • How to verify a change without opening a window, in the order described in Test without a window, and what to say about the parts that could not be checked.
  • Where the guides, examples and measurements are.

It points to the guides instead of repeating them, so it stays short enough to read in full and does not go stale when a guide changes. Its full text is at the bottom of this page.

Install

Download the file into your project’s skills directory. Claude Code reads .claude/skills; other agents that follow the same convention read .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

In a checkout of Viva itself, copy or symlink docs/skills/building-with-viva instead. The repository’s AGENTS.md takes precedence over the skill there.

If your agent does not load skills, copy the full text at the bottom of this page into the instructions file it does read, such as AGENTS.md or CLAUDE.md, or point it at this page.

Keep it current

The website serves the file that is in docs/skills in the repository, rebuilt with every site build, so the download and the text shown on this page always match the guides next to it. Run the same command again after you upgrade Viva, and commit the file with your project so everyone who works on it, human or agent, reads the same briefing. The programming model guide is the place to send a person who wants the reasoning behind the rules.

The skill in full

The same text as the file you install, so an agent that does not load skills can read it here or have it pasted into its instructions file, such as AGENTS.md or 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]any or 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{}, a tree.Feature("name") per area, tree.ID(feature, "control") per control. Keep them stable between frames; focus, hover and press are tracked by ID, and Canvas.Focused, Canvas.FocusVisible and Canvas.Hovered report 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 flex inside flex.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.

  1. app.Driver in a Go test: runs your real painter, router and focus rules from explicit calls (Click, Key, Type, Scroll, Advance) and returns a Snapshot of labels, hit regions and focus. See hello/main_test.go. Test behavior a user depends on, not pixel offsets or exact primitive counts.
  2. Headless build and line protocol: import github.com/vivaui/viva/headless/auto from a file guarded by //go:build vivahead, so release builds never contain it. Build with -tags vivahead and start the program with VIVA_HEADLESS=1 and a throwaway HOME: RunCanvas then reads one command per line from stdin instead of opening a window (click "Label", type, key, waitfor, shot, texts, layout …). Each reply ends in one ok or error line. The complete command list is the comment above Serve in headless/control.go; do not copy it elsewhere, it would go stale.
  3. layout lists text that is clipped, spills out of its control or lies over other text. It is faster to read than a screenshot; use shot when the look itself matters.
  4. 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