# Feedback - agent guide

> Normative extract of the EchoSistema template for the page
> `/componentes/feedback`. Copy it verbatim: tag names, props, events and classes
> are contract.
>
> Full guide (rules, tokens and every family): `/agents/echosistema-ui.md`

## Rules that apply before any line of code

- Colour only through tokens (`text-fg1`, `bg-panel`, `border-line`, `.es-i--*`); never a hex, never a Tailwind colour.
- Two axes: the intent gives the colour, the variant consumes it. Never combine them by hand.
- Visible text through `t()`; tag names, props and classes stay literal.
- A component does not fetch: it takes props and emits events.
- An icon alone inside a button needs an `aria-label`.

```vue
<echo-alert tone="danger" :title="t('errors.title')">…</echo-alert>
```

Tones: `info` `success` `warn` `danger` `caution` `steel-blue` `navy-blue`
`dark`. (That is the ALERT's vocabulary; the intent one calls `warn`
`warning`.)

```vue
<echo-alert v-model:open="shown" tone="success" :title="t('saved.title')" closeable :timeout="8000">…</echo-alert>
```

`closeable` adds the dismiss button; `timeout` (ms, `0` = stays) closes it on
its own, with a countdown bar on the bottom edge that PAUSES under the pointer
or with focus inside. `v-model:open` is optional - bind it only to bring the
alert back. `@close` fires on both exits.

```vue
<echo-modal ref="modal" title-id="x-title" size="lg" intent="warning" @close="onClosed">
  <template #title>…</template>
  …
  <template #footer>
    <form method="dialog"><echo-button type="submit" variant="filled" intent="primary">OK</echo-button></form>
  </template>
</echo-modal>

modal.value?.open()
```

`size`: `sm` `md` `lg` `xl` `xxl`. The body scrolls on its own and the page
behind stays locked; both come from the component.

```vue
<echo-block :loading="pending" :label="t('loading')">…</echo-block>
<echo-skeleton :lines="3" /> · <echo-skeleton circle class="size-11" />
<echo-spinner size="sm" dots intent="primary" />

useToast().push({ tone: 'success', title: 'Done', message: 'Saved.', timeout: 0 })
useToast().clear()
```

`timeout: 0` pins the toast until someone closes it.

```vue
<echo-loader :loading="loading">…the whole page…</echo-loader>

const loading = useAwaitingFirstRead(() => Boolean(data.value) || Boolean(error.value))
```

`<echo-block>` veils a REGION; `<echo-loader>` veils the PAGE that has nothing
true to show until the API answers: blur over the viewport, the brand's symbol
and the site name in its two colours in the middle, the letters breathing one
after the other. The slot goes `inert` with `aria-busy`, and a `role="status"`
with sr-only text announces the wait.

- `loading` must agree between server and client: derive it from the data, never
  from `status === 'pending'` (a `server: false` read is `idle` on the server
  and `pending` on the client - a hydration mismatch). `useAwaitingFirstRead()`
  holds that rule and drops the veil for good after the first read.
- Watchdog: after 5s the veil gives up on its own, re-armed on every rise.
- On a `backoffice` page it is NOT teleported and spares the side menu and the
  top bar (`--veil-start` and `--top-h` on `.es-bo`); everywhere else it goes to
  `<body>`, clear of any glass ancestor.
- `wordmark` (`['Echo', 'Sistema']`) overrides the split of `siteName`.

```ts
const { position, align } = useToast()
position.value = 'top'   // 'top' | 'bottom'
align.value = 'full'     // 'start' | 'center' | 'end' | 'full'
```

`full` stretches each toast edge to edge (inside the stack's gutter), for the
notice that concerns the whole page.

## Entrance animations

```vue
<article class="es-card es-anim-fade-up es-anim-delay-2">…</article>
```

`es-anim-fade` `es-anim-fade-up` `es-anim-fade-down` `es-anim-fade-start`
`es-anim-fade-end` `es-anim-zoom` `es-anim-pop`, plus `es-anim-delay-1` to
`es-anim-delay-5` (60ms apart) to stagger a list without a line of script.

They are in the spirit of animate.style but written on the system's own tokens:
the library is 90 animations and ~70 kB for the four a site uses, and it brings
its own durations and curves - a 1s entrance beside a menu that opens in 0.16s
does not read as the same product.

`start`/`end` and not `left`/`right`, so a right-to-left edition enters from the
correct side. `pop` is the only one that overshoots: it is for what has JUST
appeared (a new row, the result of a submit), never for a whole page.

## The notice surfaces

These three (alert, dialog, toast) are the system's NOTICE SURFACES: a ring in
`color-mix(--i 30%, --border)`, a tint at 8% and the wedge at the bottom-right
corner. A fourth surface that needs to say something is built from the same
recipe, not from a new one - that is what the calendar's event chip does.

---

Source: `app/components/echo/{Alert,Modal,Toaster,Block,Loader,Skeleton,Spinner}.vue`
