@aurea-uds/react 0.17.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,17 @@
1
1
  import React, { type ReactNode } from "react";
2
2
  import { type IconName } from "./system.js";
3
3
  import { type BadgeVariant } from "./feedback.js";
4
+ export type ChatDirection = "incoming" | "outgoing";
5
+ export interface MessageAttachment {
6
+ id: string;
7
+ kind: "image" | "video" | "file";
8
+ src?: string;
9
+ alt?: string;
10
+ name?: string;
11
+ bytes?: number;
12
+ duration?: number;
13
+ href?: string;
14
+ }
4
15
  export interface ChatMessage {
5
16
  id: string;
6
17
  body: ReactNode;
@@ -14,18 +25,60 @@ export interface ChatMessage {
14
25
  label: ReactNode;
15
26
  variant?: BadgeVariant;
16
27
  };
28
+ direction?: ChatDirection;
29
+ attachments?: MessageAttachment[];
30
+ replyTo?: {
31
+ id: string;
32
+ author?: ReactNode;
33
+ body: ReactNode;
34
+ };
35
+ forwardedFrom?: ReactNode;
36
+ edited?: boolean;
37
+ day?: string;
17
38
  }
18
- export declare function MessageList({ messages, label, className }: {
39
+ export interface MessageListProps {
19
40
  messages: ChatMessage[];
20
41
  label?: string;
21
42
  className?: string;
22
- }): React.JSX.Element;
23
- export declare function MessageComposer({ onSend, placeholder, label, icon, sendLabel, disabled, className }: {
24
- onSend: (text: string) => void;
43
+ hasMoreBefore?: boolean;
44
+ hasMoreAfter?: boolean;
45
+ loadingBefore?: boolean;
46
+ loadingAfter?: boolean;
47
+ onReachStart?: () => void;
48
+ onReachEnd?: () => void;
49
+ onOpenAttachment?: (message: ChatMessage, attachment: MessageAttachment) => void;
50
+ }
51
+ export declare function MessageList({ messages, label, className, hasMoreBefore, hasMoreAfter, loadingBefore, loadingAfter, onReachStart, onReachEnd, onOpenAttachment }: MessageListProps): React.JSX.Element;
52
+ export interface ComposerReply {
53
+ id: string;
54
+ author?: ReactNode;
55
+ body: ReactNode;
56
+ }
57
+ export interface ComposerEdit {
58
+ id: string;
59
+ body: string;
60
+ }
61
+ export interface ComposerAttach {
62
+ accept?: string;
63
+ maxSize?: number;
64
+ multiple?: boolean;
65
+ }
66
+ export interface MessageSendDetails {
67
+ files: File[];
68
+ replyToId?: string;
69
+ editingId?: string;
70
+ }
71
+ export declare function MessageComposer({ onSend, placeholder, label, icon, sendLabel, disabled, className, attach, replyTo, onCancelReply, editing, onCancelEdit }: {
72
+ onSend: (text: string, details?: MessageSendDetails) => void;
25
73
  placeholder?: string;
26
74
  label?: string;
27
75
  icon?: IconName;
28
76
  sendLabel?: string;
29
77
  disabled?: boolean;
30
78
  className?: string;
79
+ attach?: boolean | ComposerAttach;
80
+ replyTo?: ComposerReply | null;
81
+ onCancelReply?: () => void;
82
+ editing?: ComposerEdit | null;
83
+ onCancelEdit?: () => void;
31
84
  }): React.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
4
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
5
5
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
@@ -9,21 +9,244 @@ import { InputGroup, InputGroupAddon } from "./inputs.js";
9
9
  import { Icon } from "./system.js";
10
10
  import { IconButton } from "./actions.js";
11
11
  import { Avatar } from "./identity.js";
12
- import { Badge } from "./feedback.js";
13
- export function MessageList({ messages, label, className }) {
12
+ import { Badge, Spinner } from "./feedback.js";
13
+ import { formatSize, matchesAccept } from "./file-input.js";
14
+ // A AN-02 põe álbum na mensagem com a `Gallery` que já existe. `media` mora no "resto" do DAG,
15
+ // como este módulo, e não importa `communication` — não há ciclo.
16
+ import { Gallery } from "./media.js";
17
+ // O contêiner que rola é o ancestral com `overflow` de rolagem, ou a página.
18
+ function rolador(el) {
19
+ for (let p = el.parentElement; p; p = p.parentElement) {
20
+ const o = getComputedStyle(p).overflowY;
21
+ if (o === "auto" || o === "scroll" || o === "overlay")
22
+ return p;
23
+ }
24
+ return document.scrollingElement ?? document.documentElement;
25
+ }
26
+ const ehPagina = (sc) => sc === document.scrollingElement || sc === document.documentElement;
27
+ function useVigia(alvo, ligado, avisar, deps) {
28
+ const cb = React.useRef(avisar);
29
+ cb.current = avisar;
30
+ React.useEffect(() => {
31
+ const el = alvo.current;
32
+ if (!el || !ligado || typeof IntersectionObserver === "undefined")
33
+ return;
34
+ const sc = rolador(el);
35
+ const obs = new IntersectionObserver(es => { if (es.some(e => e.isIntersecting))
36
+ cb.current?.(); }, { root: ehPagina(sc) ? null : sc });
37
+ obs.observe(el);
38
+ return () => obs.disconnect();
39
+ // eslint-disable-next-line react-hooks/exhaustive-deps
40
+ }, [ligado, ...deps]);
41
+ }
42
+ export function MessageList({ messages, label, className, hasMoreBefore, hasMoreAfter, loadingBefore, loadingAfter, onReachStart, onReachEnd, onOpenAttachment }) {
14
43
  const s = useAureaStrings();
15
- return _jsx("div", { className: cx("stack", className), role: "log", "aria-live": "polite", "aria-label": label ?? s.chatLabel, children: messages.map(m => _jsxs("div", { className: "message", children: [m.avatar ? _jsx(Avatar, { src: m.avatar.src, fallback: m.avatar.fallback }) : _jsx("span", {}), _jsxs("div", { className: "message-bubble", children: [(m.author || m.time) && _jsxs("div", { className: "label", children: [_jsx("span", { children: m.author }), m.time && _jsx("span", { className: "hint", children: m.time })] }), m.body, m.status && _jsx("div", { className: "message-status", children: _jsxs(Badge, { variant: m.status.variant, children: [_jsx("i", { className: "status-dot" }), m.status.label] }) })] })] }, m.id)) });
44
+ const janela = !!onReachStart || !!onReachEnd;
45
+ const raiz = React.useRef(null);
46
+ const topo = React.useRef(null);
47
+ const fim = React.useRef(null);
48
+ const ancora = React.useRef(null);
49
+ const noFim = React.useRef(true);
50
+ const ultimoAntes = React.useRef(undefined);
51
+ const montou = React.useRef(false);
52
+ const [anuncio, setAnuncio] = React.useState("");
53
+ const hasMoreAfterRef = React.useRef(hasMoreAfter);
54
+ hasMoreAfterRef.current = hasMoreAfter;
55
+ // A posição de quem está no alto da tela, e se a última está à vista. Lida a cada rolagem e
56
+ // depois de cada troca: entre uma troca e outra, só a rolagem move as mensagens.
57
+ const medir = React.useCallback(() => {
58
+ const el = raiz.current;
59
+ if (!el)
60
+ return;
61
+ const sc = rolador(el), pag = ehPagina(sc);
62
+ const alto = pag ? 0 : sc.getBoundingClientRect().top, baixo = pag ? window.innerHeight : sc.getBoundingClientRect().bottom;
63
+ const itens = el.querySelectorAll("[data-message-id]");
64
+ // A âncora é a primeira que COMEÇA dentro da tela, e não a primeira que aparece: medido no banco
65
+ // em 04/10/2026, um álbum cortado no alto cresceu 480 px quando as fotos chegaram — para baixo
66
+ // dele —, o topo dele não andou, e a conversa inteira desceu sem a conta perceber. Se nenhuma
67
+ // começa na tela (uma mensagem maior que ela), vale a que está à vista.
68
+ ancora.current = null;
69
+ let vista;
70
+ for (const it of Array.from(itens)) {
71
+ const r = it.getBoundingClientRect();
72
+ if (r.bottom <= alto)
73
+ continue;
74
+ vista ??= it;
75
+ if (r.top >= alto) {
76
+ ancora.current = { id: it.dataset.messageId, top: r.top };
77
+ break;
78
+ }
79
+ }
80
+ if (!ancora.current && vista)
81
+ ancora.current = { id: vista.dataset.messageId, top: vista.getBoundingClientRect().top };
82
+ const ultimo = itens[itens.length - 1];
83
+ noFim.current = !ultimo || ultimo.getBoundingClientRect().bottom <= baixo + 1;
84
+ }, []);
85
+ React.useEffect(() => {
86
+ if (!janela || !raiz.current)
87
+ return;
88
+ const sc = rolador(raiz.current), alvo = ehPagina(sc) ? window : sc;
89
+ alvo.addEventListener("scroll", medir, { passive: true });
90
+ return () => alvo.removeEventListener("scroll", medir);
91
+ }, [janela, medir]);
92
+ const ultimoId = messages[messages.length - 1]?.id;
93
+ React.useLayoutEffect(() => {
94
+ const el = raiz.current;
95
+ if (!janela || !el) {
96
+ ultimoAntes.current = ultimoId;
97
+ return;
98
+ }
99
+ const sc = rolador(el);
100
+ const irAoFim = () => { sc.scrollTop = sc.scrollHeight; };
101
+ const antes = ultimoAntes.current;
102
+ // Chegou mensagem no fim: a última de antes continua na lista, e não é mais a última.
103
+ const ia = antes === undefined ? -1 : messages.findIndex(m => m.id === antes);
104
+ const chegou = montou.current && ia >= 0 && ia < messages.length - 1;
105
+ if (!montou.current) {
106
+ irAoFim();
107
+ montou.current = true;
108
+ }
109
+ else if (chegou && noFim.current && !hasMoreAfter)
110
+ irAoFim();
111
+ else if (ancora.current) {
112
+ const it = el.querySelector(`[data-message-id="${CSS.escape(ancora.current.id)}"]`);
113
+ if (it)
114
+ sc.scrollTop += it.getBoundingClientRect().top - ancora.current.top;
115
+ }
116
+ if (chegou) {
117
+ const novo = el.querySelector(`[data-message-id="${CSS.escape(ultimoId)}"]`);
118
+ setAnuncio(novo?.textContent ?? "");
119
+ }
120
+ ultimoAntes.current = ultimoId;
121
+ medir();
122
+ // eslint-disable-next-line react-hooks/exhaustive-deps
123
+ }, [messages]);
124
+ // A ALTURA MUDA DEPOIS DE MONTAR — a fonte chega, a foto decodifica, o app troca um texto — e
125
+ // nenhuma rolagem acontece para avisar. Medido no banco em 04/10/2026: a conversa abria na #4992
126
+ // e não na última. Então se observa o tamanho da lista: quem estava no fim continua no fim; quem
127
+ // não estava mantém a mensagem do alto onde ela estava.
128
+ React.useEffect(() => {
129
+ const el = raiz.current;
130
+ if (!janela || !el || typeof ResizeObserver === "undefined")
131
+ return;
132
+ const ro = new ResizeObserver(() => {
133
+ const sc = rolador(el);
134
+ if (noFim.current && !hasMoreAfterRef.current)
135
+ sc.scrollTop = sc.scrollHeight;
136
+ else if (ancora.current) {
137
+ const it = el.querySelector(`[data-message-id="${CSS.escape(ancora.current.id)}"]`);
138
+ if (it)
139
+ sc.scrollTop += it.getBoundingClientRect().top - ancora.current.top;
140
+ }
141
+ medir();
142
+ });
143
+ ro.observe(el);
144
+ return () => ro.disconnect();
145
+ }, [janela, medir]);
146
+ useVigia(topo, janela && !!hasMoreBefore && !loadingBefore, onReachStart, [messages.length]);
147
+ useVigia(fim, janela && !!hasMoreAfter && !loadingAfter, onReachEnd, [messages.length]);
148
+ return _jsxs(_Fragment, { children: [_jsxs("div", { ref: raiz, className: cx("stack", className), role: "log", "aria-live": janela ? "off" : "polite", "aria-busy": (janela && (loadingBefore || loadingAfter)) || undefined, "aria-label": label ?? s.chatLabel, children: [janela && hasMoreBefore && _jsx("div", { ref: topo, className: "message-more", children: loadingBefore && _jsx(Spinner, {}) }), messages.map((m, i) => {
149
+ const lado = m.direction;
150
+ const midia = (m.attachments ?? []).filter(a => a.kind !== "file" && a.src);
151
+ const arquivos = (m.attachments ?? []).filter(a => a.kind === "file");
152
+ const dia = m.day != null && m.day !== messages[i - 1]?.day;
153
+ return _jsxs(React.Fragment, { children: [dia && _jsx("div", { className: "message-day", children: _jsx(Badge, { children: m.day }) }), _jsxs("div", { className: cx("message", lado === "outgoing" && "message-out", lado === "incoming" && "message-in"), "data-message-id": m.id, children: [lado === "outgoing" ? null : m.avatar ? _jsx(Avatar, { src: m.avatar.src, fallback: m.avatar.fallback }) : _jsx("span", {}), _jsxs("div", { className: "message-bubble", children: [(m.author || m.time || m.edited) && _jsxs("div", { className: "label", children: [_jsx("span", { children: m.author }), (m.time || m.edited) && _jsxs("span", { className: "hint", children: [m.edited && _jsxs(_Fragment, { children: [s.chatEdited, m.time != null && " · "] }), m.time] })] }), m.forwardedFrom != null && _jsxs("div", { className: "message-forwarded", children: [_jsx(Icon, { name: "share-fat", size: "sm" }), _jsxs("span", { children: [s.chatForwardedFrom, " ", m.forwardedFrom] })] }), m.replyTo && _jsx("div", { className: "message-quote", children: _jsxs("span", { className: "message-quote-text", children: [_jsx("span", { className: "message-quote-title", children: m.replyTo.author }), _jsx("span", { className: "message-quote-body", children: m.replyTo.body })] }) }), midia.length > 0 && _jsx(Gallery, { className: "message-album", label: s.chatAttachments, zoom: !onOpenAttachment, ratio: midia.length === 1 ? "4/3" : "1/1", items: midia.map(a => ({ id: a.id, src: a.src, alt: a.alt ?? a.name ?? "", kind: a.kind === "video" ? "video" : "image", duration: a.duration })), onSelect: onOpenAttachment ? (id => { const a = midia.find(x => x.id === id); if (a)
154
+ onOpenAttachment(m, a); }) : undefined }), arquivos.length > 0 && _jsx("ul", { className: "file-list", "aria-label": s.chatAttachments, children: arquivos.map(a => _jsxs("li", { className: "file-item", children: [_jsx(Icon, { name: "file-text" }), a.href ? _jsx("a", { className: "file-name", href: a.href, download: a.name ?? true, children: a.name ?? a.href }) : _jsx("span", { className: "file-name", children: a.name }), a.bytes != null && _jsx("span", { className: "file-size", children: formatSize(a.bytes) })] }, a.id)) }), m.body, m.status && _jsx("div", { className: "message-status", children: _jsxs(Badge, { variant: m.status.variant, children: [_jsx("i", { className: "status-dot" }), m.status.label] }) })] })] })] }, m.id);
155
+ }), janela && hasMoreAfter && _jsx("div", { ref: fim, className: "message-more", children: loadingAfter && _jsx(Spinner, {}) })] }), janela && _jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: anuncio })] });
16
156
  }
17
- // Composer: <form> sobre InputGroup + .input; Enter envia (submit nativo) e limpa.
18
- // Texto é estado interno (não controlado — controlar de fora só com demanda real,
19
- // precedente MediaPlayer/FileInput). Não envia texto vazio/só-espaço; o botão de
20
- // envio desabilita enquanto vazio. O input sempre tem nome acessível (aria-label),
21
- // pois placeholder não serve de nome. ponytail: input de uma linha; textarea +
22
- // Shift+Enter quando surgir demanda de multilinha.
23
- export function MessageComposer({ onSend, placeholder, label, icon, sendLabel, disabled, className }) {
157
+ export function MessageComposer({ onSend, placeholder, label, icon, sendLabel, disabled, className, attach, replyTo, onCancelReply, editing, onCancelEdit }) {
24
158
  const s = useAureaStrings();
25
159
  const [text, setText] = React.useState("");
26
- const submit = (e) => { e.preventDefault(); const t = text.trim(); if (!t)
27
- return; onSend(t); setText(""); };
28
- return _jsxs("form", { className: cx("message-composer", className), onSubmit: submit, children: [_jsxs(InputGroup, { children: [icon && _jsx(InputGroupAddon, { children: _jsx(Icon, { name: icon }) }), _jsx("input", { className: "input", value: text, disabled: disabled, placeholder: placeholder, "aria-label": label ?? s.chatMessage, onChange: e => setText(e.target.value) })] }), _jsx(IconButton, { type: "submit", variant: "primary", icon: "paper-plane-tilt", label: sendLabel ?? s.chatSend, disabled: disabled || !text.trim() })] });
160
+ const [anexos, setAnexos] = React.useState([]);
161
+ const [recusados, setRecusados] = React.useState([]);
162
+ const [aviso, setAviso] = React.useState("");
163
+ const campo = React.useRef(null);
164
+ const seletor = React.useRef(null);
165
+ const seq = React.useRef(0);
166
+ const opcoes = attach === true ? {} : attach || undefined;
167
+ const editandoId = editing?.id;
168
+ const respondendoId = replyTo?.id;
169
+ // O texto antigo entra quando a edição COMEÇA (muda o id), e sai quando ela acaba — sem isso o
170
+ // texto da mensagem editada ficava no campo como rascunho de mensagem nova.
171
+ const editouAntes = React.useRef(undefined);
172
+ React.useEffect(() => {
173
+ if (editandoId !== undefined) {
174
+ setText(editing?.body ?? "");
175
+ campo.current?.focus();
176
+ }
177
+ else if (editouAntes.current !== undefined)
178
+ setText("");
179
+ editouAntes.current = editandoId;
180
+ // eslint-disable-next-line react-hooks/exhaustive-deps
181
+ }, [editandoId]);
182
+ React.useEffect(() => { if (respondendoId !== undefined)
183
+ campo.current?.focus(); }, [respondendoId]);
184
+ // As URLs das miniaturas vivem enquanto o anexo vive; desmontar solta todas (o FileInput faz igual).
185
+ const anexosRef = React.useRef(anexos);
186
+ anexosRef.current = anexos;
187
+ React.useEffect(() => () => anexosRef.current.forEach(a => a.url && URL.revokeObjectURL(a.url)), []);
188
+ const soltar = (lista) => lista.forEach(a => a.url && URL.revokeObjectURL(a.url));
189
+ const escolher = (lista) => {
190
+ if (!lista)
191
+ return;
192
+ const novos = [], fora = [];
193
+ for (const file of Array.from(lista)) {
194
+ if (!matchesAccept(file, opcoes?.accept))
195
+ fora.push({ name: file.name, reason: s.fileWrongType });
196
+ else if (opcoes?.maxSize != null && file.size > opcoes.maxSize)
197
+ fora.push({ name: file.name, reason: s.fileTooLarge });
198
+ else
199
+ novos.push({ id: `a${seq.current++}`, file, url: file.type.startsWith("image/") && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : undefined });
200
+ }
201
+ setRecusados(fora);
202
+ if (novos.length) {
203
+ if (opcoes?.multiple === false) {
204
+ soltar(anexos);
205
+ setAnexos(novos.slice(0, 1));
206
+ }
207
+ else
208
+ setAnexos(prev => [...prev, ...novos]);
209
+ setAviso(`${s.fileAdded}: ${novos.map(a => a.file.name).join(", ")}`);
210
+ }
211
+ if (seletor.current)
212
+ seletor.current.value = "";
213
+ };
214
+ const remover = (a) => { soltar([a]); setAnexos(prev => prev.filter(x => x.id !== a.id)); setAviso(`${s.fileRemoved}: ${a.file.name}`); campo.current?.focus(); };
215
+ const podeAnexar = !!opcoes && editandoId === undefined;
216
+ const vazio = !text.trim() && !(podeAnexar && anexos.length);
217
+ const submit = (e) => {
218
+ e.preventDefault();
219
+ if (vazio)
220
+ return;
221
+ const t = text.trim();
222
+ // O segundo argumento só vai quando o app usa uma prop nova: quem chamava `onSend(texto)` e
223
+ // conferia a chamada exata continua recebendo exatamente isso.
224
+ if (!opcoes && replyTo == null && editing == null)
225
+ onSend(t);
226
+ else
227
+ onSend(t, { files: podeAnexar ? anexos.map(a => a.file) : [], replyToId: editandoId === undefined ? respondendoId : undefined, editingId: editandoId });
228
+ setText("");
229
+ soltar(anexos);
230
+ setAnexos([]);
231
+ setRecusados([]);
232
+ };
233
+ const aoTeclar = (e) => {
234
+ if (e.key !== "Escape")
235
+ return;
236
+ if (editandoId !== undefined && onCancelEdit) {
237
+ e.preventDefault();
238
+ onCancelEdit();
239
+ }
240
+ else if (respondendoId !== undefined && onCancelReply) {
241
+ e.preventDefault();
242
+ onCancelReply();
243
+ }
244
+ };
245
+ const citacao = editing
246
+ ? _jsxs("div", { className: "message-quote", children: [_jsx(Icon, { name: "pencil-simple", size: "sm" }), _jsxs("span", { className: "message-quote-text", children: [_jsx("span", { className: "message-quote-title", children: s.chatEditing }), _jsx("span", { className: "message-quote-body", children: editing.body })] }), onCancelEdit && _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.chatEditCancel, onClick: onCancelEdit })] })
247
+ : replyTo
248
+ ? _jsxs("div", { className: "message-quote", children: [_jsx(Icon, { name: "arrow-bend-up-left", size: "sm" }), _jsxs("span", { className: "message-quote-text", children: [_jsxs("span", { className: "message-quote-title", children: [s.chatReplyTo, replyTo.author != null && _jsxs(_Fragment, { children: [" ", replyTo.author] })] }), _jsx("span", { className: "message-quote-body", children: replyTo.body })] }), onCancelReply && _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.chatReplyCancel, onClick: onCancelReply })] })
249
+ : null;
250
+ const temFaixa = !!citacao || (podeAnexar && (anexos.length > 0 || recusados.length > 0));
251
+ return _jsxs("form", { className: cx("message-composer", className), onSubmit: submit, children: [_jsxs(InputGroup, { children: [icon && _jsx(InputGroupAddon, { children: _jsx(Icon, { name: icon }) }), temFaixa && _jsxs("div", { className: "input-group-addon input-group-addon-start input-group-addon-block message-composer-context", children: [citacao, podeAnexar && anexos.length > 0 && _jsx("ul", { className: "file-list", "aria-label": s.chatAttachments, children: anexos.map(a => _jsxs("li", { className: "file-item", children: [a.url ? _jsx("img", { className: "file-thumb", src: a.url, alt: "" }) : _jsx(Icon, { name: "file-text" }), _jsx("span", { className: "file-name", children: a.file.name }), _jsx("span", { className: "file-size", children: formatSize(a.file.size) }), _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: `${s.fileRemove} ${a.file.name}`, onClick: () => remover(a) })] }, a.id)) }), podeAnexar && recusados.map((r, n) => _jsxs("span", { className: "field-error", children: [r.reason, ": ", r.name] }, n))] }), _jsx("input", { ref: campo, className: "input", value: text, disabled: disabled, placeholder: placeholder, "aria-label": label ?? s.chatMessage, onChange: e => setText(e.target.value), onKeyDown: aoTeclar }), podeAnexar && _jsx(InputGroupAddon, { side: "end", children: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "paperclip", label: s.chatAttach, disabled: disabled, onClick: () => seletor.current?.click() }) })] }), podeAnexar && _jsx("input", { ref: seletor, type: "file", hidden: true, accept: opcoes?.accept, multiple: opcoes?.multiple !== false, onChange: e => escolher(e.target.files) }), _jsx(IconButton, { type: "submit", variant: "primary", icon: editing ? "check" : "paper-plane-tilt", label: editing ? s.chatSave : (sendLabel ?? s.chatSend), disabled: disabled || vazio }), podeAnexar && _jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: aviso })] });
29
252
  }
@@ -1,2 +1,2 @@
1
1
  export * from "./feedback-client.js";
2
- export { Badge, formatBadgeCount, Progress, Skeleton, type BadgeVariant, type BadgeEmphasis, type BadgeSize, type BadgePlacement, type BadgeProps } from "./markup.js";
2
+ export { Badge, formatBadgeCount, Progress, Skeleton, type BadgeVariant, type BadgeEmphasis, type BadgeSize, type BadgePlacement, type BadgeProps, type ProgressProps, type ProgressTone } from "./markup.js";
@@ -26,6 +26,7 @@ export declare function matchesAccept(file: {
26
26
  name: string;
27
27
  type: string;
28
28
  }, accept?: string): boolean;
29
+ export declare function formatSize(bytes: number): string;
29
30
  export declare function FileInput({ accept, maxSize, multiple, onFilesChange, upload, label, hint, id, className, initialQueue, onQueueChange, preview, checksum }: {
30
31
  accept?: string;
31
32
  maxSize?: number;
@@ -57,7 +57,9 @@ async function somaSha256(file) {
57
57
  // nova, zero branch novo.
58
58
  const ATIVOS = new Set(["pending", "uploading", "paused"]);
59
59
  const restaurar = (q) => ({ ...q, restored: true, status: ATIVOS.has(q.status) ? "canceled" : q.status });
60
- function formatSize(bytes) {
60
+ // Exportada para dentro do pacote (não sai no barril): o `MessageComposer` mostra o tamanho do
61
+ // anexo no mesmo formato da fila daqui (AN-03).
62
+ export function formatSize(bytes) {
61
63
  if (bytes < 1024)
62
64
  return `${bytes} B`;
63
65
  const units = ["KB", "MB", "GB"];
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { cx, defaultStrings, ptBR, defaultSpriteUrl, gridStateToParams, gridStat
2
2
  export { useAureaStrings, useSpriteUrl, useTheme, useDensity } from "./internal.js";
3
3
  export { type Responsive, type Breakpoint, type ContainerBreakpoint, type Orientation, ESCALA, ESCALA_CONTAINER } from "./pure.js";
4
4
  export { ContainerScope, useValorResponsivo } from "./responsivo-runtime.js";
5
- export { AspectRatio, InputGroup, InputGroupAddon, Label, Card, Stack, Cluster, Grid, KPI, DataList, Timeline, Prose, Text, Heading, Paragraph, Code, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Topbar, Kbd, Textarea, Checkbox, Radio, Switch, Range, formatBadgeCount, type BadgeVariant, type BadgeEmphasis, type BadgeSize, type BadgePlacement, type BadgeProps, type AvatarSize, type TopbarVariant, type TextProps, type HeadingProps, type ParagraphProps, type CodeProps, type TypographyType, type TypographyColor, type TypographyWeight, type TypographyAlign, type HeadingLevel, type ParagraphSize } from "./markup.js";
5
+ export { AspectRatio, InputGroup, InputGroupAddon, Label, Card, Stack, Cluster, Grid, KPI, DataList, Timeline, Prose, Text, Heading, Paragraph, Code, Badge, Progress, Skeleton, AvatarGroup, LogStream, MediaPlayerShell, Topbar, Kbd, Textarea, Checkbox, Radio, Switch, Range, formatBadgeCount, type BadgeVariant, type BadgeEmphasis, type BadgeSize, type BadgePlacement, type BadgeProps, type ProgressProps, type ProgressTone, type AvatarSize, type TopbarVariant, type TextProps, type HeadingProps, type ParagraphProps, type CodeProps, type TypographyType, type TypographyColor, type TypographyWeight, type TypographyAlign, type HeadingLevel, type ParagraphSize } from "./markup.js";
6
6
  export * from "./system.js";
7
7
  export * from "./actions.js";
8
8
  export * from "./feedback.js";
@@ -1,7 +1,7 @@
1
1
  import { type HTMLAttributes, type RefAttributes, type ReactNode } from "react";
2
2
  import { type Orientation, type Responsive } from "./pure.js";
3
3
  import { type TopbarVariant, type SidebarVariant, type SidebarItem } from "./navigation.js";
4
- export declare function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant, topbarDivider, sidebarVariant, sidebarCollapsed, contentVariant, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
4
+ export declare function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant, topbarDivider, sidebarVariant, sidebarCollapsed, sidebarCollapsible, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant, children, className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement> & {
5
5
  brand: ReactNode;
6
6
  navigation?: ReactNode;
7
7
  navItems?: SidebarItem[];
@@ -11,7 +11,14 @@ export declare function AppShell({ brand, navigation, navItems, currentNavId, na
11
11
  topbarVariant?: Exclude<TopbarVariant, "pill">;
12
12
  topbarDivider?: boolean;
13
13
  sidebarVariant?: SidebarVariant;
14
+ /** A lateral em trilha de ícones. Controlado: quem passa decide (e o `onSidebarCollapsedChange` avisa). */
14
15
  sidebarCollapsed?: boolean;
16
+ /** AN-01: o botão de recolher na junção do menu com o conteúdo, e a trilha sozinha entre 1024 e 1279 de largura. */
17
+ sidebarCollapsible?: boolean;
18
+ /** AN-01: começa recolhida, sem controlar. A escolha vale até a janela cruzar 1280. */
19
+ defaultSidebarCollapsed?: boolean;
20
+ /** AN-01: avisa cada troca — pelo botão, ou pela largura cruzando 1280. Guardar a preferência é do app. */
21
+ onSidebarCollapsedChange?: (collapsed: boolean) => void;
15
22
  contentVariant?: "surface" | "plain";
16
23
  }): import("react").JSX.Element;
17
24
  export type SeparatorSpacing = "none" | "tight" | "normal" | "loose";
@@ -3,12 +3,14 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
3
3
  // Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
4
4
  // do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
5
5
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
6
- import { useEffect, useRef } from "react";
6
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
7
7
  import { Separator as BaseSeparator } from "@base-ui/react/separator";
8
+ import { useDirection } from "@base-ui/react/direction-provider";
8
9
  import { cx, useAureaStrings } from "./internal.js";
9
10
  import { useValorResponsivo } from "./responsivo-runtime.js";
10
11
  import { ESCALA } from "./escala.js";
11
12
  import { IconButton } from "./actions.js";
13
+ import { Tooltip } from "./overlays.js";
12
14
  import { Sidebar, Topbar } from "./navigation.js";
13
15
  // ── Auxiliar de topo: mora ANTES do primeiro export, e a posição é obrigatória ───────────────
14
16
  // O check 22 mede o corpo de um componente do `export` dele até o PRÓXIMO export — fatia longa
@@ -120,10 +122,65 @@ function useGavetaFechaNoDesktop() {
120
122
  // fundo, borda e raio do `.card`), e todo `Card` lá dentro virava caixa dentro de caixa igual — um
121
123
  // app gastou 14 `!important` para apagá-la. `surface`, a caixa, continua o padrão.
122
124
  // B-07: `topbarDivider` repassa a linha de baixo para a barra `flush` que o shell monta.
123
- export function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant = "floating", topbarDivider, sidebarVariant, sidebarCollapsed, contentVariant = "surface", children, className, ...props }) {
125
+ // AN-01 (03/10/2026): O BOTÃO DE RECOLHER E A TRILHA SOZINHA. Um consumidor viu numa janela de 1508:
126
+ // a lateral sempre aberta, sem botão, e diminuir a janela não a recolhia — só virava gaveta abaixo de
127
+ // 1024. O modo trilha existia (`sidebarCollapsed`, `.sidebar-collapsed`), mas só controlado e sem
128
+ // gatilho. A referência é a `Sidebar` do shadcn que o ReUI usa no `c-sidebar-2` (o HeroUI não tem
129
+ // moldura de app): estado aberto/recolhido, controlado e não controlado, um gatilho, e o nome em
130
+ // dica na trilha. O lugar do botão é o que o Victor marcou: no alto, na junção do menu com o
131
+ // conteúdo. E a trilha sozinha em tela média vem do pedido — a referência não a tem.
132
+ //
133
+ // Tudo LIGADO por `sidebarCollapsible`: sem ele o shell é o de antes, e quem já usa
134
+ // `sidebarCollapsed` com o próprio botão não ganha um segundo.
135
+ const MEDIA_MEDIA = `(min-width: ${ESCALA.lg}px) and (max-width: ${ESCALA.xl - 1}px)`;
136
+ const MEDIA_ESTREITA = `(max-width: ${ESCALA.lg - 1}px)`;
137
+ // A largura pela API do navegador, e não por `resize`: o `useSyncExternalStore` dá a resposta certa
138
+ // já no primeiro desenho do lado do cliente, e no servidor (sem janela) diz "não" — a folha cuida da
139
+ // tela estreita sozinha, então o HTML do servidor não mostra nada errado.
140
+ function useLargura(query) {
141
+ return useSyncExternalStore((avisar) => {
142
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function")
143
+ return () => { };
144
+ const mq = window.matchMedia(query);
145
+ mq.addEventListener?.("change", avisar);
146
+ return () => mq.removeEventListener?.("change", avisar);
147
+ }, () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(query).matches, () => false);
148
+ }
149
+ export function AppShell({ brand, navigation, navItems, currentNavId, navLabel, topbar, topbarVariant = "floating", topbarDivider, sidebarVariant, sidebarCollapsed, sidebarCollapsible, defaultSidebarCollapsed, onSidebarCollapsedChange, contentVariant = "surface", children, className, ...props }) {
124
150
  const s = useAureaStrings();
125
151
  useGavetaFechaNoDesktop();
126
- return _jsxs("div", { className: cx("app-shell", topbarVariant === "flush" && "app-shell-flush", className), ...props, children: [_jsx("a", { className: "skip-link", href: `#${SHELL_MAIN_ID}`, children: s.skipToContent }), _jsx(Topbar, { variant: topbarVariant, divider: topbarDivider, brand: _jsxs(_Fragment, { children: [_jsx(IconButton, { className: "nav-toggle", icon: "list", label: s.navigationToggle, popoverTarget: SHELL_NAV_ID }), brand] }), children: topbar }), _jsx(Sidebar, { id: SHELL_NAV_ID, popover: "auto", variant: sidebarVariant, collapsed: sidebarCollapsed, items: navItems, current: currentNavId, label: navLabel, onToggle: focoDaGaveta, children: navigation }), _jsx("main", { id: SHELL_MAIN_ID, tabIndex: -1, className: cx("content", contentVariant === "plain" && "content-plain"), children: children })] });
152
+ const controlado = sidebarCollapsed !== undefined;
153
+ // `null` = ninguém escolheu, e vale a largura.
154
+ const [escolha, setEscolha] = useState(defaultSidebarCollapsed ?? null);
155
+ const media = useLargura(MEDIA_MEDIA), estreita = useLargura(MEDIA_ESTREITA);
156
+ const recolhida = controlado ? !!sidebarCollapsed : (escolha ?? (!!sidebarCollapsible && media));
157
+ // NA GAVETA A LATERAL NUNCA É TRILHA. Abaixo de 1024 ela abre por cima de tudo, com espaço para o
158
+ // nome; recolhida ali, era uma gaveta de ícones sem nome — também para quem já passava
159
+ // `sidebarCollapsed` (medido em 03/10/2026, na mesma conferência).
160
+ const efetiva = recolhida && !estreita;
161
+ const aviso = useRef(onSidebarCollapsedChange);
162
+ aviso.current = onSidebarCollapsedChange;
163
+ // Cruzar 1280 é mudar de contexto: a escolha da pessoa sai e a largura volta a mandar. Pelo
164
+ // evento do navegador, e não comparando desenhos, para a hidratação não contar como cruzamento.
165
+ useEffect(() => {
166
+ if (!sidebarCollapsible || typeof window === "undefined" || typeof window.matchMedia !== "function")
167
+ return;
168
+ const mq = window.matchMedia(MEDIA_MEDIA);
169
+ const aoCruzar = (e) => { if (!controlado)
170
+ setEscolha(null); aviso.current?.(e.matches); };
171
+ mq.addEventListener?.("change", aoCruzar);
172
+ return () => mq.removeEventListener?.("change", aoCruzar);
173
+ }, [sidebarCollapsible, controlado]);
174
+ const alternar = () => { const nova = !recolhida; if (!controlado)
175
+ setEscolha(nova); onSidebarCollapsedChange?.(nova); };
176
+ const rotulo = efetiva ? s.sidebarExpand : s.sidebarCollapse;
177
+ // A seta aponta para onde a lateral vai: recolher é para o começo da linha, abrir é para o fim. Na
178
+ // escrita da direita para a esquerda o começo é a direita — pela direção do provedor, e não por
179
+ // `:dir(rtl)` na folha (ver a nota do `.sidebar-toggle` no core).
180
+ // A condição vai escrita no `icon=` do botão, e não numa variável: é ali que o check 46 lê os
181
+ // ícones que o shell desenha.
182
+ const paraOComeco = !efetiva, direita = useDirection() === "rtl";
183
+ return _jsxs("div", { className: cx("app-shell", topbarVariant === "flush" && "app-shell-flush", sidebarCollapsible && "app-shell-collapsible", className), ...props, children: [_jsx("a", { className: "skip-link", href: `#${SHELL_MAIN_ID}`, children: s.skipToContent }), _jsx(Topbar, { variant: topbarVariant, divider: topbarDivider, brand: _jsxs(_Fragment, { children: [_jsx(IconButton, { className: "nav-toggle", icon: "list", label: s.navigationToggle, popoverTarget: SHELL_NAV_ID }), brand] }), children: topbar }), sidebarCollapsible && _jsx("div", { className: "sidebar-toggle-slot", children: _jsx(Tooltip, { content: rotulo, side: "right", children: _jsx(IconButton, { className: "sidebar-toggle", size: "xs", icon: paraOComeco === direita ? "caret-right" : "caret-left", label: rotulo, "aria-expanded": !efetiva, "aria-controls": SHELL_NAV_ID, onClick: alternar }) }) }), _jsx(Sidebar, { id: SHELL_NAV_ID, popover: "auto", variant: sidebarVariant, collapsed: efetiva, items: navItems, current: currentNavId, label: navLabel, onToggle: focoDaGaveta, children: navigation }), _jsx("main", { id: SHELL_MAIN_ID, tabIndex: -1, className: cx("content", contentVariant === "plain" && "content-plain"), children: children })] });
127
184
  }
128
185
  export function Separator({ orientation, spacing = "none", className, ...props }) {
129
186
  const ancora = useRef(null);
package/dist/markup.d.ts CHANGED
@@ -47,14 +47,21 @@ export interface ClusterProps extends DivProps {
47
47
  wrap?: boolean;
48
48
  }
49
49
  export declare function Cluster({ gap, align, justify, wrap, className, ...props }: ClusterProps): React.JSX.Element;
50
+ /**
51
+ * A largura mínima da coluna, por nome — AN-08 (03/10/2026). O HeroUI não tem `Grid`, e nenhum dos
52
+ * quatro números é novo: cada um já mede uma grade ou uma caixa da Aurea (ver `.grid-min-*` no CSS).
53
+ * `md` é o padrão de sempre.
54
+ */
55
+ export type GridMin = "xs" | "sm" | "md" | "lg";
50
56
  export interface GridProps extends DivProps {
51
57
  /** `tight` (--space-2), `normal` (o de sempre, --space-4) ou `loose` (--space-6). */
52
58
  gap?: LayoutGap;
53
59
  /**
54
- * Largura mínima de cada coluna, em unidade de CSS (`"10rem"`, `"155px"`). Padrão `15rem`. É o
55
- * `--grid-min` que o CSS sempre leu e que nenhum tipo mostrava.
60
+ * Largura mínima de cada coluna. Por nome (AN-08): `xs` 8rem, `sm` 12rem, `md` 15rem (o padrão)
61
+ * ou `lg` 20rem. Ou em unidade de CSS (`"10rem"`, `"155px"`) — o `--grid-min` que o CSS sempre
62
+ * leu. Até a 0.17, `"sm"` passava pelo tipo e a grade virava UMA coluna, sem aviso.
56
63
  */
57
- min?: string;
64
+ min?: GridMin | (string & {});
58
65
  /** Número fixo de colunas, iguais. Com ele, `min` deixa de valer. */
59
66
  columns?: number;
60
67
  }
@@ -139,10 +146,21 @@ export interface BadgeProps extends HTMLAttributes<HTMLSpanElement>, RefAttribut
139
146
  badgeContent?: ReactNode;
140
147
  }
141
148
  export declare function Badge({ variant, emphasis, size, dot, leading, trailing, image, imageAlt, count, max, showZero, fit, anchor, anchorShape, invisible, badgeContent, children, className, ...props }: BadgeProps): React.JSX.Element;
142
- export declare function Progress({ value, label }: {
143
- value: number;
149
+ /** O tom da barra: a mesma lista fechada do `ButtonTone`. Pausado é `neutral`; falha é `danger`. */
150
+ export type ProgressTone = "brand" | "neutral" | "success" | "warning" | "danger" | "info";
151
+ export interface ProgressProps {
152
+ /** 0 a 100; fora disso é grampeado. **Sem `value`, a barra é indeterminada** (AN-07): o total
153
+ * ainda não se sabe, e um pedaço corre pelo trilho, no lugar de um 0% que parece parado. */
154
+ value?: number;
155
+ /** O nome que o leitor de tela anuncia. Não aparece. */
144
156
  label?: string;
145
- }): React.JSX.Element;
157
+ /** O texto de apoio, no alto à direita — o `ProgressBar.Output` do HeroUI: velocidade, tempo
158
+ * que falta, bytes. Em texto, vai junto no `aria-valuetext`. */
159
+ detail?: ReactNode;
160
+ tone?: ProgressTone;
161
+ className?: string;
162
+ }
163
+ export declare function Progress({ value, label, detail, tone, className }: ProgressProps): React.JSX.Element;
146
164
  export declare function Skeleton({ className, ...props }: HTMLAttributes<HTMLDivElement> & RefAttributes<HTMLDivElement>): React.JSX.Element;
147
165
  export declare function AvatarGroup({ children, max, total, label, size, className }: {
148
166
  children: ReactNode;
package/dist/markup.js CHANGED
@@ -48,11 +48,14 @@ function CardMedia({ className, ...props }) { return _jsx("div", { className: cx
48
48
  Card.Media = CardMedia;
49
49
  export function Stack({ gap, align, className, ...props }) { return _jsx("div", { className: cx("stack", gap && gap !== "normal" && `stack-gap-${gap}`, align && align !== "stretch" && `stack-align-${align}`, className), ...props }); }
50
50
  export function Cluster({ gap, align, justify, wrap, className, ...props }) { return _jsx("div", { className: cx("cluster", gap && gap !== "normal" && `cluster-gap-${gap}`, align && align !== "center" && `cluster-align-${align}`, justify && justify !== "start" && `cluster-justify-${justify}`, wrap === false && "cluster-nowrap", className), ...props }); }
51
+ const GRID_MIN = new Set(["xs", "sm", "md", "lg"]);
51
52
  export function Grid({ gap, min, columns, className, style, ...props }) {
52
- const vars = min != null || columns != null
53
- ? { ...(min != null ? { "--grid-min": min } : {}), ...(columns != null ? { "--grid-cols": String(columns) } : {}), ...style }
53
+ const nome = min != null && GRID_MIN.has(min);
54
+ const medida = min != null && !nome ? min : undefined;
55
+ const vars = medida != null || columns != null
56
+ ? { ...(medida != null ? { "--grid-min": medida } : {}), ...(columns != null ? { "--grid-cols": String(columns) } : {}), ...style }
54
57
  : style;
55
- return _jsx("div", { className: cx("grid", gap && gap !== "normal" && `grid-gap-${gap}`, columns != null && "grid-fixed", className), style: vars, ...props });
58
+ return _jsx("div", { className: cx("grid", gap && gap !== "normal" && `grid-gap-${gap}`, nome && min !== "md" && `grid-min-${min}`, columns != null && "grid-fixed", className), style: vars, ...props });
56
59
  }
57
60
  // ── Data Display ─────────────────────────────────────────────────────────────────────────────
58
61
  export function KPI({ label, value, trend, className, ...props }) { return _jsxs(Card, { className: cx("kpi", className), ...props, children: [_jsx("span", { className: "muted", children: label }), _jsx("strong", { children: value }), trend && _jsx("small", { children: trend })] }); }
@@ -88,7 +91,19 @@ export function Badge({ variant = "neutral", emphasis = "soft", size = "md", dot
88
91
  // botão do sino é anunciado "sino 8" e ninguém sabe o que é o 8.
89
92
  return _jsxs("span", { className: "badge-anchor", children: [children, !escondido && React.cloneElement(chip, { "aria-hidden": true })] });
90
93
  }
91
- export function Progress({ value, label }) { const pct = Math.max(0, Math.min(100, value)); return _jsx("div", { children: _jsx("div", { className: "progress", role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": pct, children: _jsx("span", { style: { width: `${pct}%` } }) }) }); }
94
+ // AN-07 (03/10/2026). Tudo novo é acréscimo: com `value` e sem `detail` nem `tone`, a marcação é a
95
+ // de antes, mais a classe da caixa de fora. A anatomia é a do `ProgressBar` do HeroUI 3.2.6: a
96
+ // saída no alto à direita, o trilho embaixo, e sem `aria-valuenow` quando não há total.
97
+ export function Progress({ value, label, detail, tone = "brand", className }) {
98
+ const semTotal = value == null || Number.isNaN(value);
99
+ const pct = semTotal ? undefined : Math.max(0, Math.min(100, value));
100
+ const temApoio = detail != null && detail !== false && detail !== "";
101
+ // Texto vai no `aria-valuetext`, e o desenho dele sai do leitor de tela para não ser lido duas
102
+ // vezes. Apoio que não é texto (um ícone, um link) fica onde está, para o leitor achar sozinho.
103
+ const emTexto = temApoio && (typeof detail === "string" || typeof detail === "number") ? String(detail) : undefined;
104
+ const valuetext = emTexto == null ? undefined : pct == null ? emTexto : `${pct}%, ${emTexto}`;
105
+ return _jsxs("div", { className: cx("progress-field", className), children: [temApoio && _jsx("div", { className: "progress-detail", "aria-hidden": emTexto != null || undefined, children: detail }), _jsx("div", { className: cx("progress", semTotal && "progress-indeterminate", tone !== "brand" && `progress-tone-${tone}`), role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": pct, "aria-valuetext": valuetext, children: _jsx("span", { style: pct == null ? undefined : { width: `${pct}%` } }) })] });
106
+ }
92
107
  export function Skeleton({ className, ...props }) { return _jsx("div", { className: cx("skeleton", className), "aria-hidden": "true", ...props }); }
93
108
  // ── Identity ─────────────────────────────────────────────────────────────────────────────────
94
109
  // `React.Children` não é hook — é utilitário de leitura de `children`, e roda no servidor.
@@ -27,11 +27,14 @@ export interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElemen
27
27
  render?: React.ReactElement;
28
28
  }
29
29
  export declare function Image({ ratio, fit, alt, className, style, render, onError, ...props }: ImageProps): React.JSX.Element;
30
+ export type GalleryItemKind = "image" | "video";
30
31
  export interface GalleryItem {
31
32
  id: string;
32
33
  src: string;
33
34
  alt: string;
34
35
  caption?: ReactNode;
36
+ kind?: GalleryItemKind;
37
+ duration?: number;
35
38
  }
36
39
  export interface GalleryProps extends Omit<HTMLAttributes<HTMLUListElement>, "onSelect">, RefAttributes<HTMLUListElement> {
37
40
  items: GalleryItem[];
@@ -40,8 +43,14 @@ export interface GalleryProps extends Omit<HTMLAttributes<HTMLUListElement>, "on
40
43
  onSelect?: (id: string) => void;
41
44
  zoom?: boolean;
42
45
  ratio?: string;
46
+ selectionMode?: "single" | "multiple";
47
+ selectedIds?: string[];
48
+ onSelectionChange?: (ids: string[]) => void;
49
+ hasMore?: boolean;
50
+ loading?: boolean;
51
+ onReachEnd?: () => void;
43
52
  }
44
- export declare function Gallery({ items, label, selected, onSelect, zoom, ratio, className, ...props }: GalleryProps): React.JSX.Element;
53
+ export declare function Gallery({ items, label, selected, onSelect, zoom, ratio, selectionMode, selectedIds, onSelectionChange, hasMore, loading, onReachEnd, className, ...props }: GalleryProps): React.JSX.Element;
45
54
  export interface CarouselProps extends Omit<HTMLAttributes<HTMLDivElement>, "children">, RefAttributes<HTMLDivElement> {
46
55
  children?: ReactNode;
47
56
  label?: string;
@@ -11,6 +11,8 @@ import { IconButton } from "./actions.js";
11
11
  // A galeria AMPLIA num Dialog, e o Dialog já existe (trava do item L2). O import é para `overlays`,
12
12
  // que como este módulo mora no "resto" do DAG e não importa `media` — não há ciclo.
13
13
  import { Dialog } from "./overlays.js";
14
+ // AN-05: o tempo do vídeo e a linha de "carregando mais" reusam peças que já existem.
15
+ import { Badge, Spinner } from "./feedback.js";
14
16
  // MediaPlayer (Fase 5): headless sobre <video>/<audio> nativos — o MOTOR de mídia é
15
17
  // o browser; o componente só liga estado (play/tempo/buffer/volume/legenda) às
16
18
  // classes .media-* já existentes. Sem CSS estrutural novo: o vídeo preenche o
@@ -143,23 +145,45 @@ export function Image({ ratio, fit, alt, className, style, render, onError, ...p
143
145
  return _jsx("span", { className: cx("image", "image-broken", fit === "contain" && "image-contain", className), style: estilo, role: "img", "aria-label": alt, children: _jsx(Icon, { name: "image" }) });
144
146
  return elemento;
145
147
  }
146
- export function Gallery({ items, label, selected, onSelect, zoom, ratio = "1/1", className, ...props }) {
148
+ export function Gallery({ items, label, selected, onSelect, zoom, ratio = "1/1", selectionMode = "single", selectedIds, onSelectionChange, hasMore, loading, onReachEnd, className, ...props }) {
147
149
  const s = useAureaStrings();
148
150
  const [ampliado, setAmpliado] = React.useState(null);
149
- const interativo = !!onSelect || !!zoom;
151
+ const multipla = selectionMode === "multiple";
152
+ const interativo = !!onSelect || !!zoom || multipla;
150
153
  const aberto = items.find(i => i.id === ampliado);
151
- return _jsxs(_Fragment, { children: [_jsx("ul", { className: cx("gallery", className), "aria-label": label ?? s.galleryLabel, ...props, children: items.map(i => {
154
+ const escolhidos = new Set(selectedIds);
155
+ const alternar = (id) => onSelectionChange?.(escolhidos.has(id) ? (selectedIds ?? []).filter(x => x !== id) : [...(selectedIds ?? []), id]);
156
+ const fimRef = React.useRef(null);
157
+ const avisar = React.useRef(onReachEnd);
158
+ avisar.current = onReachEnd;
159
+ React.useEffect(() => {
160
+ const fim = fimRef.current;
161
+ if (!fim || !hasMore || loading || typeof IntersectionObserver === "undefined")
162
+ return;
163
+ const obs = new IntersectionObserver(entradas => { if (entradas.some(e => e.isIntersecting))
164
+ avisar.current?.(); });
165
+ obs.observe(fim);
166
+ return () => obs.disconnect();
167
+ }, [hasMore, loading, items.length]);
168
+ return _jsxs(_Fragment, { children: [_jsx("ul", { className: cx("gallery", className), "aria-label": label ?? s.galleryLabel, "aria-busy": loading || undefined, ...props, children: items.map(i => {
152
169
  // LEGENDA VISÍVEL TORNA A MINIATURA DECORATIVA, e quem exigiu isso foi o axe, não a
153
170
  // teoria: com `alt` e legenda dizendo a mesma coisa, ele reprova `image-redundant-alt` e o
154
171
  // leitor de tela anuncia o texto DUAS vezes seguidas. É a regra de figura com legenda do
155
172
  // WAI — quando o texto ao lado já diz, a imagem entra com `alt=""`. O `alt` de verdade não
156
173
  // se perde: ele continua nomeando a foto AMPLIADA, que é onde não há legenda ao lado.
157
- const miolo = _jsxs(_Fragment, { children: [_jsx(Image, { src: i.src, alt: i.caption != null ? "" : i.alt, ratio: ratio }), i.caption != null && _jsx("span", { className: "gallery-caption", children: i.caption })] });
174
+ const foto = _jsx(Image, { src: i.src, alt: i.caption != null ? "" : i.alt, ratio: ratio });
175
+ const video = i.kind === "video";
176
+ const miolo = _jsxs(_Fragment, { children: [video
177
+ ? _jsxs("span", { className: "gallery-media", children: [foto, _jsxs(Badge, { className: "gallery-duration", children: [_jsx(Icon, { name: "play", size: "sm" }), _jsx("span", { className: "sr-only", children: ` ${s.galleryVideo}, ` }), i.duration != null && clockTime(i.duration)] })] })
178
+ : foto, i.caption != null && _jsx("span", { className: "gallery-caption", children: i.caption })] });
179
+ const marcado = multipla ? escolhidos.has(i.id) : i.id === selected;
158
180
  return _jsx("li", { className: "gallery-item", children: interativo
159
- ? _jsx("button", { type: "button", className: cx("gallery-tile", i.id === selected && "is-selected"), "aria-current": i.id === selected ? "true" : undefined, onClick: () => { onSelect?.(i.id); if (zoom)
160
- setAmpliado(i.id); }, children: miolo })
181
+ ? (multipla
182
+ ? _jsx("button", { type: "button", className: cx("gallery-tile", marcado && "is-selected"), "aria-pressed": marcado, onClick: () => alternar(i.id), children: miolo })
183
+ : _jsx("button", { type: "button", className: cx("gallery-tile", marcado && "is-selected"), "aria-current": marcado ? "true" : undefined, onClick: () => { onSelect?.(i.id); if (zoom)
184
+ setAmpliado(i.id); }, children: miolo }))
161
185
  : miolo }, i.id);
162
- }) }), zoom && _jsx(Dialog, { open: !!aberto, title: aberto ? (aberto.caption ?? aberto.alt) : "", onClose: () => setAmpliado(null), children: aberto && _jsx(Image, { src: aberto.src, alt: aberto.alt, fit: "contain" }) })] });
186
+ }) }), hasMore && _jsx("div", { ref: fimRef, className: "gallery-more", children: loading && _jsx(Spinner, {}) }), zoom && !multipla && _jsx(Dialog, { open: !!aberto, title: aberto ? (aberto.caption ?? aberto.alt) : "", onClose: () => setAmpliado(null), children: aberto && _jsx(Image, { src: aberto.src, alt: aberto.alt, fit: "contain" }) })] });
163
187
  }
164
188
  // ── Carousel (PLANO-1.0, item L1) ────────────────────────────────────────────────────────────
165
189
  // MARCAÇÃO, NÃO MOTOR — e a pergunta que o item mandava decidir primeiro ("se `scroll-snap` do
@@ -56,11 +56,14 @@ export interface TreeNode {
56
56
  label: ReactNode;
57
57
  icon?: IconName;
58
58
  children?: TreeNode[];
59
+ hasChildren?: boolean;
59
60
  }
60
- export declare function TreeView({ items, defaultExpandedIds, onSelect, label, className }: {
61
+ export declare function TreeView({ items, defaultExpandedIds, onSelect, onExpand, selectedId, label, className }: {
61
62
  items: TreeNode[];
62
63
  defaultExpandedIds?: string[];
63
64
  onSelect?: (node: TreeNode) => void;
65
+ onExpand?: (node: TreeNode) => void | Promise<unknown>;
66
+ selectedId?: string | null;
64
67
  label?: string;
65
68
  className?: string;
66
69
  }): ReactElement<unknown, string | React.JSXElementConstructor<any>>;
@@ -97,6 +100,11 @@ export declare function BottomNav({ items, current, variant, indicator, width, l
97
100
  width?: BottomNavWidth;
98
101
  label?: string;
99
102
  }): React.JSX.Element;
103
+ export interface NavListIndicator {
104
+ icon: IconName;
105
+ /** O que o sinal quer dizer ("Fixada"). Sai da tela e fica para o leitor de tela. */
106
+ label: string;
107
+ }
100
108
  export interface NavListItem {
101
109
  id: string;
102
110
  label: ReactNode;
@@ -108,6 +116,13 @@ export interface NavListItem {
108
116
  disabled?: boolean;
109
117
  /** O link do roteador do app (M-01). Linha com `render` é linha com destino: leva a seta, como a com `href`. */
110
118
  render?: ReactElement;
119
+ /** A foto da linha (AN-04) — o mesmo formato do `ChatMessage.avatar`. Toma o lugar do `icon`. */
120
+ avatar?: {
121
+ src?: string;
122
+ fallback?: ReactNode;
123
+ };
124
+ /** Os sinais ao lado do `value` (AN-04): fixada, silenciada. Pequenos e apagados, cada um com nome. */
125
+ indicators?: NavListIndicator[];
111
126
  }
112
127
  export declare function NavList({ items, className, ...props }: HTMLAttributes<HTMLUListElement> & RefAttributes<HTMLUListElement> & {
113
128
  items: NavListItem[];
@@ -5,18 +5,20 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
5
5
  // internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
6
6
  import React, { useRef } from "react";
7
7
  import { Tabs as BaseTabs } from "@base-ui/react/tabs";
8
+ import { useDirection } from "@base-ui/react/direction-provider";
8
9
  import { Autocomplete as BaseAutocomplete } from "@base-ui/react/autocomplete";
9
10
  import { useValorResponsivo } from "./responsivo-runtime.js";
10
11
  import { cx, fundirRender, useAureaStrings, usePortalContainer } from "./internal.js";
11
12
  import { Kbd } from "./markup.js";
12
13
  import { Icon } from "./system.js";
13
14
  import { Button } from "./actions.js";
14
- import { Badge } from "./feedback.js";
15
+ import { Badge, Spinner } from "./feedback.js";
15
16
  import { SearchField } from "./inputs.js";
16
17
  // `overlays` PASSOU A VIR ANTES de `navigation` no DAG (18/08/2026, MAP.md §DAG). Ele só
17
18
  // importa `internal`, `system` e `actions` — todos anteriores —, então não há ciclo: o que
18
19
  // mudou foi a ORDEM documentada, que colocava overlays no "resto" sem motivo medido.
19
20
  import { Tooltip } from "./overlays.js";
21
+ import { Avatar } from "./identity-client.js";
20
22
  export function Stepper({ items, label, className }) {
21
23
  const s = useAureaStrings();
22
24
  return _jsx("div", { role: "list", "aria-label": label ?? s.stepperLabel, className: cx("stepper", className), children: items.map((it, n) => {
@@ -89,6 +91,7 @@ function useSecaoEmVista(items, ligado, nav) {
89
91
  }, [ids, ligado]);
90
92
  return ligado ? atual : undefined;
91
93
  }
94
+ const temFilhos = (node) => !!node.children?.length || !!node.hasChildren;
92
95
  function flattenVisible(nodes, expanded, level = 1, parentId, acc = []) {
93
96
  for (const node of nodes) {
94
97
  acc.push({ node, level, parentId });
@@ -97,31 +100,46 @@ function flattenVisible(nodes, expanded, level = 1, parentId, acc = []) {
97
100
  }
98
101
  return acc;
99
102
  }
100
- export function TreeView({ items, defaultExpandedIds, onSelect, label, className }) {
103
+ export function TreeView({ items, defaultExpandedIds, onSelect, onExpand, selectedId, label, className }) {
101
104
  const s = useAureaStrings();
102
105
  const baseId = React.useId();
103
106
  const [expanded, setExpanded] = React.useState(() => new Set(defaultExpandedIds));
104
- const [selected, setSelected] = React.useState();
107
+ const [selectedInterno, setSelected] = React.useState();
108
+ const selected = selectedId !== undefined ? (selectedId ?? undefined) : selectedInterno;
109
+ const [carregando, setCarregando] = React.useState(() => new Set());
105
110
  const [active, setActive] = React.useState(() => items[0]?.id);
106
111
  const rootRef = React.useRef(null);
112
+ const rtl = useDirection() === "rtl";
107
113
  const visible = flattenVisible(items, expanded);
108
114
  // Roving tab stop derivado: se o nó ativo saiu do conjunto visível (dados
109
115
  // trocados, nó removido), o primeiro visível volta a ser tabulável — senão a
110
116
  // árvore inteira fica tabIndex=-1 e some da ordem do Tab (auditoria, MÉDIO 1).
111
117
  const effectiveActive = active !== undefined && visible.some(v => v.node.id === active) ? active : visible[0]?.node.id;
112
118
  const focusId = (id) => { setActive(id); rootRef.current?.querySelector(`[data-tree-id="${CSS.escape(id)}"]`)?.focus(); };
113
- const toggle = (id, open) => setExpanded(prev => { const n = new Set(prev); if (open)
119
+ const marcar = (id, open) => setExpanded(prev => { const n = new Set(prev); if (open)
114
120
  n.add(id);
115
121
  else
116
122
  n.delete(id); return n; });
117
- const select = (node) => { setSelected(node.id); onSelect?.(node); };
123
+ const tirarDaCarga = (id) => setCarregando(prev => { const n = new Set(prev); n.delete(id); return n; });
124
+ const toggle = (node, open) => {
125
+ marcar(node.id, open);
126
+ if (!open || !onExpand)
127
+ return;
128
+ const r = onExpand(node);
129
+ if (node.children?.length || !r || typeof r.then !== "function")
130
+ return;
131
+ setCarregando(prev => new Set(prev).add(node.id));
132
+ r.then(() => tirarDaCarga(node.id), () => { tirarDaCarga(node.id); marcar(node.id, false); });
133
+ };
134
+ const select = (node) => { if (selectedId === undefined)
135
+ setSelected(node.id); onSelect?.(node); };
118
136
  const onKeyDown = (e) => {
119
137
  const idx = visible.findIndex(v => v.node.id === effectiveActive);
120
138
  if (idx < 0)
121
139
  return;
122
- const cur = visible[idx], hasChildren = !!cur.node.children?.length, isOpen = expanded.has(cur.node.id);
123
- const rtl = getComputedStyle(e.currentTarget).direction === "rtl";
124
- const expandKey = rtl ? "ArrowLeft" : "ArrowRight", collapseKey = rtl ? "ArrowRight" : "ArrowLeft";
140
+ const cur = visible[idx], hasChildren = temFilhos(cur.node), isOpen = expanded.has(cur.node.id);
141
+ const rtlDom = getComputedStyle(e.currentTarget).direction === "rtl";
142
+ const expandKey = rtlDom ? "ArrowLeft" : "ArrowRight", collapseKey = rtlDom ? "ArrowRight" : "ArrowLeft";
125
143
  switch (e.key) {
126
144
  case "ArrowDown":
127
145
  e.preventDefault();
@@ -136,14 +154,14 @@ export function TreeView({ items, defaultExpandedIds, onSelect, label, className
136
154
  case expandKey:
137
155
  e.preventDefault();
138
156
  if (hasChildren && !isOpen)
139
- toggle(cur.node.id, true);
140
- else if (hasChildren && isOpen)
157
+ toggle(cur.node, true);
158
+ else if (isOpen && cur.node.children?.length)
141
159
  focusId(cur.node.children[0].id);
142
160
  break;
143
161
  case collapseKey:
144
162
  e.preventDefault();
145
163
  if (hasChildren && isOpen)
146
- toggle(cur.node.id, false);
164
+ toggle(cur.node, false);
147
165
  else if (cur.parentId)
148
166
  focusId(cur.parentId);
149
167
  break;
@@ -160,14 +178,14 @@ export function TreeView({ items, defaultExpandedIds, onSelect, label, className
160
178
  e.preventDefault();
161
179
  select(cur.node);
162
180
  if (hasChildren)
163
- toggle(cur.node.id, !isOpen);
181
+ toggle(cur.node, !isOpen);
164
182
  break;
165
183
  }
166
184
  };
167
185
  const renderNodes = (nodes, level) => (_jsx("ul", { ref: level === 1 ? rootRef : undefined, className: cx(level === 1 ? "tree" : "tree-group", level === 1 && className), role: level === 1 ? "tree" : "group", "aria-label": level === 1 ? (label ?? s.treeLabel) : undefined, onKeyDown: level === 1 ? onKeyDown : undefined, children: nodes.map(node => {
168
- const hasChildren = !!node.children?.length, isOpen = expanded.has(node.id), isSelected = selected === node.id, labelId = baseId + node.id;
169
- return _jsxs("li", { className: "tree-item", role: "treeitem", "data-tree-id": node.id, "aria-level": level, "aria-expanded": hasChildren ? isOpen : undefined, "aria-selected": isSelected, "aria-labelledby": labelId, tabIndex: node.id === effectiveActive ? 0 : -1, children: [_jsxs("span", { className: "tree-node", "data-selected": isSelected || undefined, style: { paddingInlineStart: `calc(var(--space-3) + ${level - 1} * var(--space-4))` }, onClick: () => { focusId(node.id); select(node); if (hasChildren)
170
- toggle(node.id, !isOpen); }, children: [hasChildren ? _jsx(Icon, { name: "caret-right", size: "sm", className: "tree-twist" }) : _jsx("span", { className: "tree-indent", "aria-hidden": "true" }), node.icon && _jsx(Icon, { name: node.icon, size: "sm" }), _jsx("span", { id: labelId, className: "tree-label", children: node.label })] }), hasChildren && isOpen && renderNodes(node.children, level + 1)] }, node.id);
186
+ const hasChildren = temFilhos(node), isOpen = expanded.has(node.id), isSelected = selected === node.id, isLoading = isOpen && carregando.has(node.id), labelId = baseId + node.id;
187
+ return _jsxs("li", { className: "tree-item", role: "treeitem", "data-tree-id": node.id, "aria-level": level, "aria-expanded": hasChildren ? isOpen : undefined, "aria-selected": isSelected, "aria-busy": isLoading || undefined, "aria-labelledby": labelId, tabIndex: node.id === effectiveActive ? 0 : -1, children: [_jsxs("span", { className: "tree-node", "data-selected": isSelected || undefined, style: { paddingInlineStart: `calc(var(--space-3) + ${level - 1} * var(--space-4))` }, onClick: () => { focusId(node.id); select(node); if (hasChildren)
188
+ toggle(node, !isOpen); }, children: [isLoading ? _jsx(Spinner, { decorative: true, className: "tree-twist" }) : hasChildren ? _jsx(Icon, { name: "caret-right", size: "sm", className: cx("tree-twist", rtl && "tree-twist-rtl") }) : _jsx("span", { className: "tree-indent", "aria-hidden": "true" }), node.icon && _jsx(Icon, { name: node.icon, size: "sm" }), _jsx("span", { id: labelId, className: "tree-label", children: node.label })] }), isOpen && !!node.children?.length && renderNodes(node.children, level + 1)] }, node.id);
171
189
  }) }));
172
190
  return renderNodes(items, 1);
173
191
  }
@@ -256,7 +274,7 @@ export function BottomNav({ items, current, variant = "floating", indicator = "n
256
274
  }
257
275
  export function NavList({ items, className, ...props }) {
258
276
  return _jsx("ul", { className: cx("nav-list", className), ...props, children: items.map(it => {
259
- const miolo = _jsxs(_Fragment, { children: [it.icon && _jsx(Icon, { name: it.icon }), _jsxs("span", { className: "nav-list-text", children: [_jsx("span", { className: "nav-list-label", children: it.label }), it.description != null && _jsx("span", { className: "nav-list-description", children: it.description })] }), it.value != null && _jsx("span", { className: "nav-list-value", children: it.value }), (it.href || it.render) && _jsx(Icon, { name: "caret-right", size: "sm", className: "nav-list-chevron" })] });
277
+ const miolo = _jsxs(_Fragment, { children: [it.avatar ? _jsx(Avatar, { src: it.avatar.src, fallback: it.avatar.fallback }) : it.icon && _jsx(Icon, { name: it.icon }), _jsxs("span", { className: "nav-list-text", children: [_jsx("span", { className: "nav-list-label", children: it.label }), it.description != null && _jsx("span", { className: "nav-list-description", children: it.description })] }), it.indicators && it.indicators.length > 0 && _jsx("span", { className: "nav-list-indicators", children: it.indicators.map(x => _jsxs("span", { children: [_jsx(Icon, { name: x.icon, size: "sm" }), _jsx("span", { className: "sr-only", children: x.label })] }, x.label)) }), it.value != null && _jsx("span", { className: "nav-list-value", children: it.value }), (it.href || it.render) && _jsx(Icon, { name: "caret-right", size: "sm", className: "nav-list-chevron" })] });
260
278
  return _jsx("li", { children: it.render && !it.disabled
261
279
  ? fundirRender(it.render, { className: "nav-list-row", onClick: it.onClick, children: miolo }, "a")
262
280
  : it.href && !it.disabled
package/dist/pure.d.ts CHANGED
@@ -88,6 +88,7 @@ export interface AureaStrings {
88
88
  carouselPrev: string;
89
89
  carouselNext: string;
90
90
  galleryLabel: string;
91
+ galleryVideo: string;
91
92
  sortableLabel: string;
92
93
  sortableHandle: string;
93
94
  sortableHelp: string;
@@ -166,12 +167,23 @@ export interface AureaStrings {
166
167
  chatLabel: string;
167
168
  chatMessage: string;
168
169
  chatSend: string;
170
+ chatAttach: string;
171
+ chatAttachments: string;
172
+ chatReplyTo: string;
173
+ chatReplyCancel: string;
174
+ chatEditing: string;
175
+ chatEditCancel: string;
176
+ chatSave: string;
177
+ chatEdited: string;
178
+ chatForwardedFrom: string;
169
179
  qrCode: string;
170
180
  chartLabel: string;
171
181
  copyCode: string;
172
182
  tocLabel: string;
173
183
  navigationToggle: string;
174
184
  sidebarLabel: string;
185
+ sidebarCollapse: string;
186
+ sidebarExpand: string;
175
187
  bottomNavLabel: string;
176
188
  themeToDark: string;
177
189
  themeToLight: string;
package/dist/pure.js CHANGED
@@ -53,10 +53,10 @@ export const universalStates = ["waiting_user", "waiting_approval", "waiting_dep
53
53
  // perdeu qualidade é avisado. Um `role="alert"` aqui interromperia o leitor de tela por uma
54
54
  // condição que não pede ação imediata, que é o oposto do que o papel serve.
55
55
  export const stateSeverity = (state) => state.startsWith("waiting_") ? "info" : "warning";
56
- export const defaultStrings = { passwordShow: "Show password", passwordHide: "Hide password", skipToContent: "Skip to content", close: "Close", confirmCancel: "Cancel", dataError: "This could not be loaded.", dataEmpty: "Nothing here yet", confirmProceed: "Continue", paginationLabel: "Pagination", previous: "Previous", next: "Next", breadcrumbLabel: "Breadcrumb", tabsLabel: "Tabs", optionsLabel: "Options", tableLabel: "Table", commandLabel: "Command palette", commandPlaceholder: "Type a command…", dismissNotification: "Dismiss notification", toolbarLabel: "Toolbar", loading: "Loading", otpDigit: "Digit", stepperLabel: "Steps", increment: "Increase", decrement: "Decrease", comboboxEmpty: "No results", comboboxClear: "Clear selection", comboboxOpen: "Open list", comboboxRemove: "Remove", comboboxLoading: "Loading…", fileDropPrompt: "Drag files here or click to select", fileAdded: "File added", fileRemoved: "File removed", fileRemove: "Remove", fileTooLarge: "File exceeds the size limit", fileWrongType: "File type not allowed", treeLabel: "Tree", notificationsLabel: "Notifications", notificationMarkAll: "Mark all as read", notificationEmpty: "No notifications", notificationUnread: "Unread", notificationNew: "new notifications", dataGridFilter: "Filter", dataGridEmpty: "No results", dataGridSelectAll: "Select all rows", dataGridSelectRow: "Select row", dataGridSelected: "selected", dataGridClearSelection: "Clear selection", dataGridBulkLabel: "Bulk actions", dataGridColumns: "Columns", dataGridResize: "Resize column", dataGridStale: "Showing data that may be out of date.", dataGridPartial: "Some rows could not be loaded. What you see is incomplete.", dataGridError: "The rows could not be loaded.", dataGridDetails: "Details", dataGridDetailPanel: "Row detail", dataGridExport: "Export", dataGridExportRows: "rows", dataGridExportFiltered: "filtered rows", dataGridExportSelected: "selected rows", mediaPlayer: "Media player", mediaPlay: "Play", mediaPause: "Pause", mediaMute: "Mute", mediaUnmute: "Unmute", mediaSeek: "Seek", mediaVolume: "Volume", mediaCaptionsShow: "Show captions", mediaCaptionsHide: "Hide captions", mediaSkipBack: "Skip back 10 seconds", mediaSkipForward: "Skip forward 10 seconds", mediaFullscreenEnter: "Enter full screen", mediaFullscreenExit: "Exit full screen", mediaEmbedOpen: "Open the video on its own site", carouselLabel: "Carousel", carouselSlide: "Slide", positionOf: "of", carouselPrev: "Previous slide", carouselNext: "Next slide", galleryLabel: "Gallery", sortableLabel: "Sortable list", sortableHandle: "Reorder", sortableHelp: "Press Space to pick this up, then Arrow Up and Arrow Down to move it. Space drops it, Escape puts it back.", sortableGrabbed: "Picked up", sortableDropped: "Dropped", sortableMoved: "Moved", sortableCanceled: "Put back", blockEditorLabel: "Content blocks", blockLabel: "Block", blockRemove: "Remove block", uploadSending: "Uploading", uploadCancel: "Cancel upload", uploadRetry: "Retry upload", uploadError: "Upload failed", uploadCanceled: "Upload canceled", uploadComplete: "Upload complete", uploadPause: "Pause upload", uploadResume: "Resume upload", uploadPaused: "Paused", uploadPending: "Waiting", uploadChecksum: "Checksum", uploadChecksumBad: "The server received different bytes than the ones sent.", fileConflict: "A file with this name is already in the queue", fileReplace: "Replace", fileKeepBoth: "Keep both", fileSkip: "Skip", uploadReceipt: "Receipt", uploadReceiptCopy: "Copy receipt", agentInspector: "Agent inspector", invocationLabel: "Invocation", invocationInput: "Input", invocationOutput: "Output", taskQueueLabel: "Task queue", taskState: { queued: "Queued", running: "Running", completed: "Completed", failed: "Failed", blocked: "Blocked", paused: "Paused" }, taskPriority: { low: "Low", medium: "Medium", high: "High" }, approvalLabel: "Approval", approvalApprove: "Approve", approvalDeny: "Deny", approvalApproved: "Approved", approvalDenied: "Denied", approvalDeadline: "Decide by", approvalRisk: { low: "Low risk", medium: "Medium risk", high: "High risk" }, permissionLabel: "Tool permissions", permissionAsk: "Ask", permissionAlways: "Always", permissionNever: "Never", eventStreamLabel: "Events", traceLabel: "Trace", healthLabel: "Health", healthState: { operational: "Operational", degraded: "Degraded", down: "Down", maintenance: "Maintenance", unknown: "Unknown" }, modelUsageLabel: "Model usage", usageMetric: { tokens: "Tokens", cost: "Cost", requests: "Requests" }, costMeterLabel: "Cost", costMeterSpent: "Spent", costMeterRemaining: "left", costMeterExceeded: "Over the limit", costMeterNear: "Spending is past the soft limit.", costMeterOver: "The limit has been reached. Further calls are blocked.", memoryLabel: "Memory ledger", memoryProvenance: "Where this came from", memoryOperation: { added: "Added", updated: "Updated", recalled: "Recalled", forgotten: "Forgotten" }, memoryScope: { episodic: "Episodic", semantic: "Semantic", procedural: "Procedural" }, agentMessageLabel: "Agent messages", agentMessageTo: "to", agentMessageBroadcast: "all agents", agentMessageReason: "Why it went this way", agentMessageKind: { request: "Request", response: "Response", handoff: "Handoff", broadcast: "Broadcast", error: "Error" }, automationWhen: "When", automationThen: "Then", automationEnabled: "Enabled", automationLastRun: "Last run", automationResult: { success: "Succeeded", failure: "Failed" }, graphLabel: "Dependency graph", agentState: { idle: "Idle", thinking: "Thinking", running: "Running", paused: "Paused", error: "Error", completed: "Completed" }, codeEditor: "Code editor", chatLabel: "Conversation", chatMessage: "Message", chatSend: "Send", qrCode: "QR code", chartLabel: "Chart", copyCode: "Copy code", tocLabel: "On this page", navigationToggle: "Navigation", sidebarLabel: "Sidebar", bottomNavLabel: "Main", themeToDark: "Switch to dark theme", themeToLight: "Switch to light theme", universalState: { waiting_user: "Waiting for someone to act.", waiting_approval: "Waiting for approval.", waiting_dependency: "Waiting for something else to finish.", offline: "No connection. This was loaded earlier.", stale: "This may be out of date.", partial: "Some of this could not be loaded.", degraded: "Working with reduced capability." } };
56
+ export const defaultStrings = { passwordShow: "Show password", passwordHide: "Hide password", skipToContent: "Skip to content", close: "Close", confirmCancel: "Cancel", dataError: "This could not be loaded.", dataEmpty: "Nothing here yet", confirmProceed: "Continue", paginationLabel: "Pagination", previous: "Previous", next: "Next", breadcrumbLabel: "Breadcrumb", tabsLabel: "Tabs", optionsLabel: "Options", tableLabel: "Table", commandLabel: "Command palette", commandPlaceholder: "Type a command…", dismissNotification: "Dismiss notification", toolbarLabel: "Toolbar", loading: "Loading", otpDigit: "Digit", stepperLabel: "Steps", increment: "Increase", decrement: "Decrease", comboboxEmpty: "No results", comboboxClear: "Clear selection", comboboxOpen: "Open list", comboboxRemove: "Remove", comboboxLoading: "Loading…", fileDropPrompt: "Drag files here or click to select", fileAdded: "File added", fileRemoved: "File removed", fileRemove: "Remove", fileTooLarge: "File exceeds the size limit", fileWrongType: "File type not allowed", treeLabel: "Tree", notificationsLabel: "Notifications", notificationMarkAll: "Mark all as read", notificationEmpty: "No notifications", notificationUnread: "Unread", notificationNew: "new notifications", dataGridFilter: "Filter", dataGridEmpty: "No results", dataGridSelectAll: "Select all rows", dataGridSelectRow: "Select row", dataGridSelected: "selected", dataGridClearSelection: "Clear selection", dataGridBulkLabel: "Bulk actions", dataGridColumns: "Columns", dataGridResize: "Resize column", dataGridStale: "Showing data that may be out of date.", dataGridPartial: "Some rows could not be loaded. What you see is incomplete.", dataGridError: "The rows could not be loaded.", dataGridDetails: "Details", dataGridDetailPanel: "Row detail", dataGridExport: "Export", dataGridExportRows: "rows", dataGridExportFiltered: "filtered rows", dataGridExportSelected: "selected rows", mediaPlayer: "Media player", mediaPlay: "Play", mediaPause: "Pause", mediaMute: "Mute", mediaUnmute: "Unmute", mediaSeek: "Seek", mediaVolume: "Volume", mediaCaptionsShow: "Show captions", mediaCaptionsHide: "Hide captions", mediaSkipBack: "Skip back 10 seconds", mediaSkipForward: "Skip forward 10 seconds", mediaFullscreenEnter: "Enter full screen", mediaFullscreenExit: "Exit full screen", mediaEmbedOpen: "Open the video on its own site", carouselLabel: "Carousel", carouselSlide: "Slide", positionOf: "of", carouselPrev: "Previous slide", carouselNext: "Next slide", galleryLabel: "Gallery", galleryVideo: "Video", sortableLabel: "Sortable list", sortableHandle: "Reorder", sortableHelp: "Press Space to pick this up, then Arrow Up and Arrow Down to move it. Space drops it, Escape puts it back.", sortableGrabbed: "Picked up", sortableDropped: "Dropped", sortableMoved: "Moved", sortableCanceled: "Put back", blockEditorLabel: "Content blocks", blockLabel: "Block", blockRemove: "Remove block", uploadSending: "Uploading", uploadCancel: "Cancel upload", uploadRetry: "Retry upload", uploadError: "Upload failed", uploadCanceled: "Upload canceled", uploadComplete: "Upload complete", uploadPause: "Pause upload", uploadResume: "Resume upload", uploadPaused: "Paused", uploadPending: "Waiting", uploadChecksum: "Checksum", uploadChecksumBad: "The server received different bytes than the ones sent.", fileConflict: "A file with this name is already in the queue", fileReplace: "Replace", fileKeepBoth: "Keep both", fileSkip: "Skip", uploadReceipt: "Receipt", uploadReceiptCopy: "Copy receipt", agentInspector: "Agent inspector", invocationLabel: "Invocation", invocationInput: "Input", invocationOutput: "Output", taskQueueLabel: "Task queue", taskState: { queued: "Queued", running: "Running", completed: "Completed", failed: "Failed", blocked: "Blocked", paused: "Paused" }, taskPriority: { low: "Low", medium: "Medium", high: "High" }, approvalLabel: "Approval", approvalApprove: "Approve", approvalDeny: "Deny", approvalApproved: "Approved", approvalDenied: "Denied", approvalDeadline: "Decide by", approvalRisk: { low: "Low risk", medium: "Medium risk", high: "High risk" }, permissionLabel: "Tool permissions", permissionAsk: "Ask", permissionAlways: "Always", permissionNever: "Never", eventStreamLabel: "Events", traceLabel: "Trace", healthLabel: "Health", healthState: { operational: "Operational", degraded: "Degraded", down: "Down", maintenance: "Maintenance", unknown: "Unknown" }, modelUsageLabel: "Model usage", usageMetric: { tokens: "Tokens", cost: "Cost", requests: "Requests" }, costMeterLabel: "Cost", costMeterSpent: "Spent", costMeterRemaining: "left", costMeterExceeded: "Over the limit", costMeterNear: "Spending is past the soft limit.", costMeterOver: "The limit has been reached. Further calls are blocked.", memoryLabel: "Memory ledger", memoryProvenance: "Where this came from", memoryOperation: { added: "Added", updated: "Updated", recalled: "Recalled", forgotten: "Forgotten" }, memoryScope: { episodic: "Episodic", semantic: "Semantic", procedural: "Procedural" }, agentMessageLabel: "Agent messages", agentMessageTo: "to", agentMessageBroadcast: "all agents", agentMessageReason: "Why it went this way", agentMessageKind: { request: "Request", response: "Response", handoff: "Handoff", broadcast: "Broadcast", error: "Error" }, automationWhen: "When", automationThen: "Then", automationEnabled: "Enabled", automationLastRun: "Last run", automationResult: { success: "Succeeded", failure: "Failed" }, graphLabel: "Dependency graph", agentState: { idle: "Idle", thinking: "Thinking", running: "Running", paused: "Paused", error: "Error", completed: "Completed" }, codeEditor: "Code editor", chatLabel: "Conversation", chatMessage: "Message", chatSend: "Send", chatAttach: "Attach file", chatAttachments: "Attachments", chatReplyTo: "Replying to", chatReplyCancel: "Cancel reply", chatEditing: "Editing message", chatEditCancel: "Cancel editing", chatSave: "Save", chatEdited: "edited", chatForwardedFrom: "Forwarded from", qrCode: "QR code", chartLabel: "Chart", copyCode: "Copy code", tocLabel: "On this page", navigationToggle: "Navigation", sidebarLabel: "Sidebar", sidebarCollapse: "Collapse sidebar", sidebarExpand: "Expand sidebar", bottomNavLabel: "Main", themeToDark: "Switch to dark theme", themeToLight: "Switch to light theme", universalState: { waiting_user: "Waiting for someone to act.", waiting_approval: "Waiting for approval.", waiting_dependency: "Waiting for something else to finish.", offline: "No connection. This was loaded earlier.", stale: "This may be out of date.", partial: "Some of this could not be loaded.", degraded: "Working with reduced capability." } };
57
57
  // pt-BR preservado como locale (mesmos valores que já eram o default): passe
58
58
  // <AureaProvider strings={ptBR}> para restaurar português.
59
- export const ptBR = { passwordShow: "Mostrar senha", passwordHide: "Ocultar senha", skipToContent: "Pular para o conteúdo", close: "Fechar", confirmCancel: "Cancelar", dataError: "Não foi possível carregar.", dataEmpty: "Ainda não há nada aqui", confirmProceed: "Continuar", paginationLabel: "Paginação", previous: "Anterior", next: "Próxima", breadcrumbLabel: "Breadcrumb", tabsLabel: "Abas", optionsLabel: "Opções", tableLabel: "Tabela", commandLabel: "Paleta de comandos", commandPlaceholder: "Digite um comando…", dismissNotification: "Dispensar notificação", toolbarLabel: "Barra de ferramentas", loading: "Carregando", otpDigit: "Dígito", stepperLabel: "Etapas", increment: "Aumentar", decrement: "Diminuir", comboboxEmpty: "Nenhum resultado", comboboxClear: "Limpar seleção", comboboxOpen: "Abrir lista", comboboxRemove: "Remover", comboboxLoading: "Carregando…", fileDropPrompt: "Arraste arquivos aqui ou clique para selecionar", fileAdded: "Arquivo adicionado", fileRemoved: "Arquivo removido", fileRemove: "Remover", fileTooLarge: "Arquivo maior que o limite", fileWrongType: "Tipo de arquivo não aceito", treeLabel: "Árvore", notificationsLabel: "Notificações", notificationMarkAll: "Marcar todas como lidas", notificationEmpty: "Nenhuma notificação", notificationUnread: "Não lida", notificationNew: "novas notificações", dataGridFilter: "Filtrar", dataGridEmpty: "Nenhum resultado", dataGridSelectAll: "Selecionar todas as linhas", dataGridSelectRow: "Selecionar linha", dataGridSelected: "selecionadas", dataGridClearSelection: "Limpar seleção", dataGridBulkLabel: "Ações em lote", dataGridColumns: "Colunas", dataGridResize: "Redimensionar coluna", dataGridStale: "Mostrando dados que podem estar desatualizados.", dataGridPartial: "Algumas linhas não puderam ser carregadas. O que aparece está incompleto.", dataGridError: "Não foi possível carregar as linhas.", dataGridDetails: "Detalhes", dataGridDetailPanel: "Detalhe da linha", dataGridExport: "Exportar", dataGridExportRows: "linhas", dataGridExportFiltered: "linhas filtradas", dataGridExportSelected: "linhas selecionadas", mediaPlayer: "Reprodutor de mídia", mediaPlay: "Reproduzir", mediaPause: "Pausar", mediaMute: "Silenciar", mediaUnmute: "Ativar som", mediaSeek: "Posição da reprodução", mediaVolume: "Volume", mediaCaptionsShow: "Ativar legendas", mediaCaptionsHide: "Desativar legendas", mediaSkipBack: "Voltar 10 segundos", mediaSkipForward: "Avançar 10 segundos", mediaFullscreenEnter: "Tela cheia", mediaFullscreenExit: "Sair da tela cheia", mediaEmbedOpen: "Abrir o vídeo no site de origem", carouselLabel: "Carrossel", carouselSlide: "Slide", positionOf: "de", carouselPrev: "Slide anterior", carouselNext: "Próximo slide", galleryLabel: "Galeria", sortableLabel: "Lista ordenável", sortableHandle: "Reordenar", sortableHelp: "Aperte Espaço para pegar, e as setas para cima e para baixo para mover. Espaço solta, Esc devolve ao lugar.", sortableGrabbed: "Pego", sortableDropped: "Solto", sortableMoved: "Movido", sortableCanceled: "Devolvido", blockEditorLabel: "Blocos de conteúdo", blockLabel: "Bloco", blockRemove: "Remover bloco", uploadSending: "Enviando", uploadCancel: "Cancelar envio", uploadRetry: "Tentar envio novamente", uploadError: "Falha no envio", uploadCanceled: "Envio cancelado", uploadComplete: "Envio concluído", uploadPause: "Pausar envio", uploadResume: "Retomar envio", uploadPaused: "Pausado", uploadPending: "Aguardando", uploadChecksum: "Soma de verificação", uploadChecksumBad: "O servidor recebeu bytes diferentes dos enviados.", fileConflict: "Já existe um arquivo com este nome na fila", fileReplace: "Substituir", fileKeepBoth: "Manter os dois", fileSkip: "Pular", uploadReceipt: "Recibo", uploadReceiptCopy: "Copiar recibo", agentInspector: "Inspetor do agente", invocationLabel: "Invocação", invocationInput: "Entrada", invocationOutput: "Saída", taskQueueLabel: "Fila de tarefas", taskState: { queued: "Na fila", running: "Executando", completed: "Concluída", failed: "Falhou", blocked: "Bloqueada", paused: "Pausada" }, taskPriority: { low: "Baixa", medium: "Média", high: "Alta" }, approvalLabel: "Aprovação", approvalApprove: "Aprovar", approvalDeny: "Negar", approvalApproved: "Aprovado", approvalDenied: "Negado", approvalDeadline: "Decidir até", approvalRisk: { low: "Risco baixo", medium: "Risco médio", high: "Risco alto" }, permissionLabel: "Permissões de ferramenta", permissionAsk: "Perguntar", permissionAlways: "Sempre", permissionNever: "Nunca", eventStreamLabel: "Eventos", traceLabel: "Rastro", healthLabel: "Saúde", healthState: { operational: "Operando", degraded: "Degradado", down: "Fora do ar", maintenance: "Em manutenção", unknown: "Desconhecido" }, modelUsageLabel: "Consumo por modelo", usageMetric: { tokens: "Tokens", cost: "Custo", requests: "Chamadas" }, costMeterLabel: "Custo", costMeterSpent: "Gasto", costMeterRemaining: "restantes", costMeterExceeded: "Acima do limite", costMeterNear: "O gasto passou do limite brando.", costMeterOver: "O limite foi atingido. As próximas chamadas estão bloqueadas.", memoryLabel: "Livro-razão de memória", memoryProvenance: "De onde isto veio", memoryOperation: { added: "Guardada", updated: "Atualizada", recalled: "Lembrada", forgotten: "Esquecida" }, memoryScope: { episodic: "Episódica", semantic: "Semântica", procedural: "Procedural" }, agentMessageLabel: "Recados entre agentes", agentMessageTo: "para", agentMessageBroadcast: "todos os agentes", agentMessageReason: "Por que foi por aqui", agentMessageKind: { request: "Pedido", response: "Resposta", handoff: "Passagem", broadcast: "Difusão", error: "Erro" }, automationWhen: "Quando", automationThen: "Então", automationEnabled: "Ativa", automationLastRun: "Última execução", automationResult: { success: "Concluiu", failure: "Falhou" }, graphLabel: "Grafo de dependências", agentState: { idle: "Ocioso", thinking: "Pensando", running: "Executando", paused: "Pausado", error: "Erro", completed: "Concluído" }, codeEditor: "Editor de código", chatLabel: "Conversa", chatMessage: "Mensagem", chatSend: "Enviar", qrCode: "Código QR", chartLabel: "Gráfico", copyCode: "Copiar código", tocLabel: "Nesta página", navigationToggle: "Navegação", sidebarLabel: "Lateral", bottomNavLabel: "Principal", themeToDark: "Mudar para o tema escuro", themeToLight: "Mudar para o tema claro", universalState: { waiting_user: "Esperando alguém agir.", waiting_approval: "Esperando aprovação.", waiting_dependency: "Esperando outra coisa terminar.", offline: "Sem conexão. Isto foi carregado antes.", stale: "Isto pode estar desatualizado.", partial: "Parte disto não pôde ser carregada.", degraded: "Funcionando com capacidade reduzida." } };
59
+ export const ptBR = { passwordShow: "Mostrar senha", passwordHide: "Ocultar senha", skipToContent: "Pular para o conteúdo", close: "Fechar", confirmCancel: "Cancelar", dataError: "Não foi possível carregar.", dataEmpty: "Ainda não há nada aqui", confirmProceed: "Continuar", paginationLabel: "Paginação", previous: "Anterior", next: "Próxima", breadcrumbLabel: "Breadcrumb", tabsLabel: "Abas", optionsLabel: "Opções", tableLabel: "Tabela", commandLabel: "Paleta de comandos", commandPlaceholder: "Digite um comando…", dismissNotification: "Dispensar notificação", toolbarLabel: "Barra de ferramentas", loading: "Carregando", otpDigit: "Dígito", stepperLabel: "Etapas", increment: "Aumentar", decrement: "Diminuir", comboboxEmpty: "Nenhum resultado", comboboxClear: "Limpar seleção", comboboxOpen: "Abrir lista", comboboxRemove: "Remover", comboboxLoading: "Carregando…", fileDropPrompt: "Arraste arquivos aqui ou clique para selecionar", fileAdded: "Arquivo adicionado", fileRemoved: "Arquivo removido", fileRemove: "Remover", fileTooLarge: "Arquivo maior que o limite", fileWrongType: "Tipo de arquivo não aceito", treeLabel: "Árvore", notificationsLabel: "Notificações", notificationMarkAll: "Marcar todas como lidas", notificationEmpty: "Nenhuma notificação", notificationUnread: "Não lida", notificationNew: "novas notificações", dataGridFilter: "Filtrar", dataGridEmpty: "Nenhum resultado", dataGridSelectAll: "Selecionar todas as linhas", dataGridSelectRow: "Selecionar linha", dataGridSelected: "selecionadas", dataGridClearSelection: "Limpar seleção", dataGridBulkLabel: "Ações em lote", dataGridColumns: "Colunas", dataGridResize: "Redimensionar coluna", dataGridStale: "Mostrando dados que podem estar desatualizados.", dataGridPartial: "Algumas linhas não puderam ser carregadas. O que aparece está incompleto.", dataGridError: "Não foi possível carregar as linhas.", dataGridDetails: "Detalhes", dataGridDetailPanel: "Detalhe da linha", dataGridExport: "Exportar", dataGridExportRows: "linhas", dataGridExportFiltered: "linhas filtradas", dataGridExportSelected: "linhas selecionadas", mediaPlayer: "Reprodutor de mídia", mediaPlay: "Reproduzir", mediaPause: "Pausar", mediaMute: "Silenciar", mediaUnmute: "Ativar som", mediaSeek: "Posição da reprodução", mediaVolume: "Volume", mediaCaptionsShow: "Ativar legendas", mediaCaptionsHide: "Desativar legendas", mediaSkipBack: "Voltar 10 segundos", mediaSkipForward: "Avançar 10 segundos", mediaFullscreenEnter: "Tela cheia", mediaFullscreenExit: "Sair da tela cheia", mediaEmbedOpen: "Abrir o vídeo no site de origem", carouselLabel: "Carrossel", carouselSlide: "Slide", positionOf: "de", carouselPrev: "Slide anterior", carouselNext: "Próximo slide", galleryLabel: "Galeria", galleryVideo: "Vídeo", sortableLabel: "Lista ordenável", sortableHandle: "Reordenar", sortableHelp: "Aperte Espaço para pegar, e as setas para cima e para baixo para mover. Espaço solta, Esc devolve ao lugar.", sortableGrabbed: "Pego", sortableDropped: "Solto", sortableMoved: "Movido", sortableCanceled: "Devolvido", blockEditorLabel: "Blocos de conteúdo", blockLabel: "Bloco", blockRemove: "Remover bloco", uploadSending: "Enviando", uploadCancel: "Cancelar envio", uploadRetry: "Tentar envio novamente", uploadError: "Falha no envio", uploadCanceled: "Envio cancelado", uploadComplete: "Envio concluído", uploadPause: "Pausar envio", uploadResume: "Retomar envio", uploadPaused: "Pausado", uploadPending: "Aguardando", uploadChecksum: "Soma de verificação", uploadChecksumBad: "O servidor recebeu bytes diferentes dos enviados.", fileConflict: "Já existe um arquivo com este nome na fila", fileReplace: "Substituir", fileKeepBoth: "Manter os dois", fileSkip: "Pular", uploadReceipt: "Recibo", uploadReceiptCopy: "Copiar recibo", agentInspector: "Inspetor do agente", invocationLabel: "Invocação", invocationInput: "Entrada", invocationOutput: "Saída", taskQueueLabel: "Fila de tarefas", taskState: { queued: "Na fila", running: "Executando", completed: "Concluída", failed: "Falhou", blocked: "Bloqueada", paused: "Pausada" }, taskPriority: { low: "Baixa", medium: "Média", high: "Alta" }, approvalLabel: "Aprovação", approvalApprove: "Aprovar", approvalDeny: "Negar", approvalApproved: "Aprovado", approvalDenied: "Negado", approvalDeadline: "Decidir até", approvalRisk: { low: "Risco baixo", medium: "Risco médio", high: "Risco alto" }, permissionLabel: "Permissões de ferramenta", permissionAsk: "Perguntar", permissionAlways: "Sempre", permissionNever: "Nunca", eventStreamLabel: "Eventos", traceLabel: "Rastro", healthLabel: "Saúde", healthState: { operational: "Operando", degraded: "Degradado", down: "Fora do ar", maintenance: "Em manutenção", unknown: "Desconhecido" }, modelUsageLabel: "Consumo por modelo", usageMetric: { tokens: "Tokens", cost: "Custo", requests: "Chamadas" }, costMeterLabel: "Custo", costMeterSpent: "Gasto", costMeterRemaining: "restantes", costMeterExceeded: "Acima do limite", costMeterNear: "O gasto passou do limite brando.", costMeterOver: "O limite foi atingido. As próximas chamadas estão bloqueadas.", memoryLabel: "Livro-razão de memória", memoryProvenance: "De onde isto veio", memoryOperation: { added: "Guardada", updated: "Atualizada", recalled: "Lembrada", forgotten: "Esquecida" }, memoryScope: { episodic: "Episódica", semantic: "Semântica", procedural: "Procedural" }, agentMessageLabel: "Recados entre agentes", agentMessageTo: "para", agentMessageBroadcast: "todos os agentes", agentMessageReason: "Por que foi por aqui", agentMessageKind: { request: "Pedido", response: "Resposta", handoff: "Passagem", broadcast: "Difusão", error: "Erro" }, automationWhen: "Quando", automationThen: "Então", automationEnabled: "Ativa", automationLastRun: "Última execução", automationResult: { success: "Concluiu", failure: "Falhou" }, graphLabel: "Grafo de dependências", agentState: { idle: "Ocioso", thinking: "Pensando", running: "Executando", paused: "Pausado", error: "Erro", completed: "Concluído" }, codeEditor: "Editor de código", chatLabel: "Conversa", chatMessage: "Mensagem", chatSend: "Enviar", chatAttach: "Anexar arquivo", chatAttachments: "Anexos", chatReplyTo: "Respondendo a", chatReplyCancel: "Cancelar resposta", chatEditing: "Editando mensagem", chatEditCancel: "Cancelar edição", chatSave: "Salvar", chatEdited: "editada", chatForwardedFrom: "Encaminhada de", qrCode: "Código QR", chartLabel: "Gráfico", copyCode: "Copiar código", tocLabel: "Nesta página", navigationToggle: "Navegação", sidebarLabel: "Lateral", sidebarCollapse: "Recolher o menu", sidebarExpand: "Abrir o menu", bottomNavLabel: "Principal", themeToDark: "Mudar para o tema escuro", themeToLight: "Mudar para o tema claro", universalState: { waiting_user: "Esperando alguém agir.", waiting_approval: "Esperando aprovação.", waiting_dependency: "Esperando outra coisa terminar.", offline: "Sem conexão. Isto foi carregado antes.", stale: "Isto pode estar desatualizado.", partial: "Parte disto não pôde ser carregada.", degraded: "Funcionando com capacidade reduzida." } };
60
60
  // Sprite de ícones: CONFIGURAÇÃO DE APLICAÇÃO, então entra pelo provider, uma vez.
61
61
  // Antes o default absoluto "/aurea-icons.svg" vivia no Icon e 15 componentes declaravam
62
62
  // e repassavam `spriteUrl` à mão — 52 sítios de código. Consequência: aplicação servida
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurea-uds/react",
3
- "version": "0.17.0",
3
+ "version": "0.19.1",
4
4
  "description": "Os componentes React da Aurea UDS. Importe @aurea-uds/fonts/css e @aurea-uds/core/css junto.",
5
5
  "keywords": [
6
6
  "aurea",