@softize/opus 19.0.0 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +5 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +15 -3
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/list.tsx +89 -78
  20. package/src/ui/components/patterns/page.tsx +20 -4
  21. package/src/ui/components/patterns/presentation.tsx +52 -37
  22. package/src/ui/components/patterns/split.tsx +44 -4
  23. package/src/ui/components/patterns/surface-anatomy.ts +35 -8
  24. package/src/ui/components/patterns/trigger.tsx +12 -2
  25. package/src/ui/components/primitives/alert.tsx +9 -1
  26. package/src/ui/components/primitives/avatar.tsx +7 -3
  27. package/src/ui/components/primitives/carousel.tsx +34 -5
  28. package/src/ui/components/primitives/checkbox.tsx +11 -3
  29. package/src/ui/components/primitives/command.tsx +2 -1
  30. package/src/ui/components/primitives/copyable.tsx +120 -10
  31. package/src/ui/components/primitives/dialog.tsx +41 -10
  32. package/src/ui/components/primitives/drawer.tsx +9 -1
  33. package/src/ui/components/primitives/empty.tsx +1 -1
  34. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  35. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  36. package/src/ui/components/primitives/input-group.tsx +9 -2
  37. package/src/ui/components/primitives/input-otp.tsx +1 -1
  38. package/src/ui/components/primitives/kbd.tsx +1 -1
  39. package/src/ui/components/primitives/label.tsx +3 -0
  40. package/src/ui/components/primitives/menu.tsx +5 -0
  41. package/src/ui/components/primitives/popover.tsx +1 -1
  42. package/src/ui/components/primitives/progress.tsx +9 -1
  43. package/src/ui/components/primitives/slider.tsx +62 -11
  44. package/src/ui/components/primitives/truncate.tsx +10 -1
  45. package/src/ui/docs/content/accordion.md +74 -27
  46. package/src/ui/docs/content/action-form-card.md +24 -15
  47. package/src/ui/docs/content/action-form-dialog.md +17 -7
  48. package/src/ui/docs/content/action-form.md +92 -77
  49. package/src/ui/docs/content/action-list-dialog.md +75 -38
  50. package/src/ui/docs/content/action-list.md +134 -52
  51. package/src/ui/docs/content/action-trigger.md +27 -10
  52. package/src/ui/docs/content/action-view.md +14 -14
  53. package/src/ui/docs/content/actions.md +161 -66
  54. package/src/ui/docs/content/ai.md +43 -44
  55. package/src/ui/docs/content/alert.md +83 -29
  56. package/src/ui/docs/content/ask.md +51 -26
  57. package/src/ui/docs/content/aspect-ratio.md +22 -24
  58. package/src/ui/docs/content/audit.md +23 -19
  59. package/src/ui/docs/content/auth.md +38 -19
  60. package/src/ui/docs/content/avatar.md +54 -5
  61. package/src/ui/docs/content/badge.md +29 -9
  62. package/src/ui/docs/content/breadcrumb.md +21 -8
  63. package/src/ui/docs/content/button.md +73 -43
  64. package/src/ui/docs/content/calendar.md +134 -26
  65. package/src/ui/docs/content/card.md +41 -15
  66. package/src/ui/docs/content/carousel.md +37 -6
  67. package/src/ui/docs/content/chat.md +118 -37
  68. package/src/ui/docs/content/checkbox.md +73 -7
  69. package/src/ui/docs/content/cli.md +94 -55
  70. package/src/ui/docs/content/collapsible.md +43 -19
  71. package/src/ui/docs/content/command.md +30 -8
  72. package/src/ui/docs/content/composer.md +23 -8
  73. package/src/ui/docs/content/content.md +31 -7
  74. package/src/ui/docs/content/copyable.md +39 -8
  75. package/src/ui/docs/content/customization.md +55 -46
  76. package/src/ui/docs/content/cycle.md +33 -18
  77. package/src/ui/docs/content/data-products.md +115 -0
  78. package/src/ui/docs/content/data-state.md +20 -6
  79. package/src/ui/docs/content/data.md +20 -19
  80. package/src/ui/docs/content/detail.md +12 -5
  81. package/src/ui/docs/content/dialog.md +107 -24
  82. package/src/ui/docs/content/dictionary-value.md +4 -4
  83. package/src/ui/docs/content/dock.md +54 -19
  84. package/src/ui/docs/content/dot.md +35 -5
  85. package/src/ui/docs/content/drawer.md +63 -19
  86. package/src/ui/docs/content/empty-value.md +20 -6
  87. package/src/ui/docs/content/empty.md +24 -3
  88. package/src/ui/docs/content/events.md +76 -16
  89. package/src/ui/docs/content/executions.md +9 -9
  90. package/src/ui/docs/content/field.md +21 -6
  91. package/src/ui/docs/content/getting-started.md +44 -34
  92. package/src/ui/docs/content/icon-picker.md +19 -13
  93. package/src/ui/docs/content/icon-tile.md +28 -5
  94. package/src/ui/docs/content/input-otp.md +58 -7
  95. package/src/ui/docs/content/input.md +22 -3
  96. package/src/ui/docs/content/item.md +53 -6
  97. package/src/ui/docs/content/kbd.md +5 -1
  98. package/src/ui/docs/content/label.md +13 -4
  99. package/src/ui/docs/content/log.md +5 -5
  100. package/src/ui/docs/content/markdown.md +5 -4
  101. package/src/ui/docs/content/mcp.md +49 -23
  102. package/src/ui/docs/content/menu.md +95 -10
  103. package/src/ui/docs/content/metric-card.md +12 -4
  104. package/src/ui/docs/content/observability.md +10 -9
  105. package/src/ui/docs/content/page.md +118 -12
  106. package/src/ui/docs/content/pagination.md +13 -5
  107. package/src/ui/docs/content/pane.md +55 -5
  108. package/src/ui/docs/content/popover.md +42 -5
  109. package/src/ui/docs/content/presentation.md +91 -47
  110. package/src/ui/docs/content/progress.md +27 -14
  111. package/src/ui/docs/content/queue.md +39 -21
  112. package/src/ui/docs/content/radio-group.md +26 -4
  113. package/src/ui/docs/content/router.md +29 -10
  114. package/src/ui/docs/content/runtime.md +90 -54
  115. package/src/ui/docs/content/scheduler.md +53 -21
  116. package/src/ui/docs/content/scroll-area.md +42 -35
  117. package/src/ui/docs/content/select.md +78 -33
  118. package/src/ui/docs/content/semantic-context.md +21 -2
  119. package/src/ui/docs/content/separator.md +15 -9
  120. package/src/ui/docs/content/sidebar.md +3 -2
  121. package/src/ui/docs/content/skeleton.md +6 -1
  122. package/src/ui/docs/content/slider.md +37 -9
  123. package/src/ui/docs/content/spinner.md +4 -3
  124. package/src/ui/docs/content/split.md +19 -0
  125. package/src/ui/docs/content/storage.md +8 -9
  126. package/src/ui/docs/content/switch.md +10 -7
  127. package/src/ui/docs/content/table.md +46 -8
  128. package/src/ui/docs/content/tabs.md +14 -4
  129. package/src/ui/docs/content/testing.md +76 -31
  130. package/src/ui/docs/content/textarea.md +5 -4
  131. package/src/ui/docs/content/toast.md +48 -7
  132. package/src/ui/docs/content/toggle.md +3 -3
  133. package/src/ui/docs/content/tokens.md +15 -26
  134. package/src/ui/docs/content/tooltip.md +18 -2
  135. package/src/ui/docs/content/truncate.md +36 -6
  136. package/src/ui/docs/content/ui.md +74 -22
  137. package/src/ui/docs/content/upgrading.md +45 -36
  138. package/src/ui/docs/plugin.ts +36 -2
  139. package/src/ui/docs/registry.tsx +6 -0
  140. package/src/ui/drivers/react.tsx +16 -8
  141. package/src/ui/meta.ts +31 -31
  142. package/src/vite/design.ts +16 -4
@@ -1,6 +1,11 @@
1
1
  ## Escolha booleana
2
2
 
3
- Sempre em par com Label (htmlFor↔id) — clicar no texto alterna a caixa. defaultChecked para o modo não controlado.
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
- onCheckedChange recebe boolean | 'indeterminate' — compare com true para guardar um boolean.
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
- Várias caixas, um estado: o conjunto marcado é a lista de valores — o padrão para anexar skills a um agente.
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 e o rótulo em par (peer-disabled no Label) — marcado ou não.
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'` | | O estado, no modo controlado — parear com onCheckedChange. |
73
- | `onCheckedChange` | `(checked: boolean \| 'indeterminate') => void` | | Chamado a cada alternância. Para guardar um boolean, compare com true. |
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 — o Label em par esmaece junto (peer-disabled). |
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 Opus traz um CLI que cobre o ciclo: faz o bootstrap, gera artefatos a partir das declarações,
8
- valida as convenções e expõe o estado vivo para os agentes via MCP. `opus --help` lista os
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
- > 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.
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 source sem executar nada e aplica regras em três grupos: actions
24
- (`action-name`, `kind`, `field-order`, `export`, `requires-sem-authorize`), UI
25
- (`ui-structure`, `ui-semantic-api`, `removed-ui-token`, `unpaired-ui-surface`) e Produtos de Dados
26
- (`data-product-export`, `-id`, `-duplicate`, `-version`, `-interfaces`, `-interface`, `-entities`
27
- e `-entity`, todas com o prefixo `data-product`). Um projeto
28
- marcado com `opus.json` e ainda sem actions passa vacuamente; sem o marcador, zero actions
29
- falha, porque um gate vazio não é aprovação. `opus check --help` descreve cada regra.
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 roda, antes de cada push: `opus copy --check`; depois
34
- `opus pre-push materialization` na raiz, para confirmar que os artefatos Opus materializados
35
- (skills, hooks, instruções) correspondem à versão adotada; e `opus check` em cada app com
36
- `opus.json`. Qualquer um com exit ≠ 0 bloqueia o push. Os mesmos comandos podem ser executados à
37
- mão para antecipar o resultado:
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ó a freshness dos artefatos materializados
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
- > Seeds registrados no `opus.config.ts` têm perfis, métricas e escopos explícitos. Listagem e gate
48
- > não abrem o banco; planejamento, aplicação e verificação só conectam com
49
- > `NODE_ENV=development` ou `NODE_ENV=test`.
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 (default: o `defaultProfile` do seed) e `--scope` declara o escopo
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
- > As declarações (`description` de entidades/actions) são a fonte; o `gen` as projeta. O manifest
70
- > é um lockfile versionado. A diferença do manifest torna a revisão objetiva.
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 (default: a do config, ou ./.gen)
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 (actions/reactions/schedules + wiring)
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`; default
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 mora em um
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
- > Os comandos `db` carregam o `opus.config.ts` e abrem conexão; por isso rodam num runner isolado
94
- > via `tsx`. O padrão é um schema idempotente: um script SQL evolutivo re-rodável, não migrations
95
- > versionadas.
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 à mão; a verdade continua sendo o script. `db migrate
104
- down` está aposentado e encerra com erro: sem histórico de migrations não há o que reverter —
105
- rollback é editar o schema e rodar `opus db migrate` de novo. O `opus.config.ts` precisa expor a
106
- factory lazy `database`, as entidades (ou domínios) e, opcionalmente, `schema` e `migrations`;
107
- `opus db --help` detalha.
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
- > `create` scaffolda um app novo com os pré-requisitos plugados; `setup` é per-app
112
- > (idempotente, nunca sobrescreve o seu).
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 canônico do zero: protocolo + UI + preview + automação
116
- opus create meu-cliente --monorepo # a RAIZ de um workspace (apps/* + packages/*)
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` versiona com o Opus (sai do mesmo pacote que o SDK que ele
122
- configura) e nasce com os gates verdes: domínio-exemplo canônico, teste, manifest e o dev
123
- server pronto para o preview do Maestro. O método geral, a memória e a revisão vêm da Base
124
- depois de `pnpm run setup`. Dois modos, por detecção:
125
- repo standalone (template inteiro) ou **app em monorepo** (dentro de um workspace pnpm:
126
- só os arquivos do app; o que a raiz precisa ter vira aviso, sem clobber).
127
-
128
- ## MCP — estado vivo para os agentes
129
-
130
- > O server MCP expõe introspecção, check e scaffold de action. É como um agente lê a estrutura e cria
131
- > action no formato canônico sem decorar convenção.
132
-
133
- `opus mcp` sobe o server (configurado em `.mcp.json`): o agente consulta a estrutura, roda o
134
- check e gera o esqueleto de uma action nova pela skill `$create-opus-action` — a mesma régua do
135
- `opus check`, por construção.
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
- CollapsibleTrigger alterna o CollapsibleContent — o Trigger já é o <button>. defaultOpen deixa o estado com o componente.
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
- Repositórios do workspace
9
- <ChevronsUpDown className="h-4 w-4 text-muted-foreground" />
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>empresa-x-api</p>
13
- <p>empresa-x-web</p>
14
- <p>empresa-x-infra</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 + onOpenChange põem o estado nas suas mãos — dá para refletir no gatilho (aqui o chevron gira) ou guardar a preferência.
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
- Skills do agente developer
30
- <ChevronDown className={open ? 'h-4 w-4 rotate-180 transition-transform' : 'h-4 w-4 transition-transform'} />
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>clean-code</p>
34
- <p>code-style</p>
35
- <p>ddd</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 trava o gatilho — a seção fica fixa no estado atual (aqui, fechada).
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
- Sessões arquivadas
49
- <ChevronsUpDown className="h-4 w-4 text-muted-foreground" />
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>Nada para mostrar enquanto o workspace está em sincronização.</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` | Bloqueia o gatilho e mantém a seção no estado atual. |
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
- Digite para filtrar uma coleção com navegação por teclado, grupos e estado vazio. Command é a base
4
- do Select pesquisável; para uma escolha em formulário, use Select.
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 /> Nova sessão <CommandShortcut>⌘N</CommandShortcut></CommandItem>
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
- ## Palette modal (CommandDialog)
26
+ ## Paleta de comandos em modal
25
27
 
26
- O Command embrulhado em um Dialog mantém um header compacto com título e close. A descrição
27
- permanece disponível para tecnologias assistivas. O atalho ⌘K (keydown no app) só troca o open —
28
- o conteúdo é o mesmo do inline.
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 palette <CommandShortcut>⌘K</CommandShortcut>
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
- A caixa de escrever da casa: textarea em uma pílula elevada (`rounded-xl` + `border` + `shadow-sm`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/ui/chat) extraído — use sozinho quando há entrada de texto mas não um chat (ex.: criar uma sessão). Controlado: o dono do texto é você. Os callbacks opcionais `onHistoryPrevious` e `onHistoryNext` permitem que esse dono consuma **↑/↓**; sem eles, as setas mantêm o comportamento nativo da textarea.
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 (barra de ações)
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
- Passe `actions` e o composer vira **duas linhas**: textarea em cima, uma barra limpa embaixo com os seletores à ESQUERDA e o enviar à direita. Estilize os controles como discretos (ghost, sem borda) — a barra não é um formulário. É onde o Maestro prende **app + task** e a GB, o **agente**. O `submitDisabled` trava o enviar além de vazio/busy (ex.: falta escolher o app), sem esconder a barra.
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 o botão enviar; só dispara quando dá para enviar. |
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` | Trava o composer enquanto o turno corre; o enviar vira spinner. |
61
- | `submitDisabled` | `boolean` | `false` | Gate extra de envio além de vazio e `busy` (ex.: falta escolher o app). |
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 textarea; ele cresce com o conteú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; presente, o composer vira duas linhas. |
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. |