@softize/opus 8.6.6
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 +1616 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/cli.mjs +528 -0
- package/bin/lib/check.mjs +307 -0
- package/bin/lib/components.mjs +151 -0
- package/bin/lib/create.mjs +208 -0
- package/bin/lib/db-check-runner.mjs +86 -0
- package/bin/lib/db-migrate-runner.mjs +89 -0
- package/bin/lib/db-scaffold-runner.mjs +84 -0
- package/bin/lib/db.mjs +261 -0
- package/bin/lib/docs-include.mjs +48 -0
- package/bin/lib/gen-dicts.mjs +134 -0
- package/bin/lib/gen-docs.mjs +288 -0
- package/bin/lib/gen-manifest.mjs +102 -0
- package/bin/lib/gen-openapi.mjs +195 -0
- package/bin/lib/gen-runner.mjs +472 -0
- package/bin/lib/gen-stubs.mjs +463 -0
- package/bin/lib/gen.mjs +311 -0
- package/bin/lib/init.mjs +514 -0
- package/bin/lib/introspect.mjs +107 -0
- package/bin/lib/mcp.mjs +85 -0
- package/bin/lib/postinstall.mjs +56 -0
- package/docs/chat-event-protocol.md +85 -0
- package/docs/code-style.md +16 -0
- package/docs/data-layer.md +246 -0
- package/docs/ownership-vs-shadcn-lock.md +102 -0
- package/docs/protocol.md +2053 -0
- package/docs/releasing.md +110 -0
- package/docs/shellnav.md +131 -0
- package/package.json +338 -0
- package/registry/hooks/hooks.json +26 -0
- package/registry/hooks/link-memory-on-start.mjs +46 -0
- package/registry/hooks/opus-check-on-stop.mjs +114 -0
- package/registry/skills/create-action/SKILL.md +49 -0
- package/registry/skills/create-action/scaffold.mjs +122 -0
- package/registry/templates/app/_gitignore +3 -0
- package/registry/templates/app/_npmrc +1 -0
- package/registry/templates/app/_opus/_gitignore +5 -0
- package/registry/templates/app/_prettierrc.json +6 -0
- package/registry/templates/app/index.html +13 -0
- package/registry/templates/app/opus.config.ts +16 -0
- package/registry/templates/app/package.json +43 -0
- package/registry/templates/app/pnpm-workspace.yaml +11 -0
- package/registry/templates/app/public/favicon.svg +4 -0
- package/registry/templates/app/src/App.tsx +37 -0
- package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
- package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
- package/registry/templates/app/src/domains/tasks/index.ts +13 -0
- package/registry/templates/app/src/index.css +18 -0
- package/registry/templates/app/src/main.tsx +25 -0
- package/registry/templates/app/tsconfig.json +20 -0
- package/registry/templates/app/vite.config.ts +46 -0
- package/registry/templates/monorepo/_gitignore +3 -0
- package/registry/templates/monorepo/_npmrc +1 -0
- package/registry/templates/monorepo/package.json +9 -0
- package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
- package/src/ai/ask.ts +64 -0
- package/src/ai/drivers/anthropic.ts +309 -0
- package/src/ai/index.ts +17 -0
- package/src/audit/drivers/console.ts +117 -0
- package/src/audit/drivers/pg.ts +172 -0
- package/src/audit/index.ts +51 -0
- package/src/auth/drivers/better-auth.ts +103 -0
- package/src/auth/drivers/jwt.ts +188 -0
- package/src/auth/index.ts +9 -0
- package/src/client/drivers/fetch.ts +202 -0
- package/src/client/index.ts +22 -0
- package/src/core/actions.ts +110 -0
- package/src/core/audit.ts +239 -0
- package/src/core/contracts.ts +137 -0
- package/src/core/domain.ts +310 -0
- package/src/core/errors.ts +181 -0
- package/src/core/index.ts +174 -0
- package/src/core/logical-type.ts +31 -0
- package/src/core/reactions.ts +81 -0
- package/src/core/runtime.ts +1167 -0
- package/src/core/schedules.ts +41 -0
- package/src/core/types.ts +1356 -0
- package/src/data/drivers/kysely.ts +389 -0
- package/src/data/index.ts +10 -0
- package/src/data/readonly-pool.ts +160 -0
- package/src/dsl/eval.ts +136 -0
- package/src/dsl/index.ts +29 -0
- package/src/dsl/kysely.ts +230 -0
- package/src/dsl/loads.ts +123 -0
- package/src/dsl/parser.ts +423 -0
- package/src/dsl/types.ts +113 -0
- package/src/events/drivers/mitt.ts +70 -0
- package/src/events/index.ts +9 -0
- package/src/log/drivers/pino.ts +57 -0
- package/src/log/index.ts +9 -0
- package/src/mcp/index.ts +62 -0
- package/src/queue/drivers/bullmq.ts +190 -0
- package/src/queue/index.ts +9 -0
- package/src/scheduler/drivers/node-cron.ts +93 -0
- package/src/scheduler/every.ts +45 -0
- package/src/scheduler/index.ts +9 -0
- package/src/schema/drivers/zod.ts +765 -0
- package/src/schema/entity.ts +439 -0
- package/src/schema/format/locale.ts +144 -0
- package/src/schema/index.ts +65 -0
- package/src/schema/openapi.ts +302 -0
- package/src/schema/scaffold.ts +160 -0
- package/src/server/drivers/fastify.ts +224 -0
- package/src/server/drivers/node.ts +386 -0
- package/src/server/index.ts +142 -0
- package/src/storage/drivers/fs.ts +90 -0
- package/src/storage/drivers/s3.ts +117 -0
- package/src/storage/index.ts +27 -0
- package/src/testing/fake.ts +298 -0
- package/src/testing/index.ts +324 -0
- package/src/ui/components/patterns/action-form-card.tsx +48 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
- package/src/ui/components/patterns/app-shell.tsx +227 -0
- package/src/ui/components/patterns/confirm.tsx +226 -0
- package/src/ui/components/patterns/data-state.tsx +75 -0
- package/src/ui/components/patterns/form-dialog.tsx +64 -0
- package/src/ui/components/patterns/form.tsx +584 -0
- package/src/ui/components/patterns/list.tsx +1488 -0
- package/src/ui/components/patterns/page.tsx +46 -0
- package/src/ui/components/patterns/section-shell.tsx +246 -0
- package/src/ui/components/patterns/shell-nav.tsx +150 -0
- package/src/ui/components/patterns/sidebar.tsx +89 -0
- package/src/ui/components/patterns/split.tsx +93 -0
- package/src/ui/components/patterns/trigger.tsx +196 -0
- package/src/ui/components/patterns/view.tsx +84 -0
- package/src/ui/components/primitives/accordion.tsx +64 -0
- package/src/ui/components/primitives/alert-dialog.tsx +190 -0
- package/src/ui/components/primitives/alert.tsx +116 -0
- package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
- package/src/ui/components/primitives/avatar.tsx +107 -0
- package/src/ui/components/primitives/badge.tsx +37 -0
- package/src/ui/components/primitives/breadcrumb.tsx +109 -0
- package/src/ui/components/primitives/button-group.tsx +83 -0
- package/src/ui/components/primitives/button.tsx +102 -0
- package/src/ui/components/primitives/calendar.tsx +218 -0
- package/src/ui/components/primitives/card.tsx +56 -0
- package/src/ui/components/primitives/carousel.tsx +239 -0
- package/src/ui/components/primitives/chat.tsx +407 -0
- package/src/ui/components/primitives/checkbox.tsx +30 -0
- package/src/ui/components/primitives/collapsible.tsx +31 -0
- package/src/ui/components/primitives/command.tsx +182 -0
- package/src/ui/components/primitives/composer.tsx +121 -0
- package/src/ui/components/primitives/copyable.tsx +50 -0
- package/src/ui/components/primitives/dialog.tsx +147 -0
- package/src/ui/components/primitives/drawer.tsx +141 -0
- package/src/ui/components/primitives/empty.tsx +104 -0
- package/src/ui/components/primitives/field.tsx +246 -0
- package/src/ui/components/primitives/icon-picker.tsx +180 -0
- package/src/ui/components/primitives/input-group.tsx +168 -0
- package/src/ui/components/primitives/input-otp.tsx +75 -0
- package/src/ui/components/primitives/input.tsx +72 -0
- package/src/ui/components/primitives/item.tsx +193 -0
- package/src/ui/components/primitives/kbd.tsx +28 -0
- package/src/ui/components/primitives/label.tsx +22 -0
- package/src/ui/components/primitives/markdown.tsx +35 -0
- package/src/ui/components/primitives/menu.tsx +255 -0
- package/src/ui/components/primitives/pagination.tsx +127 -0
- package/src/ui/components/primitives/popover.tsx +87 -0
- package/src/ui/components/primitives/progress.tsx +29 -0
- package/src/ui/components/primitives/radio-group.tsx +43 -0
- package/src/ui/components/primitives/resizable.tsx +51 -0
- package/src/ui/components/primitives/scroll-area.tsx +56 -0
- package/src/ui/components/primitives/select.tsx +479 -0
- package/src/ui/components/primitives/separator.tsx +26 -0
- package/src/ui/components/primitives/skeleton.tsx +13 -0
- package/src/ui/components/primitives/slider.tsx +61 -0
- package/src/ui/components/primitives/sonner.tsx +46 -0
- package/src/ui/components/primitives/spinner.tsx +29 -0
- package/src/ui/components/primitives/switch.tsx +33 -0
- package/src/ui/components/primitives/table.tsx +114 -0
- package/src/ui/components/primitives/tabs.tsx +104 -0
- package/src/ui/components/primitives/textarea.tsx +18 -0
- package/src/ui/components/primitives/toggle-group.tsx +81 -0
- package/src/ui/components/primitives/toggle.tsx +45 -0
- package/src/ui/components/primitives/tooltip.tsx +55 -0
- package/src/ui/components/primitives/truncate.tsx +49 -0
- package/src/ui/docs/DocBrowser.tsx +90 -0
- package/src/ui/docs/changelog.tsx +80 -0
- package/src/ui/docs/content/accordion.md +86 -0
- package/src/ui/docs/content/action-form-card.md +24 -0
- package/src/ui/docs/content/action-form-dialog.md +30 -0
- package/src/ui/docs/content/action-form.md +125 -0
- package/src/ui/docs/content/action-list-dialog.md +68 -0
- package/src/ui/docs/content/action-list.md +194 -0
- package/src/ui/docs/content/action-trigger.md +72 -0
- package/src/ui/docs/content/action-view.md +47 -0
- package/src/ui/docs/content/actions.md +138 -0
- package/src/ui/docs/content/ai.md +112 -0
- package/src/ui/docs/content/alert-dialog.md +73 -0
- package/src/ui/docs/content/alert.md +69 -0
- package/src/ui/docs/content/app-shell.md +155 -0
- package/src/ui/docs/content/aspect-ratio.md +66 -0
- package/src/ui/docs/content/audit.md +84 -0
- package/src/ui/docs/content/auth.md +70 -0
- package/src/ui/docs/content/avatar.md +94 -0
- package/src/ui/docs/content/badge.md +48 -0
- package/src/ui/docs/content/breadcrumb.md +87 -0
- package/src/ui/docs/content/button-group.md +71 -0
- package/src/ui/docs/content/button.md +60 -0
- package/src/ui/docs/content/calendar.md +62 -0
- package/src/ui/docs/content/card.md +49 -0
- package/src/ui/docs/content/carousel.md +85 -0
- package/src/ui/docs/content/chat.md +69 -0
- package/src/ui/docs/content/checkbox.md +75 -0
- package/src/ui/docs/content/cli.md +58 -0
- package/src/ui/docs/content/collapsible.md +64 -0
- package/src/ui/docs/content/command.md +56 -0
- package/src/ui/docs/content/composer.md +50 -0
- package/src/ui/docs/content/confirm.md +120 -0
- package/src/ui/docs/content/copyable.md +30 -0
- package/src/ui/docs/content/customization.md +110 -0
- package/src/ui/docs/content/cycle.md +34 -0
- package/src/ui/docs/content/data-state.md +47 -0
- package/src/ui/docs/content/data.md +99 -0
- package/src/ui/docs/content/dialog.md +60 -0
- package/src/ui/docs/content/drawer.md +55 -0
- package/src/ui/docs/content/empty.md +66 -0
- package/src/ui/docs/content/events.md +61 -0
- package/src/ui/docs/content/field.md +58 -0
- package/src/ui/docs/content/getting-started.md +109 -0
- package/src/ui/docs/content/icon-picker.md +51 -0
- package/src/ui/docs/content/input-group.md +78 -0
- package/src/ui/docs/content/input-otp.md +72 -0
- package/src/ui/docs/content/input.md +78 -0
- package/src/ui/docs/content/item.md +84 -0
- package/src/ui/docs/content/kbd.md +62 -0
- package/src/ui/docs/content/label.md +32 -0
- package/src/ui/docs/content/log.md +55 -0
- package/src/ui/docs/content/markdown.md +41 -0
- package/src/ui/docs/content/mcp.md +44 -0
- package/src/ui/docs/content/menu.md +114 -0
- package/src/ui/docs/content/microcopy.md +83 -0
- package/src/ui/docs/content/page.md +34 -0
- package/src/ui/docs/content/pagination.md +99 -0
- package/src/ui/docs/content/popover.md +49 -0
- package/src/ui/docs/content/progress.md +69 -0
- package/src/ui/docs/content/queue.md +62 -0
- package/src/ui/docs/content/radio-group.md +77 -0
- package/src/ui/docs/content/resizable.md +86 -0
- package/src/ui/docs/content/router.md +56 -0
- package/src/ui/docs/content/runtime.md +77 -0
- package/src/ui/docs/content/scheduler.md +66 -0
- package/src/ui/docs/content/scroll-area.md +89 -0
- package/src/ui/docs/content/section-shell.md +121 -0
- package/src/ui/docs/content/select.md +342 -0
- package/src/ui/docs/content/separator.md +33 -0
- package/src/ui/docs/content/sidebar.md +38 -0
- package/src/ui/docs/content/skeleton.md +34 -0
- package/src/ui/docs/content/slider.md +64 -0
- package/src/ui/docs/content/spinner.md +37 -0
- package/src/ui/docs/content/split.md +33 -0
- package/src/ui/docs/content/storage.md +69 -0
- package/src/ui/docs/content/switch.md +69 -0
- package/src/ui/docs/content/table.md +102 -0
- package/src/ui/docs/content/tabs.md +94 -0
- package/src/ui/docs/content/testing.md +89 -0
- package/src/ui/docs/content/textarea.md +30 -0
- package/src/ui/docs/content/toast.md +67 -0
- package/src/ui/docs/content/toggle-group.md +81 -0
- package/src/ui/docs/content/toggle.md +72 -0
- package/src/ui/docs/content/tokens.md +171 -0
- package/src/ui/docs/content/tooltip.md +50 -0
- package/src/ui/docs/content/truncate.md +37 -0
- package/src/ui/docs/content/ui.md +40 -0
- package/src/ui/docs/content/upgrading.md +48 -0
- package/src/ui/docs/doc-client.tsx +214 -0
- package/src/ui/docs/doc.tsx +301 -0
- package/src/ui/docs/folder.tsx +149 -0
- package/src/ui/docs/index.ts +21 -0
- package/src/ui/docs/markdown.tsx +130 -0
- package/src/ui/docs/md-raw.d.ts +4 -0
- package/src/ui/docs/plugin.ts +104 -0
- package/src/ui/docs/registry.tsx +424 -0
- package/src/ui/docs/standalone.tsx +107 -0
- package/src/ui/drivers/react.tsx +627 -0
- package/src/ui/index.ts +92 -0
- package/src/ui/lib/cn.ts +10 -0
- package/src/ui/lib/zod-pt-br.ts +38 -0
- package/src/ui/meta.ts +412 -0
- package/src/ui/react.tsx +235 -0
- package/src/ui/router.ts +96 -0
- package/src/ui/theme.css +234 -0
- package/src/vite/design.ts +652 -0
- package/src/vite/index.ts +8 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Customização
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Customização
|
|
6
|
+
|
|
7
|
+
Cinco alavancas, do global ao pontual — e um limite de propósito. O caminho previsto é compor
|
|
8
|
+
e configurar, nunca forkar componente: o que não cabe nas alavancas evolui no Opus (com
|
|
9
|
+
divergência declarada), pra valer pra casa toda.
|
|
10
|
+
|
|
11
|
+
## 1 · Identidade por tokens
|
|
12
|
+
|
|
13
|
+
> O jeito canônico de vestir a marca. Sobrescreva as vars no `:root` depois de importar o
|
|
14
|
+
> `theme.css` — todos os componentes acompanham, sem forkar nada. Dark incluso (a classe `.dark`
|
|
15
|
+
> re-declara as vars).
|
|
16
|
+
|
|
17
|
+
```css
|
|
18
|
+
/* index.css do app — depois do import do tema. */
|
|
19
|
+
@import '@softize/opus/ui/theme.css';
|
|
20
|
+
|
|
21
|
+
:root {
|
|
22
|
+
--primary: hsl(262 83% 58%); /* A cor da marca do projeto. */
|
|
23
|
+
--ring: hsl(262 83% 58%);
|
|
24
|
+
--radius: 0.5rem; /* Cantos mais retos, se for a praia do projeto. */
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 2 · className em tudo
|
|
29
|
+
|
|
30
|
+
> Todo componente termina em `cn(base, className)` com tailwind-merge: o utilitário do consumidor
|
|
31
|
+
> vence o conflito. Pra layout local (largura, margem, grid) — não pra repintar o visual da casa.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Button className="w-full">Salvar</Button>
|
|
35
|
+
<Card className="max-w-sm" />
|
|
36
|
+
<DialogContent className="sm:max-w-2xl" />
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 3 · Recomposição estrutural
|
|
40
|
+
|
|
41
|
+
> Os componentes são explodidos em slots. `asChild` (Radix Slot) renderiza como outro elemento
|
|
42
|
+
> mantendo estilo e comportamento; os `*Variants` aplicam a cara da casa num elemento arbitrário.
|
|
43
|
+
|
|
44
|
+
```tsx preview
|
|
45
|
+
<Button asChild variant="outline">
|
|
46
|
+
<a href="#" onClick={(e) => e.preventDefault()}>Âncora com cara de botão</a>
|
|
47
|
+
</Button>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// Slots: só o que a tela pede.
|
|
52
|
+
<Card>
|
|
53
|
+
<CardHeader><CardTitle>Workspace</CardTitle></CardHeader>
|
|
54
|
+
<CardContent>…</CardContent>
|
|
55
|
+
</Card>
|
|
56
|
+
|
|
57
|
+
// asChild: o filho VIRA o botão (sem forkar estilo).
|
|
58
|
+
<Button asChild><a href="/docs">Abrir documentação</a></Button>
|
|
59
|
+
|
|
60
|
+
// buttonVariants: a cara da casa num elemento qualquer.
|
|
61
|
+
<a className={buttonVariants({ variant: 'outline' })}>Link estilizado</a>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 4 · Comportamento por props
|
|
65
|
+
|
|
66
|
+
> Modo controlado em tudo que abre ou seleciona (`open`/`onOpenChange`, `value`/`onValueChange`),
|
|
67
|
+
> posicionamento (`align`/`side`/`sideOffset`), `showCloseButton`, `delayDuration`… A orquestração
|
|
68
|
+
> é do implementador; o visual continua o da casa.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
const [open, setOpen] = useState(false)
|
|
72
|
+
|
|
73
|
+
<Dialog open={open} onOpenChange={setOpen}>…</Dialog>
|
|
74
|
+
<DialogContent showCloseButton={false} /> {/* modal que exige ação */}
|
|
75
|
+
<PopoverContent align="start" sideOffset={8} />
|
|
76
|
+
<TooltipProvider delayDuration={200}>…
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 5 · Nos patterns, o contrato é a customização
|
|
80
|
+
|
|
81
|
+
> Os patterns (ActionForm/Search/View/Trigger): o spec dirige. `fields`/`widget`/`showWhen`/
|
|
82
|
+
> `fieldOptions` no form, `columns` no search, render prop no view. O caminho previsto é a feature
|
|
83
|
+
> EMBRULHAR o pattern — nunca forkar.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// O form NASCE do contrato; a tela só ajusta o que é seu.
|
|
87
|
+
<ActionForm
|
|
88
|
+
action={workspaceCreateContract}
|
|
89
|
+
fieldOptions={{ skillIds: skillsDoWorkspace }}
|
|
90
|
+
onSuccess={(d) => navigate(`/workspaces/${d.id}`)}
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
// O search recebe as colunas; a célula é render livre.
|
|
94
|
+
<ActionList action={listAction} input={{ q }} columns={[
|
|
95
|
+
{ key: 'name', header: 'Workspace', cell: (w) => w.name },
|
|
96
|
+
]} />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## O limite — de propósito
|
|
100
|
+
|
|
101
|
+
> O look curado não é customizável no app: rodapé-faixa do dialog, elevação no dark, `active:scale`
|
|
102
|
+
> do botão, a seta do tooltip — é identidade da casa, igual em todo projeto.
|
|
103
|
+
|
|
104
|
+
```tsx preview
|
|
105
|
+
<Badge variant="success">Cabe nas alavancas</Badge>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Se uma necessidade real não cabe nas alavancas (tokens · className · slots/asChild · props ·
|
|
109
|
+
contrato), o movimento não é dialeto local: é evoluir o componente **no Opus**, com a divergência
|
|
110
|
+
declarada (skill `opus`) — assim a mudança vale pra casa toda, e esta doc passa a mostrá-la.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Evolução contínua
|
|
2
|
+
|
|
3
|
+
O Opus cresce por **reincidência, nunca por especulação**. Quando o mesmo comportamento genérico reaparece em mais de um projeto, ele vira candidato a peça do Opus — e o projeto troca a implementação local pelo import. Estilo e conveniência não entram; comportamento que se repete, sim.
|
|
4
|
+
|
|
5
|
+
## Onde você aponta
|
|
6
|
+
|
|
7
|
+
No seu projeto, uma linha por apontamento em `.opus/issues.jsonl` na raiz do repo. Dois tipos:
|
|
8
|
+
|
|
9
|
+
- **`enhancement`** — código genérico que o Opus deveria ganhar (serviria a qualquer projeto). Antes de apontar, confira o que o Opus já tem (o inventário de componentes, os exports, esta doc): se já existe, use.
|
|
10
|
+
- **`bug`** — um defeito ou limitação no próprio Opus, achado usando-o.
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{"type": "enhancement", "behavior": "o comportamento em uma frase.", "file": "src/onde-vive.ts", "kind": "ui|sdk|driver|infra"}
|
|
14
|
+
{"type": "bug", "behavior": "o que quebra ou falta.", "file": "onde se manifesta.", "note": "repro + o workaround aplicado."}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Não trave esperando
|
|
18
|
+
|
|
19
|
+
O ponto do ciclo é **não bloquear a entrega**. Bateu num gap ou num bug do Opus:
|
|
20
|
+
|
|
21
|
+
1. **Contorne local** — componha um wrapper no seu projeto. O Opus entrega _source_, então dá pra embrulhar qualquer superfície dele. Nunca edite `node_modules` (some no próximo install).
|
|
22
|
+
2. **Entregue** a feature com o workaround.
|
|
23
|
+
3. **Aponte** no `.opus/issues.jsonl` e siga em frente.
|
|
24
|
+
|
|
25
|
+
O "depois" — o conserto no Opus — corre em paralelo. Ele não segura o seu trabalho; o único custo de demorar é o workaround viver um pouco mais.
|
|
26
|
+
|
|
27
|
+
## Como fecha
|
|
28
|
+
|
|
29
|
+
O apontamento é colhido e abre uma **Issue no repositório do Opus**, onde a triagem acontece (deduplicada — re-apontar o mesmo é idempotente):
|
|
30
|
+
|
|
31
|
+
- **Enhancement** aceito (com reincidência) → implementado no Opus → sai num _bump_ → seu projeto atualiza o pin (`opus.json`) e troca o workaround pelo import.
|
|
32
|
+
- **Bug** → vira _fix_ + entrada no `CHANGELOG` → no _bump_, o workaround sai.
|
|
33
|
+
|
|
34
|
+
A régua e a decisão ficam com quem mantém o Opus — hoje, a **Softize**. O registro curado das promoções vive no `PROMOTED.md` do pacote.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
## Estados
|
|
2
|
+
|
|
3
|
+
Um lugar só pro erro/carregando/vazio/conteúdo de uma carga. Carregando = Spinner centralizado;
|
|
4
|
+
vazio = texto; erro = aviso calmo (a mensagem técnica não vai pra tela). É o "antes" do conteúdo —
|
|
5
|
+
pro "depois" (ação em andamento), use o busy do Button.
|
|
6
|
+
|
|
7
|
+
```tsx preview col
|
|
8
|
+
<div className="w-full space-y-3">
|
|
9
|
+
<DataState loading>
|
|
10
|
+
<div />
|
|
11
|
+
</DataState>
|
|
12
|
+
<DataState empty emptyText="Nenhum papel.">
|
|
13
|
+
<div />
|
|
14
|
+
</DataState>
|
|
15
|
+
<DataState error={{ message: 'detalhe técnico fica no console' }}>
|
|
16
|
+
<div />
|
|
17
|
+
</DataState>
|
|
18
|
+
</div>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Em tabela (colSpan)
|
|
22
|
+
|
|
23
|
+
Em lista de tabela, passe colSpan: o estado vira UMA linha de largura cheia (`<tr><td colSpan>`)
|
|
24
|
+
que cabe direto no `<tbody>`; o conteúdo são as `<tr>` dos itens.
|
|
25
|
+
|
|
26
|
+
```tsx preview col
|
|
27
|
+
<table className="w-full overflow-hidden rounded-lg border border-border text-sm">
|
|
28
|
+
<tbody>
|
|
29
|
+
<DataState empty emptyText="Nenhum usuário." colSpan={3}>
|
|
30
|
+
<tr>
|
|
31
|
+
<td />
|
|
32
|
+
</tr>
|
|
33
|
+
</DataState>
|
|
34
|
+
</tbody>
|
|
35
|
+
</table>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Props
|
|
39
|
+
|
|
40
|
+
| Prop | Tipo | Default | Descrição |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `loading` | `boolean` | | Carregando (antes do conteúdo) — mostra o Spinner centralizado. |
|
|
43
|
+
| `empty` | `boolean` | | Sem itens — mostra o emptyText. |
|
|
44
|
+
| `emptyText` | `string` | | Texto do vazio (pt-BR, ex.: "Nenhum papel."). |
|
|
45
|
+
| `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica NÃO vai pra tela (use errorText). |
|
|
46
|
+
| `errorText` | `string` | `'Não foi possível carregar.'` | Aviso de erro, orientado ao usuário. |
|
|
47
|
+
| `colSpan` | `number` | | Em tabela: renderiza o estado como `<tr><td colSpan>` (cabe direto no tbody). |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Camada de dados
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Camada de dados
|
|
6
|
+
|
|
7
|
+
A entidade é a spec do armazenamento; o manifest a projeta. As queries falam Kysely tipado.
|
|
8
|
+
Uma regra firme atravessa tudo: o código fala camelCase, o banco fala snake — e o
|
|
9
|
+
`CamelCasePlugin` faz a ponte.
|
|
10
|
+
|
|
11
|
+
## Entidade e manifest
|
|
12
|
+
|
|
13
|
+
> `defineEntity` declara o storage (campos + tipos lógicos `t.*`). A `description` de cada
|
|
14
|
+
> entidade/action é a spec de negócio, projetada no manifest pelo `opus gen`; `opus db check`
|
|
15
|
+
> acusa drift entidade ↔ banco.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { defineEntity } from '@softize/opus/schema'
|
|
19
|
+
import { t } from '@softize/opus/schema/zod'
|
|
20
|
+
|
|
21
|
+
export const SkillEntity = defineEntity({
|
|
22
|
+
name: 'skill',
|
|
23
|
+
fields: {
|
|
24
|
+
id: t.uuid().pk(),
|
|
25
|
+
slug: t.slug(),
|
|
26
|
+
name: t.string(),
|
|
27
|
+
content: t.text(),
|
|
28
|
+
createdAt: t.datetime(),
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Código camelCase, banco snake
|
|
34
|
+
|
|
35
|
+
> Regra dura (skill `code-style`): uma camada em snake e outra em camel é defeito — não tem meio-termo.
|
|
36
|
+
|
|
37
|
+
O schema tipado do Kysely e **toda** query (select/insert/update/where) usam camelCase
|
|
38
|
+
(`workspaceId`, `ghRepo`). O banco é snake (a DDL no schema idempotente — `db/schema.sql`).
|
|
39
|
+
O `CamelCasePlugin` no runtime faz a ponte camel↔snake nas queries E nos resultados — o
|
|
40
|
+
mesmo que o prepare já faz.
|
|
41
|
+
Única exceção: um sink escrito por fora do Kysely-com-plugin (ex.: o `audit_log` do Opus, pelo
|
|
42
|
+
pool) fica snake.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { CamelCasePlugin, Kysely, PostgresDialect } from 'kysely'
|
|
46
|
+
|
|
47
|
+
const db = new Kysely<AdminDB>({
|
|
48
|
+
dialect: new PostgresDialect({ pool }),
|
|
49
|
+
plugins: [new CamelCasePlugin()], // camel no código → snake no SQL → camel no resultado
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
await db.selectFrom('agents').select(['isDefault', 'roleId']).where('workspaceId', '=', id).execute()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Migrações · prepare · seed
|
|
56
|
+
|
|
57
|
+
> Três coisas distintas — não confundir o que roda em prod.
|
|
58
|
+
|
|
59
|
+
- `opus db migrate` — aplica o **schema idempotente** (`config.schema`, um script SQL
|
|
60
|
+
evolutivo: `IF NOT EXISTS` + guards cobrem nascer do zero e upgrade no mesmo artefato)
|
|
61
|
+
e roda o drift-check entidade ↔ banco na sequência (exit ≠ 0 se divergir).
|
|
62
|
+
- `prepare` — backfill estrutural, prod-safe e idempotente; roda no deploy (depois do migrate).
|
|
63
|
+
- `seed` — fixtures de desenvolvimento. **Não** roda em prod.
|
|
64
|
+
|
|
65
|
+
## Campo `t.json()`: objeto entra, objeto sai
|
|
66
|
+
|
|
67
|
+
> A dupla `ColumnType<unknown, string, never>` + `JSON.stringify` na mão está aposentada
|
|
68
|
+
> no caminho do `kyselyRepo`.
|
|
69
|
+
|
|
70
|
+
O `kyselyRepo` serializa campo `t.json()` na ESCRITA (insert/update), guiado pela
|
|
71
|
+
declaração. Sem isso, o pg até stringifica objeto plano — mas **array vira literal de
|
|
72
|
+
array do PG** (errado pra jsonb) sem quebrar typecheck. String passa direto (quem já
|
|
73
|
+
mandava pré-serializado segue valendo); na leitura o pg devolve objeto. Query à mão
|
|
74
|
+
(fora do repo) continua responsável pelo próprio stringify.
|
|
75
|
+
|
|
76
|
+
## SQL escrito por LLM: `readOnlyContextPool`
|
|
77
|
+
|
|
78
|
+
> Action `ai: true` que executa SQL livre precisa das **quatro defesas** — todas no
|
|
79
|
+
> BANCO, nenhuma em regex sobre o texto da query.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { Pool } from 'pg'
|
|
83
|
+
import { readOnlyContextPool } from '@softize/opus/data/readonly-pool'
|
|
84
|
+
|
|
85
|
+
const bi = readOnlyContextPool({ pool: new Pool({ connectionString, max: 4 }) })
|
|
86
|
+
const { rows } = await bi.query(sqlDoLlm, { role: roleFor(ctx) })
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
1. **Pool com teto** (`max` do pool e/ou `maxConcurrent`) — query de LLM não esgota as
|
|
90
|
+
conexões do app;
|
|
91
|
+
2. **`BEGIN TRANSACTION READ ONLY`** — escrita morre no servidor;
|
|
92
|
+
3. **`SET LOCAL ROLE` por transação** — o alcance é do CONTEXTO (crie os roles com grants
|
|
93
|
+
default-fechado nas suas migrações: sem isso, `sales_read` leria `hr_employees`);
|
|
94
|
+
4. **Protocolo estendido** — o SQL roda sempre com array de valores; multi-sentença
|
|
95
|
+
(`SELECT 1; DROP …`) é recusada pelo próprio protocolo.
|
|
96
|
+
|
|
97
|
+
Saindo, `DISCARD ALL` devolve a conexão limpa; se a limpeza falhar, a conexão é
|
|
98
|
+
destruída — nunca volta suja pro pool. `statement_timeout` local por transação
|
|
99
|
+
(default 15s) segura a query fugitiva.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
## Composição
|
|
2
|
+
|
|
3
|
+
Content é superfície pura; o espaço mora nos slots (body p-5; header/footer px-5 py-4): DialogHeader (fixo, com divisor), DialogBody (rola) e DialogFooter (faixa da casa — borda + fundo muted). DialogClose fecha sem estado manual.
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
<Dialog>
|
|
7
|
+
<DialogTrigger asChild>
|
|
8
|
+
<Button variant="outline">Novo workspace</Button>
|
|
9
|
+
</DialogTrigger>
|
|
10
|
+
<DialogContent>
|
|
11
|
+
<DialogHeader>
|
|
12
|
+
<DialogTitle>Novo workspace</DialogTitle>
|
|
13
|
+
<DialogDescription>O workspace agrupa os repositórios e agentes do cliente.</DialogDescription>
|
|
14
|
+
</DialogHeader>
|
|
15
|
+
<DialogBody className="space-y-2">
|
|
16
|
+
<Label htmlFor="ws-nome">Nome</Label>
|
|
17
|
+
<Input id="ws-nome" placeholder="Ex.: Empresa X" />
|
|
18
|
+
</DialogBody>
|
|
19
|
+
<DialogFooter>
|
|
20
|
+
<DialogClose asChild>
|
|
21
|
+
<Button variant="outline" size="sm">Cancelar</Button>
|
|
22
|
+
</DialogClose>
|
|
23
|
+
<Button size="sm">Criar</Button>
|
|
24
|
+
</DialogFooter>
|
|
25
|
+
</DialogContent>
|
|
26
|
+
</Dialog>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Sem o X (ação obrigatória)
|
|
30
|
+
|
|
31
|
+
showCloseButton={false} esconde o X: o usuário decide pelos botões — pra confirmação que não pode ser dispensada no escuro.
|
|
32
|
+
|
|
33
|
+
```tsx preview
|
|
34
|
+
<Dialog>
|
|
35
|
+
<DialogTrigger asChild>
|
|
36
|
+
<Button variant="destructive">Excluir sessão</Button>
|
|
37
|
+
</DialogTrigger>
|
|
38
|
+
<DialogContent showCloseButton={false}>
|
|
39
|
+
<DialogHeader>
|
|
40
|
+
<DialogTitle>Excluir a sessão?</DialogTitle>
|
|
41
|
+
<DialogDescription>O worktree e o preview desta sessão serão removidos.</DialogDescription>
|
|
42
|
+
</DialogHeader>
|
|
43
|
+
<DialogFooter>
|
|
44
|
+
<DialogClose asChild>
|
|
45
|
+
<Button variant="outline" size="sm">Cancelar</Button>
|
|
46
|
+
</DialogClose>
|
|
47
|
+
<DialogClose asChild>
|
|
48
|
+
<Button variant="destructive" size="sm">Excluir</Button>
|
|
49
|
+
</DialogClose>
|
|
50
|
+
</DialogFooter>
|
|
51
|
+
</DialogContent>
|
|
52
|
+
</Dialog>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Props
|
|
56
|
+
|
|
57
|
+
| Prop | Tipo | Default | Descrição |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `Dialog.open / onOpenChange` | `boolean / (open: boolean) => void` | | Modo controlado (Radix) — pra abrir por código (ex.: depois de uma action). |
|
|
60
|
+
| `DialogContent.showCloseButton` | `boolean` | `true` | Mostra o X de fechar no canto. Desligue pra modal que exige ação explícita. |
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
## Painel de detalhe (à direita)
|
|
2
|
+
|
|
3
|
+
O painel que desliza de uma borda da tela — modal, com overlay, trap de foco e ESC (o motor é o Dialog do Radix). A régua contra o irmão: conteúdo curto e CENTRADO → `Dialog`; painel na borda que preserva o contexto atrás → `Drawer`.
|
|
4
|
+
|
|
5
|
+
side='right' desliza da borda direita. ESC, clique no overlay e o X fecham — sem estado manual. É o lugar de detalhe/edição lateral sem sair do contexto.
|
|
6
|
+
|
|
7
|
+
```tsx preview
|
|
8
|
+
<Drawer>
|
|
9
|
+
<DrawerTrigger asChild>
|
|
10
|
+
<Button variant="outline">Ver detalhe</Button>
|
|
11
|
+
</DrawerTrigger>
|
|
12
|
+
<DrawerContent side="right">
|
|
13
|
+
<DrawerHeader>
|
|
14
|
+
<DrawerTitle>criarTicket</DrawerTitle>
|
|
15
|
+
<DrawerDescription>Ação · domínio Suporte</DrawerDescription>
|
|
16
|
+
</DrawerHeader>
|
|
17
|
+
<div className="px-4 text-sm text-muted-foreground">Campos, entrada/saída, arquivo…</div>
|
|
18
|
+
</DrawerContent>
|
|
19
|
+
</Drawer>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Edição com rodapé (à esquerda)
|
|
23
|
+
|
|
24
|
+
side aceita top|right|bottom|left. DrawerFooter ancora as ações no rodapé; DrawerClose fecha sem estado manual.
|
|
25
|
+
|
|
26
|
+
```tsx preview
|
|
27
|
+
<Drawer>
|
|
28
|
+
<DrawerTrigger asChild>
|
|
29
|
+
<Button variant="outline">Editar</Button>
|
|
30
|
+
</DrawerTrigger>
|
|
31
|
+
<DrawerContent side="left">
|
|
32
|
+
<DrawerHeader>
|
|
33
|
+
<DrawerTitle>Editar workspace</DrawerTitle>
|
|
34
|
+
</DrawerHeader>
|
|
35
|
+
<div className="space-y-2 px-4">
|
|
36
|
+
<Label htmlFor="ws-nome">Nome</Label>
|
|
37
|
+
<Input id="ws-nome" defaultValue="Empresa X" />
|
|
38
|
+
</div>
|
|
39
|
+
<DrawerFooter>
|
|
40
|
+
<DrawerClose asChild>
|
|
41
|
+
<Button variant="outline" size="sm">Cancelar</Button>
|
|
42
|
+
</DrawerClose>
|
|
43
|
+
<Button size="sm">Salvar</Button>
|
|
44
|
+
</DrawerFooter>
|
|
45
|
+
</DrawerContent>
|
|
46
|
+
</Drawer>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Props
|
|
50
|
+
|
|
51
|
+
| Prop | Tipo | Default | Descrição |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| `DrawerContent.side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | Borda de onde o painel desliza. |
|
|
54
|
+
| `DrawerContent.showCloseButton` | `boolean` | `true` | Mostra o X de fechar no canto. Desligue pra painel que exige ação explícita. |
|
|
55
|
+
| `Drawer.open / onOpenChange` | `boolean / (open: boolean) => void` | | Modo controlado (Radix) — pra abrir/fechar por código (ex.: detalhe de um item selecionado). |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
## Com ícone
|
|
2
|
+
|
|
3
|
+
EmptyMedia variant=icon desenha o quadrado de fundo muted atrás do ícone; o EmptyHeader agrupa mídia, título e descrição no centro.
|
|
4
|
+
|
|
5
|
+
```tsx preview col
|
|
6
|
+
<Empty>
|
|
7
|
+
<EmptyHeader>
|
|
8
|
+
<EmptyMedia variant="icon">
|
|
9
|
+
<FolderGit2 />
|
|
10
|
+
</EmptyMedia>
|
|
11
|
+
<EmptyTitle>Nenhum repositório vinculado</EmptyTitle>
|
|
12
|
+
<EmptyDescription>
|
|
13
|
+
O workspace Empresa X ainda não tem repositórios. Vincule um pra abrir sessões.
|
|
14
|
+
</EmptyDescription>
|
|
15
|
+
</EmptyHeader>
|
|
16
|
+
</Empty>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Com ação
|
|
20
|
+
|
|
21
|
+
EmptyContent guarda os botões abaixo do header — é o lugar da chamada pra ação que resolve o vazio.
|
|
22
|
+
|
|
23
|
+
```tsx preview col
|
|
24
|
+
<Empty>
|
|
25
|
+
<EmptyHeader>
|
|
26
|
+
<EmptyMedia variant="icon">
|
|
27
|
+
<Sparkles />
|
|
28
|
+
</EmptyMedia>
|
|
29
|
+
<EmptyTitle>Sem skills neste agente</EmptyTitle>
|
|
30
|
+
<EmptyDescription>
|
|
31
|
+
O agente developer não tem skills anexadas. Adicione clean-code ou test pra começar.
|
|
32
|
+
</EmptyDescription>
|
|
33
|
+
</EmptyHeader>
|
|
34
|
+
<EmptyContent>
|
|
35
|
+
<Button>
|
|
36
|
+
<Plus />
|
|
37
|
+
Anexar skill
|
|
38
|
+
</Button>
|
|
39
|
+
</EmptyContent>
|
|
40
|
+
</Empty>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Mídia default
|
|
44
|
+
|
|
45
|
+
EmptyMedia variant=default (o padrão) não desenha fundo — bom pra uma ilustração ou um ícone maior que se sustenta sozinho.
|
|
46
|
+
|
|
47
|
+
```tsx preview col
|
|
48
|
+
<Empty>
|
|
49
|
+
<EmptyHeader>
|
|
50
|
+
<EmptyMedia className="text-muted-foreground">
|
|
51
|
+
<FolderGit2 className="size-12" />
|
|
52
|
+
</EmptyMedia>
|
|
53
|
+
<EmptyTitle>Nenhuma sessão por aqui</EmptyTitle>
|
|
54
|
+
<EmptyDescription>
|
|
55
|
+
Este repositório ainda não teve sessões. A primeira aparece assim que um agente começar a trabalhar.
|
|
56
|
+
</EmptyDescription>
|
|
57
|
+
</EmptyHeader>
|
|
58
|
+
</Empty>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Props
|
|
62
|
+
|
|
63
|
+
| Prop | Tipo | Default | Descrição |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| `variant (EmptyMedia)` | `'default' \| 'icon'` | `'default'` | icon desenha o quadrado de fundo muted atrás da mídia; default não desenha fundo. |
|
|
66
|
+
| `className (Empty)` | `string` | | A moldura já vem tracejada e centrada — ajuste espaçamento ou borda por aqui. |
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Eventos
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Eventos
|
|
6
|
+
|
|
7
|
+
O handler anuncia que algo aconteceu (`ctx.emit`) sem saber quem escuta. Reactions (e
|
|
8
|
+
integrações) assinam e reagem. O barramento é um adapter do core (`EventBusAdapter`); o
|
|
9
|
+
formato do evento é preservado de ponta a ponta.
|
|
10
|
+
|
|
11
|
+
## O contrato
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
interface EventBusAdapter {
|
|
15
|
+
publish(event: DomainEvent): Promise<void> | void
|
|
16
|
+
subscribe?(pattern: string, listener: (event: DomainEvent) => void): Unsubscribe
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface DomainEvent<T = unknown> {
|
|
20
|
+
type: string // 'nota.emitida'
|
|
21
|
+
id: string
|
|
22
|
+
timestamp: string
|
|
23
|
+
actor: { id: string | null; type?: 'user' | 'system' | 'integration' }
|
|
24
|
+
tenant?: string | null
|
|
25
|
+
data: T
|
|
26
|
+
source: { action: string; actionId: string; correlation?: string }
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Driver
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { mittEvents } from '@softize/opus/events/mitt'
|
|
34
|
+
|
|
35
|
+
const bus = mittEvents() // in-process (mesmo processo do runtime)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## No runtime
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const runtime = createRuntime({
|
|
42
|
+
// …server/data…
|
|
43
|
+
eventBus: bus,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// No handler: emite pelo tipo declarado; o core preenche id/timestamp/actor/source.
|
|
47
|
+
handler: async (ctx, input) => {
|
|
48
|
+
const nota = await repo.create(input)
|
|
49
|
+
ctx.emit('nota.emitida', { id: nota.id, valor: nota.valor })
|
|
50
|
+
return nota
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Actions declaram no contrato o que emitem (`emits: ['nota.emitida']`); o runtime avisa se
|
|
55
|
+
o handler emitir algo não-declarado (`emitMode`). Uma reaction assina `nota.emitida` e roda
|
|
56
|
+
como sua própria action rastreável (provenance `reaction`).
|
|
57
|
+
|
|
58
|
+
## Limites (por enquanto)
|
|
59
|
+
|
|
60
|
+
O driver `mitt` é in-process — sem durabilidade nem entre-processos. Barramentos duráveis/
|
|
61
|
+
distribuídos (Redis, NATS…) entram por reincidência de caso real.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
## Campo vertical
|
|
2
|
+
|
|
3
|
+
A composição base: FieldLabel (htmlFor↔id), o controle e FieldDescription como ajuda — empilhados, com o respiro do Field.
|
|
4
|
+
|
|
5
|
+
```tsx preview col md
|
|
6
|
+
<Field>
|
|
7
|
+
<FieldLabel htmlFor="workspace-slug">Slug do workspace</FieldLabel>
|
|
8
|
+
<Input id="workspace-slug" defaultValue="empresa-x" />
|
|
9
|
+
<FieldDescription>Vira o subdomínio do preview: empresa-x.preview.softize.com.br.</FieldDescription>
|
|
10
|
+
</Field>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Horizontal com toggle
|
|
14
|
+
|
|
15
|
+
orientation=horizontal põe o controle na lateral e a label à esquerda. FieldContent agrupa título e descrição num bloco; FieldTitle é o rótulo de texto quando o controle não é um <label>.
|
|
16
|
+
|
|
17
|
+
```tsx preview col md
|
|
18
|
+
<Field orientation="horizontal">
|
|
19
|
+
<FieldContent>
|
|
20
|
+
<FieldTitle>Acionar o revisor no handoff</FieldTitle>
|
|
21
|
+
<FieldDescription>Ao abrir o handoff, o agente reviewer entra na sessão automaticamente.</FieldDescription>
|
|
22
|
+
</FieldContent>
|
|
23
|
+
<Switch id="auto-review" defaultChecked />
|
|
24
|
+
</Field>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Conjunto com legenda e erro
|
|
28
|
+
|
|
29
|
+
FieldSet agrupa campos sob uma FieldLegend; FieldGroup dá o espaçamento entre eles; FieldSeparator marca uma divisão. FieldError mostra a mensagem só quando há erro (aria-invalid no controle casa o visual).
|
|
30
|
+
|
|
31
|
+
```tsx preview col md
|
|
32
|
+
<FieldSet>
|
|
33
|
+
<FieldLegend>Novo agente</FieldLegend>
|
|
34
|
+
<FieldGroup>
|
|
35
|
+
<Field>
|
|
36
|
+
<FieldLabel htmlFor="agent-name">Nome</FieldLabel>
|
|
37
|
+
<Input id="agent-name" defaultValue="" aria-invalid placeholder="Ex.: developer" />
|
|
38
|
+
<FieldError>Informe um nome pro agente.</FieldError>
|
|
39
|
+
</Field>
|
|
40
|
+
<FieldSeparator />
|
|
41
|
+
<Field>
|
|
42
|
+
<FieldLabel htmlFor="agent-prompt">Prompt</FieldLabel>
|
|
43
|
+
<Textarea id="agent-prompt" rows={3} defaultValue="Veste o papel de developer no workspace Empresa X." />
|
|
44
|
+
<FieldDescription>A síntese vira a primeira mensagem da sessão.</FieldDescription>
|
|
45
|
+
</Field>
|
|
46
|
+
</FieldGroup>
|
|
47
|
+
</FieldSet>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Props
|
|
51
|
+
|
|
52
|
+
| Prop | Tipo | Default | Descrição |
|
|
53
|
+
|---|---|---|---|
|
|
54
|
+
| `orientation` (Field) | `'vertical' \| 'horizontal' \| 'responsive'` | `'vertical'` | Direção do campo: vertical empilha; horizontal põe o controle ao lado (bom pra toggle); responsive vira horizontal a partir de @md. |
|
|
55
|
+
| `variant` (FieldLegend) | `'legend' \| 'label'` | `'legend'` | Tamanho da legenda do FieldSet — legend é o título da seção; label encolhe pro porte de rótulo. |
|
|
56
|
+
| `errors` (FieldError) | `Array<{ message?: string }>` | | Lista de erros (ex.: do react-hook-form) — deduplica e vira uma lista. Sem children e sem erros, o FieldError não renderiza. |
|
|
57
|
+
| `children` (FieldError) | `React.ReactNode` | | Mensagem de erro literal — tem prioridade sobre errors quando passada. |
|
|
58
|
+
| `children` (FieldSeparator) | `React.ReactNode` | | Rótulo opcional no meio da linha divisória (ex.: "ou") — sem filhos, é só a linha. |
|