@softize/opus 19.0.0 → 20.0.0

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.
Files changed (142) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +5 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +15 -3
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/list.tsx +89 -78
  20. package/src/ui/components/patterns/page.tsx +20 -4
  21. package/src/ui/components/patterns/presentation.tsx +52 -37
  22. package/src/ui/components/patterns/split.tsx +44 -4
  23. package/src/ui/components/patterns/surface-anatomy.ts +35 -8
  24. package/src/ui/components/patterns/trigger.tsx +12 -2
  25. package/src/ui/components/primitives/alert.tsx +9 -1
  26. package/src/ui/components/primitives/avatar.tsx +7 -3
  27. package/src/ui/components/primitives/carousel.tsx +34 -5
  28. package/src/ui/components/primitives/checkbox.tsx +11 -3
  29. package/src/ui/components/primitives/command.tsx +2 -1
  30. package/src/ui/components/primitives/copyable.tsx +120 -10
  31. package/src/ui/components/primitives/dialog.tsx +41 -10
  32. package/src/ui/components/primitives/drawer.tsx +9 -1
  33. package/src/ui/components/primitives/empty.tsx +1 -1
  34. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  35. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  36. package/src/ui/components/primitives/input-group.tsx +9 -2
  37. package/src/ui/components/primitives/input-otp.tsx +1 -1
  38. package/src/ui/components/primitives/kbd.tsx +1 -1
  39. package/src/ui/components/primitives/label.tsx +3 -0
  40. package/src/ui/components/primitives/menu.tsx +5 -0
  41. package/src/ui/components/primitives/popover.tsx +1 -1
  42. package/src/ui/components/primitives/progress.tsx +9 -1
  43. package/src/ui/components/primitives/slider.tsx +62 -11
  44. package/src/ui/components/primitives/truncate.tsx +10 -1
  45. package/src/ui/docs/content/accordion.md +74 -27
  46. package/src/ui/docs/content/action-form-card.md +24 -15
  47. package/src/ui/docs/content/action-form-dialog.md +17 -7
  48. package/src/ui/docs/content/action-form.md +92 -77
  49. package/src/ui/docs/content/action-list-dialog.md +75 -38
  50. package/src/ui/docs/content/action-list.md +134 -52
  51. package/src/ui/docs/content/action-trigger.md +27 -10
  52. package/src/ui/docs/content/action-view.md +14 -14
  53. package/src/ui/docs/content/actions.md +161 -66
  54. package/src/ui/docs/content/ai.md +43 -44
  55. package/src/ui/docs/content/alert.md +83 -29
  56. package/src/ui/docs/content/ask.md +51 -26
  57. package/src/ui/docs/content/aspect-ratio.md +22 -24
  58. package/src/ui/docs/content/audit.md +23 -19
  59. package/src/ui/docs/content/auth.md +38 -19
  60. package/src/ui/docs/content/avatar.md +54 -5
  61. package/src/ui/docs/content/badge.md +29 -9
  62. package/src/ui/docs/content/breadcrumb.md +21 -8
  63. package/src/ui/docs/content/button.md +73 -43
  64. package/src/ui/docs/content/calendar.md +134 -26
  65. package/src/ui/docs/content/card.md +41 -15
  66. package/src/ui/docs/content/carousel.md +37 -6
  67. package/src/ui/docs/content/chat.md +118 -37
  68. package/src/ui/docs/content/checkbox.md +73 -7
  69. package/src/ui/docs/content/cli.md +94 -55
  70. package/src/ui/docs/content/collapsible.md +43 -19
  71. package/src/ui/docs/content/command.md +30 -8
  72. package/src/ui/docs/content/composer.md +23 -8
  73. package/src/ui/docs/content/content.md +31 -7
  74. package/src/ui/docs/content/copyable.md +39 -8
  75. package/src/ui/docs/content/customization.md +55 -46
  76. package/src/ui/docs/content/cycle.md +33 -18
  77. package/src/ui/docs/content/data-products.md +115 -0
  78. package/src/ui/docs/content/data-state.md +20 -6
  79. package/src/ui/docs/content/data.md +20 -19
  80. package/src/ui/docs/content/detail.md +12 -5
  81. package/src/ui/docs/content/dialog.md +107 -24
  82. package/src/ui/docs/content/dictionary-value.md +4 -4
  83. package/src/ui/docs/content/dock.md +54 -19
  84. package/src/ui/docs/content/dot.md +35 -5
  85. package/src/ui/docs/content/drawer.md +63 -19
  86. package/src/ui/docs/content/empty-value.md +20 -6
  87. package/src/ui/docs/content/empty.md +24 -3
  88. package/src/ui/docs/content/events.md +76 -16
  89. package/src/ui/docs/content/executions.md +9 -9
  90. package/src/ui/docs/content/field.md +21 -6
  91. package/src/ui/docs/content/getting-started.md +44 -34
  92. package/src/ui/docs/content/icon-picker.md +19 -13
  93. package/src/ui/docs/content/icon-tile.md +28 -5
  94. package/src/ui/docs/content/input-otp.md +58 -7
  95. package/src/ui/docs/content/input.md +22 -3
  96. package/src/ui/docs/content/item.md +53 -6
  97. package/src/ui/docs/content/kbd.md +5 -1
  98. package/src/ui/docs/content/label.md +13 -4
  99. package/src/ui/docs/content/log.md +5 -5
  100. package/src/ui/docs/content/markdown.md +5 -4
  101. package/src/ui/docs/content/mcp.md +49 -23
  102. package/src/ui/docs/content/menu.md +95 -10
  103. package/src/ui/docs/content/metric-card.md +12 -4
  104. package/src/ui/docs/content/observability.md +10 -9
  105. package/src/ui/docs/content/page.md +118 -12
  106. package/src/ui/docs/content/pagination.md +13 -5
  107. package/src/ui/docs/content/pane.md +55 -5
  108. package/src/ui/docs/content/popover.md +42 -5
  109. package/src/ui/docs/content/presentation.md +91 -47
  110. package/src/ui/docs/content/progress.md +27 -14
  111. package/src/ui/docs/content/queue.md +39 -21
  112. package/src/ui/docs/content/radio-group.md +26 -4
  113. package/src/ui/docs/content/router.md +29 -10
  114. package/src/ui/docs/content/runtime.md +90 -54
  115. package/src/ui/docs/content/scheduler.md +53 -21
  116. package/src/ui/docs/content/scroll-area.md +42 -35
  117. package/src/ui/docs/content/select.md +78 -33
  118. package/src/ui/docs/content/semantic-context.md +21 -2
  119. package/src/ui/docs/content/separator.md +15 -9
  120. package/src/ui/docs/content/sidebar.md +3 -2
  121. package/src/ui/docs/content/skeleton.md +6 -1
  122. package/src/ui/docs/content/slider.md +37 -9
  123. package/src/ui/docs/content/spinner.md +4 -3
  124. package/src/ui/docs/content/split.md +19 -0
  125. package/src/ui/docs/content/storage.md +8 -9
  126. package/src/ui/docs/content/switch.md +10 -7
  127. package/src/ui/docs/content/table.md +46 -8
  128. package/src/ui/docs/content/tabs.md +14 -4
  129. package/src/ui/docs/content/testing.md +76 -31
  130. package/src/ui/docs/content/textarea.md +5 -4
  131. package/src/ui/docs/content/toast.md +48 -7
  132. package/src/ui/docs/content/toggle.md +3 -3
  133. package/src/ui/docs/content/tokens.md +15 -26
  134. package/src/ui/docs/content/tooltip.md +18 -2
  135. package/src/ui/docs/content/truncate.md +36 -6
  136. package/src/ui/docs/content/ui.md +74 -22
  137. package/src/ui/docs/content/upgrading.md +45 -36
  138. package/src/ui/docs/plugin.ts +36 -2
  139. package/src/ui/docs/registry.tsx +6 -0
  140. package/src/ui/drivers/react.tsx +16 -8
  141. package/src/ui/meta.ts +31 -31
  142. package/src/vite/design.ts +16 -4
@@ -6,14 +6,17 @@ recursos e envia o estado correspondente no input. Filtros avançados aparecem n
6
6
  os filtros aplicados permanecem visíveis como chips removíveis.
7
7
 
8
8
  A tabela derivada não cria uma moldura própria. A superfície que recebe o `ActionList` define os
9
- limites visuais da coleção; use `Table` diretamente quando a composição precisar da variante
10
- `framed`.
11
-
12
- A barra se adapta ao espaço disponível. A busca cede largura primeiro e filtros que deixam de caber
13
- migram para o modal; a linha só quebra quando nenhum controle restante puder ceder espaço.
14
- `toolbarActions` acrescenta operações ligadas ao recorte atual no fim da barra. Recarregar e
15
- exibição formam um `ButtonGroup` espaçado; as ações do consumidor vêm depois, com um intervalo
16
- maior para preservar a hierarquia entre controles e operações do recorte.
9
+ limites visuais da coleção; use [`Table`](/ui/table) diretamente quando a composição precisar da
10
+ variante `framed`. Carregamento, erro e vazio usam o mesmo [`DataState`](/ui/data-state) das
11
+ demais coleções.
12
+
13
+ A barra mantém em uma única linha os controles declarados como `inline`: ela não quebra e não move
14
+ filtros para o modal por falta de espaço. Seletores de valor único acompanham o valor escolhido até
15
+ `20rem` e truncam o excedente; a busca varia entre `8rem` e `16rem` conforme a largura
16
+ disponível. Quando a barra ficar longa demais, declare como `advanced` os filtros que não precisam
17
+ ficar sempre à vista, como descreve a seção “Onde cada filtro aparece”. Recarregar e
18
+ exibição formam um `ButtonGroup` espaçado; `toolbarActions` acrescenta, depois deles, operações
19
+ ligadas ao recorte atual.
17
20
 
18
21
  ```tsx preview col
19
22
  render(
@@ -31,8 +34,9 @@ render(
31
34
 
32
35
  ## Coleção nomeada
33
36
 
34
- Em uma página, envolva a listagem com `Content`. O título e a criação ocupam extremos opostos do
35
- cabeçalho da coleção; busca, filtros e atualização permanecem na toolbar do `ActionList`.
37
+ Em uma página, envolva a listagem com [`Content`](/ui/content). O título e a criação ocupam
38
+ extremos opostos do cabeçalho da coleção; busca, filtros e atualização permanecem na toolbar do
39
+ `ActionList`.
36
40
 
37
41
  ```tsx
38
42
  <Content
@@ -46,7 +50,8 @@ cabeçalho da coleção; busca, filtros e atualização permanecem na toolbar do
46
50
  ```
47
51
 
48
52
  Use `toolbarActions` somente quando a operação depender do recorte visível, como exportar os
49
- resultados filtrados. Uma ação icon-only nessa região mantém o nome no tooltip e no `aria-label`.
53
+ resultados filtrados. A propriedade recebe os nós já prontos: em uma ação somente com ícone, forneça
54
+ você mesmo o `aria-label` e o `Tooltip` com o nome da operação.
50
55
 
51
56
  ## Barra de filtros compartilhada
52
57
 
@@ -64,9 +69,9 @@ superfícies que não são listagens, como relatórios. O estado é controlado p
64
69
  />
65
70
  ```
66
71
 
67
- Seletores inline de valor único abraçam o valor selecionado até `max-w-80`. Valores maiores são
72
+ Seletores inline de valor único acompanham o valor selecionado até `20rem`. Valores maiores são
68
73
  truncados no controle, enquanto o dropdown pode crescer além dele para preservar a opção.
69
- Multiseletores mantêm `w-52`; no modal de filtros avançados, o seletor ocupa toda a largura.
74
+ Multiseletores mantêm `13rem`; no modal de filtros avançados, o seletor ocupa toda a largura.
70
75
  `filterIcons` associa um ícone leading ao nome do filtro quando a superfície precisa reforçar
71
76
  dimensões recorrentes, sem substituir o rótulo. Busca, filtros e período aparecem somente quando
72
77
  declarados.
@@ -79,7 +84,7 @@ simples usam o valor vazio como padrão; por isso, opções como “Todos” tam
79
84
  Use `periodPosition` para manter o período antes ou depois dos filtros inline. Quando ele precisa
80
85
  apenas aparecer depois de um filtro específico, use `{ afterFilter: 'dateBasis' }`. Se tipo de data
81
86
  e período formam uma única decisão, declare `period={{ basisFilter: 'dateBasis' }}`: o Opus os reúne
82
- num só controle, com ícone no início e uma única ação de limpar no fim. Quando a pessoa muda o tipo
87
+ em um só controle, com ícone no início e uma única ação de limpar no fim. Quando a pessoa muda o tipo
83
88
  ou o período padrão, essa ação aparece e restaura os dois valores. Se a âncora de `periodPosition`
84
89
  não existir, o período permanece visível no fim dos filtros inline.
85
90
 
@@ -177,7 +182,7 @@ render(
177
182
  action={docWorkspaceList}
178
183
  input={{}}
179
184
  cells={{
180
- name: (w) => (
185
+ name: (w: { name: string }) => (
181
186
  <a
182
187
  href="#"
183
188
  onClick={(e) => e.preventDefault()}
@@ -218,6 +223,65 @@ disponíveis e a quantidade de itens. Alterar busca, filtro, ordenação ou per
218
223
  página. Durante a primeira consulta, `DataState` apresenta o carregamento; em atualizações
219
224
  posteriores, a lista permanece visível com `aria-busy`.
220
225
 
226
+ ## Ocupar a altura disponível
227
+
228
+ Por padrão, a lista cresce com os resultados e a rolagem acontece na janela ou no contêiner que a
229
+ hospeda. Quando a coleção é a região principal de uma tela de altura fixa, a barra de filtros e o
230
+ rodapé podem continuar visíveis enquanto só os resultados rolam. Para isso, declare
231
+ `layout="fill"` na [`Page`](/ui/page) e componha a lista com `PageBody` e
232
+ [`Content`](/ui/content), como no exemplo abaixo. Role a tabela para ver o cabeçalho fixo.
233
+
234
+ ```tsx preview col
235
+ render(
236
+ <DocBrowserActionProvider>
237
+ <div className="h-80 w-full overflow-x-auto overflow-y-hidden rounded-lg border border-border">
238
+ <Page layout="fill">
239
+ <PageBody>
240
+ <Content level={1} variant="page">
241
+ <ContentHeader>
242
+ <ContentTitle>Workspaces</ContentTitle>
243
+ <ContentActions>
244
+ <Button>Criar workspace</Button>
245
+ </ContentActions>
246
+ </ContentHeader>
247
+ <ContentBody>
248
+ <ActionList action={docWorkspaceList} input={{}} />
249
+ </ContentBody>
250
+ </Content>
251
+ </PageBody>
252
+ </Page>
253
+ </div>
254
+ </DocBrowserActionProvider>,
255
+ );
256
+ ```
257
+
258
+ A página limita a própria altura à do contêiner que a hospeda, normalmente o shell, e `PageBody`,
259
+ `Content` e `ContentBody` repassam essa altura até a lista, que passa a usar `layout="fill"` sem que
260
+ você o declare. Em uma [`Presentation`](/ui/presentation) de lista, basta `layout="fill"` no
261
+ próprio `Presentation`. Só as regiões que contêm a lista mudam: uma seção irmã, como um resumo acima
262
+ da coleção, mantém a altura do próprio conteúdo. Os demais filhos de cada região que repassa a
263
+ altura se esticam à largura dela; dê `self-start` a um botão que deva manter a largura do conteúdo.
264
+
265
+ Com a altura limitada, a barra de filtros, o `beforeContent` e o rodapé mantêm a própria altura. A
266
+ região de resultados ocupa o espaço restante e rola nos dois eixos; o rodapé se mede pelo conteúdo
267
+ e mantém a mesma altura com e sem paginador. Na tabela, o cabeçalho fica fixo no topo da região que
268
+ rola, sobre a cor da superfície e com uma linha inferior, e a moldura dos estados de carregamento,
269
+ erro e vazio ocupa a altura disponível. Em outras visualizações e em `children`, a rolagem interna
270
+ continua, mas não existe cabeçalho fixo.
271
+
272
+ A lista só assume `fill` sozinha quando é filha direta de `PageBody`, `ContentBody` ou do corpo de
273
+ uma `Presentation`, e a altura só chega até ela se não houver um contêiner próprio entre esses
274
+ componentes no caminho. Nos demais casos, a decisão é sua:
275
+
276
+ - Para a lista manter a altura dos resultados, como em uma seção secundária cuja rolagem pertence ao
277
+ `PageBody`, declare `layout="flow"`.
278
+ - Um contêiner próprio entre `ContentBody` e a lista, como um `TabsContent`, não repassa a altura, e
279
+ a lista volta a `flow`. Declare `layout="fill"` na lista e dê ao contêiner as classes
280
+ `flex min-h-0 flex-1 flex-col`.
281
+ - Dentro de [`Card`](/ui/card), [`Dialog`](/ui/dialog), [`Drawer`](/ui/drawer) ou
282
+ [`Pane`](/ui/pane), a lista não herda o `fill` da página: a altura do corpo pertence à
283
+ superfície.
284
+
221
285
  ## Ações em lote
222
286
 
223
287
  `batch` acrescenta seleção à tabela. `can(item)` determina quais linhas podem participar: itens
@@ -250,6 +314,14 @@ fornece um `render`; `ActionList` preserva a mesma fonte de dados, filtros e est
250
314
  “Tabela” aparece quando existem colunas.
251
315
 
252
316
  ```tsx preview col
317
+ type Workspace = {
318
+ id: string;
319
+ name: string;
320
+ client: string;
321
+ status: "active" | "onboarding" | "paused";
322
+ agents: number;
323
+ };
324
+
253
325
  render(
254
326
  <DocBrowserActionProvider>
255
327
  <div className="w-full">
@@ -260,24 +332,20 @@ render(
260
332
  gallery: {
261
333
  label: "Galeria",
262
334
  icon: <LayoutGrid />,
263
- render: (workspaces) => (
335
+ render: (workspaces: Workspace[]) => (
264
336
  <div className="grid grid-cols-2 gap-3">
265
337
  {workspaces.map((w) => (
266
- <div
267
- key={w.id}
268
- className="rounded-lg border border-border p-3"
269
- >
270
- <div className="flex items-center gap-2">
271
- <span className="text-sm font-semibold">{w.name}</span>
272
- <DictionaryValue
273
- dict={docWorkspaceStatus}
274
- value={w.status}
275
- />
276
- </div>
277
- <p className="mt-1 text-xs text-muted-foreground">
278
- {w.agents} agentes.
279
- </p>
280
- </div>
338
+ <Card key={w.id}>
339
+ <CardHeader>
340
+ <CardTitle>{w.name}</CardTitle>
341
+ <CardDescription>
342
+ {w.agents === 1 ? "1 agente" : `${w.agents} agentes`}
343
+ </CardDescription>
344
+ <CardAction>
345
+ <DictionaryValue dict={docWorkspaceStatus} value={w.status} />
346
+ </CardAction>
347
+ </CardHeader>
348
+ </Card>
281
349
  ))}
282
350
  </div>
283
351
  ),
@@ -334,28 +402,42 @@ Convenção compacta (defaults omitidos): `?q=…&sort=chave:dir&status=…&view
334
402
 
335
403
  ## Composição dos itens
336
404
 
337
- Passe `children` para substituir a tabela por uma composição própria. A barra de filtros e os
338
- estados de carregamento, erro e vazio continuam sendo tratados por `ActionList`.
405
+ Passe `children` para substituir a tabela por uma composição própria, como uma lista de
406
+ [`Item`](/ui/item). A barra de filtros e os estados de carregamento, erro e vazio continuam sendo
407
+ tratados por `ActionList`. O contrato não leva o tipo do item até o componente: anote o parâmetro de
408
+ `children` (ou de `render`, em uma visualização) ou declare os genéricos, como em
409
+ `<ActionList<Input, Workspace>>`. Sem uma das duas formas, os itens chegam como `unknown`.
339
410
 
340
411
  ```tsx preview col
412
+ type Workspace = {
413
+ id: string;
414
+ name: string;
415
+ client: string;
416
+ status: "active" | "onboarding" | "paused";
417
+ agents: number;
418
+ };
419
+
341
420
  render(
342
421
  <DocBrowserActionProvider>
343
422
  <div className="w-full">
344
423
  <ActionList action={docWorkspaceList} input={{}}>
345
- {(workspaces) => (
346
- <div className="grid w-full grid-cols-2 gap-3">
347
- {workspaces.map((w) => (
348
- <div key={w.id} className="rounded-lg border border-border p-3">
349
- <div className="flex items-center gap-2">
350
- <span className="text-sm font-semibold">{w.name}</span>
351
- <DictionaryValue dict={docWorkspaceStatus} value={w.status} />
352
- </div>
353
- <p className="mt-1 text-xs text-muted-foreground">
354
- {w.agents} agentes.
355
- </p>
356
- </div>
424
+ {(workspaces: Workspace[]) => (
425
+ <ItemGroup variant="framed">
426
+ {workspaces.map((w, index) => (
427
+ <React.Fragment key={w.id}>
428
+ {index > 0 && <ItemSeparator />}
429
+ <Item>
430
+ <ItemHeader>
431
+ <ItemTitle>{w.name}</ItemTitle>
432
+ <ItemDescription>Cliente {w.client}.</ItemDescription>
433
+ </ItemHeader>
434
+ <ItemActions>
435
+ <DictionaryValue dict={docWorkspaceStatus} value={w.status} />
436
+ </ItemActions>
437
+ </Item>
438
+ </React.Fragment>
357
439
  ))}
358
- </div>
440
+ </ItemGroup>
359
441
  )}
360
442
  </ActionList>
361
443
  </div>
@@ -403,9 +485,9 @@ chamar a action.
403
485
 
404
486
  | Propriedade | Tipo | Padrão | Descrição |
405
487
  | ----------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
406
- | `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item). A paginação envia `limit`/`page` e usa o `total` devolvido. |
407
- | `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
408
- | `layout` | `'flow' \| 'fill'` | `'flow'` | Em `fill`, ocupa a altura disponível e mantém a rolagem dentro da região de resultados. |
488
+ | `action` | `ListActionLike` | | A ListAction do Opus (kind list, output = item). A paginação envia `limit`/`page` e usa o `total` devolvido. |
489
+ | `input` | `TInput` | | O escopo base, como `{ workspaceId }`. A toolbar acrescenta busca, filtros e paginação sem sobrescrever essas chaves. |
490
+ | `layout` | `'flow' \| 'fill'` | `'fill'` como filha direta de `PageBody`, `ContentBody` ou do corpo da `Presentation` em uma página `fill`; `'flow'` nos demais casos | Em `fill`, ocupa a altura de um ancestral limitado e rola somente a região de resultados; na tabela, o cabeçalho fica fixo. Sem ancestral limitado, equivale a `flow`. |
409
491
  | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
410
492
  | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
411
493
  | `filterDefaults` | `Record<string, string>` | | Valores de repouso aplicados à consulta; o `X` só aparece quando o filtro diverge deles e retorna a eles. |
@@ -415,7 +497,7 @@ chamar a action.
415
497
  | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
416
498
  | `views` | `Record<string, ActionListView<TItem>>` | | Visualizações alternativas `{ label, icon?, render }`; o seletor alterna entre a tabela e estas. |
417
499
  | `beforeContent` | `ReactNode` | | Conteúdo entre a toolbar e o corpo, como um resumo que reage ao recorte atual. |
418
- | `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
500
+ | `columns` | `ActionListColumn<TItem>[]` | | Tabela explícita, que substitui as colunas declaradas no contrato. |
419
501
  | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
420
502
  | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?: { title, message? }, destructive? }` — liga a multi-seleção. |
421
503
  | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
@@ -427,5 +509,5 @@ chamar a action.
427
509
  | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
428
510
  | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
429
511
  | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
430
- | `toolbarActions` | `ReactNode` | | Operações ligadas ao recorte atual, no fim da barra e separadas do grupo de controles. |
431
- | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram num grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
512
+ | `toolbarActions` | `ReactNode` | | Operações ligadas ao recorte atual, no fim da barra e separadas do grupo de controles. Os nós são renderizados como recebidos: nome acessível e tooltip de uma ação somente com ícone ficam com o consumidor. |
513
+ | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram em um grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
@@ -13,9 +13,11 @@ de tradução ou estiver ausente, o botão usa `action.name`.
13
13
 
14
14
  ## Confirmação declarada no contrato
15
15
 
16
- Quando a action declara `confirm`, o componente monta o diálogo e aplica o contexto `danger` se a
17
- operação for destrutiva. Use a propriedade `confirm` somente quando esta ocorrência precisar de uma
18
- mensagem diferente da declarada no contrato.
16
+ Quando a action declara `confirm`, o componente monta o diálogo antes de executar. Com
17
+ `confirm.destructive: true` no contrato, o gatilho e o botão de confirmação usam o contexto
18
+ `danger`. Use a propriedade `confirm` somente quando esta ocorrência precisar de uma mensagem
19
+ diferente da declarada no contrato; ela substitui os textos, mas o caráter destrutivo continua
20
+ vindo do contrato.
19
21
 
20
22
  ```tsx preview
21
23
  <DocBrowserActionProvider>
@@ -41,20 +43,35 @@ confirm: {
41
43
  Com `icon`, o botão exibe somente o ícone no quadrado `icon-xs` da escala (1.5rem, a ação que mora
42
44
  dentro de uma linha ou card) e usa `label`, ou `action.label`, no tooltip e no nome acessível. Uma
43
45
  composição que peça mais presença, como a barra do `PageHeader`, declara `size="icon"`. O clique
44
- não aciona o item clicável ao redor. `itemLabel` identifica o registro na mensagem de confirmação.
46
+ não aciona o item clicável ao redor.
47
+
48
+ Em uma lista, várias linhas repetem a mesma ação. Inclua o alvo em `label`, como `Excluir Unidade
49
+ Centro`, para que cada botão tenha um nome acessível distinto. `itemLabel` identifica o registro na
50
+ pergunta de confirmação.
45
51
 
46
52
  ```tsx
53
+ // No contrato:
54
+ confirm: {
55
+ title: 'Excluir a unidade?',
56
+ message: 'Os agendamentos desta unidade também serão removidos.',
57
+ confirmLabel: 'Excluir',
58
+ destructive: true,
59
+ }
60
+
61
+ // Na linha da lista:
47
62
  <ActionTrigger
48
63
  action={unitDeleteContract}
49
64
  input={{ id: unit.id }}
50
65
  icon={<Trash2 />}
51
- label="Excluir"
66
+ label={`Excluir ${unit.name}`}
52
67
  itemLabel={unit.name}
53
68
  />
54
69
  ```
55
70
 
56
- > `ActionTrigger` substitui o antigo `DeleteButton`. Ao migrar, declare `confirm` no contrato para
57
- > preservar a confirmação que o componente anterior sempre apresentava.
71
+ Sem `confirmLabel` no contrato ou `actionLabel` na propriedade `confirm`, o botão de confirmação
72
+ usa o `label` da action, como `Excluir`. O nome longo passado em `label` serve à linha; na pergunta,
73
+ o alvo já aparece por `itemLabel`. O `label` do gatilho só nomeia o botão de confirmação quando a
74
+ action não declara `label`.
58
75
 
59
76
  ## Erro que a pessoa entende
60
77
 
@@ -80,9 +97,9 @@ um atalho, um arrastar, um item de menu.
80
97
  |---|---|---|---|
81
98
  | `action` | `SimpleContract<TInput, TData>` | | A SimpleAction do Opus — label, messages e confirm vêm do contrato. |
82
99
  | `input` | `TInput` | | O que a action recebe — geralmente { id }. |
83
- | `label` | `string` | `action.label` quando é texto; senão `action.name` | Sobrepõe o texto do botão. |
84
- | `context / variant / size` | `do Button` | gatilho: `primary`/`solid`; `danger` quando a action é `destructive`; no modo ícone, `ghost` e `neutral` (ou `danger` se destrutiva) / `default`, ou `icon-xs` com `icon` | Visual do gatilho; `context` explícito vence. O botão de confirmar é sempre `solid`: `danger` quando a action é `destructive`, senão a prop `context` do gatilho (ou `primary`). |
85
- | `confirm` | `{ title, body?, actionLabel?, cancelLabel? }` | | Confirmação via prop — sobrepõe o ConfirmSpec do contrato. |
100
+ | `label` | `string` | `action.label` quando é texto; senão `action.name` | Texto do botão ou, com `icon`, seu nome acessível e tooltip. Só vira o botão de confirmação quando a action não declara `label` e nem o contrato nem `confirm` declaram outro rótulo. |
101
+ | `context / variant / size` | `do Button` | gatilho: `primary`/`solid`; `danger` quando `action.confirm.destructive` é `true`; no modo ícone, `ghost` e `neutral` (ou `danger` se destrutiva) / `default`, ou `icon-xs` com `icon` | Visual do gatilho; `context` explícito vence. O botão de confirmar é sempre `solid`: `danger` quando `action.confirm.destructive` é `true`, senão a prop `context` do gatilho (ou `primary`). |
102
+ | `confirm` | `{ title, body?, actionLabel?, cancelLabel? }` | | Confirmação para esta ocorrência; substitui os textos do `confirm` do contrato. Sem `actionLabel`, o botão de confirmar usa `action.label`. O contexto `danger` continua dependendo de `action.confirm.destructive`. |
86
103
  | `disabled` | `boolean` | `false` | Desabilita o botão independentemente da execução em andamento. |
87
104
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
88
105
  | `onLoadingChange` | `(loading: boolean) => void` | | Informa início e fim da execução a quem coordena bloqueio entre várias actions. |
@@ -2,7 +2,8 @@
2
2
 
3
3
  Use `ActionView` para carregar um recurso por uma `ViewAction` e manter carregamento e erro no
4
4
  mesmo fluxo. No exemplo, alterne entre os workspaces para observar o carregamento e a recuperação de
5
- erro. O consumidor compõe somente o conteúdo disponível.
5
+ erro. O consumidor compõe somente o conteúdo disponível, aqui com [`Detail`](/ui/detail). Para uma
6
+ coleção, use [`ActionList`](/ui/action-list).
6
7
 
7
8
  ```tsx preview col
8
9
  const [id, setId] = useState('empresa-x')
@@ -10,26 +11,25 @@ const [id, setId] = useState('empresa-x')
10
11
  render(
11
12
  <DocBrowserActionProvider>
12
13
  <div className="w-full space-y-3">
13
- <div className="flex flex-wrap gap-2">
14
+ <ButtonGroup mode="spaced">
14
15
  <Button variant="outline" size="sm" onClick={() => setId('empresa-x')}>Empresa X</Button>
15
16
  <Button variant="outline" size="sm" onClick={() => setId('empresa-y')}>Empresa Y</Button>
16
17
  <Button variant="outline" size="sm" onClick={() => setId('sessao-fantasma')}>Inexistente (erro)</Button>
17
- </div>
18
- <div className="rounded-lg border border-border p-4">
18
+ </ButtonGroup>
19
+ <Card className="p-4">
19
20
  <ActionView action={docWorkspaceView} input={{ id }}>
20
21
  {(ws) => (
21
- <div className="space-y-1.5">
22
- <div className="flex items-center gap-2">
23
- <h3 className="text-sm font-semibold">{ws.name}</h3>
24
- <DictionaryValue dict={docWorkspaceStatus} value={ws.status} />
25
- </div>
26
- <p className="text-sm text-muted-foreground">
27
- Cliente {ws.client} · {ws.agents} agentes vinculados.
28
- </p>
29
- </div>
22
+ <DetailGroup columns={3}>
23
+ <DetailField label="Workspace" value={ws.name} />
24
+ <DetailField label="Cliente" value={ws.client} />
25
+ <DetailField
26
+ label="Status"
27
+ value={<DictionaryValue dict={docWorkspaceStatus} value={ws.status} />}
28
+ />
29
+ </DetailGroup>
30
30
  )}
31
31
  </ActionView>
32
- </div>
32
+ </Card>
33
33
  </div>
34
34
  </DocBrowserActionProvider>,
35
35
  )