@softize/opus 27.1.0 → 29.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.
- package/CHANGELOG.md +113 -0
- package/bin/cli.mjs +68 -3
- package/bin/lib/check.mjs +139 -30
- package/bin/lib/copy.mjs +505 -67
- package/bin/lib/materialize.mjs +99 -1
- package/docs/adr/0015-action-size-follows-interaction-density.md +2 -0
- package/docs/adr/0028-schedules-run-actions-as-the-system-by-action-opt-in.md +1 -1
- package/docs/adr/0029-page-header-is-the-single-page-introduction.md +3 -1
- package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
- package/docs/adr/0060-surface-composition-separates-chrome-and-intro.md +161 -0
- package/docs/adr/0061-claude-md-imports-agents-md.md +41 -0
- package/docs/adr/README.md +4 -0
- package/docs/code-style.md +47 -8
- package/docs/protocol.md +1 -1
- package/docs/releasing.md +7 -0
- package/package.json +1 -1
- package/registry/instructions/opus.md +3 -1
- package/registry/skills/build-opus-ui/SKILL.md +16 -14
- package/registry/skills/build-opus-ui/references/evaluations.md +10 -0
- package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -15
- package/registry/skills/maintain-opus-docs/SKILL.md +2 -1
- package/registry/skills/maintain-opus-docs/references/editorial-standard.md +9 -0
- package/registry/skills/maintain-opus-docs/references/evaluations.md +8 -0
- package/registry/templates/app/pnpm-workspace.yaml +1 -1
- package/src/core/system-actor.ts +1 -1
- package/src/ui/components/patterns/list.tsx +63 -54
- package/src/ui/components/patterns/page.tsx +146 -38
- package/src/ui/components/patterns/presentation.tsx +44 -59
- package/src/ui/components/patterns/structural-header.tsx +42 -6
- package/src/ui/components/patterns/surface-anatomy.ts +52 -22
- package/src/ui/components/patterns/surface.tsx +2 -3
- package/src/ui/components/primitives/control.ts +2 -2
- package/src/ui/components/primitives/dialog.tsx +7 -2
- package/src/ui/components/primitives/drawer.tsx +7 -2
- package/src/ui/components/primitives/icon-picker.tsx +271 -34
- package/src/ui/components/primitives/pagination.tsx +5 -4
- package/src/ui/components/primitives/tabs.tsx +15 -2
- package/src/ui/components/primitives/toggle.tsx +1 -1
- package/src/ui/docs/content/action-list.md +12 -3
- package/src/ui/docs/content/actions.md +1 -1
- package/src/ui/docs/content/auth.md +3 -3
- package/src/ui/docs/content/button.md +7 -7
- package/src/ui/docs/content/cli.md +6 -0
- package/src/ui/docs/content/communication.md +10 -8
- package/src/ui/docs/content/compositions.md +35 -23
- package/src/ui/docs/content/dialog.md +18 -6
- package/src/ui/docs/content/drawer.md +17 -5
- package/src/ui/docs/content/events.md +13 -13
- package/src/ui/docs/content/icon-picker.md +37 -19
- package/src/ui/docs/content/input.md +1 -1
- package/src/ui/docs/content/installation.md +4 -0
- package/src/ui/docs/content/log.md +1 -1
- package/src/ui/docs/content/page.md +169 -69
- package/src/ui/docs/content/pagination.md +7 -8
- package/src/ui/docs/content/presentation.md +4 -3
- package/src/ui/docs/content/queue.md +2 -2
- package/src/ui/docs/content/router.md +1 -1
- package/src/ui/docs/content/scheduler.md +15 -15
- package/src/ui/docs/content/surface.md +8 -5
- package/src/ui/docs/content/tabs.md +12 -4
- package/src/ui/docs/content/testing.md +4 -4
- package/src/ui/meta.ts +1 -1
- package/src/ui/react.tsx +9 -1
- package/src/vite/design.ts +1 -1
|
@@ -68,7 +68,7 @@ declaração adicional:
|
|
|
68
68
|
respostas (`alert`, `prompt`, `choose`) entram no inventário como se fossem props de um
|
|
69
69
|
`ActionFormDialog`; o campo do diagnóstico é `dialog.confirm().title`,
|
|
70
70
|
`dialog.choose().actions[0].label` etc. Constantes locais resolvem normalmente. Uma função
|
|
71
|
-
que devolve template (`body: describe(
|
|
71
|
+
que devolve template (`body: describe(name)`) gera o diagnóstico `content` habitual e
|
|
72
72
|
se declara em `copy.dynamic` como `message-template`. Opções construídas em tempo de execução
|
|
73
73
|
ou mutadas depois de declaradas reprovam como estrutura, porque nenhum texto pode ser
|
|
74
74
|
atribuído a elas.
|
|
@@ -80,7 +80,7 @@ declaração adicional:
|
|
|
80
80
|
mesmo arquivo, cada literal do array vira um texto na linha em que foi escrito, em vez de um
|
|
81
81
|
diagnóstico sobre a prop. `filter`, `slice`, `toSorted` e `toReversed` podem ficar entre o
|
|
82
82
|
array e a iteração. O limite é deliberado: o array precisa ser literal e declarado no mesmo
|
|
83
|
-
arquivo; o elemento só pode ser lido (`item.
|
|
83
|
+
arquivo; o elemento só pode ser lido (`item.key`), nunca passado adiante, espalhado ou
|
|
84
84
|
atribuído; o callback não pode receber o próprio array como terceiro parâmetro; e nenhum
|
|
85
85
|
elemento pode ter a chave dinâmica. Arrays importados, derivados por spread ou por
|
|
86
86
|
concatenação continuam gerando o diagnóstico da prop.
|
|
@@ -97,13 +97,15 @@ visual que o protocolo não representa com segurança.
|
|
|
97
97
|
é copy editorial estável; `label` e `confirm.*` são as superfícies editoriais cobertas.
|
|
98
98
|
|
|
99
99
|
Quando uma prop mapeada recebe dados da aplicação, declare a superfície em
|
|
100
|
-
`base.json.copy.dynamic` em vez de excluir o arquivo inteiro. O seletor usa o caminho,
|
|
101
|
-
|
|
100
|
+
`base.json.copy.dynamic` em vez de excluir o arquivo inteiro. O seletor usa o caminho, o campo
|
|
101
|
+
e o trecho de código exibidos pelo diagnóstico, sem a linha: a declaração continua válida quando
|
|
102
|
+
o arquivo muda acima dela. `runtime-data` registra valores como nomes,
|
|
102
103
|
contagens e opções vindas de API sem transformá-los em copy editorial. `message-template`
|
|
103
104
|
acrescenta ao inventário um texto-modelo com papel semântico, para que a parte autoral de uma
|
|
104
105
|
mensagem interpolada continue passando pela política da Base. As declarações não alteram o
|
|
105
|
-
que o React renderiza; um seletor que deixa de
|
|
106
|
-
|
|
106
|
+
que o React renderiza; um seletor que deixa de encontrar o trecho reprova o gate e precisa ser
|
|
107
|
+
revisto. Declarações antigas, ancoradas por `line`, são convertidas com `opus copy --reanchor`.
|
|
108
|
+
Diagnósticos estruturais ou de transformação visual não podem ser
|
|
107
109
|
dispensados por `copy.dynamic`; ausência de conteúdo também continua bloqueante.
|
|
108
110
|
|
|
109
111
|
```json
|
|
@@ -112,7 +114,7 @@ dispensados por `copy.dynamic`; ausência de conteúdo também continua bloquean
|
|
|
112
114
|
"dynamic": [
|
|
113
115
|
{
|
|
114
116
|
"source": "src/sessions.tsx",
|
|
115
|
-
"
|
|
117
|
+
"expression": "sessionOptions",
|
|
116
118
|
"field": "<Select>.options",
|
|
117
119
|
"kind": "runtime-data",
|
|
118
120
|
"reason": "As opções são nomes de sessões retornados pela aplicação.",
|
|
@@ -120,7 +122,7 @@ dispensados por `copy.dynamic`; ausência de conteúdo também continua bloquean
|
|
|
120
122
|
},
|
|
121
123
|
{
|
|
122
124
|
"source": "src/sessions.tsx",
|
|
123
|
-
"
|
|
125
|
+
"expression": "`Excluir ${session.name}?`",
|
|
124
126
|
"field": "dialog.confirm().body",
|
|
125
127
|
"kind": "message-template",
|
|
126
128
|
"role": "dialog-body",
|
|
@@ -24,8 +24,8 @@ Cada família dá um nome próprio a cada região. A tabela mostra a correspond
|
|
|
24
24
|
| Navegação | `PageNavigation` ou `PageBack` | `DialogNavigation` | `DrawerNavigation` | Não existe |
|
|
25
25
|
| Título no cabeçalho | `PageTitle` | `DialogTitle` | `DrawerTitle` | `SurfaceTitle` |
|
|
26
26
|
| Ações do cabeçalho | `PageActions` | `DialogActions` | `DrawerActions` | `SurfaceActions` |
|
|
27
|
-
| Intro | `
|
|
28
|
-
| Ações da intro |
|
|
27
|
+
| Intro | `PageIntro` no início do `PageBody` | `DialogIntro` | `DrawerIntro` | Não existe |
|
|
28
|
+
| Ações da intro | `PageIntroActions` | `DialogIntroActions` | `DrawerIntroActions` | Não existe |
|
|
29
29
|
| Corpo | `PageBody` | `DialogBody` | `DrawerBody` | `SurfaceBody` |
|
|
30
30
|
| Rodapé | `PageFooter` | `DialogFooter` | `DrawerFooter` | `SurfaceFooter` |
|
|
31
31
|
| Divisores | Antes do `PageFooter` | `sectioned` (padrão) | `sectioned` (padrão) | `sectioned` |
|
|
@@ -97,7 +97,7 @@ function DialogWithIntro({ layout, label }) {
|
|
|
97
97
|
<DialogContent layout={layout}>
|
|
98
98
|
<DialogHeader>
|
|
99
99
|
<DialogActions>
|
|
100
|
-
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
100
|
+
<Button variant="ghost" size="icon-sm" aria-label="Abrir conversa">
|
|
101
101
|
<MessageCircle />
|
|
102
102
|
</Button>
|
|
103
103
|
</DialogActions>
|
|
@@ -142,7 +142,7 @@ function CompactDialog({ layout, label }) {
|
|
|
142
142
|
<Button variant="outline" size="sm">
|
|
143
143
|
Ver histórico
|
|
144
144
|
</Button>
|
|
145
|
-
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
145
|
+
<Button variant="ghost" size="icon-sm" aria-label="Abrir conversa">
|
|
146
146
|
<MessageCircle />
|
|
147
147
|
</Button>
|
|
148
148
|
</DialogActions>
|
|
@@ -197,7 +197,7 @@ function OrderField() {
|
|
|
197
197
|
function CollapseButton() {
|
|
198
198
|
return (
|
|
199
199
|
<DrawerClose asChild>
|
|
200
|
-
<Button variant="ghost" size="icon" aria-label="Recolher painel">
|
|
200
|
+
<Button variant="ghost" size="icon-sm" aria-label="Recolher painel">
|
|
201
201
|
<ChevronsRight />
|
|
202
202
|
</Button>
|
|
203
203
|
</DrawerClose>
|
|
@@ -216,7 +216,7 @@ function DrawerWithIntro({ layout, label }) {
|
|
|
216
216
|
<CollapseButton />
|
|
217
217
|
</DrawerNavigation>
|
|
218
218
|
<DrawerActions>
|
|
219
|
-
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
219
|
+
<Button variant="ghost" size="icon-sm" aria-label="Abrir conversa">
|
|
220
220
|
<MessageCircle />
|
|
221
221
|
</Button>
|
|
222
222
|
</DrawerActions>
|
|
@@ -261,7 +261,7 @@ function CompactDrawer({ layout, label }) {
|
|
|
261
261
|
<Button variant="outline" size="sm">
|
|
262
262
|
Ver histórico
|
|
263
263
|
</Button>
|
|
264
|
-
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
264
|
+
<Button variant="ghost" size="icon-sm" aria-label="Abrir conversa">
|
|
265
265
|
<MessageCircle />
|
|
266
266
|
</Button>
|
|
267
267
|
</DrawerActions>
|
|
@@ -300,8 +300,8 @@ painel.
|
|
|
300
300
|
|
|
301
301
|
## Page
|
|
302
302
|
|
|
303
|
-
|
|
304
|
-
página, e uma `
|
|
303
|
+
Na composição com intro, o `PageHeader` guarda a navegação e as ações da
|
|
304
|
+
página, e uma `PageIntro` abre o `PageBody` com o título e as ações do conteúdo. A página
|
|
305
305
|
precisa de exatamente um heading principal, por isso o `PageHeader` dessa composição não leva
|
|
306
306
|
`PageTitle`.
|
|
307
307
|
|
|
@@ -322,27 +322,20 @@ render(
|
|
|
322
322
|
</Breadcrumb>
|
|
323
323
|
</PageNavigation>
|
|
324
324
|
<PageActions>
|
|
325
|
-
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
325
|
+
<Button variant="ghost" size="icon-sm" aria-label="Abrir conversa">
|
|
326
326
|
<MessageCircle />
|
|
327
327
|
</Button>
|
|
328
328
|
</PageActions>
|
|
329
329
|
</PageHeader>
|
|
330
330
|
<PageBody>
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
actions={
|
|
336
|
-
<Button variant="outline" size="sm">
|
|
337
|
-
Ver histórico
|
|
338
|
-
</Button>
|
|
339
|
-
}
|
|
340
|
-
>
|
|
331
|
+
<PageIntro>
|
|
332
|
+
<PageTitle>Editar ordem de serviço</PageTitle>
|
|
333
|
+
<PageIntroActions><Button variant="outline" size="sm">Ver histórico</Button></PageIntroActions>
|
|
334
|
+
</PageIntro>
|
|
341
335
|
<div className="space-y-2">
|
|
342
336
|
<Label htmlFor="page-intro-owner">Responsável</Label>
|
|
343
337
|
<Input id="page-intro-owner" defaultValue="Marina Alves" />
|
|
344
338
|
</div>
|
|
345
|
-
</Surface>
|
|
346
339
|
</PageBody>
|
|
347
340
|
<PageFooter>
|
|
348
341
|
<ButtonGroup mode="spaced">
|
|
@@ -380,7 +373,7 @@ render(
|
|
|
380
373
|
<Button variant="outline" size="sm">
|
|
381
374
|
Ver histórico
|
|
382
375
|
</Button>
|
|
383
|
-
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
376
|
+
<Button variant="ghost" size="icon-sm" aria-label="Abrir conversa">
|
|
384
377
|
<MessageCircle />
|
|
385
378
|
</Button>
|
|
386
379
|
</PageActions>
|
|
@@ -449,7 +442,7 @@ render(
|
|
|
449
442
|
<Button variant="outline" size="sm">
|
|
450
443
|
Ver histórico
|
|
451
444
|
</Button>
|
|
452
|
-
<Button variant="ghost" size="icon" aria-label="Abrir conversa">
|
|
445
|
+
<Button variant="ghost" size="icon-sm" aria-label="Abrir conversa">
|
|
453
446
|
<MessageCircle />
|
|
454
447
|
</Button>
|
|
455
448
|
</SurfaceActions>
|
|
@@ -477,3 +470,22 @@ Os dois panes recebem conteúdos diferentes sem mudar de componente: o primeiro
|
|
|
477
470
|
[`Sidebar`](/ui/sidebar), que já tem anatomia própria, e o segundo, uma `Surface` completa. A
|
|
478
471
|
rolagem também pertence ao conteúdo: a `Surface` ocupa a altura do pane com `flex h-full flex-col`,
|
|
479
472
|
e o `SurfaceBody` declara `min-h-0 flex-1 overflow-y-auto` para rolar entre o cabeçalho e o rodapé.
|
|
473
|
+
|
|
474
|
+
## Ritmo entre regiões e blocos
|
|
475
|
+
|
|
476
|
+
Surface (incluindo cards e seções), Page, Dialog, Drawer e Tabs organizam o conteúdo
|
|
477
|
+
com intervalos de `1rem`. Os bodies separam seus blocos diretos; grupos internos podem
|
|
478
|
+
ter densidade própria. DetailGroup e Separator não criam margens externas.
|
|
479
|
+
|
|
480
|
+
Sem delimitação, Surface e Tabs não adicionam padding inferior. Uma Surface com moldura
|
|
481
|
+
ou elevação, assim como Dialog e Drawer, reserva `1rem` até a borda. No layout contínuo,
|
|
482
|
+
o intervalo vem da região anterior. No seccionado, há `1rem` entre cada divisória e o
|
|
483
|
+
conteúdo adjacente. Não some padding e gap para expressar o mesmo intervalo.
|
|
484
|
+
|
|
485
|
+
Page conserva os recuos laterais e a largura de leitura. Seu body não acrescenta padding
|
|
486
|
+
inferior quando termina a página; antes de um footer seccionado, reserva `1rem` até a linha.
|
|
487
|
+
O modo `flush` remove recuos do body para uma lista ou tabela controlar sua própria borda,
|
|
488
|
+
mas mantém o intervalo entre blocos.
|
|
489
|
+
|
|
490
|
+
Ao migrar, remova `space-y`, `gap`, `pt` e `pb` locais que apenas reconstruíam esses defaults
|
|
491
|
+
nos slots. Preserve classes de layout internas, como uma grade ou um grupo de campos compacto.
|
|
@@ -48,7 +48,7 @@ distinta da página.
|
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
O corpo cresce até o limite da janela e passa a rolar; cabeçalho e rodapé permanecem visíveis.
|
|
51
|
-
Seus filhos diretos recebem o ritmo estrutural de `
|
|
51
|
+
Seus filhos diretos recebem o ritmo estrutural de `1rem`. Um grupo compacto, uma grade ou uma
|
|
52
52
|
linha declara a própria relação em um wrapper filho; `flush` fica reservado ao conteúdo que
|
|
53
53
|
controla todo o inset.
|
|
54
54
|
O backdrop escurece a página sem aplicar desfoque.
|
|
@@ -126,13 +126,13 @@ fluxo modal encadeado, em que a superfície permanece aberta e troca de etapa.
|
|
|
126
126
|
<DialogContent>
|
|
127
127
|
<DialogHeader>
|
|
128
128
|
<DialogNavigation>
|
|
129
|
-
<Button variant="ghost" size="icon" aria-label="Voltar para a lista">
|
|
129
|
+
<Button variant="ghost" size="icon-sm" aria-label="Voltar para a lista">
|
|
130
130
|
<ArrowLeft />
|
|
131
131
|
</Button>
|
|
132
132
|
</DialogNavigation>
|
|
133
133
|
<DialogTitle>Evento 4821</DialogTitle>
|
|
134
134
|
<DialogActions>
|
|
135
|
-
<Button variant="ghost" size="icon" aria-label="Mais ações">
|
|
135
|
+
<Button variant="ghost" size="icon-sm" aria-label="Mais ações">
|
|
136
136
|
<Ellipsis />
|
|
137
137
|
</Button>
|
|
138
138
|
</DialogActions>
|
|
@@ -146,9 +146,10 @@ fluxo modal encadeado, em que a superfície permanece aberta e troca de etapa.
|
|
|
146
146
|
</Dialog>
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
O header compartilha a geometria de Page e Surface delimitada: padding de `1rem` e
|
|
150
|
+
linha mínima de `2rem`, nos dois layouts. Declare `size="sm"` nos botões textuais e
|
|
151
|
+
`size="icon-sm"` nos de ícone (`2rem`), incluindo o retorno. `CloseButton` já usa
|
|
152
|
+
`icon-sm` por padrão. Conteúdo maior pode expandir a linha; rodapés preservam sua escala.
|
|
152
153
|
|
|
153
154
|
O cabeçalho aceita `DialogDescription` como subheading opcional para orientar sobre a finalidade.
|
|
154
155
|
Omita quando repetir o título. Condições, regras de negócio e consequências necessárias à decisão
|
|
@@ -656,3 +657,14 @@ comparar as duas formas em cada `layout`, abra [Composições](/ui/compositions)
|
|
|
656
657
|
| `id` | `string` | | Identificador para associação acessível explícita. |
|
|
657
658
|
| `className` | `string` | | Ajusta a apresentação do parágrafo. |
|
|
658
659
|
| `…props` | `ComponentProps<"p">` | | Atributos nativos. |
|
|
660
|
+
|
|
661
|
+
## Espaçamento do corpo
|
|
662
|
+
|
|
663
|
+
O body separa os blocos diretos por `1rem`. Dialog e Drawer são superfícies delimitadas:
|
|
664
|
+
o conteúdo mantém `1rem` até a borda inferior, mesmo sem uma propriedade `framed`.
|
|
665
|
+
Em `sectioned`, o corpo tem `1rem` entre o conteúdo e cada divisória. Em `continuous`,
|
|
666
|
+
o cabeçalho fornece o intervalo superior; sem cabeçalho, o próprio corpo abre esse recuo.
|
|
667
|
+
Use `flush` quando o conteúdo controlar seus próprios recuos.
|
|
668
|
+
|
|
669
|
+
Na intro, `DialogTitle` usa a mesma escala de `PageTitle` (`1.875rem`). No header
|
|
670
|
+
compacto, o título conserva a escala de `1.125rem`. O nível semântico não altera essas medidas.
|
|
@@ -119,7 +119,7 @@ retorno de um fluxo encadeado, em que o painel permanece aberto e troca de etapa
|
|
|
119
119
|
<DrawerContent side="right">
|
|
120
120
|
<DrawerHeader>
|
|
121
121
|
<DrawerNavigation>
|
|
122
|
-
<Button variant="ghost" size="icon" aria-label="Voltar para a lista">
|
|
122
|
+
<Button variant="ghost" size="icon-sm" aria-label="Voltar para a lista">
|
|
123
123
|
<ArrowLeft />
|
|
124
124
|
</Button>
|
|
125
125
|
</DrawerNavigation>
|
|
@@ -130,7 +130,7 @@ retorno de um fluxo encadeado, em que o painel permanece aberto e troca de etapa
|
|
|
130
130
|
</DrawerMedia>
|
|
131
131
|
<DrawerTitle>ABC-1D23</DrawerTitle>
|
|
132
132
|
<DrawerActions>
|
|
133
|
-
<Button variant="ghost" size="icon" aria-label="Mais ações">
|
|
133
|
+
<Button variant="ghost" size="icon-sm" aria-label="Mais ações">
|
|
134
134
|
<Ellipsis />
|
|
135
135
|
</Button>
|
|
136
136
|
</DrawerActions>
|
|
@@ -144,9 +144,10 @@ retorno de um fluxo encadeado, em que o painel permanece aberto e troca de etapa
|
|
|
144
144
|
</Drawer>
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
O header compartilha a geometria de Page e Surface delimitada: padding de `1rem` e
|
|
148
|
+
linha mínima de `2rem`, nos dois layouts. Declare `size="sm"` nos botões textuais e
|
|
149
|
+
`size="icon-sm"` nos de ícone (`2rem`), incluindo o retorno. `CloseButton` já usa
|
|
150
|
+
`icon-sm` por padrão. Conteúdo maior pode expandir a linha; rodapés preservam sua escala.
|
|
150
151
|
|
|
151
152
|
O cabeçalho aceita `DrawerDescription` como subheading opcional para orientar sobre a finalidade.
|
|
152
153
|
Omita quando repetir o título. Condições, regras de negócio e consequências necessárias à decisão
|
|
@@ -323,3 +324,14 @@ comparar as duas formas em cada `layout`, abra [Composições](/ui/compositions)
|
|
|
323
324
|
| `id` | `string` | | Identificador para associação acessível explícita. |
|
|
324
325
|
| `className` | `string` | | Ajusta a apresentação do parágrafo. |
|
|
325
326
|
| `…props` | `ComponentProps<"p">` | | Atributos nativos. |
|
|
327
|
+
|
|
328
|
+
## Espaçamento do corpo
|
|
329
|
+
|
|
330
|
+
O body separa os blocos diretos por `1rem`. Dialog e Drawer são superfícies delimitadas:
|
|
331
|
+
o conteúdo mantém `1rem` até a borda inferior, mesmo sem uma propriedade `framed`.
|
|
332
|
+
Em `sectioned`, o corpo tem `1rem` entre o conteúdo e cada divisória. Em `continuous`,
|
|
333
|
+
o cabeçalho fornece o intervalo superior; sem cabeçalho, o próprio corpo abre esse recuo.
|
|
334
|
+
Use `flush` quando o conteúdo controlar seus próprios recuos.
|
|
335
|
+
|
|
336
|
+
Na intro, `DrawerTitle` usa a mesma escala de `PageTitle` (`1.875rem`). No header
|
|
337
|
+
compacto, o título conserva a escala de `1.125rem`. O nível semântico não altera essas medidas.
|
|
@@ -17,7 +17,7 @@ interface EventBusAdapter {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
interface DomainEvent<T = unknown> {
|
|
20
|
-
type: string // '
|
|
20
|
+
type: string // 'invoice.issued'
|
|
21
21
|
id: string
|
|
22
22
|
timestamp: string
|
|
23
23
|
actor: { id: string | null; type?: 'user' | 'system' | 'integration' }
|
|
@@ -39,7 +39,7 @@ const bus = mittEvents() // no mesmo processo do runtime
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
O `mitt` entrega pelo nome exato do evento ou por `*`, que recebe todos. Padrões parciais, como
|
|
42
|
-
`
|
|
42
|
+
`invoice.*`, não são reconhecidos: assine `*` e filtre no handler.
|
|
43
43
|
|
|
44
44
|
## Emitir de uma action
|
|
45
45
|
|
|
@@ -51,13 +51,13 @@ const runtime = createRuntime({
|
|
|
51
51
|
eventBus: bus,
|
|
52
52
|
})
|
|
53
53
|
|
|
54
|
-
export const
|
|
55
|
-
emits: ['
|
|
54
|
+
export const invoiceIssue = bindAction(invoiceIssueContract, {
|
|
55
|
+
emits: ['invoice.issued'],
|
|
56
56
|
handler: async (ctx, input) => {
|
|
57
|
-
const
|
|
57
|
+
const invoice = await repo.create(input)
|
|
58
58
|
// O runtime preenche id, timestamp, actor, tenant, source e trace.
|
|
59
|
-
await ctx.emit('
|
|
60
|
-
return
|
|
59
|
+
await ctx.emit('invoice.issued', { id: invoice.id, amount: invoice.amount })
|
|
60
|
+
return invoice
|
|
61
61
|
},
|
|
62
62
|
})
|
|
63
63
|
```
|
|
@@ -86,21 +86,21 @@ no código compartilhado e o efeito, no servidor:
|
|
|
86
86
|
import { bindReaction, defineReactionContract } from '@softize/opus/core'
|
|
87
87
|
|
|
88
88
|
// shared/ — qual evento e o que a reação significa.
|
|
89
|
-
export const
|
|
90
|
-
name: '
|
|
91
|
-
on: '
|
|
89
|
+
export const invoiceNotifyFinance = defineReactionContract<{ id: string; amount: number }>({
|
|
90
|
+
name: 'invoice.notify-finance',
|
|
91
|
+
on: 'invoice.issued',
|
|
92
92
|
description: 'Avisa o financeiro quando uma nota é emitida.',
|
|
93
93
|
})
|
|
94
94
|
|
|
95
95
|
// api/ — o efeito.
|
|
96
|
-
export const
|
|
96
|
+
export const invoiceNotifyFinanceImpl = bindReaction(invoiceNotifyFinance, {
|
|
97
97
|
handler: async (ctx, event) => {
|
|
98
|
-
ctx.log.info('nota emitida', { id: event.data.id,
|
|
98
|
+
ctx.log.info('nota emitida', { id: event.data.id, amount: event.data.amount })
|
|
99
99
|
},
|
|
100
100
|
})
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
Registre a reaction com as actions, em `defineDomain({ reactions: [
|
|
103
|
+
Registre a reaction com as actions, em `defineDomain({ reactions: [invoiceNotifyFinanceImpl] })`
|
|
104
104
|
ou em `runtime.register`. Com reactions registradas, o `start()` exige um `EventBusAdapter` e falha
|
|
105
105
|
com `runtime.eventbus_required` quando ele não existe. `defineReaction` declara tudo num objeto só,
|
|
106
106
|
quando não há separação entre cliente e servidor.
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
## Escolher na paleta padrão
|
|
2
2
|
|
|
3
3
|
Use `IconPicker` quando a pessoa puder escolher o ícone que identifica um registro, como um
|
|
4
|
-
relatório ou um painel. A
|
|
5
|
-
kebab-case, como `chart-line`. Selecionar novamente o ícone atual limpa o valor.
|
|
4
|
+
relatório ou um painel. A grade de ícones abre em um popover com busca em português, e o valor guardado é o nome do ícone em
|
|
5
|
+
kebab-case, como `chart-line`. Selecionar novamente o ícone atual limpa o valor. A grade exibe somente ícones; os nomes ficam disponíveis para leitores de tela e busca. A busca aceita o identificador técnico, o nome em português e sinônimos. `searchTerms` permite definir rótulos e termos de busca para uma paleta própria, sem alterar o valor salvo.
|
|
6
6
|
|
|
7
7
|
```tsx preview col md
|
|
8
|
-
const [icon, setIcon] = useState(
|
|
8
|
+
const [icon, setIcon] = useState("chart-line");
|
|
9
9
|
|
|
10
10
|
render(
|
|
11
11
|
<div className="grid gap-2">
|
|
12
12
|
<Label htmlFor="report-icon">Ícone do relatório</Label>
|
|
13
13
|
<IconPicker id="report-icon" value={icon} onChange={setIcon} />
|
|
14
14
|
</div>,
|
|
15
|
-
)
|
|
15
|
+
);
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Apresentar o ícone escolhido
|
|
@@ -22,8 +22,8 @@ padrão é exportada como `iconPickerIcons`; use-a em listas, cards e árvores,
|
|
|
22
22
|
para nomes que não existam mais, como em `iconPickerIcons[name] ?? FileText`.
|
|
23
23
|
|
|
24
24
|
```tsx preview col md
|
|
25
|
-
const [icon, setIcon] = useState(
|
|
26
|
-
const SelectedIcon = iconPickerIcons[icon] ?? FileText
|
|
25
|
+
const [icon, setIcon] = useState("store");
|
|
26
|
+
const SelectedIcon = iconPickerIcons[icon] ?? FileText;
|
|
27
27
|
|
|
28
28
|
render(
|
|
29
29
|
<div className="flex items-center gap-3">
|
|
@@ -37,7 +37,7 @@ render(
|
|
|
37
37
|
</ItemHeader>
|
|
38
38
|
</Item>
|
|
39
39
|
</div>,
|
|
40
|
-
)
|
|
40
|
+
);
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
## Paleta própria
|
|
@@ -46,27 +46,45 @@ render(
|
|
|
46
46
|
domínio, pois cada importação participa do bundle.
|
|
47
47
|
|
|
48
48
|
```tsx preview col md
|
|
49
|
-
const [icon, setIcon] = useState(
|
|
49
|
+
const [icon, setIcon] = useState("");
|
|
50
50
|
|
|
51
51
|
render(
|
|
52
52
|
<IconPicker
|
|
53
53
|
value={icon}
|
|
54
54
|
onChange={setIcon}
|
|
55
|
-
icons={{
|
|
55
|
+
icons={{ zap: Zap, star: Star, flag: Flag, heart: Heart }}
|
|
56
56
|
placeholder="Selecione um ícone…"
|
|
57
57
|
className="w-56"
|
|
58
58
|
/>,
|
|
59
|
-
)
|
|
59
|
+
);
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
## Propriedades de IconPicker
|
|
63
63
|
|
|
64
|
-
| Propriedade
|
|
65
|
-
|
|
66
|
-
| `value`
|
|
67
|
-
| `onChange`
|
|
68
|
-
| `icons`
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
64
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
65
|
+
| ---------------------------------------- | ------------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| `value` | `string` | | Nome do ícone selecionado (chave da paleta); `''` é nenhum. |
|
|
67
|
+
| `onChange` | `(name: string) => void` | | Recebe o nome escolhido. |
|
|
68
|
+
| `icons` | `Record<string, LucideIcon>` | `iconPickerIcons` | Paleta nome → componente. |
|
|
69
|
+
| `searchTerms` | `Record<string, { label: string; keywords: string[] }>` | Mapa interno em português | Rótulos acessíveis e sinônimos de busca. |
|
|
70
|
+
| `placeholder` | `string` | `'Selecione um ícone…'` | Texto do gatilho sem seleção. |
|
|
71
|
+
| `disabled` | `boolean` | `false` | Desabilita o gatilho. |
|
|
72
|
+
| `className` | `string` | | Classes do gatilho, em geral para ajustar a largura. Sem elas, o gatilho ocupa toda a largura disponível. |
|
|
73
|
+
| `id`, `aria-invalid`, `aria-describedby` | `string`, `boolean`, `string` | | Integração com `Field` e `ActionForm`: o gatilho recebe a identidade do campo, o estado inválido e a mensagem associada. Com `aria-invalid`, a moldura assume o contexto de erro, como no `Input`. |
|
|
74
|
+
|
|
75
|
+
## Catálogo sob demanda
|
|
76
|
+
|
|
77
|
+
`IconCatalogProvider` recebe um objeto `catalog` com `load` e `render`. Declare esse objeto
|
|
78
|
+
como constante de módulo ou com `useMemo`, preservando sua identidade entre renders. `load` retorna uma promessa com
|
|
79
|
+
`{ name, label, keywords }[]` e só é chamado ao abrir o picker. O consumidor pode reutilizar a
|
|
80
|
+
promessa para cache, mas deve descartá-la em caso de falha para permitir tentar novamente.
|
|
81
|
+
`render(name, className)` apresenta o ícone salvo sem precisar carregar o índice.
|
|
82
|
+
|
|
83
|
+
O picker começa com 64 resultados e carrega mais automaticamente ao se aproximar do fim do scroll.
|
|
84
|
+
A grade tem oito colunas; a busca sempre percorre o índice inteiro. Erros no carregamento oferecem nova tentativa.
|
|
85
|
+
O consumidor deve carregar imagens conforme entram na região visível e fornecer fallback.
|
|
86
|
+
Uma paleta explícita por `icons` prevalece sobre o provider, preservando seletores especializados.
|
|
87
|
+
Sem provider, o catálogo curado continua funcionando sem requisições externas.
|
|
88
|
+
|
|
89
|
+
Antes da primeira abertura, um ícone externo salvo é anunciado como “Ícone selecionado”;
|
|
90
|
+
após carregar o índice, o gatilho usa o rótulo do catálogo. O desenho não depende desse carregamento.
|
|
@@ -200,7 +200,7 @@ controle ativo, declare `data-disabled="true"` no `InputGroup`.
|
|
|
200
200
|
|
|
201
201
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
202
202
|
| ----------- | ------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
203
|
-
| `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | O subconjunto da escala única que cabe em um campo (1.5 · 2 · 1.5 ·
|
|
203
|
+
| `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | O subconjunto da escala única que cabe em um campo (1.5 · 2 · 1.5 · 2rem); os `icon-*` são quadrados. |
|
|
204
204
|
| `context` | `'neutral' \| 'primary' \| 'warning' \| 'danger'` | `'neutral'` | Contexto semântico herdado de Button. Com `variant` `solid` ou `subtle` sem contexto declarado, o padrão passa a `'primary'`. |
|
|
205
205
|
| `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'ghost'` | Tratamento visual; use `solid` quando a ação precisar de ênfase. |
|
|
206
206
|
|
|
@@ -25,6 +25,10 @@ pnpm test
|
|
|
25
25
|
O aplicativo inclui um domínio de tarefas, `opus.config.ts`, Vite, React, o tema do Opus e os
|
|
26
26
|
comandos de teste e verificação. O dev server respeita a variável `PORT`. `opus.json` registra a
|
|
27
27
|
versão do SDK adotada pelo app; `setup` materializa as instruções, skills e hooks gerenciados.
|
|
28
|
+
As instruções ficam no `AGENTS.md`, que qualquer agente lê; o `CLAUDE.md` contém só a linha
|
|
29
|
+
`@AGENTS.md`, que as importa para o Claude Code. Escreva as regras do seu projeto no `AGENTS.md`:
|
|
30
|
+
o `setup` recusa um `CLAUDE.md` com instruções que não estão no `AGENTS.md` e pede que elas sejam
|
|
31
|
+
movidas para lá.
|
|
28
32
|
|
|
29
33
|
Quando os testes passarem, continue em [Primeiros passos](/docs/getting-started).
|
|
30
34
|
|
|
@@ -44,7 +44,7 @@ const runtime = createRuntime({
|
|
|
44
44
|
|
|
45
45
|
// No handler: ctx.log já traz o request ID e a action, adicionados com child().
|
|
46
46
|
handler: async (ctx, input) => {
|
|
47
|
-
ctx.log.info('emitindo nota', {
|
|
47
|
+
ctx.log.info('emitindo nota', { amount: input.amount })
|
|
48
48
|
return repo.create(input)
|
|
49
49
|
}
|
|
50
50
|
```
|