# Card chrome & dialogs

> The card's header — title, status, badge — its overview tile, attention, resize, links, camera focus and spawning cards; toasts, confirm dialogs and the card's menu.

Source: https://docs.neurosquad.ai/en/card-sdk/api/card-ui

The card's header, overview tile and dialogs are drawn **by the app**, not by your page — so they
look native, work while your card is suspended, and show on the phone. You set their content.

**Call them as often as you like.** The app throttles these updates (title 30 a minute; status,
badge and overview 120 a minute each; attention once every 10 seconds), and the SDK absorbs that for
you: for `setTitle`, `setStatus`, `setBadge`, `setOverview` and `attention` it sends one call at a
time per method, folds calls made meanwhile into one with the latest value, and retries the latest
value when the app says `RATE_LIMITED`. They never throw for rate — updating on every render is
fine. An update that changes nothing costs nothing.

## Title

```ts
await card.setTitle('Checkout tests') // null clears it
```

The name shown in the header, the sidebar and the Squad card. If the user renamed the card, the
user's name wins; without either, the manifest's `displayName` shows. ≤ 120 characters; control,
bidi and invisible characters are stripped.

## Status

```ts
await card.setStatus('Running tests…', { busy: true })       // spinner
await card.setStatus('3 failed', { tone: 'danger' })
await card.setStatus(null)                                    // clear
```

A chip in the header, ≤ 80 characters. Tones: `default`, `accent`, `success`, `warning`, `danger`.

## Badge

```ts
await card.setBadge(3)                          // a count
await card.setBadge('NEW', { tone: 'accent' })  // or a short text, ≤ 12 characters
await card.setBadge(null)                       // clear
```

## Overview tile

When the user zooms out past the overview threshold, every card shows a tile with its main fact
instead of its body. Yours shows what you set here — and keeps showing it while the card is
suspended, and on the [phone](https://docs.neurosquad.ai/en/card-sdk/phone). The app remembers it.

```ts
await card.setOverview({
  primary: '3 failing',        // big line, ≤ 160
  secondary: 'checkout, auth', // small line, ≤ 160
  progress: 0.8,               // 0..1, a progress bar; null hides it
  tone: 'danger',
  icon: 'bug-ant'
})
```

Keep it current: it is the only thing of your card most people see on a busy canvas.

**Icons** the app can draw (heroicons, outline): `academic-cap`, `arrow-path`, `beaker`, `bell`,
`bolt`, `book-open`, `bug-ant`, `calendar`, `camera`, `chart-bar`, `chart-pie`,
`chat-bubble-left-right`, `check-circle`, `clock`, `cloud`, `code-bracket`, `command-line`,
`cpu-chip`, `cube`, `currency-dollar`, `document-text`, `exclamation-triangle`, `film`, `fire`,
`flag`, `folder`, `globe-alt`, `heart`, `inbox`, `key`, `light-bulb`, `link`, `list-bullet`, `map`,
`megaphone`, `moon`, `musical-note`, `newspaper`, `paper-airplane`, `pause`, `photo`, `play`,
`puzzle-piece`, `rocket-launch`, `server`, `shield-check`, `signal`, `sparkles`, `star`, `stop`,
`sun`, `table-cells`, `tag`, `trash`, `trophy`, `users`, `wrench-screwdriver` (the list is
`HOST_ICON_NAMES` in the SDK). Inside your own page, draw any icon you like.

## Attention

```ts
await card.attention('needs-input', 'Pick a branch to deploy')
await card.attention('info')    // a softer pulse
await card.attention('none')    // clear
```

`needs-input` makes the card pulse like an agent waiting for the user and lists it in the
**Inbox**. At most once every 10 seconds. There is no sound and no OS notification in version 1.

**What is showing now.** The app keeps a card's title, status, badge, overview and attention
across reloads, suspension and updates. `card.context.chrome` tells your page what it currently
holds, so a card that raised attention before a reload can clear a stale pulse:

```ts
const attention = card.context.chrome?.attention
if (attention && !stillNeedsTheUser()) await card.attention('none')

declare function stillNeedsTheUser(): boolean
```

`chrome` is absent on older app versions — treat that as "unknown".

## Resize

```ts
const applied = await card.requestResize({ w: 640, h: 480 })
render(applied.w, applied.h)
```

Clamped to the manifest's `minSize`/`maxSize`; resolves with the size actually applied. It goes
into the canvas undo history like a resize by hand. At most every 500 ms. The user can always resize
too — listen with [`card.lifecycle.onResized`](https://docs.neurosquad.ai/en/card-sdk/api/environment#lifecycle).

## Open a link

```ts
const opened = await card.openLink('https://github.com/acme/app/issues/42')
```

The app shows the full address in its own window-wide dialog; the link opens in the user's browser
only after **Open link**. `https://` only, and only while the card is visible (`NOT_VISIBLE` otherwise).
Resolves `false` if the user declined. A card cannot navigate itself or open windows.

## Fly to another card

```ts
await card.focusCard(cardId)
```

Moves the camera to another card of the same workspace — for "show me the failing agent" buttons.
Only while your card is visible, at most every 5 seconds.

## Spawn a card

Needs [`canvas.spawn`](https://docs.neurosquad.ai/en/card-sdk/permissions#canvas-spawn).

```ts
// A note next to this card, connected with an arrow from this card.
const noteId = await card.spawn('note', { title: 'Test report' })
await card.ports.send(noteId, 'summary', '# Report\n\nAll green.')

// Another copy of this card, with data for its first start.
await card.spawn('self', { init: { suite: 'e2e' }, connect: false })
```

Kinds: `self` (another card of your package, which gets `init` as `card.spawnInit` on its first
start), `note`, `todo`, `kanban`, `sticky`. The new card is placed next to yours and connected with
an arrow from your card unless `connect: false`. At most 4 per card and 4 a minute.

## Card info

```ts
const info = await card.getInfo() // CardInstanceInfo, fresh from the app
render(info.instanceId, info.packageId, info.commit ?? 'dev folder', info.size)
```

## Toasts

```ts
await card.ui.toast('Copied', { tone: 'success', durationMs: 2000 })
```

A small toast inside the card's box, ≤ 280 characters, 1–15 seconds, at most one every 2 seconds.

## Confirm dialog

`alert`, `confirm` and `prompt` do nothing in a card's sandbox. Ask the app instead:

```ts
const ok = await card.ui.confirm({
  title: 'Delete all saved runs?',
  message: 'This removes 42 runs from this card. It cannot be undone.',
  confirmLabel: 'Delete',
  cancelLabel: 'Keep',
  tone: 'danger'
})
if (ok) await card.storage.clear()
```

The app shows this as **its own dialog over the whole window**, not inside your card: a fixed
heading saying your card asks for confirmation, your `title` and `message` as a quote, your
`confirmLabel` (replaced with the app's "Confirm" if it reads like cancel/no/deny, names NeuroSquad
or contains control characters) and the app's own Cancel. Only while the card is visible, at most
10 a minute. Dialogs from several cards queue.

## The card's menu

Add up to 12 items to the card's **⋯** menu, after the app's own (Settings…, Reload, About…).

```ts
await card.ui.setMenu([
  { id: 'rerun', label: 'Run again', icon: 'arrow-path', onSelect: () => void rerun() },
  { id: 'export', label: 'Copy report', icon: 'document-text', onSelect: () => void copyReport() },
  { id: 'reset', label: 'Reset', tone: 'danger', disabled: true }
])

// Or handle every choice in one place:
card.ui.onMenu((id) => card.log.info('menu', id))

declare function rerun(): Promise<void>
declare function copyReport(): Promise<void>
```

Each call replaces the previous items (at most 30 calls a minute). `onSelect` stays in your card —
it is not sent to the app.
Labels ≤ 60 characters; `icon` is one of the [host icons](#overview).

> Everything on this page except dialogs and links works while the card is not on screen. Dialogs,
> links, camera focus and permission requests need the user to be looking: they fail with
> `NOT_VISIBLE` otherwise.
