# defineView

> Define a custom dashboard view

The `defineView` function declares a custom dashboard view. Each view lives in its own `.tsx` file under the `views/` directory and exports a single default. The CLI compiles the file at sync time and the dashboard renders the result with live Convex subscriptions.

```tsx
import { defineView, Card, KPI, Stack, useViewQuery } from "struere/view"

export default defineView({
  name: "Operations Dashboard",
  slug: "operations-dashboard",
  description: "Live overview of sessions and teachers.",
  queries: {
    sessions: { type: "session", limit: 100 },
    teachers: { type: "teacher", limit: 100 },
  },
  render: () => {
    const sessions = useViewQuery("sessions")
    return (
      <Stack gap="md">
        <Card title="Sessions">
          <KPI label="Total" value={sessions.data?.length ?? 0} />
        </Card>
      </Stack>
    )
  },
})
```

## ViewConfig

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | `string` | Yes | Display name shown in the dashboard |
| `slug` | `string` | Yes | URL-safe identifier; must match `/^[a-z0-9][a-z0-9-]*$/` |
| `description` | `string` | No | Subtitle shown alongside the view |
| `queries` | `Record<string, ViewQuery>` | Yes | At least one named query, declared as an object literal |
| `render` | `() => JSX` | Yes | Function that returns the view's JSX tree |

### ViewQuery

Each entry in `queries` describes a Convex subscription that is opened when the view mounts.

| Field | Type | Description |
|-------|------|-------------|
| `type` | `string` | Entity type slug. Must match an entity type that exists in the same environment |
| `filters` | `Record<string, unknown>` | Optional field-equality filter, applied server-side |
| `status` | `string` | Optional status filter |
| `limit` | `number` | Maximum number of rows to return |
| `include` | `string[]` | Reserved for future server-side joins; ignored today |

`queries` must be a plain object literal so the compiler can extract it at build time. Computed keys, spreads, and non-literal values are rejected.

## Authoring Constraints

The CLI rejects views at compile time when they break any of these rules. Each violation includes the source file, line, column, and a fix hint. See [Custom Views Compile Errors](../reference/view-compile-errors) for the full list.

### Imports

Only one import specifier is allowed: `"struere/view"`. Imports from `"react"`, `"struere"`, relative paths, or any other module are rejected.

```tsx
import { Card } from "struere/view"
```

### Raw HTML

JSX tags that start with a lowercase letter (e.g. `<div>`, `<span>`, `<table>`) are rejected. Use components from `"struere/view"` instead:

```
Raw HTML element <div> is not allowed.
Use a component from "struere/view" instead.
```

### Banned Attributes

`className`, `class`, `style`, `dangerouslySetInnerHTML`, and `ref` are all rejected. Components expose `tone`, `size`, `density`, `gap`, and similar variant props for styling.

### Banned Identifiers

The following globals are not available inside a view: `window`, `document`, `globalThis`, `self`, `parent`, `top`, `fetch`, `XMLHttpRequest`, `WebSocket`, `localStorage`, `sessionStorage`, `indexedDB`, `Worker`, `SharedWorker`, `eval`, `require`.

`setTimeout`, `setInterval`, `new Function`, `new Worker`, dynamic `import()`, and `import.meta` are also rejected. For self-updating timestamps, use `<RelativeTime />`.

### Size Caps

| Cap | Limit |
|-----|-------|
| Source file | 64 KB |
| Compiled bundle | 256 KB |

If the bundle exceeds the cap, split the view into multiple files or remove unused code.

## Slug Rules

The slug is the global identifier within an organization and environment. It is used to look up the view, to register the URL, and to detect renames during sync.

- Lowercase letters, digits, and dashes only
- Must start with a letter or digit
- Renaming a slug creates a new view; the old one stays until you delete it

## Environment Scoping

Views are stored in the `customViews` table, scoped per environment. `struere dev` syncs to `development` and `eval`; `struere deploy` syncs to `production`. A view's slug is unique within `(organizationId, environment)`.

## Next Steps

- [View Components](./view-components) — every component, with prop signatures
- [View Data Hooks](./view-data-hooks) — `useViewQuery`, `useIndex`, and reactivity
- [Custom Views Compile Errors](../reference/view-compile-errors) — every validator message
