@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,85 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
Compõe Carousel>CarouselContent>CarouselItem, com CarouselPrevious/CarouselNext pra navegar. As setas ficam fora do trilho (-left-12/-right-12), então reserve a margem lateral no entorno.
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
<Carousel className="mx-12 w-full max-w-xs">
|
|
7
|
+
<CarouselContent>
|
|
8
|
+
<CarouselItem>
|
|
9
|
+
<div className="flex h-32 items-center justify-center rounded-lg border bg-card text-sm">
|
|
10
|
+
Sessão #128
|
|
11
|
+
</div>
|
|
12
|
+
</CarouselItem>
|
|
13
|
+
<CarouselItem>
|
|
14
|
+
<div className="flex h-32 items-center justify-center rounded-lg border bg-card text-sm">
|
|
15
|
+
Sessão #131
|
|
16
|
+
</div>
|
|
17
|
+
</CarouselItem>
|
|
18
|
+
<CarouselItem>
|
|
19
|
+
<div className="flex h-32 items-center justify-center rounded-lg border bg-card text-sm">
|
|
20
|
+
Sessão #134
|
|
21
|
+
</div>
|
|
22
|
+
</CarouselItem>
|
|
23
|
+
</CarouselContent>
|
|
24
|
+
<CarouselPrevious />
|
|
25
|
+
<CarouselNext />
|
|
26
|
+
</Carousel>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Vários por vista
|
|
30
|
+
|
|
31
|
+
O basis do CarouselItem decide quantos cabem na vista — basis-1/3 mostra três slides por vez. Bom pra galeria de workspaces ou repositórios.
|
|
32
|
+
|
|
33
|
+
```tsx preview
|
|
34
|
+
<Carousel className="mx-12 w-full max-w-sm" opts={{ align: 'start' }}>
|
|
35
|
+
<CarouselContent>
|
|
36
|
+
{['Sessão #128', 'Sessão #131', 'Sessão #134', 'Sessão #137', 'Sessão #140'].map((session) => (
|
|
37
|
+
<CarouselItem key={session} className="basis-1/3">
|
|
38
|
+
<div className="flex h-24 items-center justify-center rounded-lg border bg-card text-xs">
|
|
39
|
+
{session}
|
|
40
|
+
</div>
|
|
41
|
+
</CarouselItem>
|
|
42
|
+
))}
|
|
43
|
+
</CarouselContent>
|
|
44
|
+
<CarouselPrevious />
|
|
45
|
+
<CarouselNext />
|
|
46
|
+
</Carousel>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Vertical
|
|
50
|
+
|
|
51
|
+
orientation=vertical empilha os slides; as setas migram pra cima e pra baixo (-top-12/-bottom-12). Dê uma altura ao CarouselContent pra delimitar a vista.
|
|
52
|
+
|
|
53
|
+
```tsx preview
|
|
54
|
+
<Carousel className="w-full max-w-xs" orientation="vertical">
|
|
55
|
+
<CarouselContent className="h-48">
|
|
56
|
+
<CarouselItem>
|
|
57
|
+
<div className="flex h-24 items-center justify-center rounded-lg border bg-card text-sm">
|
|
58
|
+
developer
|
|
59
|
+
</div>
|
|
60
|
+
</CarouselItem>
|
|
61
|
+
<CarouselItem>
|
|
62
|
+
<div className="flex h-24 items-center justify-center rounded-lg border bg-card text-sm">
|
|
63
|
+
reviewer
|
|
64
|
+
</div>
|
|
65
|
+
</CarouselItem>
|
|
66
|
+
<CarouselItem>
|
|
67
|
+
<div className="flex h-24 items-center justify-center rounded-lg border bg-card text-sm">
|
|
68
|
+
designer
|
|
69
|
+
</div>
|
|
70
|
+
</CarouselItem>
|
|
71
|
+
</CarouselContent>
|
|
72
|
+
<CarouselPrevious />
|
|
73
|
+
<CarouselNext />
|
|
74
|
+
</Carousel>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Props
|
|
78
|
+
|
|
79
|
+
| Prop | Tipo | Default | Descrição |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| `orientation (Carousel)` | `'horizontal' \| 'vertical'` | `'horizontal'` | Eixo do deslize — vertical empilha os slides e gira as setas pro topo/base. |
|
|
82
|
+
| `opts (Carousel)` | `CarouselOptions` | | Opções do embla (ex.: { loop: true }, { align: "start" }). Repassadas direto pro motor. |
|
|
83
|
+
| `setApi (Carousel)` | `(api: CarouselApi) => void` | | Recebe a instância do embla pra controlar de fora (scrollTo, ler o slide ativo). |
|
|
84
|
+
| `plugins (Carousel)` | `CarouselPlugin` | | Plugins do embla (ex.: autoplay) anexados ao carousel. |
|
|
85
|
+
| `className (CarouselItem)` | `string` | | O basis decide quantos slides cabem na vista (basis-full, basis-1/2, basis-1/3). |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
Um chat mínimo: lista de mensagens + composer. A conversa é gerenciada por dentro (estado, loading, auto-scroll; **Enter** envia, **Shift+Enter** quebra linha) — a inteligência vem da prop `send`. O `greeting` é o estado vazio (centrado; some quando a conversa começa e NÃO entra no transcript). Dê altura ao container.
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
<div className="h-96 rounded-lg border">
|
|
7
|
+
<Chat
|
|
8
|
+
greeting="Oi! Sou o assistente. Pergunte algo."
|
|
9
|
+
send={async (messages) => {
|
|
10
|
+
await new Promise((r) => setTimeout(r, 500))
|
|
11
|
+
const last = messages[messages.length - 1]
|
|
12
|
+
return `Você disse: "${last.content}". (Num app real, aqui rodaria o agente.)`
|
|
13
|
+
}}
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## O backend (o agente)
|
|
19
|
+
|
|
20
|
+
O `send` posta a conversa no seu endpoint, que resolve o usuário e roda o agente sobre as actions `ai:enabled` — **como o usuário logado**:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// Rota do app (server): recebe { messages }, devolve { reply }.
|
|
24
|
+
async function chatRoute(req) {
|
|
25
|
+
const base = await auth.resolveContext(req) // quem é o usuário
|
|
26
|
+
const { messages } = req.body
|
|
27
|
+
const { text } = await runtime.aiFor(base)!.run(messages) // o agente age nas actions
|
|
28
|
+
return { reply: text }
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// No cliente, o send bate nessa rota:
|
|
34
|
+
<Chat send={async (messages) => (await api.post('/chat', { messages })).reply} />
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
O modelo só faz o que o usuário pode (`ctx.can`); o que é destrutivo pede `confirm`. Para expor as actions ao agente, marque-as com `ai: { enabled: true }` no contrato — ver o recurso **IA generativa**.
|
|
38
|
+
|
|
39
|
+
## Streaming — eventos de conversa
|
|
40
|
+
|
|
41
|
+
O `send` também aceita devolver um **`AsyncIterable<ChatEvent>`** (o protocolo de
|
|
42
|
+
`docs/chat-event-protocol.md`): o texto chega incremental, o tool em uso vira o
|
|
43
|
+
**indicador vivo** (pontinhos + rótulo humano — nunca uma mensagem no transcript) e
|
|
44
|
+
artefatos produzidos na conversa rendem pelo `renderArtifact` do app. O backend liga em
|
|
45
|
+
`runtime.aiFor(base).runStream(...)` e streama por SSE; o `Promise<string>` de sempre
|
|
46
|
+
continua valendo — é o caso degenerado do protocolo.
|
|
47
|
+
|
|
48
|
+
```tsx preview
|
|
49
|
+
<div className="h-96 rounded-lg border">
|
|
50
|
+
<Chat
|
|
51
|
+
greeting="Oi! Pergunte algo — a resposta chega em streaming."
|
|
52
|
+
send={async function* (messages) {
|
|
53
|
+
yield { type: 'tool', name: 'query' }
|
|
54
|
+
await new Promise((r) => setTimeout(r, 600))
|
|
55
|
+
const last = messages[messages.length - 1]
|
|
56
|
+
for (const word of `Você disse: "${last.content}".`.split(' ')) {
|
|
57
|
+
yield { type: 'text', delta: word + ' ' }
|
|
58
|
+
await new Promise((r) => setTimeout(r, 80))
|
|
59
|
+
}
|
|
60
|
+
yield { type: 'artifact', kind: 'url', ref: 'https://opus.softize.com.br', title: 'Um artefato da conversa' }
|
|
61
|
+
yield { type: 'done', ok: true }
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Reidratação: passe `initialMessages` com o histórico persistido e troque a `key` do
|
|
68
|
+
componente ao trocar de conversa. O rótulo do indicador é customizável por
|
|
69
|
+
`humanizeTool={(name) => '…'}`.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
Sempre em par com Label (htmlFor↔id) — clicar no texto alterna a caixa. defaultChecked pro modo não controlado.
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
<div className="flex items-center gap-2">
|
|
7
|
+
<Checkbox id="run-check" defaultChecked />
|
|
8
|
+
<Label htmlFor="run-check">Rodar opus check antes do handoff</Label>
|
|
9
|
+
</div>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Controlado
|
|
13
|
+
|
|
14
|
+
onCheckedChange recebe boolean | 'indeterminate' — compare com true pra guardar um boolean.
|
|
15
|
+
|
|
16
|
+
```tsx preview
|
|
17
|
+
const [autoReview, setAutoReview] = useState(true)
|
|
18
|
+
|
|
19
|
+
render(
|
|
20
|
+
<div className="flex items-center gap-2">
|
|
21
|
+
<Checkbox
|
|
22
|
+
id="auto-review"
|
|
23
|
+
checked={autoReview}
|
|
24
|
+
onCheckedChange={(v) => setAutoReview(v === true)}
|
|
25
|
+
/>
|
|
26
|
+
<Label htmlFor="auto-review">Acionar o revisor ao abrir o handoff</Label>
|
|
27
|
+
</div>,
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Lista de opções
|
|
32
|
+
|
|
33
|
+
Várias caixas, um estado: o conjunto marcado é a lista de valores — o padrão pra anexar skills a um agente.
|
|
34
|
+
|
|
35
|
+
```tsx preview col-start
|
|
36
|
+
const [skills, setSkills] = useState(['clean-code', 'test'])
|
|
37
|
+
const toggleSkill = (skill: string): void => {
|
|
38
|
+
setSkills((prev) => (prev.includes(skill) ? prev.filter((s) => s !== skill) : [...prev, skill]))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
render(
|
|
42
|
+
<>
|
|
43
|
+
{['clean-code', 'code-style', 'ddd', 'test'].map((skill) => (
|
|
44
|
+
<div key={skill} className="flex items-center gap-2">
|
|
45
|
+
<Checkbox id={skill} checked={skills.includes(skill)} onCheckedChange={() => toggleSkill(skill)} />
|
|
46
|
+
<Label htmlFor={skill}>{skill}</Label>
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</>,
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Desabilitado
|
|
54
|
+
|
|
55
|
+
disabled esmaece a caixa e o rótulo em par (peer-disabled no Label) — marcado ou não.
|
|
56
|
+
|
|
57
|
+
```tsx preview col-start
|
|
58
|
+
<div className="flex items-center gap-2">
|
|
59
|
+
<Checkbox id="sync-multica" disabled />
|
|
60
|
+
<Label htmlFor="sync-multica">Sincronizar com o Multica</Label>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="flex items-center gap-2">
|
|
63
|
+
<Checkbox id="opus" disabled defaultChecked />
|
|
64
|
+
<Label htmlFor="opus">Opus inicializado</Label>
|
|
65
|
+
</div>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Props
|
|
69
|
+
|
|
70
|
+
| Prop | Tipo | Default | Descrição |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| `checked` | `boolean \| 'indeterminate'` | | O estado, no modo controlado — parear com onCheckedChange. |
|
|
73
|
+
| `onCheckedChange` | `(checked: boolean \| 'indeterminate') => void` | | Chamado a cada alternância. Pra guardar um boolean, compare com true. |
|
|
74
|
+
| `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). |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: CLI opus
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# CLI opus
|
|
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 pros agentes via MCP.
|
|
9
|
+
|
|
10
|
+
## Gates
|
|
11
|
+
|
|
12
|
+
> A régua de entrega: 0 violação antes de qualquer handoff. Rode no CI e antes de commitar.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
opus check src # valida as convenções das actions (exit ≠ 0 se violar)
|
|
16
|
+
opus db check # drift entidade ↔ banco (read-only)
|
|
17
|
+
opus db migrate # aplica o schema idempotente + drift-check na sequência
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Geração e introspecção
|
|
21
|
+
|
|
22
|
+
> As declarações (`description` de entidades/actions) são a fonte; o `gen` as projeta. O manifest
|
|
23
|
+
> é um lockfile commitado — diff de manifest é ouro pra review.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
opus gen # manifest / openapi / docs / stubs a partir do opus.config.ts
|
|
27
|
+
opus introspect # modelo da estrutura (actions/reactions/schedules + wiring)
|
|
28
|
+
opus introspect --json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Bootstrap e templates
|
|
32
|
+
|
|
33
|
+
> `create` scaffolda um app novo com os pré-requisitos plugados; `setup` é per-app
|
|
34
|
+
> (idempotente, nunca sobrescreve o seu); `add` copia um template do registry pro consumer.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
opus create meu-app # app canônico do zero: protocolo + UI + preview + dia zero
|
|
38
|
+
opus create meu-cliente --monorepo # a RAIZ de um workspace (apps/* + packages/*)
|
|
39
|
+
opus create apps/portal # dentro de um workspace: só o app (modo detectado)
|
|
40
|
+
opus setup # grava opus.json, mantém o bloco do CLAUDE.md e o dia zero
|
|
41
|
+
opus list # lista os templates disponíveis
|
|
42
|
+
opus add action-form # copia um template do registry pro projeto
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
O esqueleto do `create` versiona com o Opus (sai do mesmo pacote que o SDK que ele
|
|
46
|
+
configura) e nasce com os gates verdes: domínio-exemplo canônico, teste, manifest,
|
|
47
|
+
CI de fábrica e o dev server pronto pro preview do Maestro. Dois modos, por detecção:
|
|
48
|
+
repo standalone (template inteiro) ou **app em monorepo** (dentro de um workspace pnpm:
|
|
49
|
+
só os arquivos do app; o que a raiz precisa ter vira aviso, sem clobber).
|
|
50
|
+
|
|
51
|
+
## MCP — estado vivo pros agentes
|
|
52
|
+
|
|
53
|
+
> O server MCP expõe `introspect`/`check`/`create-action`. É como um agente lê a estrutura e cria
|
|
54
|
+
> action no formato canônico sem decorar convenção.
|
|
55
|
+
|
|
56
|
+
`opus mcp` sobe o server (configurado em `.mcp.json`): o agente consulta a estrutura, roda o
|
|
57
|
+
check e gera o esqueleto de uma action nova pela skill `create-action` — a mesma régua do
|
|
58
|
+
`opus check`, por construção.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
CollapsibleTrigger alterna o CollapsibleContent — o Trigger já é o <button>. defaultOpen deixa o estado com o componente.
|
|
4
|
+
|
|
5
|
+
```tsx preview col
|
|
6
|
+
<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" />
|
|
10
|
+
</CollapsibleTrigger>
|
|
11
|
+
<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>
|
|
15
|
+
</CollapsibleContent>
|
|
16
|
+
</Collapsible>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Controlado
|
|
20
|
+
|
|
21
|
+
open + onOpenChange põem o estado nas suas mãos — dá pra refletir no gatilho (aqui o chevron gira) ou guardar a preferência.
|
|
22
|
+
|
|
23
|
+
```tsx preview col
|
|
24
|
+
const [open, setOpen] = useState(false)
|
|
25
|
+
|
|
26
|
+
render(
|
|
27
|
+
<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'} />
|
|
31
|
+
</CollapsibleTrigger>
|
|
32
|
+
<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>
|
|
36
|
+
</CollapsibleContent>
|
|
37
|
+
</Collapsible>,
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Desabilitado
|
|
42
|
+
|
|
43
|
+
disabled no Collapsible trava o gatilho — a seção fica fixa no estado atual (aqui, fechada).
|
|
44
|
+
|
|
45
|
+
```tsx preview col
|
|
46
|
+
<Collapsible disabled className="w-full max-w-sm space-y-2">
|
|
47
|
+
<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" />
|
|
50
|
+
</CollapsibleTrigger>
|
|
51
|
+
<CollapsibleContent className="px-3 text-sm text-muted-foreground">
|
|
52
|
+
<p>Nada para mostrar enquanto o workspace está em sincronização.</p>
|
|
53
|
+
</CollapsibleContent>
|
|
54
|
+
</Collapsible>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Props
|
|
58
|
+
|
|
59
|
+
| Prop | Tipo | Default | Descrição |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| `defaultOpen (Collapsible)` | `boolean` | `false` | Estado inicial no modo não controlado. |
|
|
62
|
+
| `open (Collapsible)` | `boolean` | | Estado no modo controlado — pareie com onOpenChange. |
|
|
63
|
+
| `onOpenChange (Collapsible)` | `(open: boolean) => void` | | Chamado a cada abertura ou fechamento. |
|
|
64
|
+
| `disabled (Collapsible)` | `boolean` | `false` | Trava o gatilho — a seção fica presa no estado atual. |
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
## Lista filtrável inline
|
|
2
|
+
|
|
3
|
+
Digite pra filtrar — navegação por teclado, grupos e CommandEmpty de graça (cmdk). É a base do Select buscável; pra escolha em form, use o Select.
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
<Command className="max-w-sm rounded-lg border border-border">
|
|
7
|
+
<CommandInput placeholder="Buscar sessão ou ação…" />
|
|
8
|
+
<CommandList>
|
|
9
|
+
<CommandEmpty>Nada encontrado.</CommandEmpty>
|
|
10
|
+
<CommandGroup heading="Sessões">
|
|
11
|
+
<CommandItem><FolderGit2 /> Onboarding do faturamento</CommandItem>
|
|
12
|
+
<CommandItem><FolderGit2 /> Integração com o ERP</CommandItem>
|
|
13
|
+
</CommandGroup>
|
|
14
|
+
<CommandSeparator />
|
|
15
|
+
<CommandGroup heading="Ações">
|
|
16
|
+
<CommandItem><Plus /> Nova sessão <CommandShortcut>⌘N</CommandShortcut></CommandItem>
|
|
17
|
+
<CommandItem><Play /> Iniciar ambiente</CommandItem>
|
|
18
|
+
</CommandGroup>
|
|
19
|
+
</CommandList>
|
|
20
|
+
</Command>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Palette modal (CommandDialog)
|
|
24
|
+
|
|
25
|
+
O Command embrulhado num Dialog, com header sr-only pra a11y. O atalho ⌘K (keydown no app) só troca o open — o conteúdo é o mesmo do inline.
|
|
26
|
+
|
|
27
|
+
```tsx preview
|
|
28
|
+
const [open, setOpen] = useState(false)
|
|
29
|
+
|
|
30
|
+
render(
|
|
31
|
+
<>
|
|
32
|
+
<Button variant="outline" onClick={() => setOpen(true)}>
|
|
33
|
+
Abrir palette <CommandShortcut>⌘K</CommandShortcut>
|
|
34
|
+
</Button>
|
|
35
|
+
<CommandDialog open={open} onOpenChange={setOpen}>
|
|
36
|
+
<CommandInput placeholder="Buscar comando…" />
|
|
37
|
+
<CommandList>
|
|
38
|
+
<CommandEmpty>Nada encontrado.</CommandEmpty>
|
|
39
|
+
<CommandGroup heading="IA">
|
|
40
|
+
<CommandItem onSelect={() => setOpen(false)}><Bot /> Chamar agente</CommandItem>
|
|
41
|
+
<CommandItem onSelect={() => setOpen(false)}><Sparkles /> Importar skill</CommandItem>
|
|
42
|
+
</CommandGroup>
|
|
43
|
+
</CommandList>
|
|
44
|
+
</CommandDialog>
|
|
45
|
+
</>,
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Props
|
|
50
|
+
|
|
51
|
+
| Prop | Tipo | Default | Descrição |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| `CommandDialog.open / onOpenChange` | `boolean / (open: boolean) => void` | | Controle do modal — ligue ao atalho de teclado do app. |
|
|
54
|
+
| `CommandDialog.title / description` | `string` | `'Comandos' / 'Busque um comando pra executar.'` | Texto sr-only do header (a11y do Dialog) — não aparece na tela. |
|
|
55
|
+
| `CommandDialog.showCloseButton` | `boolean` | `true` | Mostra o X do Dialog — desligue se o Esc/clique fora bastarem. |
|
|
56
|
+
| `CommandItem.onSelect` | `(value: string) => void` | | Dispara ao escolher (Enter ou clique) — feche o palette aqui. |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
A caixa de escrever da casa: textarea numa pílula elevada (`rounded-card` + `ring-edge` + `shadow-card`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/components/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ê.
|
|
4
|
+
|
|
5
|
+
```tsx preview col
|
|
6
|
+
const [text, setText] = React.useState('')
|
|
7
|
+
|
|
8
|
+
render(
|
|
9
|
+
<Composer
|
|
10
|
+
value={text}
|
|
11
|
+
onChange={setText}
|
|
12
|
+
onSubmit={() => setText('')}
|
|
13
|
+
placeholder="Escreva uma mensagem…"
|
|
14
|
+
/>,
|
|
15
|
+
)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Com seletores (barra de ações)
|
|
19
|
+
|
|
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.
|
|
21
|
+
|
|
22
|
+
```tsx preview col
|
|
23
|
+
const [text, setText] = React.useState('')
|
|
24
|
+
const [app, setApp] = React.useState('admin')
|
|
25
|
+
|
|
26
|
+
render(
|
|
27
|
+
<Composer
|
|
28
|
+
value={text}
|
|
29
|
+
onChange={setText}
|
|
30
|
+
onSubmit={() => setText('')}
|
|
31
|
+
submitDisabled={app === ''}
|
|
32
|
+
rows={3}
|
|
33
|
+
placeholder="Descreva o que essa sessão vai fazer…"
|
|
34
|
+
actions={
|
|
35
|
+
<Menu>
|
|
36
|
+
<MenuTrigger asChild>
|
|
37
|
+
<Button variant="ghost" size="sm" className="gap-1 text-muted-foreground">
|
|
38
|
+
{app === 'admin' ? 'Admin' : 'Copilot'}
|
|
39
|
+
<ChevronDown className="size-3.5 opacity-60" />
|
|
40
|
+
</Button>
|
|
41
|
+
</MenuTrigger>
|
|
42
|
+
<MenuContent align="start">
|
|
43
|
+
<MenuItem onSelect={() => setApp('admin')}>Admin</MenuItem>
|
|
44
|
+
<MenuItem onSelect={() => setApp('copilot')}>Copilot</MenuItem>
|
|
45
|
+
</MenuContent>
|
|
46
|
+
</Menu>
|
|
47
|
+
}
|
|
48
|
+
/>,
|
|
49
|
+
)
|
|
50
|
+
```
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
## O trio imperativo: `dialog`
|
|
2
|
+
|
|
3
|
+
`dialog.alert` · `dialog.confirm` · `dialog.prompt` — o trio do browser (`alert`/`confirm`/`prompt`), mas **assíncrono, com tema e a11y-correto**. Cada um pergunta e **espera a resposta**:
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
await dialog.alert({ title: 'Sessão expirada', description: 'Você será desconectado.' }) // Promise<void>
|
|
7
|
+
const ok = await dialog.confirm({ title: 'Excluir a sessão?', variant: 'destructive' }) // Promise<boolean>
|
|
8
|
+
const nome = await dialog.prompt({ title: 'Nome do snapshot', placeholder: 'ex.: v1-final' }) // Promise<string | null>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
É a mesma forma do `toast` (superfície imperativa, host no shell), mas que **responde** — e é isso que tira as ~15 linhas de JSX que cada tela montava. Por baixo é o `AlertDialog`: `role="alertdialog"`, foco preso, **não fecha no clique fora** — a semântica de uma interrupção que EXIGE resposta. Fechar por ESC/Cancelar resolve o "não" de cada tipo (`void`, `false`, `null`).
|
|
12
|
+
|
|
13
|
+
```tsx preview
|
|
14
|
+
function Demo() {
|
|
15
|
+
const [last, setLast] = useState('—')
|
|
16
|
+
return (
|
|
17
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
18
|
+
<DialogHost />
|
|
19
|
+
<Button variant="outline" size="sm" onClick={async () => {
|
|
20
|
+
await dialog.alert({ title: 'Sessão expirada', description: 'Você será desconectado.' })
|
|
21
|
+
setLast('alert → OK')
|
|
22
|
+
}}>alert</Button>
|
|
23
|
+
<Button variant="outline" size="sm" onClick={async () => {
|
|
24
|
+
const ok = await dialog.confirm({ title: 'Excluir a sessão?', description: 'Some o worktree e o preview.', action: 'Excluir', variant: 'destructive' })
|
|
25
|
+
setLast('confirm → ' + (ok ? 'excluída' : 'mantida'))
|
|
26
|
+
}}>confirm</Button>
|
|
27
|
+
<Button variant="outline" size="sm" onClick={async () => {
|
|
28
|
+
const name = await dialog.prompt({ title: 'Nome do snapshot', placeholder: 'ex.: v1-final' })
|
|
29
|
+
setLast('prompt → ' + (name === null ? 'cancelado' : name))
|
|
30
|
+
}}>prompt</Button>
|
|
31
|
+
<span className="text-sm text-muted-foreground">Última: {last}</span>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
render(<Demo />)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Por que namespace, e não `confirm()` solto
|
|
39
|
+
|
|
40
|
+
`alert`/`confirm`/`prompt` são **globais do browser**. Como função solta, esquecer o import não daria erro — o TypeScript resolveria pro global e o código abriria a caixa cinza do sistema (sem tema, que webview suprime). `window.dialog` não existe, então o namespace **imuniza** a família contra esse footgun. `confirm()` segue exportado como alias (retrocompat), mas o novo código usa `dialog.confirm`.
|
|
41
|
+
|
|
42
|
+
## `body`: corpo com conteúdo próprio
|
|
43
|
+
|
|
44
|
+
Os três aceitam `body` — conteúdo que a `description` de texto não expressa (uma lista do que será afetado, um detalhe destacado). É o que antes obrigava a compor o `AlertDialog` à mão:
|
|
45
|
+
|
|
46
|
+
```tsx preview
|
|
47
|
+
function Demo() {
|
|
48
|
+
const [status, setStatus] = useState('—')
|
|
49
|
+
return (
|
|
50
|
+
<div className="flex items-center gap-3">
|
|
51
|
+
<DialogHost />
|
|
52
|
+
<Button variant="outline" size="sm" onClick={async () => {
|
|
53
|
+
const ok = await dialog.confirm({
|
|
54
|
+
title: 'Encerrar 3 sessões?',
|
|
55
|
+
description: 'Os agentes abaixo serão interrompidos:',
|
|
56
|
+
body: (
|
|
57
|
+
<ul className="list-disc space-y-1 pl-5 text-sm text-muted-foreground">
|
|
58
|
+
<li><span className="font-medium text-foreground">developer</span> — empresa-x-api</li>
|
|
59
|
+
<li><span className="font-medium text-foreground">reviewer</span> — empresa-x-web</li>
|
|
60
|
+
</ul>
|
|
61
|
+
),
|
|
62
|
+
action: 'Encerrar todas',
|
|
63
|
+
})
|
|
64
|
+
setStatus(ok ? 'encerradas' : 'mantidas')
|
|
65
|
+
}}>Encerrar sessões</Button>
|
|
66
|
+
<span className="text-sm text-muted-foreground">Resposta: {status}</span>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
render(<Demo />)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## O host
|
|
74
|
+
|
|
75
|
+
Monte **um** `<DialogHost />` no shell do app, ao lado do `<Toaster />`:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<AppShell sidebar={…}>
|
|
79
|
+
{rotas}
|
|
80
|
+
<Toaster />
|
|
81
|
+
<DialogHost />
|
|
82
|
+
</AppShell>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Sem ele, os três **lançam** — em vez de devolver uma promise que nunca resolve. Promise pendurada viraria clique sem efeito, o pior desfecho pra uma interrupção que exige resposta: a pessoa acha que respondeu, ou clica de novo. (`<ConfirmHost />` segue valendo como alias de `<DialogHost />`.)
|
|
86
|
+
|
|
87
|
+
Várias perguntas seguidas entram numa **fila** e aparecem uma por vez — cada `await` recebe a resposta do SEU diálogo (duas caixas empilhadas seriam ambíguas sobre qual clique respondeu o quê).
|
|
88
|
+
|
|
89
|
+
## Quando NÃO usar
|
|
90
|
+
|
|
91
|
+
- **Excluir uma entidade por contrato** → `ActionTrigger` com `icon={<Trash2 />}`: lê o `action.confirm`, dispara a action e fala o erro de negócio.
|
|
92
|
+
- **Um form de verdade** (validação, vários campos, submete uma action) → `ActionFormDialog`. O `dialog.prompt` é só pra UMA string livre.
|
|
93
|
+
- **Mais de duas ações** (ex.: Salvar / Descartar / Cancelar) ou **layout totalmente custom** → componha o `AlertDialog` à mão.
|
|
94
|
+
- **Só avisar, sem cobrar decisão** → `toast` (passageiro) ou `Alert` (fica na página). O `dialog.alert` é pro aviso que **bloqueia** e exige um "OK".
|
|
95
|
+
|
|
96
|
+
## Opções
|
|
97
|
+
|
|
98
|
+
Comuns aos três (`dialog.alert`/`confirm`/`prompt`):
|
|
99
|
+
|
|
100
|
+
| Opção | Tipo | Default | Descrição |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| `title` | `React.ReactNode` | | O enunciado. Obrigatório. |
|
|
103
|
+
| `description` | `React.ReactNode` | | O que a pessoa precisa saber pra decidir — a CONSEQUÊNCIA, não o óbvio. |
|
|
104
|
+
| `body` | `React.ReactNode` | | Conteúdo próprio entre a descrição e os botões (lista, detalhe). |
|
|
105
|
+
| `media` | `React.ReactNode` | | Ícone grande acima do título (o quadro do `AlertDialogMedia`). |
|
|
106
|
+
| `action` | `string` | `'OK'` / `'Confirmar'` | Rótulo do botão que confirma (default `'OK'` no alert, `'Confirmar'` nos outros). |
|
|
107
|
+
|
|
108
|
+
Só `confirm` e `prompt`:
|
|
109
|
+
|
|
110
|
+
| Opção | Tipo | Default | Descrição |
|
|
111
|
+
|---|---|---|---|
|
|
112
|
+
| `cancel` | `string` | `'Cancelar'` | Rótulo do botão que recusa. |
|
|
113
|
+
| `variant` | `'default' \| 'destructive'` | `'default'` | `destructive` pinta o confirmar — o que apaga tem que parecer o que apaga. |
|
|
114
|
+
|
|
115
|
+
Só `prompt`:
|
|
116
|
+
|
|
117
|
+
| Opção | Tipo | Default | Descrição |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| `placeholder` | `string` | | Texto-fantasma do input. |
|
|
120
|
+
| `defaultValue` | `string` | | Valor inicial (o input abre focado e selecionado). |
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5s. Bom pra toolbar ou célula estreita, ao lado de um ID/token/slug.
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
<Copyable value="opus_sk_1a2b3c4d5e6f" className="text-muted-foreground hover:text-foreground" />
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Com rótulo
|
|
10
|
+
|
|
11
|
+
Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar no conjunto copia. Componha o chrome (borda, mono) por fora, via `className`.
|
|
12
|
+
|
|
13
|
+
```tsx preview
|
|
14
|
+
<Copyable
|
|
15
|
+
value="softize-dev/opus"
|
|
16
|
+
className="rounded-md border px-2 py-1 font-mono text-xs text-muted-foreground hover:text-foreground"
|
|
17
|
+
>
|
|
18
|
+
softize-dev/opus
|
|
19
|
+
</Copyable>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Duração do feedback
|
|
23
|
+
|
|
24
|
+
`feedbackMs` ajusta quanto o check dura (default 1500). Some no-op silencioso se o clipboard não existir (contexto inseguro/SSR).
|
|
25
|
+
|
|
26
|
+
```tsx preview
|
|
27
|
+
<Copyable value="copiado devagar" feedbackMs={3000} className="text-muted-foreground hover:text-foreground">
|
|
28
|
+
copiar (3s)
|
|
29
|
+
</Copyable>
|
|
30
|
+
```
|