@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.
- package/CHANGELOG.md +239 -0
- package/bin/cli.mjs +5 -0
- package/bin/lib/check.mjs +198 -10
- package/bin/lib/copy.mjs +29 -0
- package/bin/lib/mcp.mjs +1 -1
- package/docs/adr/0020-icon-tiles-are-explicitly-composed.md +8 -0
- package/docs/adr/0021-content-carries-structure-and-semantic-element.md +7 -0
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +19 -1
- package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
- package/docs/adr/0025-content-frame-treatment-does-not-change-semantics.md +134 -0
- package/docs/data-products.md +15 -9
- package/docs/protocol.md +3 -4
- package/docs/releasing.md +2 -2
- package/package.json +2 -2
- package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
- package/registry/templates/app/package.json +1 -1
- package/src/core/runtime.ts +96 -8
- package/src/core/types.ts +5 -2
- package/src/ui/components/patterns/action-form-card.tsx +11 -1
- package/src/ui/components/patterns/content-header.tsx +109 -18
- package/src/ui/components/patterns/fill-region.ts +150 -0
- package/src/ui/components/patterns/list.tsx +91 -92
- package/src/ui/components/patterns/page.tsx +20 -4
- package/src/ui/components/patterns/presentation.tsx +52 -37
- package/src/ui/components/patterns/split.tsx +44 -4
- package/src/ui/components/patterns/surface-anatomy.ts +35 -8
- package/src/ui/components/patterns/surface-header.tsx +58 -25
- package/src/ui/components/patterns/trigger.tsx +12 -2
- package/src/ui/components/primitives/alert.tsx +9 -1
- package/src/ui/components/primitives/avatar.tsx +7 -3
- package/src/ui/components/primitives/card.tsx +1 -1
- package/src/ui/components/primitives/carousel.tsx +34 -5
- package/src/ui/components/primitives/checkbox.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +2 -1
- package/src/ui/components/primitives/copyable.tsx +120 -10
- package/src/ui/components/primitives/dialog.tsx +42 -11
- package/src/ui/components/primitives/drawer.tsx +10 -2
- package/src/ui/components/primitives/empty.tsx +1 -1
- package/src/ui/components/primitives/icon-picker.tsx +6 -2
- package/src/ui/components/primitives/icon-tile.tsx +7 -0
- package/src/ui/components/primitives/input-group.tsx +9 -2
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/kbd.tsx +1 -1
- package/src/ui/components/primitives/label.tsx +3 -0
- package/src/ui/components/primitives/menu.tsx +5 -0
- package/src/ui/components/primitives/popover.tsx +1 -1
- package/src/ui/components/primitives/progress.tsx +9 -1
- package/src/ui/components/primitives/slider.tsx +62 -11
- package/src/ui/components/primitives/truncate.tsx +10 -1
- package/src/ui/docs/content/accordion.md +74 -27
- package/src/ui/docs/content/action-form-card.md +24 -15
- package/src/ui/docs/content/action-form-dialog.md +17 -7
- package/src/ui/docs/content/action-form.md +92 -77
- package/src/ui/docs/content/action-list-dialog.md +75 -38
- package/src/ui/docs/content/action-list.md +141 -58
- package/src/ui/docs/content/action-trigger.md +27 -10
- package/src/ui/docs/content/action-view.md +14 -14
- package/src/ui/docs/content/actions.md +161 -66
- package/src/ui/docs/content/ai.md +43 -44
- package/src/ui/docs/content/alert.md +83 -29
- package/src/ui/docs/content/ask.md +51 -26
- package/src/ui/docs/content/aspect-ratio.md +22 -24
- package/src/ui/docs/content/audit.md +23 -19
- package/src/ui/docs/content/auth.md +38 -19
- package/src/ui/docs/content/avatar.md +54 -5
- package/src/ui/docs/content/badge.md +29 -9
- package/src/ui/docs/content/breadcrumb.md +21 -8
- package/src/ui/docs/content/button.md +73 -43
- package/src/ui/docs/content/calendar.md +134 -26
- package/src/ui/docs/content/card.md +41 -15
- package/src/ui/docs/content/carousel.md +37 -6
- package/src/ui/docs/content/chat.md +118 -37
- package/src/ui/docs/content/checkbox.md +73 -7
- package/src/ui/docs/content/cli.md +94 -55
- package/src/ui/docs/content/collapsible.md +43 -19
- package/src/ui/docs/content/command.md +30 -8
- package/src/ui/docs/content/composer.md +23 -8
- package/src/ui/docs/content/content.md +119 -27
- package/src/ui/docs/content/copyable.md +39 -8
- package/src/ui/docs/content/customization.md +55 -46
- package/src/ui/docs/content/cycle.md +33 -18
- package/src/ui/docs/content/data-products.md +115 -0
- package/src/ui/docs/content/data-state.md +20 -6
- package/src/ui/docs/content/data.md +20 -19
- package/src/ui/docs/content/detail.md +12 -5
- package/src/ui/docs/content/dialog.md +107 -24
- package/src/ui/docs/content/dictionary-value.md +4 -4
- package/src/ui/docs/content/dock.md +54 -19
- package/src/ui/docs/content/dot.md +35 -5
- package/src/ui/docs/content/drawer.md +63 -19
- package/src/ui/docs/content/empty-value.md +20 -6
- package/src/ui/docs/content/empty.md +24 -3
- package/src/ui/docs/content/events.md +76 -16
- package/src/ui/docs/content/executions.md +9 -9
- package/src/ui/docs/content/field.md +21 -6
- package/src/ui/docs/content/getting-started.md +44 -34
- package/src/ui/docs/content/icon-picker.md +19 -13
- package/src/ui/docs/content/icon-tile.md +38 -15
- package/src/ui/docs/content/input-otp.md +58 -7
- package/src/ui/docs/content/input.md +22 -3
- package/src/ui/docs/content/item.md +53 -6
- package/src/ui/docs/content/kbd.md +5 -1
- package/src/ui/docs/content/label.md +13 -4
- package/src/ui/docs/content/log.md +5 -5
- package/src/ui/docs/content/markdown.md +5 -4
- package/src/ui/docs/content/mcp.md +49 -23
- package/src/ui/docs/content/menu.md +95 -10
- package/src/ui/docs/content/metric-card.md +12 -4
- package/src/ui/docs/content/observability.md +10 -9
- package/src/ui/docs/content/page.md +118 -12
- package/src/ui/docs/content/pagination.md +13 -5
- package/src/ui/docs/content/pane.md +55 -5
- package/src/ui/docs/content/popover.md +42 -5
- package/src/ui/docs/content/presentation.md +91 -47
- package/src/ui/docs/content/progress.md +27 -14
- package/src/ui/docs/content/queue.md +39 -21
- package/src/ui/docs/content/radio-group.md +26 -4
- package/src/ui/docs/content/router.md +29 -10
- package/src/ui/docs/content/runtime.md +90 -54
- package/src/ui/docs/content/scheduler.md +53 -21
- package/src/ui/docs/content/scroll-area.md +42 -35
- package/src/ui/docs/content/select.md +78 -33
- package/src/ui/docs/content/semantic-context.md +21 -2
- package/src/ui/docs/content/separator.md +15 -9
- package/src/ui/docs/content/sidebar.md +3 -2
- package/src/ui/docs/content/skeleton.md +6 -1
- package/src/ui/docs/content/slider.md +37 -9
- package/src/ui/docs/content/spinner.md +4 -3
- package/src/ui/docs/content/split.md +19 -0
- package/src/ui/docs/content/storage.md +8 -9
- package/src/ui/docs/content/switch.md +10 -7
- package/src/ui/docs/content/table.md +46 -8
- package/src/ui/docs/content/tabs.md +14 -4
- package/src/ui/docs/content/testing.md +76 -31
- package/src/ui/docs/content/textarea.md +5 -4
- package/src/ui/docs/content/toast.md +48 -7
- package/src/ui/docs/content/toggle.md +3 -3
- package/src/ui/docs/content/tokens.md +15 -26
- package/src/ui/docs/content/tooltip.md +18 -2
- package/src/ui/docs/content/truncate.md +36 -6
- package/src/ui/docs/content/ui.md +74 -22
- package/src/ui/docs/content/upgrading.md +45 -36
- package/src/ui/docs/plugin.ts +36 -2
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/drivers/react.tsx +16 -8
- package/src/ui/meta.ts +32 -32
- package/src/ui/react.tsx +1 -0
- package/src/vite/design.ts +16 -4
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
## Escolha booleana
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Checkbox` quando a pessoa precisar marcar uma opção que só vale ao salvar um formulário, como
|
|
4
|
+
aceitar uma condição ou incluir uma etapa. Para uma preferência que entra em vigor no clique, use
|
|
5
|
+
[`Switch`](/ui/switch).
|
|
6
|
+
|
|
7
|
+
Associe cada caixa a um [`Label`](/ui/label) pelo par `htmlFor` e `id`: clicar no texto também
|
|
8
|
+
alterna a caixa. `defaultChecked` define o estado inicial no modo não controlado.
|
|
4
9
|
|
|
5
10
|
```tsx preview
|
|
6
11
|
<div className="flex items-center gap-2">
|
|
@@ -11,7 +16,8 @@ Sempre em par com Label (htmlFor↔id) — clicar no texto alterna a caixa. defa
|
|
|
11
16
|
|
|
12
17
|
## Controlado
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
Use `checked` e `onCheckedChange` quando o estado pertencer ao consumidor. O callback recebe
|
|
20
|
+
`boolean` ou `'indeterminate'`; compare com `true` para guardar um booleano.
|
|
15
21
|
|
|
16
22
|
```tsx preview
|
|
17
23
|
const [autoReview, setAutoReview] = useState(true)
|
|
@@ -30,7 +36,8 @@ render(
|
|
|
30
36
|
|
|
31
37
|
## Lista de opções
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
Quando várias caixas alimentam um único valor, guarde a lista dos itens marcados. É o caso de
|
|
40
|
+
escolher as habilidades de um agente.
|
|
34
41
|
|
|
35
42
|
```tsx preview col-start
|
|
36
43
|
const [skills, setSkills] = useState(['clean-code', 'test'])
|
|
@@ -50,9 +57,53 @@ render(
|
|
|
50
57
|
)
|
|
51
58
|
```
|
|
52
59
|
|
|
60
|
+
## Seleção parcial
|
|
61
|
+
|
|
62
|
+
Quando uma caixa controla um grupo, como “Selecionar todas”, ela precisa mostrar também o caso em
|
|
63
|
+
que só parte do grupo está marcada. Passe `checked="indeterminate"` nesse caso: a caixa fica
|
|
64
|
+
preenchida e mostra um traço no lugar do visto, e leitores de tela a anunciam como parcialmente
|
|
65
|
+
marcada. Ao clicar em uma caixa indeterminada, `onCheckedChange` recebe `true`.
|
|
66
|
+
|
|
67
|
+
```tsx preview col-start
|
|
68
|
+
const all = ['clean-code', 'code-style', 'ddd', 'test']
|
|
69
|
+
const [skills, setSkills] = useState(['clean-code', 'test'])
|
|
70
|
+
const allState = skills.length === all.length ? true : skills.length > 0 ? 'indeterminate' : false
|
|
71
|
+
const toggleSkill = (skill: string): void => {
|
|
72
|
+
setSkills((prev) => (prev.includes(skill) ? prev.filter((s) => s !== skill) : [...prev, skill]))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
render(
|
|
76
|
+
<>
|
|
77
|
+
<div className="flex items-center gap-2">
|
|
78
|
+
<Checkbox
|
|
79
|
+
id="skills-all"
|
|
80
|
+
checked={allState}
|
|
81
|
+
onCheckedChange={(v) => setSkills(v === true ? all : [])}
|
|
82
|
+
/>
|
|
83
|
+
<Label htmlFor="skills-all">Selecionar todas</Label>
|
|
84
|
+
</div>
|
|
85
|
+
<div className="flex flex-col gap-2 pl-6">
|
|
86
|
+
{all.map((skill) => (
|
|
87
|
+
<div key={skill} className="flex items-center gap-2">
|
|
88
|
+
<Checkbox
|
|
89
|
+
id={`partial-${skill}`}
|
|
90
|
+
checked={skills.includes(skill)}
|
|
91
|
+
onCheckedChange={() => toggleSkill(skill)}
|
|
92
|
+
/>
|
|
93
|
+
<Label htmlFor={`partial-${skill}`}>{skill}</Label>
|
|
94
|
+
</div>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
</>,
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Com o grupo inteiro marcado, o clique em “Selecionar todas” recebe `false` e limpa a lista.
|
|
102
|
+
|
|
53
103
|
## Desabilitado
|
|
54
104
|
|
|
55
|
-
disabled esmaece a caixa
|
|
105
|
+
`disabled` esmaece e bloqueia a caixa, marcada ou não. O `Label` escrito logo depois da caixa, no
|
|
106
|
+
mesmo contêiner, esmaece junto.
|
|
56
107
|
|
|
57
108
|
```tsx preview col-start
|
|
58
109
|
<div className="flex items-center gap-2">
|
|
@@ -65,11 +116,26 @@ disabled esmaece a caixa e o rótulo em par (peer-disabled no Label) — marcado
|
|
|
65
116
|
</div>
|
|
66
117
|
```
|
|
67
118
|
|
|
119
|
+
## Estado inválido
|
|
120
|
+
|
|
121
|
+
Quando a marcação for obrigatória e estiver ausente, declare `aria-invalid` na caixa e explique o
|
|
122
|
+
que falta com `FieldError`. `data-invalid="true"` no [`Field`](/ui/field) leva o contexto de erro
|
|
123
|
+
também ao rótulo.
|
|
124
|
+
|
|
125
|
+
```tsx preview col-start
|
|
126
|
+
<Field orientation="horizontal" data-invalid="true">
|
|
127
|
+
<Checkbox id="accept-terms" aria-invalid aria-describedby="accept-terms-error" />
|
|
128
|
+
<FieldLabel htmlFor="accept-terms">Li e aceito os termos de uso</FieldLabel>
|
|
129
|
+
</Field>
|
|
130
|
+
<FieldError id="accept-terms-error">Aceite os termos para continuar.</FieldError>
|
|
131
|
+
```
|
|
132
|
+
|
|
68
133
|
## Propriedades de Checkbox
|
|
69
134
|
|
|
70
135
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
71
136
|
|---|---|---|---|
|
|
72
|
-
| `checked` | `boolean \| 'indeterminate'` | |
|
|
73
|
-
| `onCheckedChange` | `(checked: boolean \| 'indeterminate') => void` | | Chamado a cada alternância. Para guardar um
|
|
137
|
+
| `checked` | `boolean \| 'indeterminate'` | | Estado no modo controlado. Use com `onCheckedChange`. |
|
|
138
|
+
| `onCheckedChange` | `(checked: boolean \| 'indeterminate') => void` | | Chamado a cada alternância. Para guardar um booleano, compare com `true`. |
|
|
74
139
|
| `defaultChecked` | `boolean` | `false` | Estado inicial no modo não controlado. |
|
|
75
|
-
| `disabled` | `boolean` | `false` | Esmaece e bloqueia
|
|
140
|
+
| `disabled` | `boolean` | `false` | Esmaece e bloqueia a caixa; o `Label` irmão posterior esmaece junto. |
|
|
141
|
+
| `aria-invalid` | `boolean` | | Aplica a borda e o anel de erro. |
|
|
@@ -4,14 +4,14 @@ title: CLI opus
|
|
|
4
4
|
|
|
5
5
|
# CLI opus
|
|
6
6
|
|
|
7
|
-
O
|
|
8
|
-
valida as convenções e
|
|
9
|
-
comandos e `opus <comando> --help` detalha cada um.
|
|
7
|
+
O CLI `opus` acompanha o projeto do início à entrega: cria o app, gera artefatos a partir das
|
|
8
|
+
declarações, valida as convenções e oferece a estrutura do projeto a agentes por MCP. `opus --help`
|
|
9
|
+
lista os comandos e `opus <comando> --help` detalha cada um.
|
|
10
10
|
|
|
11
11
|
## Gates
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
Use os gates no CI e antes de entregar uma mudança. Cada um informa o que precisa de ajuste e
|
|
14
|
+
encerra com sucesso (exit 0) quando não encontra violação.
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
opus check src # convenções das actions e da UI (exit ≠ 0 se violar)
|
|
@@ -20,33 +20,37 @@ opus db check # drift entidade ↔ banco (read-only)
|
|
|
20
20
|
opus seed check # bindings, dependências, ciclos e scripts paralelos de seed
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
`opus check` lê o
|
|
24
|
-
(`action-name`, `kind`, `field-order`, `export`, `requires-sem-authorize`), UI
|
|
25
|
-
(`ui-structure`, `ui-semantic-api`, `removed-ui-token`, `unpaired-ui-surface`) e
|
|
26
|
-
(`data-product-export`, `-id`, `-duplicate`, `-version`,
|
|
27
|
-
e `-entity`, todas com o prefixo `data-product`). Um projeto
|
|
28
|
-
marcado com `opus.json` e ainda sem actions passa
|
|
29
|
-
falha, porque um gate
|
|
23
|
+
`opus check` lê o código-fonte sem executá-lo e aplica regras em três grupos: actions
|
|
24
|
+
(`action-name`, `kind`, `field-order`, `export`, `requires-sem-authorize`, `ignored-by-kind`), UI
|
|
25
|
+
(`ui-structure`, `ui-structure-split`, `ui-semantic-api`, `removed-ui-token`, `unpaired-ui-surface`) e
|
|
26
|
+
[Produtos de Dados](/docs/data-products) (`data-product-export`, `-id`, `-duplicate`, `-version`,
|
|
27
|
+
`-interfaces`, `-interface`, `-entities` e `-entity`, todas com o prefixo `data-product`). Um projeto
|
|
28
|
+
marcado com `opus.json` e ainda sem actions passa sem achados; sem o marcador, nenhuma action
|
|
29
|
+
encontrada é falha, porque um gate que não verificou nada não é aprovação. `opus check --help` descreve cada regra.
|
|
30
30
|
|
|
31
31
|
### Hook de pré-push
|
|
32
32
|
|
|
33
|
-
O hook Git que `opus setup` instala
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`opus
|
|
37
|
-
|
|
33
|
+
O hook Git que `opus setup` instala executa, antes de cada push:
|
|
34
|
+
|
|
35
|
+
1. `opus copy --check`;
|
|
36
|
+
2. `opus pre-push materialization` na raiz, que confirma se os artefatos Opus materializados
|
|
37
|
+
(skills, hooks e instruções) correspondem à versão adotada;
|
|
38
|
+
3. `opus check` em cada app com `opus.json`.
|
|
39
|
+
|
|
40
|
+
Qualquer comando com exit ≠ 0 bloqueia o push. Para antecipar o resultado, execute os mesmos
|
|
41
|
+
comandos manualmente:
|
|
38
42
|
|
|
39
43
|
```bash
|
|
40
44
|
opus pre-push # o mesmo opus check no diretório atual
|
|
41
45
|
opus pre-push apps/portal # o check de um app específico
|
|
42
|
-
opus pre-push materialization # só
|
|
46
|
+
opus pre-push materialization # só confere se os artefatos materializados estão atualizados
|
|
43
47
|
```
|
|
44
48
|
|
|
45
49
|
## Seeds de desenvolvimento e teste
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
Seeds registrados no `opus.config.ts` têm perfis, métricas e escopos explícitos. Listagem e gate
|
|
52
|
+
não abrem o banco; planejamento, aplicação e verificação só conectam com `NODE_ENV=development` ou
|
|
53
|
+
`NODE_ENV=test`.
|
|
50
54
|
|
|
51
55
|
```bash
|
|
52
56
|
opus seed list
|
|
@@ -58,7 +62,7 @@ NODE_ENV=development opus seed verify customers.scenarios --profile smoke --scop
|
|
|
58
62
|
Com qualquer outro valor de `NODE_ENV`, inclusive ausente, esses três comandos falham antes de
|
|
59
63
|
abrir a conexão. O escopo informado também precisa constar em `safety.scopes` do seed.
|
|
60
64
|
|
|
61
|
-
`--profile` escolhe o perfil (
|
|
65
|
+
`--profile` escolhe o perfil (padrão: o `defaultProfile` do seed) e `--scope` declara o escopo
|
|
62
66
|
dos dados (a variável `OPUS_SEED_SCOPE` é a alternativa). `--json` devolve o resultado
|
|
63
67
|
estruturado, inclusive em caso de erro. `apply` converge quando repetido; não há reset ou
|
|
64
68
|
truncate no contrato. A skill `$create-opus-seed` estrutura um novo dataset, e
|
|
@@ -66,33 +70,47 @@ truncate no contrato. A skill `$create-opus-seed` estrutura um novo dataset, e
|
|
|
66
70
|
|
|
67
71
|
## Geração e introspecção
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
As declarações, incluindo a `description` de entidades e actions, são a fonte; `opus gen` as
|
|
74
|
+
projeta. O manifest é versionado como um lockfile, e sua diferença mostra na revisão o que mudou
|
|
75
|
+
no contrato.
|
|
71
76
|
|
|
72
77
|
```bash
|
|
73
78
|
opus gen # manifest / openapi / docs / stubs a partir do opus.config.ts
|
|
74
79
|
opus gen --config ./apps/api/opus.config.ts # config fora do diretório atual
|
|
75
|
-
opus gen --output ./custom/gen # pasta de saída (
|
|
80
|
+
opus gen --output ./custom/gen # pasta de saída (padrão: a do config, ou ./.gen)
|
|
76
81
|
opus copy # inventário semântico de copy dos contratos
|
|
77
|
-
opus introspect # modelo da estrutura
|
|
82
|
+
opus introspect # modelo da estrutura: actions, reactions, schedules e ligações
|
|
78
83
|
opus introspect --json
|
|
79
84
|
```
|
|
80
85
|
|
|
81
86
|
`opus copy` projeta os textos de `defineAction`/`defineContract` no inventário que a política de
|
|
82
|
-
copy da `@softize/base` valida. O caminho vem de `base.json` (`copy.inventory`;
|
|
87
|
+
copy da `@softize/base` valida. O caminho vem de `base.json` (`copy.inventory`; padrão
|
|
83
88
|
`.base/copy-inventory.json` na raiz Git), a escrita é atômica e o arquivo gerado deve ser
|
|
84
89
|
versionado. `--check` compara sem escrever e falha quando o inventário está ausente,
|
|
85
90
|
desatualizado ou contém copy dinâmica não declarada.
|
|
86
91
|
|
|
92
|
+
`opus introspect` lê o código sem executá-lo e só reconhece `defineAction`, `defineReaction`,
|
|
93
|
+
`defineSchedule` e `defineDataProduct`. Actions do par `defineContract` e `bindAction`, reactions do
|
|
94
|
+
par `defineReactionContract` e `bindReaction` e schedules declarados sem `defineSchedule` ficam fora
|
|
95
|
+
do modelo. Para conferir as actions do split, use `opus check`.
|
|
96
|
+
|
|
87
97
|
`--config` vale para `gen`, `db` e `seed` e sempre aponta para um caminho dentro do projeto. O
|
|
88
|
-
`gen` aceita `--force` por compatibilidade, mas a flag não altera nada: a saída
|
|
98
|
+
`gen` aceita `--force` por compatibilidade, mas a flag não altera nada: a saída fica em um
|
|
89
99
|
diretório dedicado e é sempre sobrescrita.
|
|
90
100
|
|
|
101
|
+
### Variáveis de ambiente
|
|
102
|
+
|
|
103
|
+
`gen`, `db` e `seed` carregam o `.env` que está ao lado do `opus.config.ts` antes de importar o
|
|
104
|
+
config. Assim, a conexão aberta pelo config usa as mesmas variáveis do projeto. Uma variável já
|
|
105
|
+
definida no ambiente vence o arquivo: `DATABASE_URL=… opus db migrate` usa a URL informada no
|
|
106
|
+
comando. A ausência do `.env` não é erro, porque containers e CI costumam injetar as variáveis
|
|
107
|
+
diretamente.
|
|
108
|
+
|
|
91
109
|
## Banco
|
|
92
110
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
Os comandos `db` carregam o `opus.config.ts` e abrem conexão; por isso rodam num processo separado,
|
|
112
|
+
com `tsx`. O padrão é um schema idempotente: um único script SQL que pode ser executado de novo a
|
|
113
|
+
cada mudança, em vez de migrations numeradas.
|
|
96
114
|
|
|
97
115
|
```bash
|
|
98
116
|
opus db check # compara o schema do banco com as entidades (read-only)
|
|
@@ -100,36 +118,57 @@ opus db migrate # aplica o schema idempotente e roda o drift-check na sequên
|
|
|
100
118
|
opus db scaffold # rascunho kysely a partir do diff, como referência para escrever o SQL
|
|
101
119
|
```
|
|
102
120
|
|
|
103
|
-
`db scaffold` gera um rascunho para revisão
|
|
104
|
-
down`
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
`opus
|
|
121
|
+
`db scaffold` gera um rascunho para revisão manual; a fonte continua sendo o script. `db migrate
|
|
122
|
+
down` foi descontinuado e encerra com erro: sem histórico de migrations, não há o que reverter.
|
|
123
|
+
Para desfazer uma mudança, edite o schema e rode `opus db migrate` de novo.
|
|
124
|
+
|
|
125
|
+
O `opus.config.ts` precisa expor a factory `database`, chamada só quando um comando precisa do
|
|
126
|
+
banco, as entidades ou os domínios e, opcionalmente, `schema` e `migrations`; `opus db --help`
|
|
127
|
+
detalha cada campo.
|
|
108
128
|
|
|
109
129
|
## Bootstrap
|
|
110
130
|
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
`create` gera um app novo com os pré-requisitos configurados. `setup` roda em cada app, pode ser
|
|
132
|
+
repetido e não sobrescreve arquivos do projeto.
|
|
113
133
|
|
|
114
134
|
```bash
|
|
115
|
-
opus create meu-app # app
|
|
116
|
-
opus create meu-cliente --monorepo # a
|
|
135
|
+
opus create meu-app # app novo: protocolo, UI, preview e automação
|
|
136
|
+
opus create meu-cliente --monorepo # a raiz de um workspace (apps/* + packages/*)
|
|
117
137
|
opus create apps/portal # dentro de um workspace: só o app (modo detectado)
|
|
118
138
|
opus setup # grava opus.json e materializa a camada específica do SDK
|
|
119
139
|
```
|
|
120
140
|
|
|
121
|
-
O esqueleto do `create`
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
depois de `pnpm run setup`.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
O esqueleto do `create` vem do mesmo pacote que o SDK e acompanha sua versão. O app nasce com os
|
|
142
|
+
gates aprovados: domínio de exemplo, teste, manifest e dev server que respeita a variável `PORT`,
|
|
143
|
+
para que um orquestrador de preview escolha a porta. O método geral, a memória e a revisão vêm da
|
|
144
|
+
Base depois de `pnpm run setup`.
|
|
145
|
+
|
|
146
|
+
O `create` detecta o modo. Fora de um workspace pnpm, gera o repositório inteiro. Dentro de um
|
|
147
|
+
workspace, gera só os arquivos do app e lista como aviso o que a raiz precisa ter, sem alterar os
|
|
148
|
+
arquivos dela.
|
|
149
|
+
|
|
150
|
+
## MCP para agentes de desenvolvimento
|
|
151
|
+
|
|
152
|
+
`opus mcp` inicia, por stdio, um servidor MCP para o agente que trabalha no código do projeto. Com
|
|
153
|
+
ele, o agente consulta a estrutura, valida convenções, gera actions no formato esperado e
|
|
154
|
+
consulta o catálogo de componentes. Esse servidor não executa actions do app; para expor actions a
|
|
155
|
+
agentes em produção, use o [servidor MCP do runtime](/docs/mcp).
|
|
156
|
+
|
|
157
|
+
| Tool | O que faz |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `opus_introspect` | Devolve o modelo de `opus introspect`, com os mesmos limites descritos acima. |
|
|
160
|
+
| `opus_check` | Executa as regras de `opus check` e devolve os achados. |
|
|
161
|
+
| `opus_create_action` | Devolve, sem gravar, o contrato e o binding iniciais de uma action a partir de recurso, verbo e kind. A skill `$create-opus-action` usa o mesmo gerador. |
|
|
162
|
+
| `opus_list_components` | Lista os componentes da UI, quando usar cada um e o que importar. |
|
|
163
|
+
| `opus_get_component` | Detalha um componente pelo nome. |
|
|
164
|
+
|
|
165
|
+
O setup não cria a configuração do cliente MCP. Em clientes que leem `.mcp.json` na raiz do
|
|
166
|
+
projeto, registre o servidor assim:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"mcpServers": {
|
|
171
|
+
"opus": { "command": "pnpm", "args": ["exec", "opus", "mcp"] }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
@@ -1,38 +1,44 @@
|
|
|
1
1
|
## Seção recolhível
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Collapsible` para esconder um bloco secundário atrás de um gatilho, mantendo o restante da
|
|
4
|
+
tela visível. `CollapsibleTrigger` já renderiza um `<button>` e alterna o `CollapsibleContent`.
|
|
5
|
+
Sem `open`, o componente guarda o próprio estado; `defaultOpen` apenas define se ele começa aberto.
|
|
4
6
|
|
|
5
7
|
```tsx preview col
|
|
6
8
|
<Collapsible defaultOpen className="w-full max-w-sm space-y-2">
|
|
7
|
-
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted">
|
|
8
|
-
|
|
9
|
-
<
|
|
9
|
+
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted [&[data-state=open]>svg]:rotate-180">
|
|
10
|
+
Serviços incluídos na revisão
|
|
11
|
+
<ChevronDown className="size-4 text-muted-foreground transition-transform" />
|
|
10
12
|
</CollapsibleTrigger>
|
|
11
13
|
<CollapsibleContent className="space-y-1 px-3 text-sm text-muted-foreground">
|
|
12
|
-
<p>
|
|
13
|
-
<p>
|
|
14
|
-
<p>
|
|
14
|
+
<p>Troca de óleo</p>
|
|
15
|
+
<p>Filtro de ar</p>
|
|
16
|
+
<p>Alinhamento</p>
|
|
15
17
|
</CollapsibleContent>
|
|
16
18
|
</Collapsible>
|
|
17
19
|
```
|
|
18
20
|
|
|
21
|
+
O gatilho recebe `data-state="open"` ou `data-state="closed"`. O seletor
|
|
22
|
+
`[&[data-state=open]>svg]:rotate-180` usa esse atributo para girar o ícone sem estado próprio.
|
|
23
|
+
|
|
19
24
|
## Controlado
|
|
20
25
|
|
|
21
|
-
open
|
|
26
|
+
Use `open` e `onOpenChange` quando outra parte da tela precisar conhecer o estado, por exemplo para
|
|
27
|
+
trocar o texto do gatilho ou guardar a preferência da pessoa.
|
|
22
28
|
|
|
23
29
|
```tsx preview col
|
|
24
30
|
const [open, setOpen] = useState(false)
|
|
25
31
|
|
|
26
32
|
render(
|
|
27
33
|
<Collapsible open={open} onOpenChange={setOpen} className="w-full max-w-sm space-y-2">
|
|
28
|
-
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted">
|
|
29
|
-
|
|
30
|
-
<ChevronDown className=
|
|
34
|
+
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted [&[data-state=open]>svg]:rotate-180">
|
|
35
|
+
{open ? 'Ocultar peças utilizadas' : 'Mostrar peças utilizadas'}
|
|
36
|
+
<ChevronDown className="size-4 text-muted-foreground transition-transform" />
|
|
31
37
|
</CollapsibleTrigger>
|
|
32
38
|
<CollapsibleContent className="space-y-1 px-3 text-sm text-muted-foreground">
|
|
33
|
-
<p>
|
|
34
|
-
<p>
|
|
35
|
-
<p>
|
|
39
|
+
<p>Filtro de óleo</p>
|
|
40
|
+
<p>Pastilhas de freio</p>
|
|
41
|
+
<p>Velas de ignição</p>
|
|
36
42
|
</CollapsibleContent>
|
|
37
43
|
</Collapsible>,
|
|
38
44
|
)
|
|
@@ -40,16 +46,17 @@ render(
|
|
|
40
46
|
|
|
41
47
|
## Desabilitado
|
|
42
48
|
|
|
43
|
-
disabled no Collapsible
|
|
49
|
+
Use `disabled` no `Collapsible` quando a seção não puder mudar de estado por enquanto. O gatilho
|
|
50
|
+
fica desabilitado e a seção permanece como está; no exemplo, fechada.
|
|
44
51
|
|
|
45
52
|
```tsx preview col
|
|
46
53
|
<Collapsible disabled className="w-full max-w-sm space-y-2">
|
|
47
54
|
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium disabled:cursor-not-allowed disabled:opacity-50">
|
|
48
|
-
|
|
49
|
-
<
|
|
55
|
+
Revisões anteriores
|
|
56
|
+
<ChevronDown className="size-4 text-muted-foreground" />
|
|
50
57
|
</CollapsibleTrigger>
|
|
51
58
|
<CollapsibleContent className="px-3 text-sm text-muted-foreground">
|
|
52
|
-
<p>
|
|
59
|
+
<p>O histórico fica disponível depois da sincronização com a concessionária.</p>
|
|
53
60
|
</CollapsibleContent>
|
|
54
61
|
</Collapsible>
|
|
55
62
|
```
|
|
@@ -61,4 +68,21 @@ disabled no Collapsible trava o gatilho — a seção fica fixa no estado atual
|
|
|
61
68
|
| `defaultOpen` | `boolean` | `false` | Estado inicial no modo não controlado. |
|
|
62
69
|
| `open` | `boolean` | | Estado no modo controlado. Use com `onOpenChange`. |
|
|
63
70
|
| `onOpenChange` | `(open: boolean) => void` | | Chamado a cada abertura ou fechamento. |
|
|
64
|
-
| `disabled` | `boolean` | `false` |
|
|
71
|
+
| `disabled` | `boolean` | `false` | Desabilita o gatilho e mantém a seção no estado atual. |
|
|
72
|
+
| `className` | `string` | | Ajusta o contêiner. |
|
|
73
|
+
|
|
74
|
+
## Propriedades de CollapsibleTrigger
|
|
75
|
+
|
|
76
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `asChild` | `boolean` | `false` | Usa o filho como gatilho, como um `Button`, sem criar outro `<button>`. |
|
|
79
|
+
| `className` | `string` | | Ajusta o gatilho. O estado chega em `data-state`. |
|
|
80
|
+
| `children` | `ReactNode` | | Rótulo do gatilho e, quando houver, o ícone de estado. |
|
|
81
|
+
|
|
82
|
+
## Propriedades de CollapsibleContent
|
|
83
|
+
|
|
84
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `forceMount` | `boolean` | `false` | Mantém o conteúdo montado mesmo fechado, por exemplo para animá-lo por CSS. |
|
|
87
|
+
| `className` | `string` | | Ajusta a região recolhível. |
|
|
88
|
+
| `children` | `ReactNode` | | Conteúdo exibido quando a seção está aberta. |
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
## Lista filtrável inline
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Use `Command` quando a pessoa precisar encontrar um item ou uma ação digitando parte do nome e
|
|
4
|
+
escolher pelo teclado. A lista filtra enquanto a busca muda, organiza os itens em grupos e mostra
|
|
5
|
+
um estado vazio quando nada corresponde. `Command` também é a base do `Select` pesquisável; para
|
|
6
|
+
escolher um valor em um formulário, use [`Select`](/ui/select).
|
|
5
7
|
|
|
6
8
|
```tsx preview
|
|
7
9
|
<Command className="max-w-sm rounded-lg border border-border">
|
|
@@ -14,26 +16,38 @@ do Select pesquisável; para uma escolha em formulário, use Select.
|
|
|
14
16
|
</CommandGroup>
|
|
15
17
|
<CommandSeparator />
|
|
16
18
|
<CommandGroup heading="Ações">
|
|
17
|
-
<CommandItem><Plus />
|
|
19
|
+
<CommandItem><Plus /> Criar sessão <CommandShortcut>⌘N</CommandShortcut></CommandItem>
|
|
18
20
|
<CommandItem><Play /> Iniciar ambiente</CommandItem>
|
|
19
21
|
</CommandGroup>
|
|
20
22
|
</CommandList>
|
|
21
23
|
</Command>
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
##
|
|
26
|
+
## Paleta de comandos em modal
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
`CommandDialog` apresenta a mesma lista dentro de um [`Dialog`](/ui/dialog), com um cabeçalho
|
|
29
|
+
compacto que mostra o título e o botão de fechar. A descrição fica disponível somente para
|
|
30
|
+
tecnologias assistivas. O componente não registra atalho de teclado: para abrir a paleta com
|
|
31
|
+
⌘K, o aplicativo escuta o evento e altera `open`, como no exemplo.
|
|
29
32
|
|
|
30
33
|
```tsx preview
|
|
31
34
|
const [open, setOpen] = useState(false)
|
|
32
35
|
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const onKeyDown = (event: KeyboardEvent): void => {
|
|
38
|
+
if (event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
|
|
39
|
+
event.preventDefault()
|
|
40
|
+
setOpen((current) => !current)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
document.addEventListener('keydown', onKeyDown)
|
|
44
|
+
return () => document.removeEventListener('keydown', onKeyDown)
|
|
45
|
+
}, [])
|
|
46
|
+
|
|
33
47
|
render(
|
|
34
48
|
<>
|
|
35
49
|
<Button variant="outline" onClick={() => setOpen(true)}>
|
|
36
|
-
Abrir
|
|
50
|
+
Abrir paleta <CommandShortcut>⌘K</CommandShortcut>
|
|
37
51
|
</Button>
|
|
38
52
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
|
39
53
|
<CommandInput placeholder="Buscar comando…" />
|
|
@@ -80,6 +94,7 @@ render(
|
|
|
80
94
|
| `title` | `string` | `'Paleta de comandos'` | Título exibido no header compacto; também nomeia o diálogo para tecnologias assistivas. |
|
|
81
95
|
| `description` | `string` | `'Busque um comando para executar…'` | Descrição disponível somente para tecnologias assistivas. |
|
|
82
96
|
| `showCloseButton` | `boolean` | `true` | Exibe o botão de fechamento. |
|
|
97
|
+
| `className` | `string` | | Classes do conteúdo do diálogo, como a largura. |
|
|
83
98
|
|
|
84
99
|
## Propriedades de CommandItem
|
|
85
100
|
|
|
@@ -96,3 +111,10 @@ render(
|
|
|
96
111
|
|---|---|---|---|
|
|
97
112
|
| `heading` | `React.ReactNode` | | Título exibido acima dos itens do grupo. |
|
|
98
113
|
| `value` | `string` | | Identificador do grupo; obrigatório e único quando não há `heading`. |
|
|
114
|
+
|
|
115
|
+
`CommandList` limita a altura a `18.75rem` e rola o excedente; `CommandEmpty` aparece somente quando
|
|
116
|
+
nenhum item corresponde à busca; `CommandSeparator` divide grupos; e `CommandShortcut` mostra, à
|
|
117
|
+
direita do item, a combinação de teclas que o aplicativo associou à ação. Esses componentes não têm
|
|
118
|
+
propriedades próprias além das props nativas do elemento que renderizam; `className` soma classes
|
|
119
|
+
ao estilo padrão de cada um. O atalho exibido por `CommandShortcut` é apenas informativo:
|
|
120
|
+
registrar o evento continua com o aplicativo.
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
## Envio de texto
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Composer` quando a tela precisar receber um texto para envio sem apresentar uma conversa, como
|
|
4
|
+
a descrição que abre uma nova sessão de trabalho. É a mesma caixa de escrita do [Chat](/ui/chat):
|
|
5
|
+
um campo que cresce com o texto, com o botão de enviar dentro da moldura. **Enter** envia e
|
|
6
|
+
**Shift+Enter** quebra a linha.
|
|
7
|
+
|
|
8
|
+
O componente é controlado: `value` e `onChange` pertencem a quem o compõe, que também limpa o texto
|
|
9
|
+
depois do envio. Os callbacks opcionais `onHistoryPrevious` e `onHistoryNext` permitem que esse
|
|
10
|
+
dono use **↑** e **↓** para navegar por um histórico próprio; sem eles, as setas mantêm o
|
|
11
|
+
comportamento nativo do campo.
|
|
4
12
|
|
|
5
13
|
```tsx preview col
|
|
6
14
|
const [text, setText] = React.useState('')
|
|
@@ -15,9 +23,15 @@ render(
|
|
|
15
23
|
)
|
|
16
24
|
```
|
|
17
25
|
|
|
18
|
-
## Com seletores
|
|
26
|
+
## Com seletores na barra de ações
|
|
27
|
+
|
|
28
|
+
Com `actions`, o composer passa a ter duas linhas: o campo em cima e, embaixo, uma barra com os
|
|
29
|
+
seletores à esquerda e o botão de enviar à direita. Use ali escolhas que definem o destino do texto,
|
|
30
|
+
como o aplicativo, a tarefa ou o agente. Mantenha os controles discretos, como um `Button` `ghost` ou
|
|
31
|
+
um [`Select`](/ui/select) `variant="ghost"`: a barra acompanha o texto e não é um formulário.
|
|
19
32
|
|
|
20
|
-
|
|
33
|
+
`submitDisabled` impede o envio enquanto falta uma condição além do texto, como escolher o
|
|
34
|
+
aplicativo, sem esconder a barra.
|
|
21
35
|
|
|
22
36
|
```tsx preview col
|
|
23
37
|
const [text, setText] = React.useState('')
|
|
@@ -55,11 +69,12 @@ render(
|
|
|
55
69
|
|---|---|---|---|
|
|
56
70
|
| `value` | `string` | | Texto controlado; o dono do estado é quem compõe. |
|
|
57
71
|
| `onChange` | `(value: string) => void` | | Recebe cada alteração do texto. |
|
|
58
|
-
| `onSubmit` | `() => void` | | Enter (sem Shift) ou
|
|
72
|
+
| `onSubmit` | `() => void` | | Chamado por Enter (sem Shift) ou pelo botão de enviar, somente quando há texto e nem `busy` nem `submitDisabled` estão ativos. |
|
|
59
73
|
| `onHistoryPrevious` / `onHistoryNext` | `() => boolean` | | Navegação por ↑/↓ no histórico do dono; retorne `true` quando a tecla foi consumida. |
|
|
60
|
-
| `busy` | `boolean` | `false` |
|
|
61
|
-
| `submitDisabled` | `boolean` | `false` |
|
|
74
|
+
| `busy` | `boolean` | `false` | Bloqueia o envio enquanto a operação anterior corre e troca o ícone de enviar por um spinner. O campo continua editável. |
|
|
75
|
+
| `submitDisabled` | `boolean` | `false` | Bloqueia o envio por uma condição do consumidor, como um seletor ainda sem escolha. |
|
|
62
76
|
| `placeholder` | `string` | `'Escreva uma mensagem…'` | Texto de orientação do campo. |
|
|
63
|
-
| `rows` | `number` | `1` | Linhas iniciais do
|
|
77
|
+
| `rows` | `number` | `1` | Linhas iniciais do campo, que cresce com o conteúdo até `10rem`. |
|
|
64
78
|
| `autoFocus` | `boolean` | | Foca o campo ao montar. |
|
|
65
|
-
| `actions` | `ReactNode` | | Controles discretos à esquerda do enviar;
|
|
79
|
+
| `actions` | `ReactNode` | | Controles discretos à esquerda do botão de enviar; com eles, o composer passa a ter duas linhas. |
|
|
80
|
+
| `className` | `string` | | Classes aplicadas à moldura do composer. |
|