@softize/opus 19.0.0 → 20.1.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 (148) hide show
  1. package/CHANGELOG.md +239 -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/0020-icon-tiles-are-explicitly-composed.md +8 -0
  7. package/docs/adr/0021-content-carries-structure-and-semantic-element.md +7 -0
  8. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +19 -1
  9. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  10. package/docs/adr/0025-content-frame-treatment-does-not-change-semantics.md +134 -0
  11. package/docs/data-products.md +15 -9
  12. package/docs/protocol.md +3 -4
  13. package/docs/releasing.md +2 -2
  14. package/package.json +2 -2
  15. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  16. package/registry/templates/app/package.json +1 -1
  17. package/src/core/runtime.ts +96 -8
  18. package/src/core/types.ts +5 -2
  19. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  20. package/src/ui/components/patterns/content-header.tsx +109 -18
  21. package/src/ui/components/patterns/fill-region.ts +150 -0
  22. package/src/ui/components/patterns/list.tsx +91 -92
  23. package/src/ui/components/patterns/page.tsx +20 -4
  24. package/src/ui/components/patterns/presentation.tsx +52 -37
  25. package/src/ui/components/patterns/split.tsx +44 -4
  26. package/src/ui/components/patterns/surface-anatomy.ts +35 -8
  27. package/src/ui/components/patterns/surface-header.tsx +58 -25
  28. package/src/ui/components/patterns/trigger.tsx +12 -2
  29. package/src/ui/components/primitives/alert.tsx +9 -1
  30. package/src/ui/components/primitives/avatar.tsx +7 -3
  31. package/src/ui/components/primitives/card.tsx +1 -1
  32. package/src/ui/components/primitives/carousel.tsx +34 -5
  33. package/src/ui/components/primitives/checkbox.tsx +11 -3
  34. package/src/ui/components/primitives/command.tsx +2 -1
  35. package/src/ui/components/primitives/copyable.tsx +120 -10
  36. package/src/ui/components/primitives/dialog.tsx +42 -11
  37. package/src/ui/components/primitives/drawer.tsx +10 -2
  38. package/src/ui/components/primitives/empty.tsx +1 -1
  39. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  40. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  41. package/src/ui/components/primitives/input-group.tsx +9 -2
  42. package/src/ui/components/primitives/input-otp.tsx +1 -1
  43. package/src/ui/components/primitives/kbd.tsx +1 -1
  44. package/src/ui/components/primitives/label.tsx +3 -0
  45. package/src/ui/components/primitives/menu.tsx +5 -0
  46. package/src/ui/components/primitives/popover.tsx +1 -1
  47. package/src/ui/components/primitives/progress.tsx +9 -1
  48. package/src/ui/components/primitives/slider.tsx +62 -11
  49. package/src/ui/components/primitives/truncate.tsx +10 -1
  50. package/src/ui/docs/content/accordion.md +74 -27
  51. package/src/ui/docs/content/action-form-card.md +24 -15
  52. package/src/ui/docs/content/action-form-dialog.md +17 -7
  53. package/src/ui/docs/content/action-form.md +92 -77
  54. package/src/ui/docs/content/action-list-dialog.md +75 -38
  55. package/src/ui/docs/content/action-list.md +141 -58
  56. package/src/ui/docs/content/action-trigger.md +27 -10
  57. package/src/ui/docs/content/action-view.md +14 -14
  58. package/src/ui/docs/content/actions.md +161 -66
  59. package/src/ui/docs/content/ai.md +43 -44
  60. package/src/ui/docs/content/alert.md +83 -29
  61. package/src/ui/docs/content/ask.md +51 -26
  62. package/src/ui/docs/content/aspect-ratio.md +22 -24
  63. package/src/ui/docs/content/audit.md +23 -19
  64. package/src/ui/docs/content/auth.md +38 -19
  65. package/src/ui/docs/content/avatar.md +54 -5
  66. package/src/ui/docs/content/badge.md +29 -9
  67. package/src/ui/docs/content/breadcrumb.md +21 -8
  68. package/src/ui/docs/content/button.md +73 -43
  69. package/src/ui/docs/content/calendar.md +134 -26
  70. package/src/ui/docs/content/card.md +41 -15
  71. package/src/ui/docs/content/carousel.md +37 -6
  72. package/src/ui/docs/content/chat.md +118 -37
  73. package/src/ui/docs/content/checkbox.md +73 -7
  74. package/src/ui/docs/content/cli.md +94 -55
  75. package/src/ui/docs/content/collapsible.md +43 -19
  76. package/src/ui/docs/content/command.md +30 -8
  77. package/src/ui/docs/content/composer.md +23 -8
  78. package/src/ui/docs/content/content.md +119 -27
  79. package/src/ui/docs/content/copyable.md +39 -8
  80. package/src/ui/docs/content/customization.md +55 -46
  81. package/src/ui/docs/content/cycle.md +33 -18
  82. package/src/ui/docs/content/data-products.md +115 -0
  83. package/src/ui/docs/content/data-state.md +20 -6
  84. package/src/ui/docs/content/data.md +20 -19
  85. package/src/ui/docs/content/detail.md +12 -5
  86. package/src/ui/docs/content/dialog.md +107 -24
  87. package/src/ui/docs/content/dictionary-value.md +4 -4
  88. package/src/ui/docs/content/dock.md +54 -19
  89. package/src/ui/docs/content/dot.md +35 -5
  90. package/src/ui/docs/content/drawer.md +63 -19
  91. package/src/ui/docs/content/empty-value.md +20 -6
  92. package/src/ui/docs/content/empty.md +24 -3
  93. package/src/ui/docs/content/events.md +76 -16
  94. package/src/ui/docs/content/executions.md +9 -9
  95. package/src/ui/docs/content/field.md +21 -6
  96. package/src/ui/docs/content/getting-started.md +44 -34
  97. package/src/ui/docs/content/icon-picker.md +19 -13
  98. package/src/ui/docs/content/icon-tile.md +38 -15
  99. package/src/ui/docs/content/input-otp.md +58 -7
  100. package/src/ui/docs/content/input.md +22 -3
  101. package/src/ui/docs/content/item.md +53 -6
  102. package/src/ui/docs/content/kbd.md +5 -1
  103. package/src/ui/docs/content/label.md +13 -4
  104. package/src/ui/docs/content/log.md +5 -5
  105. package/src/ui/docs/content/markdown.md +5 -4
  106. package/src/ui/docs/content/mcp.md +49 -23
  107. package/src/ui/docs/content/menu.md +95 -10
  108. package/src/ui/docs/content/metric-card.md +12 -4
  109. package/src/ui/docs/content/observability.md +10 -9
  110. package/src/ui/docs/content/page.md +118 -12
  111. package/src/ui/docs/content/pagination.md +13 -5
  112. package/src/ui/docs/content/pane.md +55 -5
  113. package/src/ui/docs/content/popover.md +42 -5
  114. package/src/ui/docs/content/presentation.md +91 -47
  115. package/src/ui/docs/content/progress.md +27 -14
  116. package/src/ui/docs/content/queue.md +39 -21
  117. package/src/ui/docs/content/radio-group.md +26 -4
  118. package/src/ui/docs/content/router.md +29 -10
  119. package/src/ui/docs/content/runtime.md +90 -54
  120. package/src/ui/docs/content/scheduler.md +53 -21
  121. package/src/ui/docs/content/scroll-area.md +42 -35
  122. package/src/ui/docs/content/select.md +78 -33
  123. package/src/ui/docs/content/semantic-context.md +21 -2
  124. package/src/ui/docs/content/separator.md +15 -9
  125. package/src/ui/docs/content/sidebar.md +3 -2
  126. package/src/ui/docs/content/skeleton.md +6 -1
  127. package/src/ui/docs/content/slider.md +37 -9
  128. package/src/ui/docs/content/spinner.md +4 -3
  129. package/src/ui/docs/content/split.md +19 -0
  130. package/src/ui/docs/content/storage.md +8 -9
  131. package/src/ui/docs/content/switch.md +10 -7
  132. package/src/ui/docs/content/table.md +46 -8
  133. package/src/ui/docs/content/tabs.md +14 -4
  134. package/src/ui/docs/content/testing.md +76 -31
  135. package/src/ui/docs/content/textarea.md +5 -4
  136. package/src/ui/docs/content/toast.md +48 -7
  137. package/src/ui/docs/content/toggle.md +3 -3
  138. package/src/ui/docs/content/tokens.md +15 -26
  139. package/src/ui/docs/content/tooltip.md +18 -2
  140. package/src/ui/docs/content/truncate.md +36 -6
  141. package/src/ui/docs/content/ui.md +74 -22
  142. package/src/ui/docs/content/upgrading.md +45 -36
  143. package/src/ui/docs/plugin.ts +36 -2
  144. package/src/ui/docs/registry.tsx +6 -0
  145. package/src/ui/drivers/react.tsx +16 -8
  146. package/src/ui/meta.ts +32 -32
  147. package/src/ui/react.tsx +1 -0
  148. package/src/vite/design.ts +16 -4
@@ -1,9 +1,12 @@
1
1
  ## Navegação entre páginas
2
2
 
3
- Componha `Pagination`, `PaginationContent` e um `PaginationItem` para cada link. `page` fornece o
3
+ Use `Pagination` quando uma coleção for dividida em páginas e a pessoa precisar saltar entre elas.
4
+ Em uma listagem derivada de uma action, [`ActionList`](/ui/action-list) já monta esta paginação no
5
+ rodapé; componha-a diretamente apenas em coleções próprias.
6
+
7
+ Monte `Pagination`, `PaginationContent` e um `PaginationItem` para cada link. `page` fornece o
4
8
  número e o nome acessível; `isActive` identifica a página atual. Os controles mantêm altura fixa e
5
- a largura cresce quando o número precisa de mais espaço. `ActionList` usa esta mesma primitiva no
6
- rodapé.
9
+ a largura cresce quando o número precisa de mais espaço.
7
10
 
8
11
  ```tsx preview
9
12
  <Pagination>
@@ -30,8 +33,9 @@ rodapé.
30
33
  ## Números longos
31
34
 
32
35
  A largura mínima é quadrada e cresce conforme o conteúdo. Na escala densa de um rodapé, as setas e a
33
- elipse usam `size="icon-sm"` (o quadrado de 1.75rem da escala única, com o glifo de 0.875rem); os
34
- números, que não têm um degrau de 1.75rem na escala, ajustam por `className` (`h-7 min-w-7 text-xs`).
36
+ elipse usam `size="icon-sm"`, o quadrado de `1.75rem` da escala única, com ícone de `0.875rem`. Os
37
+ números não têm um degrau textual de `1.75rem` na escala e, por isso, recebem a mesma medida por
38
+ `className`, como no exemplo.
35
39
 
36
40
  ```tsx preview
37
41
  <Pagination>
@@ -148,3 +152,7 @@ render(
148
152
  |---|---|---|---|
149
153
  | `size` | `'icon-xs' \| 'icon-sm' \| 'icon' \| 'icon-lg'` | `'icon'` | Quadrado da escala única; use o mesmo tamanho das setas ao lado. |
150
154
  | `className` | `string` | | Classes adicionais aplicadas à elipse. |
155
+
156
+ `Pagination` renderiza um `<nav>` com o nome acessível “Paginação”, `PaginationContent` um `<ul>` e
157
+ `PaginationItem` um `<li>`. Os três não têm propriedades próprias e aceitam as props nativas do
158
+ respectivo elemento, incluindo `className`.
@@ -62,11 +62,55 @@ render(
62
62
 
63
63
  O cabeçalho reconhece quatro slots — `PaneMedia`, `PaneTitle`, `PaneDescription` e `PaneActions` —
64
64
  e monta a linha sozinho: mídia, título e descrição de um lado, ações no extremo oposto. A ordem em
65
- que os slots são escritos não altera a posição, e um nó que não seja um desses slots interrompe a
66
- renderização com um erro. Conteúdo livre vai para `PaneBody`.
65
+ que os slots são escritos não altera a posição. Cada slot aparece no máximo uma vez: repetir um
66
+ deles interrompe a renderização com um erro que lista os slots aceitos.
67
67
 
68
68
  Título e descrição truncam quando a coluna encolhe, em vez de quebrar a altura da faixa.
69
69
 
70
+ Nas ações do cabeçalho, use `Button` `ghost` no tamanho `icon-sm` (1.75rem), como nos exemplos. O
71
+ pane é uma superfície densa, com título e descrição em escala menor, e o quadrado menor acompanha
72
+ essa escala. [`Dialog`](/ui/dialog) e [`Drawer`](/ui/drawer) usam `icon` (2.25rem) no voltar e nas
73
+ ações do cabeçalho, e a [`Presentation`](/ui/presentation) usa o mesmo tamanho no voltar e no botão
74
+ que abre o assistente.
75
+
76
+ ## Busca ou marca no cabeçalho
77
+
78
+ Uma coluna às vezes precisa, no topo, de algo que não é título nem ação: uma busca que filtra a
79
+ lista, a marca de uma barra lateral. Escreva esse conteúdo direto dentro do `PaneHeader`, ao lado
80
+ dos slots. Só o `Pane` aceita conteúdo livre no cabeçalho; os headers de `Card`, `Dialog` e `Drawer`
81
+ aceitam apenas os próprios slots.
82
+
83
+ ```tsx preview
84
+ render(
85
+ <div className="h-56 overflow-hidden rounded-lg border border-border">
86
+ <Split>
87
+ <Pane initialSize="18rem">
88
+ <PaneHeader>
89
+ <Input icon={<Search />} placeholder="Buscar conversa" aria-label="Buscar conversa" />
90
+ <PaneActions>
91
+ <Button variant="ghost" size="icon-sm" aria-label="Nova conversa">
92
+ <SquarePen />
93
+ </Button>
94
+ </PaneActions>
95
+ </PaneHeader>
96
+ <PaneBody className="space-y-1">
97
+ <p className="text-sm">Revisão do contrato</p>
98
+ <p className="text-sm text-muted-foreground">Importação de clientes</p>
99
+ </PaneBody>
100
+ </Pane>
101
+ <Pane grow>
102
+ <p className="text-sm text-muted-foreground">Conversa selecionada.</p>
103
+ </Pane>
104
+ </Split>
105
+ </div>,
106
+ );
107
+ ```
108
+
109
+ O conteúdo livre ocupa o lugar do título e absorve a largura disponível, por isso as ações
110
+ continuam no extremo oposto. Quando há título, o conteúdo livre vem sempre depois do título e da descrição,
111
+ qualquer que seja a ordem no JSX: uma busca escrita junto de um `PaneTitle` aparece abaixo dele, não
112
+ ao lado. Para algo antes do título, use `PaneMedia`; para algo ao lado, use `PaneActions`.
113
+
70
114
  ## Escolher entre seccionado e contínuo
71
115
 
72
116
  `layout` declara a anatomia da superfície inteira e fica no root; os slots leem esse valor e não o
@@ -86,6 +130,11 @@ Os divisores são derivados da vizinhança: cada região desenha a linha quando
86
130
  lado. Um cabeçalho sem corpo não fecha nada, e um cabeçalho seguido direto de um rodapé produz uma
87
131
  linha só. A tela não escreve `border-b` nem `border-t`.
88
132
 
133
+ No `continuous`, um cabeçalho sem corpo nem rodapé fecha o pane com o mesmo recuo que tem em cima.
134
+ Uma classe `pb-*` no `PaneHeader` não altera esse recuo: a regra só vale quando o cabeçalho é a
135
+ última região e tem precedência sobre o `className`. No `sectioned`, a faixa tem altura fixa e
136
+ centraliza a linha, então não há recuo de baixo a ajustar.
137
+
89
138
  ## Recuo do pane e recuo das regiões
90
139
 
91
140
  `inset` é o respiro interno do pane sem slots: `md` por padrão, com `none`, `sm` e `lg`
@@ -99,11 +148,12 @@ O ajuste enxerga só o primeiro nível. Se os slots vierem dentro de outro compo
99
148
 
100
149
  ## A matriz de layouts e slots
101
150
 
102
- As regiões são independentes e o recuo se ajusta a cada combinação, nos dois layouts.
151
+ As regiões são independentes: a coluna usa só as que precisa, nos dois layouts, e os divisores
152
+ acompanham as regiões presentes.
103
153
 
104
154
  ```tsx preview col
105
155
  render(
106
- <div className="grid gap-4 md:grid-cols-2">
156
+ <div className="grid grid-cols-2 gap-4">
107
157
  <div className="h-56 overflow-hidden rounded-lg border border-border">
108
158
  <Pane className="h-full">
109
159
  <PaneHeader>
@@ -198,7 +248,7 @@ documento não muda a aparência da faixa.
198
248
 
199
249
  | Propriedade | Tipo | Padrão | Descrição |
200
250
  |---|---|---|---|
201
- | `children` | `ReactNode` | | Somente `PaneMedia`, `PaneTitle`, `PaneDescription` e `PaneActions`, no máximo um de cada. |
251
+ | `children` | `ReactNode` | | `PaneMedia`, `PaneTitle`, `PaneDescription` e `PaneActions`, no máximo um de cada, e conteúdo livre, que aparece depois do título e da descrição. |
202
252
  | `className` | `string` | | Ajusta a faixa fixa sobre o recuo do layout. |
203
253
 
204
254
  ## Propriedades de PaneMedia
@@ -2,7 +2,8 @@
2
2
 
3
3
  Use `Popover` para apresentar conteúdo livre junto a um gatilho, sem abrir um modal.
4
4
  `PopoverHeader` agrupa título e descrição; o restante do painel aceita formulários curtos ou
5
- detalhes.
5
+ detalhes. Para uma lista de ações, use [`Menu`](/ui/menu); para uma dica curta ao passar o ponteiro,
6
+ use [`Tooltip`](/ui/tooltip).
6
7
 
7
8
  ```tsx preview
8
9
  <Popover>
@@ -12,7 +13,7 @@ detalhes.
12
13
  <PopoverContent align="start">
13
14
  <PopoverHeader>
14
15
  <PopoverTitle>Renomear</PopoverTitle>
15
- <PopoverDescription>O slug técnico não muda — só o título.</PopoverDescription>
16
+ <PopoverDescription>O endereço da sessão continua o mesmo; só o título muda.</PopoverDescription>
16
17
  </PopoverHeader>
17
18
  <div className="mt-3 space-y-2">
18
19
  <Label htmlFor="pop-titulo">Título</Label>
@@ -23,9 +24,14 @@ detalhes.
23
24
  </Popover>
24
25
  ```
25
26
 
26
- ## Alinhamento
27
+ O painel mede `18rem` de largura por padrão e abre abaixo do gatilho. Clicar fora ou pressionar
28
+ Esc fecha o painel.
27
29
 
28
- `align` posiciona o painel em relação ao gatilho e `sideOffset` define a distância entre eles.
30
+ ## Posição e largura
31
+
32
+ `side` escolhe o lado preferido, `align` alinha o painel ao gatilho e `sideOffset` define a
33
+ distância entre eles. Quando não há espaço no lado escolhido, o painel abre no lado oposto. Ajuste
34
+ a largura com `className`, como `w-56` para `14rem`.
29
35
 
30
36
  ```tsx preview
31
37
  <Popover>
@@ -40,6 +46,12 @@ detalhes.
40
46
  <PopoverDescription>Alinhado à borda direita do gatilho.</PopoverDescription>
41
47
  </PopoverContent>
42
48
  </Popover>
49
+ <Popover>
50
+ <PopoverTrigger asChild><Button variant="ghost">À direita</Button></PopoverTrigger>
51
+ <PopoverContent side="right" className="w-56">
52
+ <PopoverDescription>Abre ao lado direito do gatilho.</PopoverDescription>
53
+ </PopoverContent>
54
+ </Popover>
43
55
  ```
44
56
 
45
57
  ## Propriedades de Popover
@@ -47,7 +59,15 @@ detalhes.
47
59
  | Propriedade | Tipo | Padrão | Descrição |
48
60
  |---|---|---|---|
49
61
  | `open` | `boolean` | | Estado no modo controlado. |
50
- | `onOpenChange` | `(open: boolean) => void` | | Atualiza o estado para permitir abertura ou fechamento por código. |
62
+ | `onOpenChange` | `(open: boolean) => void` | | Chamado quando o painel abre ou fecha; atualize `open` com o valor recebido. |
63
+ | `defaultOpen` | `boolean` | `false` | Estado inicial no modo não controlado. |
64
+ | `modal` | `boolean` | `false` | Com `true`, prende o foco no painel e bloqueia a interação com o restante da página enquanto ele está aberto. |
65
+
66
+ ## Propriedades de PopoverTrigger
67
+
68
+ | Propriedade | Tipo | Padrão | Descrição |
69
+ |---|---|---|---|
70
+ | `asChild` | `boolean` | `false` | Faz do filho (por exemplo, um `Button`) o gatilho, em vez de renderizar outro `<button>`. |
51
71
 
52
72
  ## Propriedades de PopoverAnchor
53
73
 
@@ -59,5 +79,22 @@ não introduzir um nó extra; sem ele, o `PopoverTrigger` é a âncora.
59
79
 
60
80
  | Propriedade | Tipo | Padrão | Descrição |
61
81
  |---|---|---|---|
82
+ | `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'bottom'` | Lado preferido do painel; sem espaço, ele abre no lado oposto. |
62
83
  | `align` | `'start' \| 'center' \| 'end'` | `'center'` | Alinhamento do painel em relação ao gatilho. |
63
84
  | `sideOffset` | `number` | `4` | Distância entre o gatilho e o painel. |
85
+ | `className` | `string` | | Classes do painel. A largura padrão é `w-72` (`18rem`). |
86
+
87
+ ## Propriedades de PopoverHeader
88
+
89
+ `PopoverHeader` empilha `PopoverTitle` e `PopoverDescription` com espaçamento curto. Não tem props
90
+ próprias; aceita as de um elemento `div`.
91
+
92
+ ## Propriedades de PopoverTitle
93
+
94
+ `PopoverTitle` destaca o nome do painel com peso médio. Ele renderiza um `div`, sem nível de
95
+ heading. Não tem props próprias; aceita as de um elemento `div`.
96
+
97
+ ## Propriedades de PopoverDescription
98
+
99
+ `PopoverDescription` apresenta o texto de apoio em cor atenuada. Não tem props próprias; aceita as
100
+ de um elemento `p`.
@@ -1,45 +1,11 @@
1
- `Presentation` preserva a identidade de um recurso quando o contexto pede uma página, um dialog ou
2
- um drawer. A superfície decide quanto espaço ocupar; o recurso continua com a mesma navegação,
3
- cabeçalho, body e rodapé.
4
-
5
- Dialog e Drawer mantêm no cabeçalho a ordem horizontal de navegação ou retorno, título e ações.
6
- O retorno aparece em uma surface modal somente
7
- quando ela foi aberta sobre outro Dialog ou Drawer; a Page ao fundo sustenta o modal, mas não cria
8
- uma etapa de navegação. Formulários modais dimensionam o footer pelo conteúdo, com `Cancelar` em
9
- `ghost` e a ação principal sólida.
10
- Em Page hospedada por `PageShell`, a barra concentra navegação e ações globais. Uma Presentation
11
- de listagem materializa título e comandos de header no `Content` que envolve o `ActionList`; assim
12
- a criação permanece próxima da coleção. Esses comandos usam o tamanho `default`: o que abre outra
13
- Presentation é um `Button` na variante padrão (`solid`), e uma action `simple` vira um
14
- `ActionTrigger` na variante `ghost`. Nas demais Presentations, o título abre o conteúdo em
15
- `PageIntro` e os comandos da superfície seguem a mesma regra.
16
-
17
- Use esse padrão quando uma lista puder abrir um detalhe lateral, quando a mesma edição precisar
18
- funcionar em modal e em rota própria ou quando um fluxo começar compacto e crescer sem ganhar uma
19
- segunda implementação.
20
-
21
- Dentro de `PageShell`, a superfície `page` usa a anatomia real da aplicação: a Presentation projeta
22
- navigation e actions globais por `PageHeader`, mantém a introdução no conteúdo quando existir e
23
- compõe `PageBody` e, quando necessário, `PageFooter`. Para uma action `list`, `PageBody` recebe
24
- `Content + ActionList`; o header da coleção recebe o título e os comandos declarados no placement
25
- `header`. Dialog e Drawer continuam usando o header da própria superfície.
26
- Os comandos da coleção ficam no extremo oposto ao título e permanecem textuais, mesmo quando a
27
- action declara um ícone. A criação usa `Criar recurso`, sem ícone, em um `Button` na variante
28
- padrão (`solid`) e no tamanho `default`.
29
- Não envolva a Presentation em um card para simular a página; valide proporção, rolagem e ações no
30
- shell que efetivamente hospeda a rota.
1
+ Use `Presentation` quando o mesmo recurso precisa aparecer ora como página, ora como dialog, ora
2
+ como drawer: uma lista que abre o detalhe ao lado, uma edição que funciona em modal e em rota
3
+ própria, um fluxo que começa compacto e cresce sem ganhar uma segunda implementação. A superfície
4
+ decide quanto espaço ocupar; o recurso mantém a mesma navegação, cabeçalho, corpo e rodapé.
31
5
 
32
- O exemplo declara a Presentation sobre um contrato de formulário e alterna a superfície. O
6
+ O exemplo declara uma Presentation sobre um contrato de formulário e alterna a superfície. O
33
7
  `DocBrowserActionProvider` simula o cliente de actions; na aplicação, esse papel é do provider real.
34
8
 
35
- Quando o recurso aceita assistência contextual, declare somente o gatilho serializável em
36
- `assistant`. O renderer coloca esse gatilho no cabeçalho e abre o painel à direita do body da
37
- própria Page, Dialog ou Drawer. Cabeçalho e rodapé continuam ocupando toda a largura da superfície;
38
- enquanto o painel está aberto, o gatilho sai do cabeçalho e o próprio painel assume sua identidade
39
- visual. O dado vivo do recurso não entra na definição: a página que o carregou fornece
40
- `assistant.render`, captura identidade, nome e demais dados autorizados e monta o chat. Assim,
41
- contexto de conversa não vira autorização nem dado persistido no manifest.
42
-
43
9
  ```tsx live
44
10
  const workspacePresentation = definePresentation({
45
11
  schemaVersion: 1,
@@ -64,7 +30,7 @@ function Example() {
64
30
  <DocBrowserActionProvider>
65
31
  <div className="space-y-4">
66
32
  <ButtonGroup mode="spaced">
67
- {["page", "dialog", "drawer"].map((value) => (
33
+ {(["page", "dialog", "drawer"] as const).map((value) => (
68
34
  <Button
69
35
  key={value}
70
36
  size="sm"
@@ -115,14 +81,48 @@ function Example() {
115
81
  render(<Example />);
116
82
  ```
117
83
 
84
+ ## Como cada superfície apresenta o recurso
85
+
86
+ Em Dialog e Drawer, o cabeçalho da própria superfície reúne, nesta ordem, o retorno, o título e as
87
+ ações. O retorno só aparece quando o modal foi aberto sobre outro Dialog ou Drawer: a página ao
88
+ fundo sustenta o modal, mas não é uma etapa para onde voltar. Formulários modais dimensionam o
89
+ rodapé pelo conteúdo, com `Cancelar` em `ghost` e a ação principal sólida.
90
+
91
+ Dentro de [`PageShell`](/ui/page), a superfície `page` usa a anatomia real da aplicação. O
92
+ `PageHeader` recebe só a navegação e as ações globais; o título abre o conteúdo em `PageIntro`, e o
93
+ corpo vai para `PageBody`, com `PageFooter` quando há ações de rodapé.
94
+
95
+ Uma Presentation de listagem exibida como página muda essa distribuição para manter a criação perto
96
+ da coleção. O `PageBody` recebe um [`Content`](/ui/content) com a
97
+ [`ActionList`](/ui/action-list), e o cabeçalho desse `Content` recebe o título e os comandos
98
+ declarados com `placement: "header"`, no extremo oposto ao título. Os comandos de cabeçalho usam o
99
+ tamanho `default` e permanecem textuais, mesmo quando a action declara um ícone: o que abre outra
100
+ Presentation é um `Button` sólido, e uma action `simple` vira um
101
+ [`ActionTrigger`](/ui/action-trigger) `ghost`. O rótulo vem do `label` da action, como `Criar workspace`.
102
+
103
+ Não envolva a Presentation em um card para simular a página. Valide proporção, rolagem e ações no
104
+ shell que efetivamente hospeda a rota.
105
+
106
+ ## Assistência contextual
107
+
108
+ Quando o recurso aceita uma conversa sobre ele, declare em `assistant` somente o gatilho
109
+ serializável, como no exemplo acima. O renderer coloca o gatilho no cabeçalho e abre o painel à
110
+ direita do corpo da própria página, dialog ou drawer. Cabeçalho e rodapé continuam ocupando toda a
111
+ largura; enquanto o painel está aberto, o gatilho sai do cabeçalho e o painel assume a identidade
112
+ visual da assistência.
113
+
114
+ O dado vivo do recurso não entra na definição. A página que carregou o recurso fornece
115
+ `assistant.render`, reúne identidade, nome e demais dados autorizados e monta o chat. Assim, o
116
+ contexto da conversa não vira autorização nem dado persistido no manifest.
117
+
118
118
  ## A declaração
119
119
 
120
120
  `definePresentation` cria o artefato serializável. O body recebe uma action `form`, `list` ou
121
- `view`; uma action `simple` aparece como comando no cabeçalho ou no rodapé. Em formulário,
122
- `body.submitLabel` sobrescreve o padrão “Salvar” quando a operação tem outro resultado observável,
123
- como “Renomear” ou “Criar nova versão”. Actions que precisam de
124
- interface abrem outra Presentation. Bindings fornecem valores vindos da rota, do registro, da
125
- seleção, da sessão ou do resultado anterior sem guardar callbacks no JSON.
121
+ `view`, ou um componente React, descrito em “Conteúdo React no body”; uma action `simple` aparece
122
+ como comando no cabeçalho ou no rodapé. Em formulário, `body.submitLabel` sobrescreve o padrão
123
+ “Salvar” quando a operação tem outro resultado observável, como “Renomear” ou “Criar nova versão”.
124
+ Actions que precisam de interface abrem outra Presentation. Bindings fornecem valores vindos da
125
+ rota, do registro, da seleção, da sessão ou do resultado anterior sem guardar callbacks no JSON.
126
126
 
127
127
  Quando a exibição precisa sobreviver a refresh, deep link ou histórico, declare `route` na própria
128
128
  Presentation. O `path` aceita segmentos fixos e parâmetros como `:id`; `surface` determina como a
@@ -154,6 +154,48 @@ const workspaceList = definePresentation({
154
154
  });
155
155
  ```
156
156
 
157
+ ## Conteúdo React no body
158
+
159
+ Uma tela que já existe em React pode adotar a casca da Presentation — rota, superfície, título,
160
+ navegação e ações — sem migrar o miolo para actions declarativas de uma vez. Declare
161
+ `body: { component }` com um identificador estável para esse conteúdo e passe o conteúdo como
162
+ `children` do `Presentation`:
163
+
164
+ ```tsx
165
+ const appearance = definePresentation({
166
+ schemaVersion: 1,
167
+ id: "settings.appearance",
168
+ title: "Aparência",
169
+ route: { path: "/settings/appearance", surface: "page" },
170
+ body: { component: "settings.appearance.content" },
171
+ });
172
+
173
+ <Presentation
174
+ definition={appearance}
175
+ definitions={[appearance]}
176
+ actions={{}}
177
+ invocation={invocation}
178
+ >
179
+ <AppearanceSettings />
180
+ </Presentation>;
181
+ ```
182
+
183
+ O identificador de `component` vai para o manifest; o JSX, não. Uma Presentation com body de
184
+ componente exige `children`, e uma com body de action os recusa: nos dois casos, a renderização
185
+ lança um erro que nomeia a Presentation.
186
+
187
+ Com `heading: "shell"`, o padrão, a casca emite o título da definição. Quando o conteúdo React já
188
+ declara o heading principal da página, como um `Content level={1}`, use `heading: "content"`: dentro
189
+ de `PageShell`, a casca deixa de emitir o `PageIntro` e o documento continua com um único `h1`. Em
190
+ Dialog e Drawer, o título continua no cabeçalho da superfície.
191
+
192
+ Se o conteúdo precisa ocupar a altura da página e controlar a própria rolagem, como uma lista com
193
+ cabeçalho fixo, passe `layout="fill"` ao `Presentation`. Com o padrão `flow`, a página rola como um
194
+ documento. Uma [`ActionList`](/ui/action-list) filha direta do conteúdo React recebe essa altura e
195
+ assume `fill` sozinha.
196
+
197
+ ## Registrar as declarações
198
+
157
199
  Registre as declarações em `presentations` no `opus.config.ts`. `opus gen` valida as referências e
158
200
  projeta o JSON no manifest; a Lens pode então exibir o artefato completo sem reconstruí-lo a partir
159
201
  do JSX.
@@ -161,7 +203,7 @@ do JSX.
161
203
  ## A invocação
162
204
 
163
205
  `definePresentationInvocation` cria o estado vivo de uma exibição. Ele contém somente valores JSON,
164
- a surface atual e a pilha necessária para voltar. A definição continua estática no manifest; input
206
+ a superfície atual e a pilha necessária para voltar. A definição continua estática no manifest; input
165
207
  de rota, sessão e resultado existe apenas na invocação.
166
208
 
167
209
  ```ts
@@ -194,7 +236,7 @@ no manifest e concentre a inspeção na Lens em vez de criar um launcher flutuan
194
236
  | `definition` | `PresentationDefinition` | | Artefato estático que descreve o recurso. |
195
237
  | `definitions` | `PresentationDefinition[]` | | Registry alcançável por navegação, abertura e retorno. |
196
238
  | `actions` | `PresentationActionRegistry` | | Contratos compartilháveis referenciados pelo artefato. |
197
- | `invocation` | `PresentationInvocation` | | Surface, input e pilha da exibição atual. |
239
+ | `invocation` | `PresentationInvocation` | | Superfície, input e pilha da exibição atual. |
198
240
  | `bindingContext` | `PresentationBindingContext` | | Rota, item, seleção, sessão e resultado disponíveis. |
199
241
  | `assistant` | `PresentationAssistantRuntime` | | Painel contextual fornecido pela superfície hospedeira. |
200
242
  | `onInvocationChange` | `(next: PresentationInvocation \| null) => void` | | Recebe navegação, retorno e fechamento. |
@@ -205,6 +247,8 @@ no manifest e concentre a inspeção na Lens em vez de criar um launcher flutuan
205
247
  | `onOpenChange` | `(open: boolean) => void` | | Notifica abertura e fechamento da superfície modal. |
206
248
  | `className` | `string` | | Classes adicionais da superfície. |
207
249
  | `bodyClassName` | `string` | | Classes adicionais do body, aplicadas uma única vez. |
250
+ | `layout` | `'flow' \| 'fill'` | `'flow'` | Em `page`, `fill` limita a altura ao shell e entrega a rolagem ao conteúdo do body; a lista de um body `list` ocupa essa altura e rola só os resultados. Dialog e Drawer ignoram. |
251
+ | `children` | `ReactNode` | | Conteúdo React do body declarado com `body.component`. Obrigatório nesse caso e recusado com `body.action`. |
208
252
 
209
253
  ## Propriedades de PresentationInspector
210
254
 
@@ -1,18 +1,26 @@
1
1
  ## Progresso determinado
2
2
 
3
- Use `Progress` quando a tarefa informar uma porcentagem de conclusão. `value` aceita valores de 0 a
4
- 100 e anima o preenchimento a cada mudança. Sem valor, a barra permanece vazia.
3
+ Use `Progress` quando a tarefa informar quanto já foi concluído. `value` aceita valores de 0 a
4
+ `max`, que por padrão é 100, e anima o preenchimento a cada mudança. Para contar etapas, declare
5
+ `max` com o total: `value={3} max={4}` preenche três quartos da barra. Quando a duração da espera
6
+ não é conhecida, use [`Spinner`](/ui/spinner).
7
+
8
+ A barra é exposta aos leitores de tela como `progressbar`, com o valor atual, mas o texto ao lado
9
+ dela não a nomeia automaticamente. Ligue o rótulo visível à barra com `aria-labelledby` ou, sem
10
+ rótulo visível, passe `aria-label`.
5
11
 
6
12
  ```tsx preview col
7
13
  <div className="w-full max-w-sm space-y-2">
8
14
  <div className="flex items-center justify-between text-sm">
9
- <span>opus check</span>
15
+ <span id="progress-check">opus check</span>
10
16
  <span className="text-muted-foreground">65%</span>
11
17
  </div>
12
- <Progress value={65} />
18
+ <Progress value={65} aria-labelledby="progress-check" />
13
19
  </div>
14
20
  ```
15
21
 
22
+ Sem `value`, ou com `null`, a barra fica vazia e é exposta como progresso indeterminado.
23
+
16
24
  ## Controlado
17
25
 
18
26
  Controle `value` externamente quando o progresso acompanhar uma tarefa em andamento. No exemplo,
@@ -20,15 +28,16 @@ cada clique avança uma etapa da sincronização.
20
28
 
21
29
  ```tsx preview col
22
30
  const [step, setStep] = useState(40)
31
+ const labelId = useId()
23
32
  const advance = (): void => setStep((prev) => Math.min(prev + 20, 100))
24
33
 
25
34
  render(
26
35
  <div className="w-full max-w-sm space-y-3">
27
36
  <div className="flex items-center justify-between text-sm">
28
- <span>Sincronizar Empresa X</span>
37
+ <span id={labelId}>Sincronizar Empresa X</span>
29
38
  <span className="text-muted-foreground">{step}%</span>
30
39
  </div>
31
- <Progress value={step} />
40
+ <Progress value={step} aria-labelledby={labelId} />
32
41
  <Button variant="outline" size="sm" onClick={advance}>
33
42
  Avançar etapa
34
43
  </Button>
@@ -39,18 +48,19 @@ render(
39
48
  ## Altura e contexto
40
49
 
41
50
  `context` diz o que o andamento comunica — `success` para meta atingida, `warning` ou `danger`
42
- para atenção — e tinge trilha e preenchimento com a família semântica. `className` compõe sobre o
43
- padrão para ajustar a altura.
51
+ para atenção — e tinge trilha e preenchimento com a família semântica. A cor reforça o significado,
52
+ mas não o substitui: mantenha o rótulo e o valor legíveis. `className` compõe sobre o padrão para
53
+ ajustar a altura.
44
54
 
45
55
  ```tsx preview col
46
56
  <div className="w-full max-w-sm space-y-4">
47
57
  <div className="space-y-1.5">
48
- <span className="text-sm">Cobertura de testes</span>
49
- <Progress value={92} context="success" className="h-1.5" />
58
+ <span id="progress-coverage" className="text-sm">Cobertura de testes</span>
59
+ <Progress value={92} context="success" className="h-1.5" aria-labelledby="progress-coverage" />
50
60
  </div>
51
61
  <div className="space-y-1.5">
52
- <span className="text-sm">Skills cobertas pelo revisor</span>
53
- <Progress value={45} context="warning" className="h-3" />
62
+ <span id="progress-review" className="text-sm">Skills cobertas pelo revisor</span>
63
+ <Progress value={45} context="warning" className="h-3" aria-labelledby="progress-review" />
54
64
  </div>
55
65
  </div>
56
66
  ```
@@ -59,6 +69,9 @@ padrão para ajustar a altura.
59
69
 
60
70
  | Propriedade | Tipo | Padrão | Descrição |
61
71
  |---|---|---|---|
62
- | `value` | `number \| null` | | O progresso de 0 a 100. O preenchimento anima a cada mudança; null/ausente deixa a barra vazia. |
72
+ | `value` | `number \| null` | | O progresso de 0 a `max`. O preenchimento anima a cada mudança; sem valor, a barra fica vazia e indeterminada. |
73
+ | `max` | `number` | `100` | O valor que representa a conclusão. O preenchimento é a fração `value / max`. |
63
74
  | `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'primary'` | O que o andamento comunica; tinge trilha e preenchimento pela família `--context-*`. |
64
- | `className` | `string` | | Compõe sobre o padrão — ajuste a altura (h-1.5/h-3). |
75
+ | `aria-label` | `string` | | Nome acessível da barra quando não há rótulo visível. |
76
+ | `aria-labelledby` | `string` | | `id` do rótulo visível que nomeia a barra. |
77
+ | `className` | `string` | | Compõe sobre o padrão — ajuste a altura (`h-1.5`, `h-3`). |
@@ -5,7 +5,7 @@ title: Filas
5
5
  # Filas
6
6
 
7
7
  Use uma action em background quando a operação não puder manter a requisição aberta. O
8
- `QueueAdapter` enfileira o trabalho e devolve um `JobHandle` para acompanhamento pelo cliente.
8
+ `QueueAdapter` enfileira o trabalho e devolve um `JobHandle` para acompanhar o andamento.
9
9
 
10
10
  ## O contrato
11
11
 
@@ -21,18 +21,21 @@ interface JobHandle<T = unknown> {
21
21
  jobId: string
22
22
  action: string
23
23
  status: 'queued' | 'running' | 'done' | 'failed' | 'cancelled'
24
- progress?: { percent?: number; message?: string }
24
+ progress?: { percent?: number; message?: string; data?: unknown }
25
25
  data?: T
26
26
  error?: ActionError
27
+ enqueuedAt: string
28
+ startedAt?: string
29
+ finishedAt?: string
27
30
  attempts: number
28
31
  trace?: TraceContext
29
32
  }
30
33
  ```
31
34
 
32
- `runtime.execute()` valida a chamada, cria o `JobSpec` e inclui o trace efetivo, request ID e
33
- provenance original. O adapter persiste o envelope; o handle também expõe esse trace. No
34
- worker, `runtime.executeJob()` revalida o envelope e executa um span filho, preservando a
35
- cadeia request → enqueue → job sem aceitar baggage ou campos extras no carrier.
35
+ `runtime.execute()` valida a chamada, cria o `JobSpec` e inclui o trace efetivo, o request ID e a
36
+ provenance original. O adapter persiste esse envelope, e o handle também expõe o trace. No worker,
37
+ `runtime.executeJob()` revalida o envelope e executa um span filho, preservando a cadeia requisição
38
+ → enfileiramento → job sem aceitar baggage ou campos extras.
36
39
 
37
40
  ## Driver
38
41
 
@@ -43,26 +46,41 @@ import { bullmqQueue } from '@softize/opus/queue/bullmq'
43
46
  const queue = bullmqQueue({ queue: new Queue('opus', { connection: { host: 'localhost' } }) })
44
47
  ```
45
48
 
46
- Redis-backed (durável, entre-processos, retries). Peers opcionais (`bullmq`, `ioredis`) só
47
- para quem usa o driver.
49
+ O driver usa Redis por meio do BullMQ, com durabilidade, entrega entre processos e novas tentativas.
50
+ `bullmq` é dependência opcional: só precisa ser instalado por quem usa o driver.
48
51
 
49
- ## No runtime
52
+ ## Declarar a action em background
53
+
54
+ Configure a fila no runtime e declare `background` no binding da action:
50
55
 
51
56
  ```ts
52
57
  const runtime = createRuntime({
53
58
  // …server/data…
54
59
  queue,
55
60
  })
61
+
62
+ export const relatorioGerar = bindAction(relatorioGerarContract, {
63
+ background: { enabled: true, retry: { attempts: 3 }, progress: true },
64
+ handler: async (ctx, input, loaded, progress) => {
65
+ await progress?.report({ percent: 50, message: 'Consultando dados' })
66
+ return gerarRelatorio(input)
67
+ },
68
+ })
56
69
  ```
57
70
 
58
- A action declara `background: { enabled: true }` (+ retry/priority no contrato); chamá-la devolve um
59
- `JobHandle` em vez do resultado, e o cliente pola `status(jobId)` (ou `subscribe`) até
60
- `done`/`failed`.
71
+ Só actions `simple` e `form` executam em background; além de `retry` e `progress`, a configuração
72
+ aceita `queue`, `priority` e `timeout`. Em uma action `list` ou `view`, o tipo do binding aceita
73
+ `background`, mas o runtime o ignora e executa a action na própria requisição. Para evitar essa
74
+ surpresa, o runtime avisa ao registrar a action fora de produção (`runtime.action_field_ignored`), e
75
+ o [`opus check`](/docs/cli) aponta a regra `ignored-by-kind`. Sem `QueueAdapter`, a chamada falha com
76
+ `runtime.queue_required`. Chamar a action devolve um `JobHandle` em vez do resultado. O Opus não
77
+ monta uma rota de acompanhamento: exponha `queue.status(jobId)` ou `queue.subscribe` por uma action
78
+ ou rota do próprio app até o job chegar a `done` ou `failed`.
61
79
 
62
80
  ## Worker BullMQ
63
81
 
64
- O app continua dono do `Worker` e da reidratação de autenticação. O processor chama
65
- `executeJob()` — nunca `execute()`, que criaria outro job:
82
+ O app continua dono do `Worker` e de reconstruir a autenticação de quem enfileirou. O processor
83
+ chama `executeJob()`, e não `execute()`, que criaria outro job:
66
84
 
67
85
  ```ts
68
86
  new Worker('opus', async (job) => {
@@ -76,13 +94,13 @@ new Worker('opus', async (job) => {
76
94
  }, { connection })
77
95
  ```
78
96
 
79
- O runtime confirma que o actor reidratado corresponde a `spec.ctx.userId`; tenant, request,
80
- trace e provenance vêm do envelope. O driver mapeia `attempts`, backoff nativo e prioridade.
81
- Multiplicador exponencial diferente de 2 ou `maxMs` exige `backoffStrategy` custom do worker e
82
- é rejeitado pelo driver em vez de ser silenciosamente ignorado. `timeout` permanece no
83
- envelope para o worker aplicar, pois não é uma opção de execução do `Queue.add`.
97
+ O runtime confirma que o usuário reconstruído corresponde a `spec.ctx.userId`; tenant, request,
98
+ trace e provenance vêm do envelope. O driver traduz `attempts`, o backoff nativo e a prioridade para
99
+ o BullMQ. Um backoff exponencial com multiplicador diferente de 2 ou com `maxMs` exige uma
100
+ `backoffStrategy` própria no worker, e o driver o recusa em vez de ignorá-lo em silêncio. `timeout`
101
+ permanece no envelope para o worker aplicar, porque não é uma opção de execução do `Queue.add`.
84
102
 
85
103
  ## Limites atuais
86
104
 
87
- Driver hoje: `bullmq` (Redis). In-memory para dev e outros backends (SQS, pg-boss…) entram
88
- por reincidência.
105
+ O único driver é o `bullmq`. Uma fila em memória para desenvolvimento e outros backends, como SQS e
106
+ pg-boss, ainda não existem.