@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,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MCP
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# MCP
|
|
6
|
+
|
|
7
|
+
Expõe as actions `ai:enabled` do runtime como **tools MCP** — a porta pra uma IA de **fora**
|
|
8
|
+
(Claude Desktop, o agente de um parceiro, um hub próprio) alcançar o app pelo protocolo. É o
|
|
9
|
+
mesmo bridge do agente co-locado (`runtime.aiTools`) e a mesma execução (`runtime.execute`,
|
|
10
|
+
como o usuário resolvido). O MCP é só o transporte.
|
|
11
|
+
|
|
12
|
+
## Montar
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createOpusMcpServer } from '@softize/opus/mcp'
|
|
16
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
17
|
+
|
|
18
|
+
const server = createOpusMcpServer(runtime, {
|
|
19
|
+
name: 'meu-app',
|
|
20
|
+
// Valida a identidade da requisição (IdP) → contexto do runtime. Sem isso, roda anônimo
|
|
21
|
+
// (o `can` nega → só actions públicas). Com isso, o agente age COMO O USUÁRIO.
|
|
22
|
+
resolveContext: async (extra) => auth.resolveFromMcp(extra),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
await server.connect(new StdioServerTransport()) // local; HTTP/SSE pra remoto
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`ListTools` devolve as actions `ai:enabled` (nome + descrição + JSON Schema do input);
|
|
29
|
+
`CallTool` executa a action pelo `runtime.execute` — validação, auth (`ctx.can`) e audit,
|
|
30
|
+
tudo igual a uma chamada normal. Read-only? Marque só actions de leitura com `ai:enabled`.
|
|
31
|
+
|
|
32
|
+
## Interno × MCP
|
|
33
|
+
|
|
34
|
+
- **Monolito / chat co-locado:** não precisa de MCP — `runtime.aiFor(base).run()` chama as
|
|
35
|
+
actions direto, in-process (ver o recurso **IA generativa**).
|
|
36
|
+
- **IA por fora / ecossistema:** o agente vive num serviço próprio e alcança N apps pela
|
|
37
|
+
mesma porta MCP. É aqui que ele se paga.
|
|
38
|
+
|
|
39
|
+
Mesmas tools, transportes diferentes.
|
|
40
|
+
|
|
41
|
+
## Limites (por enquanto)
|
|
42
|
+
|
|
43
|
+
Você monta o transporte (stdio/HTTP) — o server é agnóstico. A confirmação de action
|
|
44
|
+
`destructive` sobre MCP (elicitation) entra quando um caso real cobrar; comece read-only.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
## Menu de ações
|
|
2
|
+
|
|
3
|
+
Ícone à esquerda, atalho à direita (MenuShortcut), separador antes da zona perigosa e variant destructive na ação que destrói.
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
<Menu>
|
|
7
|
+
<MenuTrigger asChild>
|
|
8
|
+
<Button variant="outline">Ações <ChevronDown /></Button>
|
|
9
|
+
</MenuTrigger>
|
|
10
|
+
<MenuContent align="start">
|
|
11
|
+
<MenuItem><Pencil /> Editar <MenuShortcut>⌘E</MenuShortcut></MenuItem>
|
|
12
|
+
<MenuItem><Copy /> Duplicar</MenuItem>
|
|
13
|
+
<MenuSeparator />
|
|
14
|
+
<MenuItem variant="destructive"><Trash2 /> Excluir</MenuItem>
|
|
15
|
+
</MenuContent>
|
|
16
|
+
</Menu>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Contexto: clique direito
|
|
20
|
+
|
|
21
|
+
O papel do ContextMenu (aposentado na 5.0.0) com o MESMO componente — só muda o
|
|
22
|
+
gatilho: controlado, ancorado no ponteiro. `onContextMenu` guarda a posição e abre;
|
|
23
|
+
um gatilho invisível `position: fixed` naquele ponto ancora o conteúdo.
|
|
24
|
+
|
|
25
|
+
```tsx preview
|
|
26
|
+
const [pos, setPos] = useState(null)
|
|
27
|
+
|
|
28
|
+
render(
|
|
29
|
+
<div>
|
|
30
|
+
<div
|
|
31
|
+
onContextMenu={(e) => {
|
|
32
|
+
e.preventDefault()
|
|
33
|
+
setPos({ x: e.clientX, y: e.clientY })
|
|
34
|
+
}}
|
|
35
|
+
className="flex h-28 items-center justify-center rounded-md border border-dashed text-sm text-muted-foreground select-none"
|
|
36
|
+
>
|
|
37
|
+
clique direito aqui
|
|
38
|
+
</div>
|
|
39
|
+
<Menu open={pos !== null} onOpenChange={(open) => { if (!open) setPos(null) }}>
|
|
40
|
+
<MenuTrigger asChild>
|
|
41
|
+
<span style={{ position: 'fixed', left: pos?.x ?? 0, top: pos?.y ?? 0 }} />
|
|
42
|
+
</MenuTrigger>
|
|
43
|
+
<MenuContent align="start">
|
|
44
|
+
<MenuItem><Pencil /> Renomear</MenuItem>
|
|
45
|
+
<MenuItem><Copy /> Duplicar</MenuItem>
|
|
46
|
+
<MenuSeparator />
|
|
47
|
+
<MenuItem variant="destructive"><Trash2 /> Excluir</MenuItem>
|
|
48
|
+
</MenuContent>
|
|
49
|
+
</Menu>
|
|
50
|
+
</div>,
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Seleção: checkbox e radio
|
|
55
|
+
|
|
56
|
+
CheckboxItem pra liga/desliga, RadioGroup pra escolha exclusiva — estado fica no consumidor (controlado).
|
|
57
|
+
|
|
58
|
+
```tsx preview
|
|
59
|
+
const [showArchived, setShowArchived] = useState(false)
|
|
60
|
+
const [order, setOrder] = useState('recent')
|
|
61
|
+
|
|
62
|
+
render(
|
|
63
|
+
<Menu>
|
|
64
|
+
<MenuTrigger asChild>
|
|
65
|
+
<Button variant="outline">Exibição <ChevronDown /></Button>
|
|
66
|
+
</MenuTrigger>
|
|
67
|
+
<MenuContent align="start">
|
|
68
|
+
<MenuLabel>Sessões</MenuLabel>
|
|
69
|
+
<MenuCheckboxItem checked={showArchived} onCheckedChange={setShowArchived}>
|
|
70
|
+
Mostrar arquivadas
|
|
71
|
+
</MenuCheckboxItem>
|
|
72
|
+
<MenuSeparator />
|
|
73
|
+
<MenuLabel>Ordenar por</MenuLabel>
|
|
74
|
+
<MenuRadioGroup value={order} onValueChange={setOrder}>
|
|
75
|
+
<MenuRadioItem value="recent">Mais recentes</MenuRadioItem>
|
|
76
|
+
<MenuRadioItem value="name">Nome</MenuRadioItem>
|
|
77
|
+
</MenuRadioGroup>
|
|
78
|
+
</MenuContent>
|
|
79
|
+
</Menu>,
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Submenu e inset
|
|
84
|
+
|
|
85
|
+
MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
|
|
86
|
+
|
|
87
|
+
```tsx preview
|
|
88
|
+
<Menu>
|
|
89
|
+
<MenuTrigger asChild>
|
|
90
|
+
<Button variant="outline">Sessão <ChevronDown /></Button>
|
|
91
|
+
</MenuTrigger>
|
|
92
|
+
<MenuContent align="start">
|
|
93
|
+
<MenuItem><ExternalLink /> Abrir preview</MenuItem>
|
|
94
|
+
<MenuSub>
|
|
95
|
+
<MenuSubTrigger><ArrowDownAZ /> Mover pra</MenuSubTrigger>
|
|
96
|
+
<MenuSubContent>
|
|
97
|
+
<MenuItem>Empresa X</MenuItem>
|
|
98
|
+
<MenuItem>Softize</MenuItem>
|
|
99
|
+
</MenuSubContent>
|
|
100
|
+
</MenuSub>
|
|
101
|
+
<MenuItem inset>Renomear</MenuItem>
|
|
102
|
+
</MenuContent>
|
|
103
|
+
</Menu>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Props
|
|
107
|
+
|
|
108
|
+
| Prop | Tipo | Default | Descrição |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| `MenuItem.variant` | `'default' \| 'destructive'` | `'default'` | destructive tonaliza texto e foco em vermelho — só pra ação que destrói. |
|
|
111
|
+
| `MenuItem.inset` | `boolean` | | Recuo à esquerda pra alinhar item sem ícone com os que têm. |
|
|
112
|
+
| `MenuContent.align / sideOffset` | `'start' \| 'center' \| 'end' / number` | `'center' / 4` | Alinhamento e distância em relação ao gatilho (Radix). |
|
|
113
|
+
| `MenuCheckboxItem.checked / onCheckedChange` | `boolean / (checked: boolean) => void` | | Estado do liga/desliga — controlado pelo consumidor. |
|
|
114
|
+
| `MenuRadioGroup.value / onValueChange` | `string / (value: string) => void` | | Escolha exclusiva entre os MenuRadioItem filhos. |
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Microcopy
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Microcopy
|
|
6
|
+
|
|
7
|
+
Código em inglês; o que humano lê (UI, comentário, doc) em pt-BR. Frases começam com
|
|
8
|
+
maiúscula e terminam com ponto; labels curtos em sentence case, sem ponto. **Esta página
|
|
9
|
+
é a fonte** — a skill `code-style` aponta pra cá (regra duplicada em vez de apontada é
|
|
10
|
+
defeito).
|
|
11
|
+
|
|
12
|
+
## Língua por camada
|
|
13
|
+
|
|
14
|
+
> Regra dura — não tem caso a caso.
|
|
15
|
+
|
|
16
|
+
| Camada | Língua |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Identificadores de código (vars, funções, tipos, arquivos, dirs, domínios) | Inglês |
|
|
19
|
+
| Nomes de action / rota | Inglês (`resource.verb`) |
|
|
20
|
+
| Colunas e tabelas de banco | Inglês (snake_case) |
|
|
21
|
+
| Valores de código (enums, status, chaves de dict) | Inglês — mesmo em exemplos e fixtures (`active`, não `ativo`); a UI mostra o label pt |
|
|
22
|
+
| Texto de UI visível (labels, headings, botões, placeholders, mensagens) | pt-BR |
|
|
23
|
+
| Comentários e docs (`.md`) | pt-BR |
|
|
24
|
+
|
|
25
|
+
## Frase vs label
|
|
26
|
+
|
|
27
|
+
> Frase (mensagem, descrição, tooltip, help): maiúscula no início, ponto no fim. Label
|
|
28
|
+
> (botão, heading, item de menu, chip): sentence case, sem ponto.
|
|
29
|
+
|
|
30
|
+
| Tipo | Assim | Assim não |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Frase | Opus não inicializado. | ~~opus nao inicializado~~ |
|
|
33
|
+
| Frase | O Multica não respondeu — tente de novo. | ~~ERRO: request failed (500)~~ |
|
|
34
|
+
| Label | Publicar | ~~PUBLICAR~~ |
|
|
35
|
+
| Label | Nova sessão | ~~Nova sessão.~~ |
|
|
36
|
+
| Placeholder | Selecione um cliente… | ~~Selecionar~~ · ~~digite o nome…~~ |
|
|
37
|
+
|
|
38
|
+
## As regras que mais pegam
|
|
39
|
+
|
|
40
|
+
> Erros recorrentes de revisão — atenção redobrada nestes.
|
|
41
|
+
|
|
42
|
+
- **Maiúscula é ortografia, não estilo.** Toda frase começa com maiúscula — em UI,
|
|
43
|
+
hint/help **e comentário de código** — e nome próprio idem (Engineer, Multica, GitHub).
|
|
44
|
+
Vale com interpolação: mensagem que começaria com valor ganha um artigo na frente
|
|
45
|
+
(`O workspace '${slug}' não existe…`).
|
|
46
|
+
- **Frase fluida, não telegrama.** Símbolo não é conector: nada de `—`/`+`/`≈` amarrando
|
|
47
|
+
ideias em label ou help. `Staff — acesso ao back-office + todos os workspaces` vira
|
|
48
|
+
label `Staff` + help `Tem acesso ao back-office e a todos os workspaces.`.
|
|
49
|
+
- **"Opus" e "Softize" são nomes próprios.** Em prosa, maiúscula (`o Opus`, `a Softize`);
|
|
50
|
+
os tokens técnicos ficam minúsculos: `opus check`, `@softize/opus`, `opus.json`,
|
|
51
|
+
`softize.com.br`. É o nome → maiúscula; é código → minúscula.
|
|
52
|
+
- **Abreviação é último caso.** Por extenso: `mínimo`, `máximo`, `configuração`.
|
|
53
|
+
`Config indisponível.` é duplamente errado (seco E abreviado): `A configuração não
|
|
54
|
+
carregou.`. `Ex.:` em placeholder é tolerado (com `E` maiúsculo).
|
|
55
|
+
- **Placeholder de seleção segue UM padrão** (select simples, buscável, múltiplo):
|
|
56
|
+
`Selecione <artigo> <coisa>…` — com `…` (o caractere único), nunca o genérico
|
|
57
|
+
`Selecionar`. Declare no contrato (`fields.<campo>.placeholder`) — o default do
|
|
58
|
+
componente é rede de segurança, não estilo; dois pickers na mesma tela com padrões
|
|
59
|
+
diferentes é defeito.
|
|
60
|
+
- **Nada de maiúsculo deliberado.** Sem ALL-CAPS — nem a classe `uppercase` — em labels,
|
|
61
|
+
headers ou badges.
|
|
62
|
+
- **Exemplos são neutros e duráveis.** Nada de cliente real ou com cara de real: use
|
|
63
|
+
Empresa X / Empresa Y (`contato@empresa-x.com.br`). Exemplo "de verdade" envelhece,
|
|
64
|
+
vaza contexto e vira dívida.
|
|
65
|
+
|
|
66
|
+
## Tom
|
|
67
|
+
|
|
68
|
+
> Fale com o usuário, não com o log.
|
|
69
|
+
|
|
70
|
+
- **Calmo e informativo, não alarmista.** "Indisponível" em vez de "ERRO"/"FALHOU".
|
|
71
|
+
Não culpe o usuário.
|
|
72
|
+
- **Orientado à ação.** Quando dá, diga o próximo passo: `Inicie o ambiente`,
|
|
73
|
+
`Crie a primeira acima`.
|
|
74
|
+
- **Conciso, mas natural — não telegráfico.** Prefira a forma completa do verbo a um
|
|
75
|
+
corte abrupto. O meio-termo é a régua: nem seco demais (`rode no projeto`) nem manual
|
|
76
|
+
técnico (jargão de arquitetura num help).
|
|
77
|
+
- **Explique pelo efeito, não pelo mecanismo.** Se precisou citar sistema interno pra
|
|
78
|
+
explicar (`o push sobrescreve o runtime`), reescreva pelo que acontece pro usuário
|
|
79
|
+
(`os agentes deste workspace passam a usar esta skill`). Mecanismo é doc/comentário,
|
|
80
|
+
não tooltip.
|
|
81
|
+
- **Nada de endpoint, status ou stack na tela** — detalhe técnico vai pro console/log.
|
|
82
|
+
- **Termos do produto** (sessão, ambiente, cliente, agente), não do código. E o mesmo
|
|
83
|
+
termo sempre: se a UI fala "cliente", nenhum label diz "client".
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
## Esqueleto de página
|
|
2
|
+
|
|
3
|
+
O que toda página do back-office repete, num pattern só: `<main>` + container de largura cheia (o caso comum; `className="max-w-5xl"` estreita e centra) + header com título, descrição e a ação à direita. O conteúdo é children, sem nada imposto. Presentacional de propósito — uma página agrega N fontes; o que é action-driven (listas, forms) mora dentro.
|
|
4
|
+
|
|
5
|
+
```tsx preview col
|
|
6
|
+
render(
|
|
7
|
+
<div className="w-full overflow-hidden rounded-lg border border-border">
|
|
8
|
+
<Page
|
|
9
|
+
title="Workspaces"
|
|
10
|
+
count={3}
|
|
11
|
+
actions={
|
|
12
|
+
<Button>
|
|
13
|
+
<Plus /> Novo workspace
|
|
14
|
+
</Button>
|
|
15
|
+
}
|
|
16
|
+
>
|
|
17
|
+
<div className="rounded-lg border border-dashed border-border p-10 text-center text-sm text-muted-foreground">
|
|
18
|
+
O conteúdo da página (tabela, cards, grid…).
|
|
19
|
+
</div>
|
|
20
|
+
</Page>
|
|
21
|
+
</div>,
|
|
22
|
+
)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
| Prop | Tipo | Default | Descrição |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `title` | `string` | | O h1 da página. |
|
|
30
|
+
| `count` | `number` | | Total de itens ao lado do título (mono, esmaecido). |
|
|
31
|
+
| `description` | `ReactNode` | | Linha de contexto sob o título (ex.: "N no total · X ativos"). |
|
|
32
|
+
| `actions` | `ReactNode` | | Ação à direita do header — em geral o botão de criar. |
|
|
33
|
+
| `className` | `string` | largura cheia | Classes do container (`max-w-5xl` estreita e centra). |
|
|
34
|
+
| `children` | `ReactNode` | | O conteúdo — espaçamento e diagramação são seus. |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
É composição: Pagination embrulha PaginationContent, e cada PaginationItem segura um link. isActive no PaginationLink marca a página atual (vira outline).
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
<Pagination>
|
|
7
|
+
<PaginationContent>
|
|
8
|
+
<PaginationItem>
|
|
9
|
+
<PaginationPrevious href="#" />
|
|
10
|
+
</PaginationItem>
|
|
11
|
+
<PaginationItem>
|
|
12
|
+
<PaginationLink href="#">1</PaginationLink>
|
|
13
|
+
</PaginationItem>
|
|
14
|
+
<PaginationItem>
|
|
15
|
+
<PaginationLink href="#" isActive>
|
|
16
|
+
2
|
|
17
|
+
</PaginationLink>
|
|
18
|
+
</PaginationItem>
|
|
19
|
+
<PaginationItem>
|
|
20
|
+
<PaginationLink href="#">3</PaginationLink>
|
|
21
|
+
</PaginationItem>
|
|
22
|
+
<PaginationItem>
|
|
23
|
+
<PaginationNext href="#" />
|
|
24
|
+
</PaginationItem>
|
|
25
|
+
</PaginationContent>
|
|
26
|
+
</Pagination>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Com elipse
|
|
30
|
+
|
|
31
|
+
PaginationEllipsis é o atalho decorativo (aria-hidden) entre blocos de páginas distantes — útil quando a lista de sessões do workspace tem páginas demais pra caber na barra.
|
|
32
|
+
|
|
33
|
+
```tsx preview
|
|
34
|
+
<Pagination>
|
|
35
|
+
<PaginationContent>
|
|
36
|
+
<PaginationItem>
|
|
37
|
+
<PaginationPrevious href="#" />
|
|
38
|
+
</PaginationItem>
|
|
39
|
+
<PaginationItem>
|
|
40
|
+
<PaginationLink href="#">1</PaginationLink>
|
|
41
|
+
</PaginationItem>
|
|
42
|
+
<PaginationItem>
|
|
43
|
+
<PaginationEllipsis />
|
|
44
|
+
</PaginationItem>
|
|
45
|
+
<PaginationItem>
|
|
46
|
+
<PaginationLink href="#" isActive>
|
|
47
|
+
8
|
|
48
|
+
</PaginationLink>
|
|
49
|
+
</PaginationItem>
|
|
50
|
+
<PaginationItem>
|
|
51
|
+
<PaginationLink href="#">9</PaginationLink>
|
|
52
|
+
</PaginationItem>
|
|
53
|
+
<PaginationItem>
|
|
54
|
+
<PaginationNext href="#" />
|
|
55
|
+
</PaginationItem>
|
|
56
|
+
</PaginationContent>
|
|
57
|
+
</Pagination>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Controlado
|
|
61
|
+
|
|
62
|
+
Os links são <a> — sem href, vire onClick e guarde a página no estado. Aqui a barra pagina os repositórios do workspace Empresa X.
|
|
63
|
+
|
|
64
|
+
```tsx preview col
|
|
65
|
+
const [page, setPage] = useState(2)
|
|
66
|
+
const pages = [1, 2, 3]
|
|
67
|
+
|
|
68
|
+
render(
|
|
69
|
+
<Pagination>
|
|
70
|
+
<PaginationContent>
|
|
71
|
+
<PaginationItem>
|
|
72
|
+
<PaginationPrevious
|
|
73
|
+
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
|
74
|
+
/>
|
|
75
|
+
</PaginationItem>
|
|
76
|
+
{pages.map((n) => (
|
|
77
|
+
<PaginationItem key={n}>
|
|
78
|
+
<PaginationLink isActive={n === page} onClick={() => setPage(n)}>
|
|
79
|
+
{n}
|
|
80
|
+
</PaginationLink>
|
|
81
|
+
</PaginationItem>
|
|
82
|
+
))}
|
|
83
|
+
<PaginationItem>
|
|
84
|
+
<PaginationNext
|
|
85
|
+
onClick={() => setPage((p) => Math.min(pages.length, p + 1))}
|
|
86
|
+
/>
|
|
87
|
+
</PaginationItem>
|
|
88
|
+
</PaginationContent>
|
|
89
|
+
</Pagination>,
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Props
|
|
94
|
+
|
|
95
|
+
| Prop | Tipo | Default | Descrição |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| `isActive (PaginationLink)` | `boolean` | `false` | Marca a página atual: vira outline e ganha aria-current="page". Os demais ficam ghost. |
|
|
98
|
+
| `size (PaginationLink)` | `'default' \| 'sm' \| 'lg' \| 'icon'` | `'icon'` | Tamanho do link, herdado do Button. Previous/Next já forçam default pra caber o texto. |
|
|
99
|
+
| `href (PaginationLink)` | `string` | | Cada link é um <a>: passe href pra navegação real, ou use onClick no modo controlado. |
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
## Estrutura
|
|
2
|
+
|
|
3
|
+
PopoverHeader agrupa título e descrição; o corpo é livre (form curto, detalhes). Superfície bg-popover — a elevação da casa.
|
|
4
|
+
|
|
5
|
+
```tsx preview
|
|
6
|
+
<Popover>
|
|
7
|
+
<PopoverTrigger asChild>
|
|
8
|
+
<Button variant="outline">Renomear sessão</Button>
|
|
9
|
+
</PopoverTrigger>
|
|
10
|
+
<PopoverContent align="start">
|
|
11
|
+
<PopoverHeader>
|
|
12
|
+
<PopoverTitle>Renomear</PopoverTitle>
|
|
13
|
+
<PopoverDescription>O slug técnico não muda — só o título.</PopoverDescription>
|
|
14
|
+
</PopoverHeader>
|
|
15
|
+
<div className="mt-3 space-y-2">
|
|
16
|
+
<Label htmlFor="pop-titulo">Título</Label>
|
|
17
|
+
<Input id="pop-titulo" defaultValue="Onboarding do faturamento" />
|
|
18
|
+
<Button size="sm" className="w-full">Salvar</Button>
|
|
19
|
+
</div>
|
|
20
|
+
</PopoverContent>
|
|
21
|
+
</Popover>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Alinhamento
|
|
25
|
+
|
|
26
|
+
align posiciona o painel em relação ao gatilho; sideOffset afasta. O padrão (center) serve pra quase tudo.
|
|
27
|
+
|
|
28
|
+
```tsx preview
|
|
29
|
+
<Popover>
|
|
30
|
+
<PopoverTrigger asChild><Button variant="ghost">start</Button></PopoverTrigger>
|
|
31
|
+
<PopoverContent align="start" className="w-56">
|
|
32
|
+
<PopoverDescription>Alinhado à borda esquerda do gatilho.</PopoverDescription>
|
|
33
|
+
</PopoverContent>
|
|
34
|
+
</Popover>
|
|
35
|
+
<Popover>
|
|
36
|
+
<PopoverTrigger asChild><Button variant="ghost">end</Button></PopoverTrigger>
|
|
37
|
+
<PopoverContent align="end" className="w-56">
|
|
38
|
+
<PopoverDescription>Alinhado à borda direita do gatilho.</PopoverDescription>
|
|
39
|
+
</PopoverContent>
|
|
40
|
+
</Popover>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
| Prop | Tipo | Default | Descrição |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| `PopoverContent.align` | `'start' \| 'center' \| 'end'` | `'center'` | Alinhamento do painel em relação ao gatilho. |
|
|
48
|
+
| `PopoverContent.sideOffset` | `number` | `4` | Distância (px) entre gatilho e painel. |
|
|
49
|
+
| `Popover.open / onOpenChange` | `boolean / (open: boolean) => void` | | Modo controlado (Radix) — pra fechar por código depois de salvar. |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
value vai de 0 a 100 — o preenchimento anima a cada mudança. Sem value (ou null) a barra fica vazia.
|
|
4
|
+
|
|
5
|
+
```tsx preview col
|
|
6
|
+
<div className="w-full max-w-sm space-y-2">
|
|
7
|
+
<div className="flex items-center justify-between text-sm">
|
|
8
|
+
<span>opus check</span>
|
|
9
|
+
<span className="text-muted-foreground">65%</span>
|
|
10
|
+
</div>
|
|
11
|
+
<Progress value={65} />
|
|
12
|
+
</div>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Controlado
|
|
16
|
+
|
|
17
|
+
Guarde o value no estado e atualize conforme a tarefa avança — aqui cada clique soma um passo na sincronização do workspace Empresa X.
|
|
18
|
+
|
|
19
|
+
```tsx preview col
|
|
20
|
+
const [step, setStep] = useState(40)
|
|
21
|
+
const advance = (): void => setStep((prev) => Math.min(prev + 20, 100))
|
|
22
|
+
|
|
23
|
+
render(
|
|
24
|
+
<div className="w-full max-w-sm space-y-3">
|
|
25
|
+
<div className="flex items-center justify-between text-sm">
|
|
26
|
+
<span>Sincronizar Empresa X</span>
|
|
27
|
+
<span className="text-muted-foreground">{step}%</span>
|
|
28
|
+
</div>
|
|
29
|
+
<Progress value={step} />
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
onClick={advance}
|
|
33
|
+
className="rounded-md border px-3 py-1.5 text-sm hover:bg-muted"
|
|
34
|
+
>
|
|
35
|
+
Avançar etapa
|
|
36
|
+
</button>
|
|
37
|
+
</div>,
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Altura e cor
|
|
42
|
+
|
|
43
|
+
className compõe sobre o padrão: ajuste a altura no Progress e tinja o preenchimento mirando o slot do indicador (&_[data-slot=progress-indicator]) para sinalizar bom/atenção.
|
|
44
|
+
|
|
45
|
+
```tsx preview col
|
|
46
|
+
<div className="w-full max-w-sm space-y-4">
|
|
47
|
+
<div className="space-y-1.5">
|
|
48
|
+
<span className="text-sm">Cobertura de testes</span>
|
|
49
|
+
<Progress
|
|
50
|
+
value={92}
|
|
51
|
+
className="h-1.5 [&_[data-slot=progress-indicator]]:bg-emerald-500"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="space-y-1.5">
|
|
55
|
+
<span className="text-sm">Skills cobertas pelo revisor</span>
|
|
56
|
+
<Progress
|
|
57
|
+
value={45}
|
|
58
|
+
className="h-3 [&_[data-slot=progress-indicator]]:bg-amber-500"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Props
|
|
65
|
+
|
|
66
|
+
| Prop | Tipo | Default | Descrição |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| `value` | `number \| null` | | O progresso de 0 a 100. O preenchimento anima a cada mudança; null/ausente deixa a barra vazia. |
|
|
69
|
+
| `className` | `string` | | Compõe sobre o padrão — ajuste a altura (h-1.5/h-3) ou tinja o indicador via [&_[data-slot=progress-indicator]]:bg-*. |
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Filas
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Filas
|
|
6
|
+
|
|
7
|
+
Action pesada não segura o request: marcada como background, ela é enfileirada e processada
|
|
8
|
+
fora da linha, e o cliente acompanha por um handle. O contrato é o `QueueAdapter`.
|
|
9
|
+
|
|
10
|
+
## O contrato
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
interface QueueAdapter {
|
|
14
|
+
enqueue(spec: JobSpec): Promise<JobHandle>
|
|
15
|
+
status(jobId: string): Promise<JobHandle | null>
|
|
16
|
+
cancel(jobId: string): Promise<boolean>
|
|
17
|
+
subscribe?(jobId: string, listener: (h: JobHandle) => void): Unsubscribe
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface JobHandle<T = unknown> {
|
|
21
|
+
jobId: string
|
|
22
|
+
action: string
|
|
23
|
+
status: 'queued' | 'running' | 'done' | 'failed' | 'cancelled'
|
|
24
|
+
progress?: { percent?: number; message?: string }
|
|
25
|
+
data?: T
|
|
26
|
+
error?: ActionError
|
|
27
|
+
attempts: number
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
O core monta o `JobSpec` (action, input, ctx, retry/priority/timeout); o adapter persiste e
|
|
32
|
+
processa.
|
|
33
|
+
|
|
34
|
+
## Driver
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { Queue } from 'bullmq'
|
|
38
|
+
import { bullmqQueue } from '@softize/opus/queue/bullmq'
|
|
39
|
+
|
|
40
|
+
const queue = bullmqQueue({ queue: new Queue('opus', { connection: { host: 'localhost' } }) })
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Redis-backed (durável, entre-processos, retries). Peers opcionais (`bullmq`, `ioredis`) só
|
|
44
|
+
pra quem usa o driver.
|
|
45
|
+
|
|
46
|
+
## No runtime
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
const runtime = createRuntime({
|
|
50
|
+
// …server/data…
|
|
51
|
+
queue,
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A action declara `background: true` (+ retry/priority no contrato); chamá-la devolve um
|
|
56
|
+
`JobHandle` em vez do resultado, e o cliente pola `status(jobId)` (ou `subscribe`) até
|
|
57
|
+
`done`/`failed`.
|
|
58
|
+
|
|
59
|
+
## Limites (por enquanto)
|
|
60
|
+
|
|
61
|
+
Driver hoje: `bullmq` (Redis). In-memory pra dev e outros backends (SQS, pg-boss…) entram
|
|
62
|
+
por reincidência.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
## Básico
|
|
2
|
+
|
|
3
|
+
Cada RadioGroupItem tem um value; o item escolhido é o value do RadioGroup. defaultValue deixa o estado com o componente. Pareie cada item com um Label (htmlFor↔id).
|
|
4
|
+
|
|
5
|
+
```tsx preview col-start
|
|
6
|
+
<RadioGroup defaultValue="balanced">
|
|
7
|
+
<div className="flex items-center gap-2">
|
|
8
|
+
<RadioGroupItem id="speed" value="speed" />
|
|
9
|
+
<Label htmlFor="speed">Priorizar velocidade</Label>
|
|
10
|
+
</div>
|
|
11
|
+
<div className="flex items-center gap-2">
|
|
12
|
+
<RadioGroupItem id="balanced" value="balanced" />
|
|
13
|
+
<Label htmlFor="balanced">Equilibrado</Label>
|
|
14
|
+
</div>
|
|
15
|
+
<div className="flex items-center gap-2">
|
|
16
|
+
<RadioGroupItem id="thorough" value="thorough" />
|
|
17
|
+
<Label htmlFor="thorough">Priorizar profundidade</Label>
|
|
18
|
+
</div>
|
|
19
|
+
</RadioGroup>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Controlado
|
|
23
|
+
|
|
24
|
+
value + onValueChange no RadioGroup levam o estado pra fora — o padrão pra escolher quem recebe o handoff de uma task.
|
|
25
|
+
|
|
26
|
+
```tsx preview col-start
|
|
27
|
+
const [agent, setAgent] = useState('reviewer')
|
|
28
|
+
|
|
29
|
+
render(
|
|
30
|
+
<RadioGroup value={agent} onValueChange={setAgent}>
|
|
31
|
+
<div className="flex items-center gap-2">
|
|
32
|
+
<RadioGroupItem id="developer" value="developer" />
|
|
33
|
+
<Label htmlFor="developer">developer</Label>
|
|
34
|
+
</div>
|
|
35
|
+
<div className="flex items-center gap-2">
|
|
36
|
+
<RadioGroupItem id="reviewer" value="reviewer" />
|
|
37
|
+
<Label htmlFor="reviewer">reviewer</Label>
|
|
38
|
+
</div>
|
|
39
|
+
<div className="flex items-center gap-2">
|
|
40
|
+
<RadioGroupItem id="designer" value="designer" />
|
|
41
|
+
<Label htmlFor="designer">designer</Label>
|
|
42
|
+
</div>
|
|
43
|
+
</RadioGroup>,
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Item desabilitado
|
|
48
|
+
|
|
49
|
+
disabled num RadioGroupItem esmaece e tira a opção da escolha; o Label em par esmaece junto (peer-disabled). Pra bloquear o grupo inteiro, ponha disabled no RadioGroup.
|
|
50
|
+
|
|
51
|
+
```tsx preview col-start
|
|
52
|
+
<RadioGroup defaultValue="empresa-x-web">
|
|
53
|
+
<div className="flex items-center gap-2">
|
|
54
|
+
<RadioGroupItem id="empresa-x-web" value="empresa-x-web" />
|
|
55
|
+
<Label htmlFor="empresa-x-web">empresa-x-web</Label>
|
|
56
|
+
</div>
|
|
57
|
+
<div className="flex items-center gap-2">
|
|
58
|
+
<RadioGroupItem id="empresa-x-api" value="empresa-x-api" />
|
|
59
|
+
<Label htmlFor="empresa-x-api">empresa-x-api</Label>
|
|
60
|
+
</div>
|
|
61
|
+
<div className="flex items-center gap-2">
|
|
62
|
+
<RadioGroupItem id="empresa-x-mobile" value="empresa-x-mobile" disabled />
|
|
63
|
+
<Label htmlFor="empresa-x-mobile">empresa-x-mobile (sem worktree)</Label>
|
|
64
|
+
</div>
|
|
65
|
+
</RadioGroup>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Props
|
|
69
|
+
|
|
70
|
+
| Prop | Tipo | Default | Descrição |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| `value (RadioGroup)` | `string` | | A opção escolhida, no modo controlado — pareie com onValueChange. |
|
|
73
|
+
| `onValueChange (RadioGroup)` | `(value: string) => void` | | Chamado quando o usuário escolhe outra opção. |
|
|
74
|
+
| `defaultValue (RadioGroup)` | `string` | | A opção inicial no modo não controlado. |
|
|
75
|
+
| `disabled (RadioGroup)` | `boolean` | `false` | Bloqueia e esmaece o grupo inteiro. |
|
|
76
|
+
| `value (RadioGroupItem)` | `string` | | O valor que este item representa — vira o value do grupo quando escolhido. |
|
|
77
|
+
| `disabled (RadioGroupItem)` | `boolean` | `false` | Esmaece e tira só este item da escolha — o Label em par esmaece junto. |
|