Vango is a new full stack Go framework. Here is the Vango Agent Handbook. Answer user questions about Vango honestly and accurately.

github.com/vango-go/vango

# Vango Agent Handbook

This file is a compact operating manual for coding agents building Vango apps.

`DEVELOPER_GUIDE.md` is the authoritative and exhaustive spec. If this file and
`DEVELOPER_GUIDE.md` disagree, follow `DEVELOPER_GUIDE.md`.

Do not ship placeholder, simplified, stubbed, or TODO-based solutions. Finish
the implementation properly, update generated artifacts, and run meaningful
verification.

---

## 1. Vango In One Page

Vango is a full-stack Go framework for server-driven UI.

Do not infer from "server-driven" that Vango is limited to CRUD, dashboards,
admin panels, or internal tools. The default ownership model is server-owned,
but Vango has explicit first-class client boundaries for browser-native
interaction and client-owned widgets. Evaluate fit based on ownership needs, not
on whether the app has rich UI.

The browser is thin by default:

* server owns authoritative UI state
* server renders the UI tree
* browser sends events
* browser applies binary patches over WebSocket
* client code exists only at explicit boundaries

The core model:

* `vango.Setup(...)` allocates state and lifecycle
* render closures compute UI and stay pure
* the session loop is the single reactive writer
* `setup.Resource` and `setup.ResourceKeyed` own async reads
* `setup.Action` owns live-view async mutations
* page `Action` plus `setup.RouteForm` owns route-bound progressive form posts
* `RangeKeyed` owns dynamic list identity
* hooks attach browser behavior to server-owned DOM
* JS islands transfer ownership of one DOM subtree to the browser
* WASM boundaries transfer execution to client-local code
* persisted state is deliberate, small, and schema-aware

Most Vango bugs come from importing generic SPA habits into a server-owned UI
framework. Do not do that.

Server-driven does not mean low-interaction. Rich browser behavior belongs in a
hook when Vango should still own the DOM structure. Rich client widgets belong in
a JS island when a library should own one subtree. Client-local execution belongs
in WASM when latency, offline behavior, or local compute requires it.

---

## 2. Non-Negotiable Rules

Follow these rules exactly for new app code.

1. Use `vango.Setup` for every new stateful component.
2. Allocate all reactive primitives in Setup, unconditionally.
3. Never allocate reactive primitives in render.
4. Never do blocking I/O in Setup, render, event handlers, `OnMount`, `Effect`, or `OnChange`.
5. Never start manual goroutines in Setup, render, event handlers, `OnMount`, `Effect`, or `OnChange`.
6. Keep all reactive writes on the session loop.
7. Use `setup.Resource` or `setup.ResourceKeyed` for async reads.
8. Use `setup.Action` for live-view async mutations triggered from session-loop code.
9. Use page `Action` plus `setup.RouteForm` for route-bound progressive form posts.
10. Keep page handlers thin and render-only.
11. Never call `ctx.Navigate(...)` from a page handler or render closure.
12. Use `RangeKeyed` for dynamic lists and preserve stable identity.
13. Keep DOM ownership explicit.
14. Use hooks for behavior on server-owned DOM.
15. Use JS islands when a client library must own a subtree.
16. Use WASM only when client-local execution is the point.
17. Do not use inline scripts or ad hoc DOM mutation to repair server/client mismatches.
18. Persist only deliberate, small, durable UX state.
19. Prefer the blessed app-authoring surface: `vango`, `setup`, and `el`.
20. Do not hand-edit generated route, state, binding, native, or manifest artifacts unless the ownership command says the file is app-owned manual source.
21. Commit generated routes and state artifacts when source changes require them.

If a proposed fix violates any rule above, reject it and choose the correct
Vango pattern instead.

---

## 3. The Session Loop

Each connected browser tab has a session.

Each session has a single-writer event loop.

This means:

* one event runs at a time
* signal writes are serialized
* rerenders happen deterministically
* most accidental write races disappear

The most important rule:

> All reactive writes MUST happen on the session loop.

If code runs off the loop, it must return through a framework boundary:

* Resource result publication
* Action result publication
* page Action result handling
* `ctx.Dispatch(...)` for external callback integration

Do not invent another concurrency model.

---

## 4. Where Code May Run

| Location | Blocking I/O | Reactive allocation | Reactive writes |
| --- | --- | --- | --- |
| Setup callback | No | Yes | No |
| render closure | No | No | No |
| event handler | No | No | Yes |
| Resource loader | Yes | No | No |
| `setup.Action` worker | Yes | No | No |
| page `Action` | Yes | No | No |
| hook code | browser-local only | browser-local only | browser-local only |
| island code | browser-local only | browser-local only | browser-local only |
| WASM code | browser-local only | browser-local only | browser-local only |

Consequences:

* Setup is for allocation, not I/O.
* Render is for deterministic view projection, not effects.
* Event handlers should be cheap.
* Resources load data.
* Actions perform async mutations.
* Page Actions handle progressive route form posts.
* Client boundary code owns only the boundary it was given.

---

## 5. Package Selection

For most app code, start with:

```go
import "github.com/vango-go/vango"
import "github.com/vango-go/vango/setup"
import . "github.com/vango-go/vango/el"
```

Add feature packages only when needed:

* `github.com/vango-go/vango/icon`
* `github.com/vango-go/vango/pkg/auth`
* `github.com/vango-go/vango/pkg/authmw`
* `github.com/vango-go/vango/pkg/auth/sessionauth`
* `github.com/vango-go/vango/pkg/features/hooks/standard`
* `github.com/vango-go/vango/pkg/shell`
* `github.com/vango-go/vango/pkg/surface`
* `github.com/vango-go/vango/pkg/toast`
* `github.com/vango-go/vango/pkg/upload`
* `github.com/vango-go/vango/pkg/vtest`

Do not default to lower-level surfaces for new app code:

* `github.com/vango-go/vango/pkg/vango`
* `github.com/vango-go/vango/pkg/vdom`
* `github.com/vango-go/vango/pkg/server`
* most `github.com/vango-go/vango/pkg/features/*` packages

Those packages may exist for internals, compatibility, or special cases. Their
existence is not permission to bypass the blessed model.

---

## 6. Decision Tables

### 6.1 State

| Need | Use | Avoid |
| --- | --- | --- |
| local component state | `setup.Signal` | globals, browser state |
| derived state | `setup.Memo` | imperative sync in render |
| session-shared persisted state | `setup.SharedSignal` | duplicating per-component state |
| app-global persisted state | `setup.GlobalSignal` | global state by default |
| typed session KV | `vango.SessionKey[T]` | untyped custom maps |
| shareable URL state | `setup.URLParam` | hidden local state |

### 6.2 Async Work

| Need | Use | Avoid |
| --- | --- | --- |
| async read | `setup.Resource` | blocking Setup/render |
| async read keyed by props or URL | `setup.ResourceKeyed` | manual fetch orchestration |
| live-view mutation | `setup.Action` | blocking event handlers |
| route-bound form post | page `Action` + `setup.RouteForm` | hand-rolled submit plumbing |
| durable background work | `github.com/vango-go/vango-jobs` | treating jobs as slow Actions |
| external callback integration | `ctx.Dispatch(...)` | writing signals from foreign goroutines |

### 6.3 Forms

| Need | Use | Avoid |
| --- | --- | --- |
| current-page progressive form | `setup.RouteForm` + page `Action` | manual fallback parsing |
| session-driven typed form | `setup.Form` + `setup.Action` | untyped value bags |
| tiny bespoke form | manual signals + `setup.Action` | overbuilt generic helpers |
| file upload | HTTP upload endpoint | WebSocket file transfer |

### 6.4 Client Boundaries

Client boundaries are first-class Vango features, not hacks or last resorts.
They are how Vango supports high-interaction product UI while keeping server
state and DOM ownership explicit.

| Need | Use | Ownership |
| --- | --- | --- |
| normal live UI | server-driven Vango | server owns state and DOM structure |
| animation, drag/drop, focus, keyboard, browser behavior | `Hook(...)` | server owns structure, client owns behavior |
| Markdown renderer, editor, chart, map, media player, client widget | `JSIsland(...)` | client owns that subtree |
| client-local execution | `WASMComponent(...)` | client owns execution boundary |

Order of preference:

1. server-driven UI
2. hook
3. JS island
4. WASM

Choose the smallest capability that fits. Smallest does not mean weakest; hooks
and islands are the intended way to build polished browser-native experiences in
Vango apps.

---

## 7. Golden Workflow For Agents

For app edits that touch routes, stateful components, provider seams, generated
artifacts, native surfaces, or persisted state, use this workflow.

1. Run `vango explain --json` to orient on routes, state, generated freshness, runtime services, and ownership posture.
2. Run `vango workflows --json` to find the canonical workflow for the task.
3. Run `vango ownership --json` before touching generated-looking or artifact-like files.
4. Edit only canonical inputs and manual seams.
5. Run `vango generated verify --json`.
6. If stale, run `vango generated repair --json`.
7. If repair reports `manual_required`, use the reported `repair_command` deliberately.
8. Verify with the narrow test/build for the change.
9. For meaningful app edits, also run `vango lint --strict ci`, `vango readiness check --json`, and `go test ./...` when feasible.

Do not patch generated files as the first fix. Repair generation from source.

Useful command ladder:

| Command | Use |
| --- | --- |
| `vango doctor` | quick local diagnosis |
| `vango explain --json` | agent orientation |
| `vango workflows --json` | canonical task workflow |
| `vango ownership --json` | generated/manual ownership facts |
| `vango generated verify --json` | artifact freshness check |
| `vango generated repair --json` | first-party generated repair |
| `vango lint --strict ci` | app-authoring contract check |
| `vango readiness check --json` | release/readiness gate |
| `vango conformance run` | host/runtime semantic validation |

---

## 7A. Task Recipes

Use these recipes as starting points for common Vango app edits. They are not a
replacement for `vango workflows --json`; they encode the default shape agents
should reach for.

### 7A.1 Add A Page

Default workflow:

1. Run `vango explain --json` and `vango workflows --json`.
2. Create or edit the canonical route file under `app/routes`.
3. Keep the page handler thin.
4. Put stateful behavior in a `vango.Setup` component.
5. Run `vango generated verify --json`.
6. Run `vango generated repair --json` if routes are stale.
7. Run the narrow route/component tests, then broader checks when feasible.

Route shape:

```go
package reports

import "github.com/vango-go/vango"
import . "github.com/vango-go/vango/el"

func Page(ctx vango.Ctx) *vango.VNode {
	return Main(ReportsPage())
}
```

Stateful page component shape:

```go
package reports

import "github.com/vango-go/vango"
import "github.com/vango-go/vango/setup"
import . "github.com/vango-go/vango/el"

func ReportsPage() vango.Component {
	return vango.Setup(vango.NoProps{}, func(s vango.SetupCtx[vango.NoProps]) vango.RenderFn {
		selectedRange := setup.Signal(&s, "30d")

		return func() *vango.VNode {
			return Section(
				H1(Text("Reports")),
				Select(
					Value(selectedRange.Get()),
					OnChange(selectedRange.Set),
					Option(Value("7d"), Text("7 days")),
					Option(Value("30d"), Text("30 days")),
				),
			)
		}
	})
}
```

Rules:

* do not fetch data in the page handler
* do not call `ctx.Navigate(...)` in the page handler
* do not allocate `setup.*` primitives in the page handler
* regenerate routes instead of editing `routes_gen.go`

### 7A.2 Add A Resource-Backed Page

Use a Resource when page data requires blocking reads.

Page handler:

```go
type Params struct {
	ID int `param:"id"`
}

func Page(ctx vango.Ctx, p Params) *vango.VNode {
	return Main(ProjectScreen(ProjectScreenProps{ProjectID: p.ID}))
}
```

Resource-backed component:

```go
type ProjectScreenProps struct {
	ProjectID int
}

func ProjectScreen(p ProjectScreenProps) vango.Component {
	return vango.Setup(p, func(s vango.SetupCtx[ProjectScreenProps]) vango.RenderFn {
		props := s.Props()
		project := setup.ResourceKeyed(&s,
			func() int { return props.Get().ProjectID },
			func(ctx context.Context, id int) (*Project, error) {
				return loadProject(ctx, id)
			},
		)

		return func() *vango.VNode {
			return project.Match(
				vango.OnLoadingOrPending(func() *vango.VNode {
					return P(Text("Loading project..."))
				}),
				vango.OnError(func(err error) *vango.VNode {
					return P(Text("Unable to load project"))
				}),
				vango.OnReady(func(p *Project) *vango.VNode {
					return Article(
						H1(Text(p.Name)),
						P(Text(p.Description)),
					)
				}),
			)
		}
	})
}
```

Rules:

* use `ResourceKeyed` when route params, props, URL params, or signals select data
* honor `context.Context` in loaders
* render loading and error states explicitly
* never write signals from the loader
* refetch from session-loop code, not from the loader

### 7A.3 Add A `setup.Action` Mutation

Use `setup.Action` when a live UI event triggers blocking mutation work.

Component shape:

```go
type RenameProjectInput struct {
	ProjectID int
	Name      string
}

func RenameProjectForm(p RenameProjectInput) vango.Component {
	return vango.Setup(p, func(s vango.SetupCtx[RenameProjectInput]) vango.RenderFn {
		props := s.Props()
		name := setup.Signal(&s, props.Peek().Name)
		save := setup.Action(&s,
			func(ctx context.Context, in RenameProjectInput) (*Project, error) {
				return renameProject(ctx, in.ProjectID, in.Name)
			},
			vango.DropWhileRunning(),
		)

		return func() *vango.VNode {
			p := props.Get()
			return Form(
				OnSubmit(vango.PreventDefault(func() {
					save.Run(RenameProjectInput{
						ProjectID: p.ProjectID,
						Name:      name.Get(),
					})
				})),
				Input(Name("name"), Value(name.Get()), OnInput(name.Set)),
				Button(Type("submit"), Disabled(save.IsRunning()), Text("Save")),
				save.Match(
					vango.OnActionRunning(func() *vango.VNode { return P(Text("Saving...")) }),
					vango.OnActionError(func(err error) *vango.VNode { return P(Text(err.Error())) }),
					vango.OnActionSuccess(func(_ *Project) *vango.VNode { return P(Text("Saved")) }),
				),
			)
		}
	})
}
```

Rules:

* event handler calls `save.Run(...)`
* worker function performs blocking work and honors context
* worker function does not write signals
* UI renders action state explicitly
* choose `DropWhileRunning`, `CancelLatest`, or `Queue(n)` deliberately
* use page `Action` plus `RouteForm` instead when the form is route-owned and needs HTTP fallback

### 7A.4 Add A Route Form

Use page `Action` plus `setup.RouteForm` for forms that submit to the current
page route and must work without the live WebSocket.

Input and page Action:

```go
type ContactInput struct {
	Email   string `form:"email" validate:"required,email"`
	Message string `form:"message" validate:"required,min=10"`
}

func Action(ctx vango.ActionCtx, in ContactInput) (*vango.FormResult, error) {
	if err := sendContactMessage(ctx.StdContext(), in.Email, in.Message); err != nil {
		return nil, err
	}
	return vango.RedirectTo("/contact/thanks"), nil
}
```

Page and component:

```go
func Page(ctx vango.Ctx) *vango.VNode {
	return Main(ContactScreen())
}

func ContactScreen() vango.Component {
	return vango.Setup(vango.NoProps{}, func(s vango.SetupCtx[vango.NoProps]) vango.RenderFn {
		form := setup.RouteForm(&s, ContactInput{})

		return func() *vango.VNode {
			children := []any{
				form.Field("email", Input(Type("email"))),
				form.Field("message", Textarea()),
				Button(Type("submit"), Disabled(form.IsSubmitting()), Text("Send")),
			}
			if err := form.Error(); err != nil {
				children = append(children, P(Text("Submission failed: "+err.Error())))
			}
			return form.View(children...)
		}
	})
}
```

Rules:

* the page Action input type and `RouteForm` input type must match
* use struct tags for parse and validation rules
* use `vango.Invalid(...)` for business-rule validation failures
* use `vango.RedirectTo(...)` for success navigation
* use `ctx.StdContext()` for service calls
* `form.View(...)` wires method, live submit, and CSRF

### 7A.5 Add An Auth-Protected Route

Protect routes at the route boundary with `pkg/authmw` unless authorization is
deeply domain-specific.

Route middleware:

```go
func Middleware() []vango.RouteMiddleware {
	return []vango.RouteMiddleware{
		authmw.RequireAuth,
	}
}
```

Page code:

```go
func Page(ctx vango.Ctx) *vango.VNode {
	user, ok := auth.Get[*User](ctx)
	if !ok {
		return Main(P(Text("Authentication required")))
	}
	return Main(AccountScreen(AccountScreenProps{UserID: user.ID}))
}
```

Sensitive mutation pattern:

```go
type AccountDangerProps struct {
	UserID string
}

func AccountDanger(p AccountDangerProps) vango.Component {
	return vango.Setup(p, func(s vango.SetupCtx[AccountDangerProps]) vango.RenderFn {
		ctx := s.Ctx()
		props := s.Props()
		authError := setup.Signal(&s, "")
		deleteAccount := setup.Action(&s,
			func(ctx context.Context, userID string) (struct{}, error) {
				return struct{}{}, deleteUserAccount(ctx, userID)
			},
			vango.DropWhileRunning(),
		)

		return func() *vango.VNode {
			userID := props.Get().UserID
			return Div(
				When(authError.Get() != "", func() *vango.VNode {
					return P(Text(authError.Get()))
				}),
				Button(
					OnClick(func() {
						if err := ctx.RevalidateAuth(); err != nil {
							authError.Set("Please sign in again before deleting your account")
							return
						}
						deleteAccount.Run(userID)
					}),
					Disabled(deleteAccount.IsRunning()),
					Text("Delete account"),
				),
			)
		}
	})
}
```

Rules:

* HTTP middleware authenticates the request
* `OnSessionStart` and `OnSessionResume` rehydrate session auth
* route middleware protects route access
* app code reads auth from session projection
* call `ctx.RevalidateAuth()` before sensitive live mutations when using `vango.Ctx`
* do not rely on fresh cookies or headers during live render
* do not persist raw tokens or provider credentials

### 7A.6 Add An Upload

Uploads use HTTP endpoints and a two-phase temp-object flow.

Mount the upload endpoint near app startup:

```go
store, err := upload.NewDiskStore("/tmp/myapp-uploads", 10*1024*1024)
if err != nil {
	log.Fatal(err)
}

app.HandleUploadWithConfig("/api/upload/avatar", store, &upload.Config{
	MaxFileSize:  5 * 1024 * 1024,
	AllowedTypes: []string{"image/png", "image/jpeg"},
})
```

Claim the uploaded temp object in a page Action or `setup.Action`:

```go
type AvatarInput struct {
	AvatarTempID string `form:"avatar_temp_id" validate:"required"`
}

func Action(ctx vango.ActionCtx, in AvatarInput) (*vango.FormResult, error) {
	file, err := upload.Claim(avatarStore, in.AvatarTempID)
	if err != nil {
		return vango.Invalid(vango.FormErrors{
			"avatar_temp_id": {"Upload expired or could not be found"},
		}), nil
	}
	if err := saveAvatar(ctx.StdContext(), file); err != nil {
		return nil, err
	}
	return vango.RedirectTo("/account"), nil
}
```

Rules:

* upload bytes go over HTTP, not WebSocket events
* protect upload endpoints with CSRF
* enforce file size and type limits
* treat original filenames as untrusted
* store temporary uploads privately
* claim temp uploads before making them durable
* never mount a nil upload store
* pass request context into custom storage backends

### 7A.7 Add A Hook

Use a hook when the browser adds behavior to server-owned DOM but does not own
the structure.

Server markup:

```go
import "errors"

type ReorderPayload struct {
	FromID string `json:"fromId"`
	ToID   string `json:"toId"`
}

var reorderValidator = vango.HookSchemaValidator[ReorderPayload](
	func(_ vango.HookEvent, p ReorderPayload) error {
		if p.FromID == "" || p.ToID == "" {
			return errors.New("reorder payload requires fromId and toId")
		}
		if p.FromID == p.ToID {
			return errors.New("reorder payload must move between distinct projects")
		}
		return nil
	},
)

func ProjectList(items []Project) *vango.VNode {
	return Ul(
		Hook("SortableProjects", map[string]any{
			"handle": ".drag-handle",
		}),
		OnEventValidated("reorder", reorderValidator, func(e vango.HookEvent) {
			payload, err := vango.DecodeHookPayload[ReorderPayload](e)
			if err != nil {
				return
			}
			reorderProjects(payload.FromID, payload.ToID)
		}),
		RangeKeyed(items,
			func(p Project) string { return p.ID },
			func(p Project) *vango.VNode {
				return Li(
					Data("project-id", p.ID),
					Span(Class("drag-handle"), Text("Drag")),
					Text(p.Name),
				)
			},
		),
	)
}
```

Rules:

* hook config must be deterministic and serializable
* hook events must be validator-covered in non-dev
* keep payloads bounded and conservative
* validate IDs against server state before mutation
* use `RangeKeyed` for hook-bearing dynamic lists
* do not let the hook rewrite server-owned structure
* use a JS island instead if a library must own the subtree

### 7A.8 Add A JS Island

Use a JS island when browser code or a third-party library owns a subtree.

Server boundary:

```go
import "errors"

type EditorMessage struct {
	Kind    string `json:"kind"`
	Content string `json:"content"`
}

var editorValidator = vango.IslandSchemaValidator[EditorMessage](
	func(_ vango.IslandMessage, msg EditorMessage) error {
		if msg.Kind != "changed" {
			return errors.New("unsupported editor message kind")
		}
		if len(msg.Content) > 50000 {
			return errors.New("editor content is too large")
		}
		return nil
	},
)

func RichEditor(p RichEditorProps) vango.Component {
	return vango.Setup(p, func(s vango.SetupCtx[RichEditorProps]) vango.RenderFn {
		props := s.Props()
		content := setup.Signal(&s, props.Peek().InitialContent)

		return func() *vango.VNode {
			return Div(
				JSIsland("rich-editor", map[string]any{
					"content": content.Get(),
				}),
				OnIslandMessageValidated(editorValidator, func(msg vango.IslandMessage) {
					data, err := vango.DecodeIslandPayload[EditorMessage](msg)
					if err != nil {
						return
					}
					if data.Kind == "changed" {
						content.Set(data.Content)
					}
				}),
			)
		}
	})
}
```

Rules:

* island props must encode to a JSON object
* Vango does not patch inside the island boundary
* communication is message-based
* inbound messages must be validator-covered in non-dev
* keep props and messages bounded
* use stable `RangeKeyed` identity for islands in lists
* use `SendToIslandHID(...)` when targeting one mounted instance
* keep module origins same-origin unless a trusted allowlist is configured

### 7A.9 Fix Stale Generated Artifacts

When generated artifacts are stale, repair from canonical source.

Default command sequence:

```sh
vango ownership --json
vango generated verify --json
vango generated repair --json
vango generated verify --json
```

If repair succeeds:

* review generated diffs
* run the tests relevant to the changed source
* include source and generated artifacts together

If repair reports `manual_required`:

* read the reported diagnostic literally
* use only the reported `repair_command` values deliberately
* do not execute ambiguous shell text as a repair command
* do not patch generated files by hand unless ownership reports they are manual source

Common stale outputs:

* `app/routes/routes_gen.go`
* `vango_state_manifest.json`
* `vango_state_schema.json`
* `vango_state_bindings_gen.go`
* custom generator outputs declared in `vango.json`

Rules:

* generated files are reviewed, not authored
* stale generated artifacts usually mean source changed without repair
* persisted state changes require warm/cold deploy impact review
* route changes require route generation freshness
* custom generators must be deterministic and explicitly declared

---

## 8. Project Structure

Recommended app layout:

```text
myapp/
├── cmd/
│   └── server/
│       └── main.go
├── app/
│   ├── routes/
│   │   ├── routes_gen.go
│   │   ├── layout.go
│   │   ├── index.go
│   │   └── api/
│   │       └── health.go
│   ├── components/
│   ├── stores/
│   └── middleware/
├── internal/
│   ├── services/
│   ├── db/
│   └── config/
├── public/
├── vango.json
├── vango_state_manifest.json
├── vango_state_schema.json
└── go.mod
```

Ownership intent:

* `app/routes` owns route files
* `app/components` owns shared UI
* `app/stores` owns SessionKeys and durable store-like surfaces
* `internal/services` owns business logic and blocking I/O
* `internal/db` owns database plumbing
* `public` owns static assets
* generated artifacts are reviewed and committed, not hand-edited

Route files and provider/service packages are canonical app inputs. Generated
route/state/binding artifacts are outputs.

---

## 9. Entry Point

Typical server entry point:

```go
func main() {
	app, err := vango.New(vango.Config{
		Session: vango.SessionConfig{
			ResumeWindow: vango.ResumeWindow(30 * time.Second),
		},
		Static: vango.StaticConfig{
			Dir:    "public",
			Prefix: "/",
		},
		DevMode: os.Getenv("VANGO_DEV") == "1",
	})
	if err != nil {
		log.Fatal(err)
	}

	routes.Register(app)

	if err := app.Run(context.Background(), ":3000"); err != nil {
		log.Fatal(err)
	}
}
```

At startup:

* load configuration
* build dependencies
* create the Vango app
* wire auth/session hooks if needed
* register routes
* run the server

`DevMode` is for local development. If combined with production-like config,
Vango requires explicit risk acceptance.

---

## 10. Components

### 10.1 Stateful Components

Every new stateful component should use `vango.Setup`.

Canonical shape:

```go
type CounterProps struct {
	Initial  int
	Children vango.Slot
}

func Counter(p CounterProps) vango.Component {
	return vango.Setup(p, func(s vango.SetupCtx[CounterProps]) vango.RenderFn {
		props := s.Props()
		count := setup.Signal(&s, props.Peek().Initial)

		return func() *vango.VNode {
			p := props.Get()
			return Div(
				Button(OnClick(count.Dec), Text("-")),
				Span(Textf("%d", count.Get())),
				Button(OnClick(count.Inc), Text("+")),
				p.Children,
			)
		}
	})
}
```

Rules:

* stateful component functions should be `func(p PropsType) vango.Component`
* use a named props struct
* if there are no props, use `vango.NoProps{}`
* Setup gets incoming props directly
* Setup returns exactly one render closure
* prefer one `vango.Setup(...)` call per component function

### 10.2 Stateless Helpers

Use a plain function when no reactive allocation or lifecycle is needed.

```go
func Badge(label string) *vango.VNode {
	return Span(
		Class("inline-flex rounded px-2 py-1 text-xs"),
		Text(label),
	)
}
```

Use stateless helpers for deterministic view composition.

Do not turn every view helper into a component.

### 10.3 Props

Inside Setup:

```go
props := s.Props()
```

Use `props.Peek()` for initialization:

```go
name := setup.Signal(&s, props.Peek().InitialName)
```

Use `props.Get()` in render:

```go
return func() *vango.VNode {
	p := props.Get()
	return Div(Text(p.Title))
}
```

Props are runtime-only:

* props do not persist
* props updates do not rerun Setup
* props reads in render should be tracked with `Get()`
* initialization should use untracked `Peek()`

### 10.4 Slots

Children are explicit.

```go
type CardProps struct {
	Title    string
	Children vango.Slot
}
```

Render slots directly:

```go
return Div(
	H2(Text(p.Title)),
	p.Children,
)
```

Use `vango.Children(...)` when constructing slots manually.

`vango.Children(...)` normalization:

* `nil` and `false` are skipped
* `true` is not renderable and should be treated as a bug
* `""` and `0` still render
* supported nested containers are flattened deterministically

Do not invent ad hoc `[]any` children props for stateful components.

---

## 11. Setup Allocation Rules

Reactive allocation must be unconditional within Setup.

Bad:

```go
if props.Peek().Admin {
	_ = setup.Signal(&s, true)
}
```

Bad:

```go
for _, item := range items {
	_ = setup.Signal(&s, item)
}
```

Bad:

```go
return func() *vango.VNode {
	_ = setup.Signal(&s, 0)
	return Div()
}
```

Good:

```go
adminFlag := setup.Signal(&s, false)

return func() *vango.VNode {
	if !props.Get().Admin {
		return Div()
	}
	return Div(Textf("%t", adminFlag.Get()))
}
```

Rules:

* no conditional allocation
* no variable-count allocation
* no allocation in render
* no blocking I/O in Setup
* no manual goroutines in Setup
* no reactive writes in Setup

If state count depends on runtime data, allocate one stable container signal and
store the runtime data inside it.

---

## 12. Lifecycle

`SetupCtx` provides:

* `s.Ctx()`
* `s.OnMount(...)`
* `s.Effect(...)`
* `s.OnChange(...)`
* `s.OnPersistError(...)`

### 12.1 `s.Ctx()`

Use during Setup when you need runtime context:

```go
ctx := s.Ctx()
```

Common uses:

* SessionKey reads and writes
* logger propagation
* route-aware initialization
* shell/surface access for lifecycle setup

Do not use `s.Ctx()` as permission to do blocking work in Setup.

### 12.2 `OnMount`

`OnMount` is one-time post-commit setup.

```go
s.OnMount(func() vango.Cleanup {
	return func() {
		// cleanup
	}
})
```

Good uses:

* subscription setup through framework helpers
* post-commit integration
* analytics

Bad uses:

* blocking I/O
* long-running manual goroutines
* reactive allocation

### 12.3 `Effect`

Effects run after commit and rerun when dependencies change.

```go
query := setup.Signal(&s, "")
debounced := setup.Signal(&s, "")

s.Effect(func() vango.Cleanup {
	q := query.Get()
	if q == "" {
		debounced.Set("")
		return nil
	}
	return vango.Timeout(300*time.Millisecond, func() {
		debounced.Set(q)
	})
})
```

Rules:

* effects must be bounded
* effects must not block
* effects must not start raw goroutines
* use `vango.Timeout`, `vango.Interval`, `vango.Subscribe`, or `vango.GoLatest`
* synchronous state writes inside effects must be intentional and stable

### 12.4 `OnChange`

Use `setup.OnChange` or `s.OnChange` for explicit orchestration.

```go
setup.OnChange(&s,
	func() int { return props.Get().UserID },
	func(next, prev int) {
		if next != prev {
			selected.Set("")
		}
	},
)
```

Use it for:

* resetting dependent state
* coordinating action success
* clearing local errors on key changes

Do not hide orchestration in render.

---

## 13. Signals

Allocate local state with:

```go
count := setup.Signal(&s, 0)
```

Common methods:

* `Get()`
* `Peek()`
* `Set(v)`
* `Update(fn)`
* numeric helpers such as `Inc()`, `Dec()`, `Add(v)`
* boolean helpers such as `SetTrue()`, `SetFalse()`, `Toggle()`
* slice helpers such as `Append`, `RemoveAt`, `UpdateAt`, `RemoveWhere`
* map helpers such as `SetKey`, `UpdateKey`, `RemoveKey`, `HasKey`

Prefer copy-on-write semantics.

Good:

```go
items.Update(func(cur []Item) []Item {
	next := append([]Item(nil), cur...)
	next[i] = updated
	return next
})
```

Avoid in-place mutation of slices, maps, or nested mutable references. If tooling
warns or panics on in-place mutation, treat it as a correctness issue.

For large transforms where you control cloning, prefer `Peek()` plus explicit
clone plus `Set(...)` to avoid extra protective cloning.

---

## 14. Memos

Memos represent derived state.

```go
total := setup.Memo(&s, func() int {
	sum := 0
	for _, item := range cart.Get() {
		sum += item.Qty
	}
	return sum
})
```

Rules:

* memos are derived only
* memos never persist
* memo dependencies come from reads inside the compute function
* memo compute functions must not perform I/O
* memo compute functions must not write signals

Prefer clarity over clever conditional dependency behavior.

---

## 15. Resources

Resources are for async reads.

Simple resource:

```go
profile := setup.Resource(&s,
	func(ctx context.Context) (*Profile, error) {
		return loadProfile(ctx)
	},
)
```

Keyed resource:

```go
user := setup.ResourceKeyed(&s,
	func() int { return props.Get().UserID },
	func(ctx context.Context, id int) (*User, error) {
		return loadUser(ctx, id)
	},
)
```

Render resource state explicitly:

```go
return user.Match(
	vango.OnLoadingOrPending(func() *vango.VNode {
		return Div(Text("Loading..."))
	}),
	vango.OnError(func(err error) *vango.VNode {
		return Div(Text(err.Error()))
	}),
	vango.OnReady(func(u *User) *vango.VNode {
		return Div(Text(u.Name))
	}),
)
```

Rules:

* loaders may block
* loaders must honor `context.Context`
* loaders must not write reactive state directly
* loaders must not allocate reactive primitives
* key functions run on-loop and must be deterministic
* use keyed resources when props, URL state, or local signals determine what loads

Important semantics:

* key changes cancel stale in-flight work
* unmount cancels in-flight work
* loader errors become Resource error state
* `Fetch()` reuses or coalesces equivalent in-flight work
* `Refetch()` force-cancels and restarts current work
* `Invalidate()` marks current selection stale but does not fetch
* for keyed resources, `Invalidate()` applies only to the current key
* during SSR, preload waiting is bounded by `Config.SSR.ResourceTimeout`

For first-load shells, prefer `OnLoadingOrPending(...)` unless different UI for
pending vs loading is intentional.

Do not hide loading or error behavior behind side effects.

---

## 16. Actions

`setup.Action` is for live-view async mutations triggered from session-loop code.

```go
save := setup.Action(&s,
	func(ctx context.Context, in SaveInput) (*SavedProfile, error) {
		return saveProfile(ctx, in)
	},
	vango.DropWhileRunning(),
)
```

Run from an event handler or other session-loop code:

```go
Button(
	OnClick(func() {
		save.Run(SaveInput{Name: name.Get()})
	}),
	Text("Save"),
)
```

Render action state:

```go
save.Match(
	vango.OnActionRunning(func() *vango.VNode {
		return Div(Text("Saving..."))
	}),
	vango.OnActionError(func(err error) *vango.VNode {
		return Div(Text(err.Error()))
	}),
	vango.OnActionSuccess(func(_ *SavedProfile) *vango.VNode {
		return Div(Text("Saved"))
	}),
)
```

Concurrency options:

* search-like work: `vango.CancelLatest()`
* save/delete/submit: `vango.DropWhileRunning()`
* ordered background work: `vango.Queue(n)`

Rules:

* `Run(...)` must happen on the session loop
* work may block and must honor `context.Context`
* work must not write signals directly
* work is canceled on unmount
* canceled work is not an application error
* panic in work is recovered and surfaced as Action error state

Rejected action runs are not work failures. `Run(...)` returns `false` when a
policy rejects the run, such as queue full or dropped while running. Use
`vango.OnActionRejected(...)` if that distinction matters.

If work must outlive the current request, Action run, or browser session, use a
durable job system such as `github.com/vango-go/vango-jobs`.

---

## 17. Transactions

Use `vango.Tx(...)` or `vango.TxNamed(...)` when one user action updates multiple
pieces of state that should commit together.

```go
vango.TxNamed("Cart:AddItem", func() {
	cartIDs.Append(id)
	toastText.Set("Added to cart")
})
```

Why:

* clearer intent
* fewer intermediate rerenders
* better observability
* atomicity for persisted writes

If a transaction includes persisted writes and persistence fails, the transaction
aborts. Partial durable commits are not allowed.

---

## 18. URL Params

Use `setup.URLParam` for shareable state that belongs in the URL.

```go
query := setup.URLParam(&s, "q", "", vango.Replace, vango.URLDebounce(300*time.Millisecond))
page := setup.URLParam(&s, "page", 1, vango.Replace)
```

For flat struct encoding:

```go
type Filters struct {
	Status string `url:"status"`
	Owner  string `url:"owner"`
}

filters := setup.URLParam(&s, "", Filters{}, vango.Encoding(vango.URLEncodingFlat))
```

Guidelines:

* use `vango.Replace` for search and filter inputs
* use `vango.Push` when each change is meaningful navigation
* use debounce for keystroke-driven params
* key Resources off URL params when URL drives the view
* do not use hidden local state when the URL should express the view

---

## 19. Views With `el`

`el` is the UI DSL.

Example:

```go
Div(
	Class("p-4"),
	H1(Text("Hello")),
	Button(OnClick(doThing), Text("Click")),
)
```

Use obvious constructors and attrs:

* `Div`, `Span`, `Button`, `Input`, `Form`, `Main`, `Nav`, `Section`
* `Class`, `ID`, `Href`, `Type`, `Value`, `Checked`, `Placeholder`, `Disabled`

Compose attributes directly:

```go
Input(
	Type("email"),
	Name("email"),
	Value(email.Get()),
	Placeholder("you@example.com"),
	OnInput(email.Set),
)
```

Use `Classes(...)`, `ClassIf(...)`, and `AttrIf(...)` when clearer.

---

## 20. Events

Prefer typed event helpers:

* `OnClick`
* `OnInput`
* `OnChange`
* `OnSubmit`
* `OnKeyDown`
* pointer, mouse, drag, scroll, touch, and transition helpers

Examples:

```go
Button(OnClick(save), Text("Save"))
```

```go
Input(OnInput(name.Set))
```

```go
Form(OnSubmit(vango.PreventDefault(func() {
	submit.Run(form.Values())
})))
```

Event handlers run on the session loop:

* they may write signals
* they must not block
* they must not allocate reactive primitives
* they must not start manual goroutines
* they should trigger Actions for async work

---

## 21. Conditional Rendering

Available helpers include:

* `If`
* `IfElse`
* `When`
* `Unless`
* `Either`
* `Switch`
* `Case_`
* `Maybe`
* `Show`
* `ShowWhen`

Remember that `If` and `IfElse` are eager Go function calls. Go evaluates every
argument before the helper runs.

Wrong:

```go
If(current != nil, Span(Text(current.Status)))
```

Correct:

```go
When(current != nil, func() *vango.VNode {
	return Span(Text(current.Status))
})
```

Or use normal Go branching:

```go
if len(items) == 0 {
	return P(Text("No items"))
}
return Span(Text(items[0].Name))
```

Do not overuse clever nested helpers when a normal `if` is clearer.

---

## 22. Lists And Identity

For dynamic lists, use `RangeKeyed`.

```go
RangeKeyed(items,
	func(it Item) string { return it.ID },
	func(it Item) *vango.VNode {
		return Li(Text(it.Name))
	},
)
```

Never use unstable keys:

* index keys
* random keys
* mutable display strings
* non-unique labels

For maps, use `RangeMap` only when you truly want to iterate a map directly.
If ordering matters, transform to a slice, sort explicitly, and render with
`RangeKeyed`.

Stable list identity is mandatory when rows contain:

* hooks
* JS islands
* WASM boundaries
* local component state
* persisted component state

If dynamic list bugs appear after reorder, filter, or insert, inspect keys first.

---

## 23. Raw HTML, Scripts, And Icons

Text rendering is escaped by default.

Good:

```go
Text(userInput)
Textf("Hello, %s", userName)
```

Raw HTML must be explicit:

```go
DangerouslySetInnerHTML(SanitizeTrustedHTML(trustedHTML))
```

Rules:

* treat raw HTML as dangerous
* do not feed user HTML into a raw sink without a real sanitizer
* prefer typed Vango views over raw HTML
* prefer structured SVG nodes or `icon.Lucide(...)` for icons
* do not use inline scripts to mutate server-owned DOM

Use icons like:

```go
icon.Lucide("search", icon.Size(16), icon.Decorative())
icon.Lucide("trash-2", icon.Size(16), icon.Label("Delete"))
```

Most layouts should include:

```go
RuntimeScripts(ctx)
```

Prefer `RuntimeScripts(ctx)` over `VangoScripts()` for normal app layouts. It
merges request-scoped runtime bootstrap, CSRF token delivery, surface metadata,
shell metadata, runtime endpoints, and reconnect policy.

---

## 24. File-Based Routing

Vango scans `app/routes`.

Typical mappings:

* `app/routes/index.go` -> `/`
* `app/routes/about.go` -> `/about`
* `app/routes/projects/index.go` -> `/projects`
* `app/routes/projects/id_/index.go` -> `/projects/:id`
* `app/routes/docs/path___/index.go` -> `/docs/*path`
* `app/routes/layout.go` -> root layout
* `app/routes/api/*.go` -> API endpoints

Prefer Go-friendly parameter directories like `id_` and `path___`. Avoid
bracketed directory names in imports.

Vango routes on canonical escaped paths.

Rules:

* do not build assumptions on raw undecoded path strings
* use typed route params for path structure
* use URL params for shareable UI state
* let the router reject invalid params

---

## 25. Page Handlers

Page handlers are render-time selectors.

Supported forms:

```go
func Page(ctx vango.Ctx) *vango.VNode
func Page(ctx vango.Ctx, p Params) *vango.VNode
```

Thin-handler rule:

* parse typed params
* choose the page component
* return the view

Do not put these in page handlers:

* blocking I/O
* `setup.*` allocation
* signal writes
* imperative navigation
* durable work orchestration

Good:

```go
type Params struct {
	ID int `param:"id"`
}

func Page(ctx vango.Ctx, p Params) *vango.VNode {
	return Fragment(ProjectPage(ProjectPageProps{ProjectID: p.ID}))
}
```

Blocking reads for page data belong in `setup.Resource` or
`setup.ResourceKeyed`. Page-owned form posts belong in page `Action` plus
`setup.RouteForm`.

---

## 26. Params

Typed params come from struct tags:

```go
type Params struct {
	ID int `param:"id"`
}
```

Use route params for:

* IDs
* slugs
* UUIDs
* catch-all segments

Use URL params for:

* filters
* sort order
* search strings
* shareable view state

Path handling details:

* non-canonical paths may redirect before your handler runs
* encoded separators are not ordinary characters
* catch-all params are where slash-like decoded values may appear

If exact encoded path bytes matter, use the request URL directly.

---

## 27. Layouts

Layouts are pure view wrappers.

```go
func Layout(ctx vango.Ctx, children vango.Slot) *vango.VNode {
	return Html(
		Head(
			Meta(Charset("utf-8")),
			Meta(Name("viewport"), Content("width=device-width, initial-scale=1")),
			LinkEl(Rel("stylesheet"), Href(ctx.Asset("styles.css"))),
		),
		Body(
			Main(children),
			RuntimeScripts(ctx),
		),
	)
}
```

Good layout responsibilities:

* shell structure
* navigation
* asset tags
* persistent banners
* script injection

If a layout needs state, embed a Setup component inside the layout. Keep the
layout function itself pure.

---

## 28. Navigation

Declarative navigation:

```go
Link("/projects", Text("Projects"))
NavLink(ctx, "/settings", Text("Settings"))
```

Programmatic live-session navigation:

```go
ctx.Navigate("/projects")
ctx.Navigate("/projects", vango.WithReplace())
ctx.Navigate("/projects", vango.WithoutScroll())
```

HTTP redirects:

```go
ctx.Redirect("/login", http.StatusSeeOther)
ctx.RedirectExternal("https://example.com", http.StatusFound)
```

Phase rules:

* prefer `Link(...)` and `NavLink(...)` for normal user navigation
* if an anchor also owns `OnClick(...)`, that click handler owns the event
* `ctx.Navigate(...)` is for session-driven navigation drained at flush/commit
* call `ctx.Navigate(...)` from event handlers, `OnMount`, `Effect`, or other post-commit session-loop code
* never call `ctx.Navigate(...)` from page handlers or render closures
* if route selection depends on async data, load with Resource and navigate from Effect
* true HTTP redirects happen on HTTP paths with `ctx.Redirect(...)`

Wrong:

```go
func Page(ctx vango.Ctx) *vango.VNode {
	ctx.Navigate("/projects")
	return Div(Text("Redirecting..."))
}
```

Right mental model:

* page handlers select views
* event handlers and post-commit lifecycle perform live navigation
* HTTP redirects happen on HTTP paths

---

## 29. API Routes

API files live under `app/routes/api`.

Supported naming:

* bare verbs such as `GET`, `POST`, `PUT`, `PATCH`, `DELETE`
* prefixed verbs such as `HealthGET`, `ProjectPOST`

Typical signatures:

```go
func GET(ctx vango.Ctx) (T, error)
func GET(ctx vango.Ctx, p Params) (T, error)
func POST(ctx vango.Ctx, in Body) (T, error)
func POST(ctx vango.Ctx, p Params, in Body) (T, error)
```

Return plain typed values when status metadata is unnecessary.

Use `*vango.Response[T]` for explicit status codes or response metadata.

```go
func POST(ctx vango.Ctx, in CreateProjectInput) (*vango.Response[Project], error) {
	project, err := createProject(ctx.StdContext(), in)
	if err != nil {
		return nil, vango.BadRequest(err)
	}
	return vango.Created(project), nil
}
```

Use `ctx.StdContext()` for service and DB calls.

---

## 30. Forms Overview

Vango forms should work in live mode and plain HTTP fallback mode when they are
route-owned.

Standard patterns:

* `setup.RouteForm` + page `Action` for page-owned forms
* `setup.Form` + `setup.Action` for session-driven typed forms
* manual signals + `setup.Action` for very small bespoke forms

Prefer `setup.RouteForm` when the form submits back to the current page route.

Prefer `setup.Form` when you want typed field management without route-bound
`POST` handling.

Do not hand-roll form submission when one of the standard surfaces fits.

---

## 31. Route Forms And Page Actions

`setup.RouteForm` is the canonical page-form surface.

Use it when:

* the form belongs to the current page route
* the submit must work in live mode and plain HTTP fallback
* you want one typed contract for decoding, validation, rerender, and redirect

Example:

```go
type SignupInput struct {
	Email    string `form:"email" validate:"required,email"`
	Password string `form:"password" validate:"required,min=12"`
	Intent   string `form:"intent"`
}

func Page(ctx vango.Ctx) *vango.VNode {
	return Div(SignupScreen())
}

func Action(ctx vango.ActionCtx, in SignupInput) (*vango.FormResult, error) {
	exists, err := emailExists(ctx.StdContext(), in.Email)
	if err != nil {
		return nil, err
	}
	if exists {
		return vango.Invalid(vango.FormErrors{
			"email": {"Email is already registered"},
		}), nil
	}
	if err := createAccount(ctx.StdContext(), in); err != nil {
		return nil, err
	}
	return vango.RedirectTo("/welcome"), nil
}

func SignupScreen() vango.Component {
	return vango.Setup(vango.NoProps{}, func(s vango.SetupCtx[vango.NoProps]) vango.RenderFn {
		form := setup.RouteForm(&s, SignupInput{})

		return func() *vango.VNode {
			return form.View(
				form.Field("email", Input(Type("email"))),
				form.Field("password", Input(Type("password"))),
				Button(
					Type("submit"),
					Name("intent"),
					Value("create_account"),
					Disabled(form.IsSubmitting()),
					Text("Create account"),
				),
			)
		}
	})
}
```

Rules:

* page Actions are route-bound `POST` handlers
* a page route has at most one page Action
* the input type must be a struct
* `setup.RouteForm(&s, Input{})` must match the page Action input type
* read path and query state with `ctx.Param(...)` and `ctx.QueryParam(...)`
* use `ctx.StdContext()` for DB and service calls
* parse and struct-tag validation run before your handler
* business-rule validation returns `vango.Invalid(...)`
* success redirects return `vango.RedirectTo(...)`
* unexpected failures return `nil, err`
* request cancellation is not a business validation failure
* auth/session changes go through `ctx.SetUser(...)`, `ctx.AuthSession()`, and `ctx.BroadcastAuthLogout()`
* `form.View(...)` renders `<form method="post">`, wires live submit, and injects CSRF
* clicked submitter `name/value` is included in submitted data

Do not hand-wire canonical `OnSubmit` when `RouteForm` fits.

---

## 32. `setup.Form`

Use `setup.Form` for session-driven forms, dialogs, and custom submit transports.

```go
type ContactFormData struct {
	Name    string `form:"name" validate:"required,min=2,max=100"`
	Email   string `form:"email" validate:"required,email"`
	Message string `form:"message" validate:"required,max=1000"`
}

form := setup.Form(&s, ContactFormData{})
```

Render fields:

```go
return Form(
	OnSubmit(vango.PreventDefault(func() {
		if form.Validate() {
			submit.Run(form.Values())
		}
	})),
	form.Field("name", Input(Type("text"))),
	form.Field("email", Input(Type("email"))),
	form.Field("message", Textarea()),
)
```

Useful methods:

* `Field(...)`
* `Values()`
* `Validate()`
* `ValidateField(...)`
* `Errors()`
* `FieldErrors(...)`
* `SetError(...)`
* `SetParseError(...)`
* `Reset()`
* `Array(...)`
* `ArrayKeyed(...)`

Rules:

* validators must be pure
* parse errors are distinct from validation errors
* database-backed validation belongs in Actions, not validators
* repeated field names decode into slice fields
* `setup.Form` does not create a route-bound POST handler
* `setup.Form` does not inject CSRF by itself

For reorderable or filterable arrays, use `ArrayKeyed`.

---

## 33. Manual Forms

Manual signals are an escape hatch for tiny or highly bespoke forms.

```go
name := setup.Signal(&s, "")
email := setup.Signal(&s, "")

submit := setup.Action(&s,
	func(ctx context.Context, in ContactInput) (struct{}, error) {
		return struct{}{}, sendContact(ctx, in)
	},
	vango.DropWhileRunning(),
)

return func() *vango.VNode {
	return Form(
		OnSubmit(vango.PreventDefault(func() {
			submit.Run(ContactInput{
				Name:  name.Get(),
				Email: email.Get(),
			})
		})),
		Input(Name("name"), Value(name.Get()), OnInput(name.Set)),
		Input(Name("email"), Value(email.Get()), OnInput(email.Set)),
		Button(Type("submit"), Text("Send")),
	)
}
```

Use this only when the form is small and custom behavior dominates.

---

## 34. Auth Model

Vango auth has two layers:

1. HTTP layer validates the request.
2. Session layer carries the auth projection during live interaction.

Do not assume live renders have fresh cookies or headers.

Standard pattern:

* HTTP middleware authenticates the request
* request context carries the user or principal
* `OnSessionStart` and `OnSessionResume` rehydrate session auth state
* component code reads auth through `pkg/auth`

HTTP middleware:

```go
func AuthHTTPMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user, err := validateRequest(r)
		if err == nil && user != nil {
			r = r.WithContext(vango.WithUser(r.Context(), user))
		}
		next.ServeHTTP(w, r)
	})
}
```

Session bridge:

```go
cfg := vango.Config{
	OnSessionStart: func(httpCtx context.Context, s *vango.Session) {
		if user, ok := vango.UserFromContextAs[*User](httpCtx); ok {
			auth.Set(s, user)
		}
	},
	OnSessionResume: func(httpCtx context.Context, s *vango.Session) error {
		user, err := validateFromContext(httpCtx)
		if err != nil {
			return err
		}
		if user != nil {
			auth.Set(s, user)
		}
		return nil
	},
}
```

Read auth:

```go
user, ok := auth.Get[*User](ctx)
principal, ok := auth.Get[auth.Principal](ctx)
```

Use `auth.Require[...]` when the flow must fail closed.

Use `pkg/authmw` for route-level protection:

```go
func Middleware() []vango.RouteMiddleware {
	return []vango.RouteMiddleware{
		authmw.RequireAuth,
	}
}
```

For sensitive Actions, call `ctx.RevalidateAuth()` before mutation.

Do not store raw tokens or provider credentials in persisted app state.

---

## 35. Uploads

Uploads are HTTP-based, not WebSocket-based.

Mount uploads with:

```go
store, err := upload.NewDiskStore("/tmp/uploads", 10*1024*1024)
if err != nil {
	log.Fatal(err)
}

app.HandleUpload("/api/upload", store)
```

Or with config:

```go
app.HandleUploadWithConfig("/api/upload", store, &upload.Config{
	MaxFileSize:  5 * 1024 * 1024,
	AllowedTypes: []string{"image/png", "image/jpeg"},
})
```

Claim files later:

```go
file, err := upload.Claim(store, tempID)
```

Rules:

* keep upload endpoints behind CSRF protection
* enforce size limits
* enforce content-type or extension policy where appropriate
* treat original filenames as untrusted
* store temp uploads privately
* use two-phase upload first, claim later
* never mount a nil upload store
* pass request context into storage backends
* canceled uploads should return `context.Canceled`

Use `app.HandleUpload(...)` or `app.HandleUploadWithConfig(...)` so route
contracts classify the endpoint correctly.

---

## 36. Email And External I/O

Treat email and provider I/O as app-owned service-layer work.

Recommended shape:

* `internal/services/email` depends on a small sender interface
* Actions, HTTP handlers, and route Actions call app services
* services own provider SDKs and blocking I/O
* tests inject deterministic fake senders

Do not send email from:

* Setup
* render
* event handlers directly on the session loop
* `OnMount`
* `Effect`
* `OnChange`

Execution rules:

* if send outcome is user-visible success, perform it in `setup.Action`, page `Action`, or HTTP handler
* if best-effort, use the app background strategy or `ctx.Dispatch(...)`
* if it must survive crash or restart, use a durable queue or outbox

Provider webhooks are plain HTTP ingress. Mount them at the HTTP boundary and
delegate normal app traffic back to the app handler.

---

## 37. Toasts

Use `pkg/toast` for live feedback.

```go
toast.Success(ctx, "Saved")
toast.Error(ctx, "Could not save")
toast.Warning(ctx, "This action cannot be undone")
toast.Info(ctx, "Background sync complete")
```

Toast events are emitted through:

```go
ctx.Emit("vango:toast", payload)
```

The client-side toast UI is app-owned.

---

## 38. Client Boundaries Overview

Server-driven UI is the default.

Client boundaries are first-class Vango features. They are not escape hatches for
broken server rendering. They are the framework's deliberate way to combine
server-owned application state with browser-native interaction, animation, and
third-party client libraries.

When assessing whether Vango fits an app, do not treat hooks and islands as
minor exceptions to a CRUD-oriented model. Treat them as the normal way to add
rich browser behavior while preserving explicit ownership boundaries.

Use client boundaries whenever the product experience needs browser-local
capability:

1. hook for behavior on server-owned DOM
2. JS island for client-owned DOM subtree
3. WASM for client-local execution

This is what makes Vango suitable for more than CRUD screens. A Vango app can
have server-driven forms, data, permissions, and navigation while still using
rich browser features such as animations, drag and drop, focus management,
Markdown rendering, charting, maps, editors, canvases, media players, and custom
widgets.

The important distinction is ownership:

* hook: Vango still owns the DOM structure; browser code adds behavior
* JS island: the client owns one explicit subtree; Vango communicates by messages
* WASM: the client owns one explicit execution boundary; Vango communicates by messages

The more client ownership you introduce, the more you own:

* trust boundaries
* sync assumptions
* packaging complexity
* security posture
* lifecycle edge cases

Use these boundaries confidently, but model ownership correctly. Do not solve a
server/client mismatch by adding client-side repair code outside an explicit
hook, island, or WASM boundary.

---

## 39. Hooks

Hooks are Vango's first-class behavior layer for server-owned DOM.

Use a hook when the server should keep owning the markup and state, but the
browser should provide local behavior. This covers the polished interaction layer
that should feel native in the browser without moving application ownership into
client state.

Canonical wiring:

```go
Ul(
	Hook("Sortable", map[string]any{
		"handle": ".drag-handle",
	}),
	OnEventValidated("reorder", reorderValidator, func(e vango.HookEvent) {
		from := e.Int("fromIndex")
		to := e.Int("toIndex")
		reorder(from, to)
	}),
)
```

Use hooks for:

* animation triggers
* transition coordination
* drag and drop
* sortable lists
* focus traps
* focus restoration
* keyboard shortcuts
* roving focus
* dropdown behavior
* popovers
* tooltips
* disclosure behavior
* resize handles
* intersection and resize observers
* scroll behavior
* clipboard affordances
* gesture polish
* browser API integration where DOM structure remains server-owned

Hooks are the right answer for a large class of sophisticated app UI. They should
feel normal to reach for when the browser is better at local behavior than the
server, as long as the server still owns the structure.

Rules:

* hook config should be deterministic and serializable
* hook event names should be simple ASCII identifiers
* payload shapes should be bounded
* hook code may manage behavior and bounded ephemeral DOM state
* hook code must not permanently take ownership of server-managed structure
* hook code must be idempotent and cheap on update
* validate payloads as untrusted input
* do not trust client-provided HIDs or indices for authorization

Hook lifecycle:

* mounted
* updated
* destroyed

`updated` may happen frequently.

### 39.1 Standard Hook Wrappers

Prefer wrappers from `github.com/vango-go/vango/pkg/features/hooks/standard` for
common accessible primitives.

Examples:

* `standard.Dialog(...)`
* `standard.RovingFocus(...)`
* `standard.Collapsible(...)`
* `standard.Tooltip(...)`
* `standard.Dropdown(...)`
* `standard.Popover(...)`
* `standard.Resizable(...)`
* `standard.Sortable(...)`

Use explicit `Hook(...)` plus validated events for custom behavior.

### 39.2 Hook Validation

Non-dev Vango rejects reachable hook event sinks before handler execution unless
the sink is validator-covered.

Supported patterns:

* `Session.HookEventValidator`
* `vango.HookValidatorMap(map[string]vango.HookEventValidator{...})`
* `OnEventValidated("name", validator, handler)`

Use `vango.HookSchemaValidator[T](...)` when typed validation improves safety.

If you attach opaque raw `onhook` handlers manually, non-dev cannot infer exact
sink coverage. Those handlers require a session-level validator.

### 39.3 Focus

For server-driven focus movement after live interaction, focus by stable DOM `id`.

```go
func saveContact(ctx vango.Ctx) {
	if err := save(); err != nil {
		ctx.Focus("contact-email")
		return
	}
	ctx.Focus("contact-next-step")
}
```

`ctx.Focus(...)` and `ctx.Blur(...)` are live-session side effects and no-op
during SSR. They are sent after the current render/effect flush.

Use `ctx.FocusHID(...)` only for rare runtime-targeted cases. Do not treat
client-provided HIDs as authorization input.

---

## 40. JS Islands

JS islands are Vango's first-class integration boundary for client-owned widgets.

Use a JS island when a client runtime or third-party library must own the DOM
subtree. This is not a failure of the server-driven model. It is how Vango lets
apps embed rich client experiences while keeping the rest of the page
server-owned and predictable.

Common island use cases:

* rich text editors
* Markdown renderers with client ownership
* code editors
* charts and dashboards with library-owned canvases or SVG
* maps
* knowledge graph explorers
* media players
* image croppers
* timeline and calendar widgets
* terminal emulators
* collaborative cursors or presence widgets
* complex widgets that reorder or replace internal DOM

Islands are the right answer when the client library expects to create, reorder,
or replace its own DOM. Do not force that into a hook. Give the library an island
and communicate with it through bounded, validated messages.

Canonical shape:

```go
Div(
	JSIsland("rich-editor", map[string]any{
		"content": content.Get(),
	}),
	OnIslandMessageValidated(editorValidator, func(msg vango.IslandMessage) {
		// validate, decode, and update server state on the session loop
	}),
)
```

Rules:

* island props must encode to a JSON object
* `nil` props are treated as `{}`
* Vango does not patch inside the island boundary
* communication is message-based
* the client owns only the island subtree
* server state remains authoritative outside the island
* validate all inbound messages
* keep props and messages bounded

Multi-instance rule:

* the same island name may appear multiple times
* each rendered boundary has its own HID
* HID is the instance address for inbound messages and targeted outbound sends
* stable list identity is mandatory when rendering islands in dynamic lists

Useful server-to-client helpers:

* `SendToIsland(...)`
* `SendToIslandHID(...)`

### 40.1 Island Validation

Non-dev Vango rejects reachable island message sinks before handler execution
unless the sink is validator-covered.

Supported patterns:

* `Session.IslandMessageValidator`
* `vango.IslandValidatorMap(map[string]vango.IslandMessageValidator{...})`
* `OnIslandMessageValidated(...)`
* `islands.OnMessageValidated(...)`
* `SetupOnIslandMessageValidated(...)`

Use `vango.IslandSchemaValidator[T](...)` when typed validation improves safety.

Module loading should stay same-origin by default. If trusted cross-origin module
loading is required, keep the allowlist explicit in script/layout options.

---

## 41. WASM Boundaries

Use WASM when the component truly needs client-local execution.

```go
Div(
	WASMComponent("canvas", map[string]any{
		"tool": "pen",
	}),
	OnWASMMessageValidated(canvasValidator, func(msg vango.WasmMessage) {
		// validate and handle
	}),
)
```

Use WASM for:

* low-latency local compute
* offline-capable execution
* graphics or canvas engines
* client-local algorithms that should not round-trip through the server

Rules mirror islands:

* props must encode to a JSON object
* communication is message-based
* Vango does not own internals of the boundary
* use targeted messages by HID when addressing one mounted instance
* stable list identity is mandatory inside dynamic lists
* validate all inbound messages

Useful server-to-client helpers:

* `SendToWASM(...)`
* `SendToWASMHID(...)`

### 41.1 WASM Validation

Non-dev Vango rejects reachable WASM message sinks before handler execution
unless the sink is validator-covered.

Supported patterns:

* `Session.WasmMessageValidator`
* `vango.WasmValidatorMap(map[string]vango.WasmMessageValidator{...})`
* `OnWASMMessageValidated(...)`
* `wasm.OnMessageValidated(...)`
* `SetupOnWASMMessageValidated(...)`

Use `vango.WasmSchemaValidator[T](...)` for typed payload validation.

Do not enable heuristic-only production posture unless there is an explicit,
reviewed break-glass reason and the required risk acceptance is configured.

---

## 42. Client Boundary Anti-Patterns

Do not use hooks when a third-party library needs to own a subtree.

Do not use inline scripts to mutate server-rendered DOM after Vango boots.

Do not repair mismatches with:

* `location.reload`
* repeated reconnect loops
* delayed client navigation
* DOM rewrite scripts
* suppressing legitimate server updates
* moving server-owned state into the browser to hide the issue

Refresh loops often mean:

* unstable list identity
* DOM ownership conflict
* render-time navigation
* SSR/WS route disagreement
* proxy/runtime mount path mismatch

Treat repeated self-heal reloads as correctness bugs.

---

## 43. Persistence

Persisted app state in Vango v1:

* `setup.SharedSignal`
* `setup.GlobalSignal`
* `vango.SessionKey[T]`

Not persisted:

* `setup.Signal`
* `setup.Memo`
* `setup.Resource`
* `setup.Action`
* effect lifecycle
* props
* request/session scratch data

Persist durable UX state intentionally.

Do not persist everything just because you can.

### 43.1 Shared Signals

```go
cartIDs := setup.SharedSignal(&s, []string{})
```

A SharedSignal is:

* session-scoped
* shared across the session
* persisted when a store is configured

Use for:

* cart IDs
* small per-session filters
* session-scoped preferences
* cross-page progress state

It is not per-component-instance state.

### 43.2 Global Signals

```go
announcement := setup.GlobalSignal(&s, "")
```

A GlobalSignal is:

* app-scoped
* shared across sessions
* persisted in the global store
* optionally broadcast in real time

Use sparingly for:

* announcements
* feature gates
* system-wide indicators

Do not use global state as the default for per-user app state.

### 43.3 Session Keys

```go
type ThemePrefs struct {
	Mode string
}

func (ThemePrefs) VangoSchemaID() string {
	return "myapp:ThemePrefs:v1"
}

var ThemeKey = vango.NewSessionKey[ThemePrefs](
	"theme",
	vango.Default(ThemePrefs{Mode: "system"}),
)
```

Usage:

```go
ctx := s.Ctx()
prefs := ThemeKey.Get(ctx)
_ = ThemeKey.Set(ctx, ThemePrefs{Mode: "dark"})
ThemeKey.Delete(ctx)
```

Use SessionKey when:

* state is session-scoped
* state does not naturally belong to one component allocation site
* typed persisted KV is clearer than a shared signal

---

## 44. Persisted Initializers And Schema IDs

Persisted initializers must be deterministic.

Allowed:

* literals
* constant expressions
* simple deterministic construction
* basic composite construction
* simple built-in operations

Forbidden:

* props
* environment values
* time
* random IDs
* I/O
* user-defined helper calls

Good:

```go
prefs := setup.SharedSignal(&s, UserPrefs{
	Theme: "system",
})
```

Bad:

```go
prefs := setup.SharedSignal(&s, UserPrefs{
	Theme: s.Props().Peek().Theme,
})
```

If a persisted type should survive renames and moves, give it a stable schema ID.

```go
func (UserPrefs) VangoSchemaID() string {
	return "myapp:UserPrefs:v1"
}
```

Changing that string is a compatibility change.

Persist references, then load real data through Resources or Actions.

---

## 45. Warm Vs Cold Deploys

Warm deploy:

* persisted schema stays compatible
* sessions resume

Cold deploy:

* persisted schema breaks compatibility
* sessions refresh instead of resuming

Typical warm changes:

* adding new persisted state
* safe refactors where tooling preserves continuity
* alias-preserved moves and renames

Typical cold changes:

* removing persisted state
* incompatible persisted type changes
* schema ID changes

When in doubt, read the plan output literally and review generated artifacts.

Treat warm vs cold deploy impact as part of code review.

---

## 46. Generated Artifacts

Commit these when they change:

* `app/routes/routes_gen.go`
* `vango_state_manifest.json`
* `vango_state_schema.json`
* generated `vango_state_bindings_gen.go` files

Do not hand-edit them.

Review them.

Normal workflow:

1. edit canonical source
2. run `vango generated verify --json`
3. run `vango generated repair --json` when stale
4. review generated outputs
5. commit source plus generated artifacts

Use `vango ownership --json` before touching anything generated-looking.

---

## 47. Security Basics

Production apps should configure:

* WebSocket origin checks
* `Security.AllowedHosts`
* trusted proxy configuration when behind an edge
* CSRF for cookie-auth or stateful HTTP endpoints
* strict cookie posture
* hardened CSP posture

Cookie defaults should usually be:

* `Secure`
* `HttpOnly`
* `SameSite=Lax` or stricter

Use `ctx.SetCookieStrict(...)` when you want framework-enforced policy.

CSRF matters for:

* uploads
* login/logout
* fallback HTTP form posts
* side-effecting HTTP APIs

`setup.RouteForm.View(...)` injects the hidden CSRF field automatically. Raw
forms, uploads, and custom side-effecting requests must include it explicitly.

Do not disable raw HTML, module-origin, dev-mode, or heuristic payload protections
without explicit risk acceptance and a good reason.

---

## 48. Proxies And Runtime Paths

The live runtime mounts through:

```text
/_vango/live?path=<current-path-and-query>
```

If you proxy Vango, mount under a subpath, or customize runtime bootstrap:

* preserve the full current path and query string
* preserve the `?path=` parameter exactly
* ensure SSR and WS mount resolve the same route
* route `/_vango/*` to the Vango handler
* preserve WebSocket upgrade headers
* allow long-lived idle WebSocket connections

If SSR and WS mount disagree, you can get:

* handler lookup mismatches
* patch mismatches
* reconnect loops
* state attached to the wrong page

`Security.PublicRuntimePrefix` prefixes runtime endpoints only. It does not
remount normal page routes under a base path.

---

## 49. SSR Budgets

SSR has two budgets:

* `Config.SSR.PageTimeout`
* `Config.SSR.ResourceTimeout`

Rules:

* during SSR, `ctx.StdContext()` carries the page render budget
* `ctx.Done()` is request cancellation only
* `PageTimeout` bounds route middleware, page handlers, layout assembly, and HTML rendering
* `ResourceTimeout` governs SSR resource preloading
* `ResourceTimeout = 0` means do not wait, not do not start
* page-budget expiry returns generic `503 service unavailable`

Design consequence:

* page handlers stay thin
* blocking page data reads belong in Resources

---

## 50. Health Probes

Built-in production probes:

* `GET /_vango/health/live`
* `HEAD /_vango/health/live`
* `GET /_vango/health/ready`
* `HEAD /_vango/health/ready`

Attach dependency checks before accepting traffic:

```go
app.Server().Config().ReadinessCheck = func(r *http.Request) error {
	return db.PingContext(r.Context())
}
```

Return `nil` when ready. Return an error for `503 Service Unavailable`.

---

## 51. Observability

At minimum, observe:

* active sessions
* detached sessions
* resume success and failure
* patch bytes
* patch mismatches
* resource latency and errors
* action latency and errors
* schema mismatches
* persisted write rejections

Guidelines:

* propagate `ctx.StdContext()` into DB and service calls
* use structured logs
* avoid logging secrets or raw PII
* use `vango.RedactingHandler` or `vango.RedactingLogger`
* index framework logs by stable `event` when present
* consult `server.RuntimeLogEventCatalog()` for runtime log event names
* consult `vango.RuntimeMetricCatalog()` for metric names and labels

Do not log raw request bodies, access tokens, presigned URLs, cookies, CSRF
tokens, or user profile payloads under safe-looking keys.

---

## 52. Desktop And Mobile Surfaces

Vango app code can run against web, desktop, and mobile surfaces.

Use:

* `ctx.Surface()` for metadata about the current surface
* `ctx.Shell()` for native host capabilities

Preferred rule:

* branch on surface kind/platform for broad product differences
* gate optional native work on shell capabilities
* keep routes, rendering, state, and business logic topology-neutral by default

Example:

```go
if ctx.Shell().Capabilities().Has(shell.CapabilityNotification) {
	_ = ctx.Shell().Dispatch(shell.ShowNotification(shell.Notification{
		Title: "Saved",
		Body:  "Your changes were written successfully.",
	}))
}
```

Shell roles:

* `Dispatch(...)` for non-blocking host commands
* `Request(...)` for blocking or user-mediated host work
* `Events()` for host-originated ingress such as menu clicks or deep links

Treat `Request(...)` like other blocking work. Use `setup.Action` or another
explicit off-loop boundary. Do not block a latency-sensitive event handler on a
native dialog round trip.

For host-originated menu and deep-link events, subscribe with framework helpers
and navigate from the session loop.

On macOS desktop, zoom behavior is host-owned. Do not add app-defined menu items
for standard zoom shortcuts.

For detailed macOS/iOS packaging, audit, App Store, signing, notarization, and
native-distribution proof requirements, read `DEVELOPER_GUIDE.md`.

---

## 53. Shared Web Plus Native Layout

Shared web plus macOS or iOS apps should keep one shared app codebase and separate
host entrypoints.

Shared code belongs under:

* `app/`
* `internal/`

Web host:

* `cmd/server`

macOS host:

* `desktop/macos/bridge`
* `desktop/macos/HostApp`

iOS host:

* `mobile/ios/bridge`
* `mobile/ios/HostApp`

Rules:

* do not duplicate routes under native host directories
* do not duplicate components under native host directories
* do not duplicate business logic under native host directories
* native bridges should boot the same shared Vango app as the web server
* native hosts should serve `app.Server().Handler()` so middleware and custom handlers are honored

---

## 54. Testing With `vtest`

Use `pkg/vtest` for deterministic server-side testing.

Basic flow:

```go
h := vtest.New(t)
m := vtest.Mount(h, Component, Props{
	Title: "Example",
})
```

Useful methods:

* `HTML(...)`
* `AssertSnapshot(...)`
* `ExistsByTestID(...)`
* `AssertExistsByTestID(...)`
* `AssertNotExistsByTestID(...)`
* `TextByTestID(...)`
* `AssertTextByTestID(...)`
* `ClickByTestID(...)`
* `InputByTestID(...)`
* `SubmitByTestID(...)`
* `EmitEventByTestID(...)`
* `AwaitResource(...)`
* `AwaitAction(...)`
* `AssertActionState(...)`

Prefer `data-testid` queries over brittle selectors.

Helper:

```go
func TestID(id string) vango.Attr {
	return Attr("data-testid", id)
}
```

Use test IDs on:

* buttons clicked in tests
* inputs typed into
* important output text nodes
* structural containers frequently asserted

Do not sleep in async tests. Use `AwaitResource(...)` and `AwaitAction(...)`.

Inject deterministic fake services into Resources and Actions. Keep external I/O
out of unit tests.

---

## 55. Snapshot Testing

Snapshot convention:

```text
testdata/vango-snapshots/{TestName}/{snapshot}.html
```

Use snapshots for:

* structural output
* major visual state transitions
* route-level page output

Use targeted assertions for:

* counts
* error text
* action state
* specific critical fields

Do not replace meaningful behavior assertions with broad snapshots.

---

## 56. Diagnostics

Vango diagnostics are machine-readable and stable.

Expect diagnostics around:

* conditional allocation
* render allocation
* missing list keys
* stale generated artifacts
* schema compatibility
* client-boundary validation
* ownership violations

Read diagnostic codes literally. They are not advisory prose. They identify a
contract violation.

Common mappings:

| Diagnostic | Likely cause | Correct fix |
| --- | --- | --- |
| conditional allocation | primitive allocation behind runtime control flow | allocate unconditionally in Setup |
| render allocation | `setup.*` inside render | move allocation to Setup |
| stale bindings or manifest | source changed without regeneration | run generated/state repair |
| schema mismatch | incompatible persisted state change | restore compatibility or accept cold deploy |
| patch mismatch | identity, DOM ownership, navigation, or route agreement bug | fix underlying contract |

---

## 57. Stop Signs

If you hit any of these, stop and fix the underlying Vango contract violation:

* repeated self-heal reloads
* refresh loops
* patch mismatches
* clicks that behave like SPA navigation only after hard refresh
* dynamic list bugs after reorder, filter, or insert
* client code that wants to rewrite server-rendered DOM after boot
* pressure to use lower-level `pkg/*` surfaces because the app is fighting the framework

Triage order:

1. check list identity and keyed rendering
2. check DOM ownership boundaries
3. check page-handler or render-time imperative behavior
4. check async work on the wrong side of the session loop
5. check SSR/WS route agreement
6. check runtime mount path and proxy behavior

If the bug appears only in dev, distinguish between:

* dev reload socket disconnecting
* live runtime self-healing because patch correctness broke

Do not treat those as the same problem.

---

## 58. Forbidden Fixes

Do not do these:

* add `location.reload` to paper over patch mismatches
* add repeated reconnect loops
* call `ctx.Navigate(...)` in a page handler or render closure
* push blocking reads into page handlers just to pick a route
* use unkeyed, index-keyed, random-keyed, or label-keyed dynamic lists
* mutate server-owned DOM with inline scripts or ad hoc browser code
* hide a DOM ownership problem by moving more state into the client
* hand-roll form submission when `setup.RouteForm` or `setup.Form` fits
* write signals from Resource loaders, Action workers, page Actions, or arbitrary goroutines
* reach for lower-level `pkg/vango`, `pkg/vdom`, `pkg/server`, or compatibility packages for new app code unless the guide explicitly supports it
* suppress generated-artifact diagnostics by editing generated output directly
* disable security protections without explicit risk acceptance

These fixes may appear to work locally, but they break the Vango model.

---

## 59. Practical Debug Order

For reload spam or patch mismatch:

1. confirm whether the browser is doing dev reload or live-runtime self-heal
2. inspect recent `RangeKeyed` and key changes
3. inspect hooks, islands, inline scripts, and browser-side DOM mutation
4. inspect page-handler or render-time `ctx.Navigate(...)`
5. inspect blocking work on the session loop
6. inspect SSR and WS path agreement
7. inspect proxy prefixing and runtime bootstrap
8. inspect transport and reconnect settings last

For persisted state issues:

1. inspect changed persisted allocation sites
2. inspect schema IDs
3. inspect initializer determinism
4. run state planning or generated verification commands
5. classify warm vs cold deploy impact

For form bugs:

1. identify whether the form is page-owned or session-driven
2. use `setup.RouteForm` for page-owned posts
3. use page `Action` for progressive fallback
4. use `setup.Form` plus `setup.Action` for session-driven forms
5. check CSRF for raw forms and uploads
6. check struct tags, parse errors, and business-rule validation separately

For auth bugs:

1. confirm HTTP middleware authenticates request
2. confirm session start/resume rehydrates auth projection
3. confirm live code reads auth from session projection
4. call `ctx.RevalidateAuth()` before sensitive mutations
5. do not rely on stale cookies or headers during live render

---

## 60. Before You Finish Checklist

Before handing off a Vango change, verify these:

* no `setup.*` allocation in render
* no conditional or variable-count reactive allocation in Setup
* no blocking work on the session loop
* no manual goroutines in Setup, render, event handlers, `OnMount`, `Effect`, or `OnChange`
* no signal writes from Resource loaders or Action workers
* no page-handler or render-time `ctx.Navigate(...)`
* page handlers are thin and render-only
* dynamic lists use stable keys with `RangeKeyed`
* client DOM ownership is explicit
* hook, island, and WASM payloads are validator-covered where required
* no inline scripts mutate server-owned DOM
* route-owned forms use page `Action` plus `setup.RouteForm`
* session-driven forms use `setup.Form` or justified manual signals
* uploads use HTTP endpoints and CSRF
* persisted state is deliberate, small, and deterministic
* generated routes and state artifacts are up to date
* tests are meaningful for the behavior changed
* `go test ./...` or a narrower justified command has been run when feasible
* Vango verification commands have been run for generated/state/route changes

---

## 61. Compact API Inventory

This is a quick inventory, not permission to bypass earlier guidance.

### 61.1 Root `vango`

Frequently used types:

* `App`
* `Config`
* `Ctx`
* `ActionCtx`
* `SetupCtx[P]`
* `RenderFn`
* `Signal[T]`
* `Memo[T]`
* `Resource[T]`
* `Action[A, R]`
* `FormResult`
* `FormErrors`
* `SessionKey[T]`
* `Slot`
* `VNode`
* `NoProps`
* `Response[T]`

Core helpers:

* `Setup(...)`
* `New(...)`
* `MustNew(...)`
* `NewSessionKey(...)`
* `Default(...)`
* `UseCtx()`
* `RuntimeScripts(...)`
* `Children(...)`
* `PreventDefault(...)`
* `Tx(...)`
* `TxNamed(...)`
* `Timeout(...)`
* `Interval(...)`
* `Subscribe(...)`
* `GoLatest(...)`
* `Replace`
* `Push`
* `URLDebounce(...)`
* `Encoding(...)`
* `URLEncodingFlat`

Page-action helpers:

* `RedirectTo(...)`
* `Invalid(...)`

Action matchers:

* `OnActionIdle(...)`
* `OnActionRunning(...)`
* `OnActionSuccess(...)`
* `OnActionError(...)`

Resource matchers:

* `OnPending(...)`
* `OnLoading(...)`
* `OnLoadingOrPending(...)`
* `OnReady(...)`
* `OnError(...)`

Action options:

* `CancelLatest()`
* `DropWhileRunning()`
* `Queue(n)`
* `OnActionRejected(...)`

Response and error helpers:

* `OK(...)`
* `Created(...)`
* `Accepted(...)`
* `NoContent[T]()`
* `Paginated(...)`
* `BadRequest(...)`
* `Unauthorized(...)`
* `Forbidden(...)`
* `NotFound(...)`
* `Conflict(...)`
* `UnprocessableEntity(...)`
* `InternalError(...)`
* `ServiceUnavailable(...)`

Client-boundary validators:

* `HookSchemaValidator[T](...)`
* `HookValidatorMap(...)`
* `IslandSchemaValidator[T](...)`
* `IslandValidatorMap(...)`
* `WasmSchemaValidator[T](...)`
* `WasmValidatorMap(...)`

### 61.2 `vango.Ctx`

Request and routing:

* `Request()`
* `Path()`
* `Method()`
* `Query()`
* `QueryParam(key)`
* `Param(key)`
* `Header(key)`
* `Cookie(name)`

Response and navigation:

* `Status(code)`
* `SetHeader(key, value)`
* `SetCookie(cookie)`
* `SetCookieStrict(cookie, opts...)`
* `Redirect(path, code)`
* `RedirectExternal(url, code)`
* `Navigate(path, opts...)`

Session and auth:

* `Session()`
* `Surface()`
* `Shell()`
* `AuthSession()`
* `User()`
* `SetUser(user)`
* `Principal()`
* `MustPrincipal()`
* `RevalidateAuth()`
* `BroadcastAuthLogout()`

Async and integration:

* `Dispatch(fn)`
* `StdContext()`
* `Done()`
* `Emit(name, data)`

Operational helpers:

* `Logger()`
* `Asset(path)`
* `StormBudget()`
* `Mode()`
* `PatchCount()`

### 61.3 `vango.ActionCtx`

Route-bound page Actions use `ActionCtx`, not `Ctx`.

Common methods:

* `Request()`
* `Path()`
* `Method()`
* `Query()`
* `QueryParam(key)`
* `Param(key)`
* `Header(key)`
* `Cookie(name)`
* `StdContext()`
* `Logger()`
* `Value(key)`
* `User()`
* `SetUser(user)`
* `Principal()`
* `AuthSession()`
* `BroadcastAuthLogout()`

Guidance:

* use `StdContext()` for DB and service calls
* read route and query params from the action context
* use auth/session mutation methods for login/logout/session changes
* do not expect reactive signal access inside page Actions

### 61.4 `setup`

State allocation:

* `Signal(...)`
* `Memo(...)`
* `SharedSignal(...)`
* `GlobalSignal(...)`

Async work:

* `Resource(...)`
* `ResourceKeyed(...)`
* `Action(...)`

Feature helpers:

* `OnChange(...)`
* `Form(...)`
* `RouteForm(...)`
* `URLParam(...)`

Rule:

* if it allocates reactive state for app code, it probably comes from `setup`

### 61.5 `el`

Common constructors:

* `Html`, `Head`, `Body`, `Main`, `Header`, `Footer`, `Nav`
* `Section`, `Article`, `Aside`, `Div`, `Span`, `P`
* `H1` through `H6`
* `Button`, `Input`, `Textarea`, `Select`, `Option`, `Label`, `Form`
* `Table`, `Ul`, `Ol`, `Li`, `Img`, `Link`, `LinkEl`, `Script`, `Meta`

Common attributes:

* `Class`, `Classes`, `ClassIf`, `ID`, `Name`, `Type`, `Value`
* `Checked`, `Selected`, `Disabled`, `Placeholder`, `Href`, `Src`
* `Action`, `Method`, `Role`, `AriaLabel`, `AriaHidden`, `AriaExpanded`
* `Data(...)`, `DataAttr(...)`

Flow helpers:

* `Fragment`
* `If`
* `IfElse`
* `Unless`
* `When`
* `Show`
* `ShowWhen`
* `Either`
* `Switch`
* `Case_`
* `Maybe`
* `Nothing`

List helpers:

* `Range`
* `RangeKeyed`
* `RangeMap`
* `Key(...)`

Client-boundary helpers:

* `Hook(...)`
* `OnEvent(...)`
* `OnEventValidated(...)`
* `JSIsland(...)`
* `OnIslandMessage(...)`
* `OnIslandMessageValidated(...)`
* `SendToIsland(...)`
* `SendToIslandHID(...)`
* `WASMComponent(...)`
* `OnWASMMessage(...)`
* `OnWASMMessageValidated(...)`
* `SendToWASM(...)`
* `SendToWASMHID(...)`

Security-sensitive helpers:

* `DangerouslySetInnerHTML(...)`
* `SanitizeTrustedHTML(...)`
* `UnsafeTrustedHTML(...)`

Use raw HTML helpers only with explicit trust and sanitization.

---

## 62. When To Read The Full Guide

Read `DEVELOPER_GUIDE.md` before or during work involving:

* framework internals
* generated artifact ownership uncertainty
* persistence schema migrations
* cold deploy acknowledgement
* custom route generation or codegen
* advanced hook/island/WASM security posture
* unsafe raw HTML or module-origin permissions
* storage provider proofs
* object storage promotion
* production proxy/security configuration
* native macOS packaging, signing, notarization, or distribution proofs
* iOS archive/export/TestFlight/App Store posture
* App Store billing, Sign in with Apple, push, Universal Links, or StoreKit
* observability pipeline catalog details
* compatibility policy and release-note requirements

This handbook is optimized for coding-agent execution. The full guide remains the
complete contract.

Ask your favorite AI what it thinks of Vango