@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.
Files changed (286) hide show
  1. package/CHANGELOG.md +1616 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -0
  4. package/bin/cli.mjs +528 -0
  5. package/bin/lib/check.mjs +307 -0
  6. package/bin/lib/components.mjs +151 -0
  7. package/bin/lib/create.mjs +208 -0
  8. package/bin/lib/db-check-runner.mjs +86 -0
  9. package/bin/lib/db-migrate-runner.mjs +89 -0
  10. package/bin/lib/db-scaffold-runner.mjs +84 -0
  11. package/bin/lib/db.mjs +261 -0
  12. package/bin/lib/docs-include.mjs +48 -0
  13. package/bin/lib/gen-dicts.mjs +134 -0
  14. package/bin/lib/gen-docs.mjs +288 -0
  15. package/bin/lib/gen-manifest.mjs +102 -0
  16. package/bin/lib/gen-openapi.mjs +195 -0
  17. package/bin/lib/gen-runner.mjs +472 -0
  18. package/bin/lib/gen-stubs.mjs +463 -0
  19. package/bin/lib/gen.mjs +311 -0
  20. package/bin/lib/init.mjs +514 -0
  21. package/bin/lib/introspect.mjs +107 -0
  22. package/bin/lib/mcp.mjs +85 -0
  23. package/bin/lib/postinstall.mjs +56 -0
  24. package/docs/chat-event-protocol.md +85 -0
  25. package/docs/code-style.md +16 -0
  26. package/docs/data-layer.md +246 -0
  27. package/docs/ownership-vs-shadcn-lock.md +102 -0
  28. package/docs/protocol.md +2053 -0
  29. package/docs/releasing.md +110 -0
  30. package/docs/shellnav.md +131 -0
  31. package/package.json +338 -0
  32. package/registry/hooks/hooks.json +26 -0
  33. package/registry/hooks/link-memory-on-start.mjs +46 -0
  34. package/registry/hooks/opus-check-on-stop.mjs +114 -0
  35. package/registry/skills/create-action/SKILL.md +49 -0
  36. package/registry/skills/create-action/scaffold.mjs +122 -0
  37. package/registry/templates/app/_gitignore +3 -0
  38. package/registry/templates/app/_npmrc +1 -0
  39. package/registry/templates/app/_opus/_gitignore +5 -0
  40. package/registry/templates/app/_prettierrc.json +6 -0
  41. package/registry/templates/app/index.html +13 -0
  42. package/registry/templates/app/opus.config.ts +16 -0
  43. package/registry/templates/app/package.json +43 -0
  44. package/registry/templates/app/pnpm-workspace.yaml +11 -0
  45. package/registry/templates/app/public/favicon.svg +4 -0
  46. package/registry/templates/app/src/App.tsx +37 -0
  47. package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
  48. package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
  49. package/registry/templates/app/src/domains/tasks/index.ts +13 -0
  50. package/registry/templates/app/src/index.css +18 -0
  51. package/registry/templates/app/src/main.tsx +25 -0
  52. package/registry/templates/app/tsconfig.json +20 -0
  53. package/registry/templates/app/vite.config.ts +46 -0
  54. package/registry/templates/monorepo/_gitignore +3 -0
  55. package/registry/templates/monorepo/_npmrc +1 -0
  56. package/registry/templates/monorepo/package.json +9 -0
  57. package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
  58. package/src/ai/ask.ts +64 -0
  59. package/src/ai/drivers/anthropic.ts +309 -0
  60. package/src/ai/index.ts +17 -0
  61. package/src/audit/drivers/console.ts +117 -0
  62. package/src/audit/drivers/pg.ts +172 -0
  63. package/src/audit/index.ts +51 -0
  64. package/src/auth/drivers/better-auth.ts +103 -0
  65. package/src/auth/drivers/jwt.ts +188 -0
  66. package/src/auth/index.ts +9 -0
  67. package/src/client/drivers/fetch.ts +202 -0
  68. package/src/client/index.ts +22 -0
  69. package/src/core/actions.ts +110 -0
  70. package/src/core/audit.ts +239 -0
  71. package/src/core/contracts.ts +137 -0
  72. package/src/core/domain.ts +310 -0
  73. package/src/core/errors.ts +181 -0
  74. package/src/core/index.ts +174 -0
  75. package/src/core/logical-type.ts +31 -0
  76. package/src/core/reactions.ts +81 -0
  77. package/src/core/runtime.ts +1167 -0
  78. package/src/core/schedules.ts +41 -0
  79. package/src/core/types.ts +1356 -0
  80. package/src/data/drivers/kysely.ts +389 -0
  81. package/src/data/index.ts +10 -0
  82. package/src/data/readonly-pool.ts +160 -0
  83. package/src/dsl/eval.ts +136 -0
  84. package/src/dsl/index.ts +29 -0
  85. package/src/dsl/kysely.ts +230 -0
  86. package/src/dsl/loads.ts +123 -0
  87. package/src/dsl/parser.ts +423 -0
  88. package/src/dsl/types.ts +113 -0
  89. package/src/events/drivers/mitt.ts +70 -0
  90. package/src/events/index.ts +9 -0
  91. package/src/log/drivers/pino.ts +57 -0
  92. package/src/log/index.ts +9 -0
  93. package/src/mcp/index.ts +62 -0
  94. package/src/queue/drivers/bullmq.ts +190 -0
  95. package/src/queue/index.ts +9 -0
  96. package/src/scheduler/drivers/node-cron.ts +93 -0
  97. package/src/scheduler/every.ts +45 -0
  98. package/src/scheduler/index.ts +9 -0
  99. package/src/schema/drivers/zod.ts +765 -0
  100. package/src/schema/entity.ts +439 -0
  101. package/src/schema/format/locale.ts +144 -0
  102. package/src/schema/index.ts +65 -0
  103. package/src/schema/openapi.ts +302 -0
  104. package/src/schema/scaffold.ts +160 -0
  105. package/src/server/drivers/fastify.ts +224 -0
  106. package/src/server/drivers/node.ts +386 -0
  107. package/src/server/index.ts +142 -0
  108. package/src/storage/drivers/fs.ts +90 -0
  109. package/src/storage/drivers/s3.ts +117 -0
  110. package/src/storage/index.ts +27 -0
  111. package/src/testing/fake.ts +298 -0
  112. package/src/testing/index.ts +324 -0
  113. package/src/ui/components/patterns/action-form-card.tsx +48 -0
  114. package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
  115. package/src/ui/components/patterns/app-shell.tsx +227 -0
  116. package/src/ui/components/patterns/confirm.tsx +226 -0
  117. package/src/ui/components/patterns/data-state.tsx +75 -0
  118. package/src/ui/components/patterns/form-dialog.tsx +64 -0
  119. package/src/ui/components/patterns/form.tsx +584 -0
  120. package/src/ui/components/patterns/list.tsx +1488 -0
  121. package/src/ui/components/patterns/page.tsx +46 -0
  122. package/src/ui/components/patterns/section-shell.tsx +246 -0
  123. package/src/ui/components/patterns/shell-nav.tsx +150 -0
  124. package/src/ui/components/patterns/sidebar.tsx +89 -0
  125. package/src/ui/components/patterns/split.tsx +93 -0
  126. package/src/ui/components/patterns/trigger.tsx +196 -0
  127. package/src/ui/components/patterns/view.tsx +84 -0
  128. package/src/ui/components/primitives/accordion.tsx +64 -0
  129. package/src/ui/components/primitives/alert-dialog.tsx +190 -0
  130. package/src/ui/components/primitives/alert.tsx +116 -0
  131. package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
  132. package/src/ui/components/primitives/avatar.tsx +107 -0
  133. package/src/ui/components/primitives/badge.tsx +37 -0
  134. package/src/ui/components/primitives/breadcrumb.tsx +109 -0
  135. package/src/ui/components/primitives/button-group.tsx +83 -0
  136. package/src/ui/components/primitives/button.tsx +102 -0
  137. package/src/ui/components/primitives/calendar.tsx +218 -0
  138. package/src/ui/components/primitives/card.tsx +56 -0
  139. package/src/ui/components/primitives/carousel.tsx +239 -0
  140. package/src/ui/components/primitives/chat.tsx +407 -0
  141. package/src/ui/components/primitives/checkbox.tsx +30 -0
  142. package/src/ui/components/primitives/collapsible.tsx +31 -0
  143. package/src/ui/components/primitives/command.tsx +182 -0
  144. package/src/ui/components/primitives/composer.tsx +121 -0
  145. package/src/ui/components/primitives/copyable.tsx +50 -0
  146. package/src/ui/components/primitives/dialog.tsx +147 -0
  147. package/src/ui/components/primitives/drawer.tsx +141 -0
  148. package/src/ui/components/primitives/empty.tsx +104 -0
  149. package/src/ui/components/primitives/field.tsx +246 -0
  150. package/src/ui/components/primitives/icon-picker.tsx +180 -0
  151. package/src/ui/components/primitives/input-group.tsx +168 -0
  152. package/src/ui/components/primitives/input-otp.tsx +75 -0
  153. package/src/ui/components/primitives/input.tsx +72 -0
  154. package/src/ui/components/primitives/item.tsx +193 -0
  155. package/src/ui/components/primitives/kbd.tsx +28 -0
  156. package/src/ui/components/primitives/label.tsx +22 -0
  157. package/src/ui/components/primitives/markdown.tsx +35 -0
  158. package/src/ui/components/primitives/menu.tsx +255 -0
  159. package/src/ui/components/primitives/pagination.tsx +127 -0
  160. package/src/ui/components/primitives/popover.tsx +87 -0
  161. package/src/ui/components/primitives/progress.tsx +29 -0
  162. package/src/ui/components/primitives/radio-group.tsx +43 -0
  163. package/src/ui/components/primitives/resizable.tsx +51 -0
  164. package/src/ui/components/primitives/scroll-area.tsx +56 -0
  165. package/src/ui/components/primitives/select.tsx +479 -0
  166. package/src/ui/components/primitives/separator.tsx +26 -0
  167. package/src/ui/components/primitives/skeleton.tsx +13 -0
  168. package/src/ui/components/primitives/slider.tsx +61 -0
  169. package/src/ui/components/primitives/sonner.tsx +46 -0
  170. package/src/ui/components/primitives/spinner.tsx +29 -0
  171. package/src/ui/components/primitives/switch.tsx +33 -0
  172. package/src/ui/components/primitives/table.tsx +114 -0
  173. package/src/ui/components/primitives/tabs.tsx +104 -0
  174. package/src/ui/components/primitives/textarea.tsx +18 -0
  175. package/src/ui/components/primitives/toggle-group.tsx +81 -0
  176. package/src/ui/components/primitives/toggle.tsx +45 -0
  177. package/src/ui/components/primitives/tooltip.tsx +55 -0
  178. package/src/ui/components/primitives/truncate.tsx +49 -0
  179. package/src/ui/docs/DocBrowser.tsx +90 -0
  180. package/src/ui/docs/changelog.tsx +80 -0
  181. package/src/ui/docs/content/accordion.md +86 -0
  182. package/src/ui/docs/content/action-form-card.md +24 -0
  183. package/src/ui/docs/content/action-form-dialog.md +30 -0
  184. package/src/ui/docs/content/action-form.md +125 -0
  185. package/src/ui/docs/content/action-list-dialog.md +68 -0
  186. package/src/ui/docs/content/action-list.md +194 -0
  187. package/src/ui/docs/content/action-trigger.md +72 -0
  188. package/src/ui/docs/content/action-view.md +47 -0
  189. package/src/ui/docs/content/actions.md +138 -0
  190. package/src/ui/docs/content/ai.md +112 -0
  191. package/src/ui/docs/content/alert-dialog.md +73 -0
  192. package/src/ui/docs/content/alert.md +69 -0
  193. package/src/ui/docs/content/app-shell.md +155 -0
  194. package/src/ui/docs/content/aspect-ratio.md +66 -0
  195. package/src/ui/docs/content/audit.md +84 -0
  196. package/src/ui/docs/content/auth.md +70 -0
  197. package/src/ui/docs/content/avatar.md +94 -0
  198. package/src/ui/docs/content/badge.md +48 -0
  199. package/src/ui/docs/content/breadcrumb.md +87 -0
  200. package/src/ui/docs/content/button-group.md +71 -0
  201. package/src/ui/docs/content/button.md +60 -0
  202. package/src/ui/docs/content/calendar.md +62 -0
  203. package/src/ui/docs/content/card.md +49 -0
  204. package/src/ui/docs/content/carousel.md +85 -0
  205. package/src/ui/docs/content/chat.md +69 -0
  206. package/src/ui/docs/content/checkbox.md +75 -0
  207. package/src/ui/docs/content/cli.md +58 -0
  208. package/src/ui/docs/content/collapsible.md +64 -0
  209. package/src/ui/docs/content/command.md +56 -0
  210. package/src/ui/docs/content/composer.md +50 -0
  211. package/src/ui/docs/content/confirm.md +120 -0
  212. package/src/ui/docs/content/copyable.md +30 -0
  213. package/src/ui/docs/content/customization.md +110 -0
  214. package/src/ui/docs/content/cycle.md +34 -0
  215. package/src/ui/docs/content/data-state.md +47 -0
  216. package/src/ui/docs/content/data.md +99 -0
  217. package/src/ui/docs/content/dialog.md +60 -0
  218. package/src/ui/docs/content/drawer.md +55 -0
  219. package/src/ui/docs/content/empty.md +66 -0
  220. package/src/ui/docs/content/events.md +61 -0
  221. package/src/ui/docs/content/field.md +58 -0
  222. package/src/ui/docs/content/getting-started.md +109 -0
  223. package/src/ui/docs/content/icon-picker.md +51 -0
  224. package/src/ui/docs/content/input-group.md +78 -0
  225. package/src/ui/docs/content/input-otp.md +72 -0
  226. package/src/ui/docs/content/input.md +78 -0
  227. package/src/ui/docs/content/item.md +84 -0
  228. package/src/ui/docs/content/kbd.md +62 -0
  229. package/src/ui/docs/content/label.md +32 -0
  230. package/src/ui/docs/content/log.md +55 -0
  231. package/src/ui/docs/content/markdown.md +41 -0
  232. package/src/ui/docs/content/mcp.md +44 -0
  233. package/src/ui/docs/content/menu.md +114 -0
  234. package/src/ui/docs/content/microcopy.md +83 -0
  235. package/src/ui/docs/content/page.md +34 -0
  236. package/src/ui/docs/content/pagination.md +99 -0
  237. package/src/ui/docs/content/popover.md +49 -0
  238. package/src/ui/docs/content/progress.md +69 -0
  239. package/src/ui/docs/content/queue.md +62 -0
  240. package/src/ui/docs/content/radio-group.md +77 -0
  241. package/src/ui/docs/content/resizable.md +86 -0
  242. package/src/ui/docs/content/router.md +56 -0
  243. package/src/ui/docs/content/runtime.md +77 -0
  244. package/src/ui/docs/content/scheduler.md +66 -0
  245. package/src/ui/docs/content/scroll-area.md +89 -0
  246. package/src/ui/docs/content/section-shell.md +121 -0
  247. package/src/ui/docs/content/select.md +342 -0
  248. package/src/ui/docs/content/separator.md +33 -0
  249. package/src/ui/docs/content/sidebar.md +38 -0
  250. package/src/ui/docs/content/skeleton.md +34 -0
  251. package/src/ui/docs/content/slider.md +64 -0
  252. package/src/ui/docs/content/spinner.md +37 -0
  253. package/src/ui/docs/content/split.md +33 -0
  254. package/src/ui/docs/content/storage.md +69 -0
  255. package/src/ui/docs/content/switch.md +69 -0
  256. package/src/ui/docs/content/table.md +102 -0
  257. package/src/ui/docs/content/tabs.md +94 -0
  258. package/src/ui/docs/content/testing.md +89 -0
  259. package/src/ui/docs/content/textarea.md +30 -0
  260. package/src/ui/docs/content/toast.md +67 -0
  261. package/src/ui/docs/content/toggle-group.md +81 -0
  262. package/src/ui/docs/content/toggle.md +72 -0
  263. package/src/ui/docs/content/tokens.md +171 -0
  264. package/src/ui/docs/content/tooltip.md +50 -0
  265. package/src/ui/docs/content/truncate.md +37 -0
  266. package/src/ui/docs/content/ui.md +40 -0
  267. package/src/ui/docs/content/upgrading.md +48 -0
  268. package/src/ui/docs/doc-client.tsx +214 -0
  269. package/src/ui/docs/doc.tsx +301 -0
  270. package/src/ui/docs/folder.tsx +149 -0
  271. package/src/ui/docs/index.ts +21 -0
  272. package/src/ui/docs/markdown.tsx +130 -0
  273. package/src/ui/docs/md-raw.d.ts +4 -0
  274. package/src/ui/docs/plugin.ts +104 -0
  275. package/src/ui/docs/registry.tsx +424 -0
  276. package/src/ui/docs/standalone.tsx +107 -0
  277. package/src/ui/drivers/react.tsx +627 -0
  278. package/src/ui/index.ts +92 -0
  279. package/src/ui/lib/cn.ts +10 -0
  280. package/src/ui/lib/zod-pt-br.ts +38 -0
  281. package/src/ui/meta.ts +412 -0
  282. package/src/ui/react.tsx +235 -0
  283. package/src/ui/router.ts +96 -0
  284. package/src/ui/theme.css +234 -0
  285. package/src/vite/design.ts +652 -0
  286. package/src/vite/index.ts +8 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * opusDocs() — plugin vite que serve a doc do DocBrowser numa rota DEV (`/__docs`) do app.
3
+ *
4
+ * Por quê: o Maestro mostra a "verdade da base" em /docs; cada app expõe a MESMA doc
5
+ * dentro do próprio dev server → veste o tema do projeto e a versão do Opus pinada ali
6
+ * (a "verdade por-projeto"). Como o preview da sessão proxeia por Host pro dev server
7
+ * do worktree, abrir /__docs no preview mostra a doc com a identidade daquele projeto.
8
+ *
9
+ * Como: um módulo virtual importa o CSS do app (default /src/index.css — onde mora o
10
+ * import do theme.css + as sobrescritas de token + o @source do tailwind) e então chama
11
+ * `mountDocs`. Importando o MESMO CSS do app, a doc herda identidade e utilitários de
12
+ * graça. É dev-only (apply: 'serve') — nada vai pro bundle de produção.
13
+ *
14
+ * Uso (vite.config.ts do app):
15
+ * import { opusDocs } from '@softize/opus/ui/docs/plugin'
16
+ * export default defineConfig({ plugins: [react(), tailwindcss(), opusDocs()] })
17
+ *
18
+ * Requer no app: react-dom e @tanstack/react-query (as páginas de pattern exercitam o
19
+ * driver do Opus) — peers que os apps web do Opus já têm. Sem react-query, a doc não sobe.
20
+ */
21
+ import type { Plugin } from 'vite'
22
+
23
+ export interface opusDocsOptions {
24
+ /** Prefixo da rota. Default '/__docs'. */
25
+ basePath?: string
26
+ /** Entry CSS do app (a doc herda tema + tailwind dele). Default '/src/index.css'. */
27
+ css?: string
28
+ /** Pasta de markdown DO PROJETO a servir (ex.: 'docs'). Ausente = a doc do próprio Opus
29
+ * (DOC_SECTIONS). Com `source`, varre os `.md` da pasta e monta a nav espelhando a árvore. */
30
+ source?: string
31
+ /** Título no topo (só quando `source`). Default 'Docs'. */
32
+ title?: string
33
+ }
34
+
35
+ const VIRTUAL_ID = 'virtual:opus-docs-entry'
36
+ // O `/@id/` é como o vite serve módulos por id; o `\0` (convenção de virtual) vira `__x00__` na URL.
37
+ const RESOLVED_ID = '\0' + VIRTUAL_ID
38
+ const ENTRY_URL = '/@id/__x00__' + VIRTUAL_ID
39
+
40
+ export function opusDocs(options: opusDocsOptions = {}): Plugin {
41
+ const basePath = options.basePath ?? '/__docs'
42
+ const css = options.css ?? '/src/index.css'
43
+ const source = options.source?.replace(/^\/+|\/+$/g, '')
44
+ const title = options.title ?? 'Docs'
45
+
46
+ return {
47
+ name: 'opus-docs',
48
+ apply: 'serve',
49
+ resolveId(id) {
50
+ if (id === VIRTUAL_ID) return RESOLVED_ID
51
+ return undefined
52
+ },
53
+ load(id) {
54
+ if (id !== RESOLVED_ID) return undefined
55
+ // Sempre importa o CSS do app (tema + tailwind) antes de subir a doc.
56
+ const head = `import ${JSON.stringify(css)}`
57
+ if (source === undefined || source === '') {
58
+ // Sem `source`: a doc do próprio Opus (DOC_SECTIONS), como sempre.
59
+ return [head, `import { mountDocs } from '@softize/opus/ui/docs/standalone'`, `mountDocs({ basePath: ${JSON.stringify(basePath)} })`].join('\n')
60
+ }
61
+ // Com `source`: varre a pasta `.md` do projeto (glob eager) → DocSection[].
62
+ // O glob roda no contexto do app (a chave é `/<source>/…`); o transform mora no Opus.
63
+ return [
64
+ head,
65
+ `import { mountDocs, docSectionsFromFolder } from '@softize/opus/ui/docs/standalone'`,
66
+ `const files = import.meta.glob(${JSON.stringify('/' + source + '/**/*.md')}, { query: '?raw', import: 'default', eager: true })`,
67
+ `mountDocs({ basePath: ${JSON.stringify(basePath)}, title: ${JSON.stringify(title)}, sections: docSectionsFromFolder(files, { root: ${JSON.stringify(source)} }) })`,
68
+ ].join('\n')
69
+ },
70
+ configureServer(server) {
71
+ server.middlewares.use((req, res, next) => {
72
+ const url = (req.url ?? '').split('?')[0] ?? ''
73
+ const onDocBrowser = url === basePath || url.startsWith(basePath + '/')
74
+ if (!onDocBrowser) {
75
+ next()
76
+ return
77
+ }
78
+ const html = [
79
+ '<!doctype html>',
80
+ '<html lang="pt-BR">',
81
+ ' <head>',
82
+ ' <meta charset="UTF-8" />',
83
+ ' <meta name="viewport" content="width=device-width, initial-scale=1.0" />',
84
+ ' <title>Components</title>',
85
+ ' </head>',
86
+ ' <body>',
87
+ ' <div id="root"></div>',
88
+ ` <script type="module" src=${JSON.stringify(ENTRY_URL)}></script>`,
89
+ ' </body>',
90
+ '</html>',
91
+ ].join('\n')
92
+ // transformIndexHtml injeta o client de HMR + roda os outros plugins de html.
93
+ server
94
+ .transformIndexHtml(req.originalUrl ?? url, html)
95
+ .then((out) => {
96
+ res.statusCode = 200
97
+ res.setHeader('Content-Type', 'text/html')
98
+ res.end(out)
99
+ })
100
+ .catch(next)
101
+ })
102
+ },
103
+ }
104
+ }
@@ -0,0 +1,424 @@
1
+ /**
2
+ * registry.tsx — o mapa da doc: seção → grupo (opcional) → página. Toda página nova
3
+ * entra AQUI (e só aqui) — o shell deriva o resto. O site tem DUAS superfícies de
4
+ * NAVEGAÇÃO do MESMO SDK (a UI faz parte dele): `PROTOCOL_SECTIONS` (/docs — o protocolo,
5
+ * recursos, ciclo de vida) e `UI_SECTIONS` (/ui — o catálogo de componentes, design,
6
+ * patterns); `DOC_SECTIONS` é a união (Maestro e /__docs mostram tudo numa nav só).
7
+ *
8
+ * O CORPO de cada página vive em markdown (`./content/*.md`, importado como `?raw`) e é
9
+ * renderizado pelo DocMarkdown — inclusive os exemplos vivos (```tsx preview). Páginas de
10
+ * componente ganham o cabeçalho (título + badge de origem + whenToUse) do `meta` central via o
11
+ * helper `comp`; páginas conceituais renderizam o markdown direto (o próprio md traz o H1/lead).
12
+ */
13
+ import { useEffect } from 'react'
14
+ import * as lucideIcons from 'lucide-react'
15
+ import * as opusUi from '../react.tsx'
16
+ import { componentMeta } from '../meta.ts'
17
+ import { navigate } from '../router.ts'
18
+ import { DocPage } from './doc.tsx'
19
+ import { DocMarkdown } from './markdown.tsx'
20
+ import { OpusChangelog } from './changelog.tsx'
21
+
22
+ // — Conceituais (Começar/Framework/Design/Introdução) —
23
+ import gettingStartedMd from './content/getting-started.md?raw'
24
+ import upgradingMd from './content/upgrading.md?raw'
25
+ import cycleMd from './content/cycle.md?raw'
26
+ import authMd from './content/auth.md?raw'
27
+ import auditMd from './content/audit.md?raw'
28
+ import eventsMd from './content/events.md?raw'
29
+ import logMd from './content/log.md?raw'
30
+ import queueMd from './content/queue.md?raw'
31
+ import schedulerMd from './content/scheduler.md?raw'
32
+ import actionsMd from './content/actions.md?raw'
33
+ import dataMd from './content/data.md?raw'
34
+ import storageMd from './content/storage.md?raw'
35
+ import aiMd from './content/ai.md?raw'
36
+ import mcpMd from './content/mcp.md?raw'
37
+ import testingMd from './content/testing.md?raw'
38
+ import runtimeMd from './content/runtime.md?raw'
39
+ import uiMd from './content/ui.md?raw'
40
+ import cliMd from './content/cli.md?raw'
41
+ import tokensMd from './content/tokens.md?raw'
42
+ import microcopyMd from './content/microcopy.md?raw'
43
+ import customizationMd from './content/customization.md?raw'
44
+
45
+ // — Componentes —
46
+ import accordionMd from './content/accordion.md?raw'
47
+ import alertMd from './content/alert.md?raw'
48
+ import alertDialogMd from './content/alert-dialog.md?raw'
49
+ import confirmMd from './content/confirm.md?raw'
50
+ import aspectRatioMd from './content/aspect-ratio.md?raw'
51
+ import avatarMd from './content/avatar.md?raw'
52
+ import badgeMd from './content/badge.md?raw'
53
+ import breadcrumbMd from './content/breadcrumb.md?raw'
54
+ import buttonMd from './content/button.md?raw'
55
+ import buttonGroupMd from './content/button-group.md?raw'
56
+ import calendarMd from './content/calendar.md?raw'
57
+ import cardMd from './content/card.md?raw'
58
+ import carouselMd from './content/carousel.md?raw'
59
+ import chatMd from './content/chat.md?raw'
60
+ import checkboxMd from './content/checkbox.md?raw'
61
+ import collapsibleMd from './content/collapsible.md?raw'
62
+ import iconPickerMd from './content/icon-picker.md?raw'
63
+ import commandMd from './content/command.md?raw'
64
+ import composerMd from './content/composer.md?raw'
65
+ import copyableMd from './content/copyable.md?raw'
66
+ import dialogMd from './content/dialog.md?raw'
67
+ import drawerMd from './content/drawer.md?raw'
68
+ import menuMd from './content/menu.md?raw'
69
+ import emptyMd from './content/empty.md?raw'
70
+ import fieldMd from './content/field.md?raw'
71
+ import inputMd from './content/input.md?raw'
72
+ import inputGroupMd from './content/input-group.md?raw'
73
+ import inputOtpMd from './content/input-otp.md?raw'
74
+ import itemMd from './content/item.md?raw'
75
+ import kbdMd from './content/kbd.md?raw'
76
+ import labelMd from './content/label.md?raw'
77
+ import markdownMd from './content/markdown.md?raw'
78
+ import paginationMd from './content/pagination.md?raw'
79
+ import popoverMd from './content/popover.md?raw'
80
+ import progressMd from './content/progress.md?raw'
81
+ import radioGroupMd from './content/radio-group.md?raw'
82
+ import resizableMd from './content/resizable.md?raw'
83
+ import scrollAreaMd from './content/scroll-area.md?raw'
84
+ import selectMd from './content/select.md?raw'
85
+ import separatorMd from './content/separator.md?raw'
86
+ import skeletonMd from './content/skeleton.md?raw'
87
+ import sliderMd from './content/slider.md?raw'
88
+ import spinnerMd from './content/spinner.md?raw'
89
+ import switchMd from './content/switch.md?raw'
90
+ import tableMd from './content/table.md?raw'
91
+ import tabsMd from './content/tabs.md?raw'
92
+ import textareaMd from './content/textarea.md?raw'
93
+ import toastMd from './content/toast.md?raw'
94
+ import toggleMd from './content/toggle.md?raw'
95
+ import toggleGroupMd from './content/toggle-group.md?raw'
96
+ import tooltipMd from './content/tooltip.md?raw'
97
+ import truncateMd from './content/truncate.md?raw'
98
+
99
+ // — Patterns (markdown + palco doc-client injetado no scope dos previews) —
100
+ import {
101
+ DocBrowserActionProvider,
102
+ docSessionArchive,
103
+ docSessionDelete,
104
+ docWorkspaceCreate,
105
+ docWorkspaceList,
106
+ docWorkspaceView,
107
+ } from './doc-client'
108
+ import actionFormMd from './content/action-form.md?raw'
109
+ import actionFormDialogMd from './content/action-form-dialog.md?raw'
110
+ import actionFormCardMd from './content/action-form-card.md?raw'
111
+ import actionSearchMd from './content/action-list.md?raw'
112
+ import actionViewMd from './content/action-view.md?raw'
113
+ import actionTriggerMd from './content/action-trigger.md?raw'
114
+ import actionSearchDialogMd from './content/action-list-dialog.md?raw'
115
+ import pageMd from './content/page.md?raw'
116
+ import appShellMd from './content/app-shell.md?raw'
117
+ import sectionShellMd from './content/section-shell.md?raw'
118
+ import sidebarMd from './content/sidebar.md?raw'
119
+ import splitMd from './content/split.md?raw'
120
+ import routerMd from './content/router.md?raw'
121
+ import dataStateMd from './content/data-state.md?raw'
122
+
123
+ export interface DocEntry {
124
+ slug: string
125
+ title: string
126
+ /** Tag técnica opcional exibida na nav ao lado do título — o subpath do SDK (ex.: 'auth'). */
127
+ badge?: string
128
+ render: () => React.ReactElement
129
+ }
130
+
131
+ export interface DocGroup {
132
+ /** Sub-rótulo opcional dentro da seção (ex.: Primitivos). Ausente = páginas direto na seção. */
133
+ label?: string
134
+ pages: DocEntry[]
135
+ }
136
+
137
+ export interface DocSection {
138
+ label: string
139
+ groups: DocGroup[]
140
+ }
141
+
142
+ /**
143
+ * O scope de previews da doc do Opus: a paleta de ícones (lucide inteiro) que os exemplos citam
144
+ * por nome. Fica AQUI (no catálogo), não no DocMarkdown — assim só a superfície que exibe estes
145
+ * docs empacota o lucide; um consumidor que só usa o renderer (docs do próprio projeto) fica leve.
146
+ */
147
+ // Ícone homônimo de componente (Badge, Table, Calendar, Command…) fica FORA do scope:
148
+ // o scope injetado entra POR CIMA do default no DocMarkdown, e `<Badge>` num exemplo
149
+ // tem que ser o COMPONENTE (chip), não o selo do lucide. Ícone colidido, se um exemplo
150
+ // precisar, entra por alias no scope do chamador.
151
+ const iconScope: Record<string, unknown> = Object.fromEntries(
152
+ Object.entries(lucideIcons).filter(([name]) => !(name in opusUi)),
153
+ )
154
+
155
+ /** Página conceitual (Começar/Framework/Design/Intro): markdown direto (o H1/lead moram no md). */
156
+ const doc = (content: string) => (): React.ReactElement => <DocMarkdown content={content} scope={iconScope} />
157
+
158
+ /**
159
+ * Página de componente: o cabeçalho (título + badge de origem + whenToUse) vem do `meta` central
160
+ * (fonte única — manifesto e ferramentas leem o mesmo), o corpo (exemplos vivos + props) vem do
161
+ * markdown. Fiel ao DocPage original — só o corpo deixou de ser JSX e virou `.md`.
162
+ */
163
+ const comp =
164
+ (title: string, key: keyof typeof componentMeta, content: string) =>
165
+ (): React.ReactElement => (
166
+ <DocPage title={title} meta={componentMeta[key]}>
167
+ <DocMarkdown content={content} scope={iconScope} />
168
+ </DocPage>
169
+ )
170
+
171
+ /** O palco dos patterns no scope dos previews: ícones + client fake + contratos de mentira. */
172
+ const patternScope = {
173
+ ...iconScope,
174
+ DocBrowserActionProvider,
175
+ docWorkspaceCreate,
176
+ docWorkspaceList,
177
+ docWorkspaceView,
178
+ docSessionArchive,
179
+ docSessionDelete,
180
+ }
181
+
182
+ /** Página de pattern: igual ao `comp`, mas os previews enxergam o palco doc-client no scope. */
183
+ const pattern =
184
+ (title: string, key: keyof typeof componentMeta, content: string) =>
185
+ (): React.ReactElement => (
186
+ <DocPage title={title} meta={componentMeta[key]}>
187
+ <DocMarkdown content={content} scope={patternScope} />
188
+ </DocPage>
189
+ )
190
+
191
+ /** Entrada 'UI' do Docs: a UI é um recurso do MESMO SDK, mas mora na superfície `/ui` (o
192
+ * catálogo é grande e visual). Em vez de duplicar, a página redireciona pra lá. */
193
+ function UiRedirect(): React.ReactElement {
194
+ useEffect(() => {
195
+ if (typeof window === 'undefined') return
196
+ // `replace`: um redirect não merece uma entrada no histórico — com `push`, o "voltar"
197
+ // cairia aqui de novo e redirecionaria outra vez.
198
+ navigate('/ui', { replace: true })
199
+ }, [])
200
+ return <p className="text-sm text-muted-foreground">Redirecionando para a UI…</p>
201
+ }
202
+
203
+ /** As seções da doc do SDK (surface `/docs` do site). "Começar" fica na RAIZ (seção sem
204
+ * label); depois o Protocolo (núcleo invariante) e os Recursos (peças opt-in). */
205
+ export const PROTOCOL_SECTIONS: DocSection[] = [
206
+ {
207
+ // Sem label → páginas soltas no topo do menu (sem cabeçalho de seção).
208
+ label: '',
209
+ groups: [
210
+ {
211
+ pages: [
212
+ { slug: 'getting-started', title: 'Getting started', render: doc(gettingStartedMd) },
213
+ { slug: 'cycle', title: 'Evolução contínua', render: doc(cycleMd) },
214
+ { slug: 'upgrading', title: 'Atualizar a base', render: doc(upgradingMd) },
215
+ ],
216
+ },
217
+ ],
218
+ },
219
+ {
220
+ // O núcleo invariante do protocolo — uma página por recurso. Opus é protocolo + peças
221
+ // opcionais entregues como SDK (não Framework: a inversão de controle é do app).
222
+ label: 'Protocolo',
223
+ groups: [
224
+ {
225
+ pages: [
226
+ { slug: 'actions', title: 'Actions & contratos', render: doc(actionsMd) },
227
+ { slug: 'runtime', title: 'Runtime & adapters', render: doc(runtimeMd) },
228
+ { slug: 'data', title: 'Camada de dados', render: doc(dataMd) },
229
+ { slug: 'testing', title: 'Testes de action', render: doc(testingMd) },
230
+ { slug: 'cli', title: 'CLI opus', render: doc(cliMd) },
231
+ ],
232
+ },
233
+ ],
234
+ },
235
+ {
236
+ // As peças OPT-IN que você liga (adapter + driver). A UI também é recurso do SDK, mas
237
+ // com catálogo próprio — a entrada 'ui' redireciona pra superfície /ui.
238
+ label: 'Recursos',
239
+ groups: [
240
+ {
241
+ pages: [
242
+ { slug: 'auth', title: 'Autenticação', badge: 'auth', render: doc(authMd) },
243
+ { slug: 'audit', title: 'Auditoria', badge: 'audit', render: doc(auditMd) },
244
+ { slug: 'events', title: 'Eventos', badge: 'events', render: doc(eventsMd) },
245
+ { slug: 'log', title: 'Log', badge: 'log', render: doc(logMd) },
246
+ { slug: 'queue', title: 'Filas', badge: 'queue', render: doc(queueMd) },
247
+ { slug: 'scheduler', title: 'Agendador', badge: 'scheduler', render: doc(schedulerMd) },
248
+ { slug: 'storage', title: 'Armazenamento', badge: 'storage', render: doc(storageMd) },
249
+ { slug: 'ai', title: 'IA generativa', badge: 'ai', render: doc(aiMd) },
250
+ { slug: 'mcp', title: 'MCP', badge: 'mcp', render: doc(mcpMd) },
251
+ { slug: 'ui', title: 'UI ↗', render: () => <UiRedirect /> },
252
+ ],
253
+ },
254
+ ],
255
+ },
256
+ ]
257
+
258
+ /** As seções da parte de UI do SDK (`/ui`): consumo, design, catálogo de componentes e
259
+ * patterns. Mesma família das seções de protocolo — superfície própria só pelo volume. */
260
+ export const UI_SECTIONS: DocSection[] = [
261
+ {
262
+ // Raiz (sem label): o "getting started" da UI.
263
+ label: '',
264
+ groups: [{ pages: [{ slug: 'como-consumir', title: 'Getting started', render: doc(uiMd) }] }],
265
+ },
266
+ {
267
+ label: 'Design',
268
+ groups: [
269
+ {
270
+ pages: [
271
+ { slug: 'tokens', title: 'Tokens & Tema', render: doc(tokensMd) },
272
+ { slug: 'microcopy', title: 'Microcopy', render: doc(microcopyMd) },
273
+ { slug: 'customization', title: 'Customização', render: doc(customizationMd) },
274
+ ],
275
+ },
276
+ ],
277
+ },
278
+ {
279
+ label: 'Componentes',
280
+ groups: [
281
+ {
282
+ label: 'Fundamentos',
283
+ pages: [
284
+ { slug: 'router', title: 'Router', render: pattern('Router', 'router', routerMd) },
285
+ ],
286
+ },
287
+ {
288
+ label: 'Layout',
289
+ pages: [
290
+ { slug: 'split', title: 'Split & Pane', render: pattern('Split & Pane', 'split', splitMd) },
291
+ { slug: 'sidebar', title: 'Sidebar', render: pattern('Sidebar', 'sidebar', sidebarMd) },
292
+ { slug: 'page', title: 'Page', render: pattern('Page', 'page', pageMd) },
293
+ { slug: 'accordion', title: 'Accordion', render: comp('Accordion', 'accordion', accordionMd) },
294
+ { slug: 'aspect-ratio', title: 'Aspect Ratio', render: comp('Aspect Ratio', 'aspect-ratio', aspectRatioMd) },
295
+ { slug: 'card', title: 'Card', render: comp('Card', 'card', cardMd) },
296
+ { slug: 'collapsible', title: 'Collapsible', render: comp('Collapsible', 'collapsible', collapsibleMd) },
297
+ { slug: 'resizable', title: 'Resizable', badge: componentMeta.resizable.deprecated ? 'deprecated' : undefined, render: comp('Resizable', 'resizable', resizableMd) },
298
+ { slug: 'scroll-area', title: 'Scroll Area', render: comp('Scroll Area', 'scroll-area', scrollAreaMd) },
299
+ { slug: 'separator', title: 'Separator', render: comp('Separator', 'separator', separatorMd) },
300
+ { slug: 'app-shell', title: 'App Shell', badge: componentMeta['app-shell'].deprecated ? 'deprecated' : undefined, render: pattern('App Shell', 'app-shell', appShellMd) },
301
+ { slug: 'section-shell', title: 'Section Shell', badge: componentMeta['section-shell'].deprecated ? 'deprecated' : undefined, render: pattern('Section Shell', 'section-shell', sectionShellMd) },
302
+ ],
303
+ },
304
+ {
305
+ label: 'Actions',
306
+ pages: [
307
+ { slug: 'action-form', title: 'Action Form', render: pattern('Action Form', 'action-form', actionFormMd) },
308
+ { slug: 'action-form-card', title: 'Action Form Card', render: pattern('Action Form Card', 'action-form-card', actionFormCardMd) },
309
+ { slug: 'action-form-dialog', title: 'Action Form Dialog', render: pattern('Action Form Dialog', 'action-form-dialog', actionFormDialogMd) },
310
+ { slug: 'action-list', title: 'Action List', render: pattern('Action List', 'action-list', actionSearchMd) },
311
+ { slug: 'action-list-dialog', title: 'Action List Dialog', render: pattern('Action List Dialog', 'action-list-dialog', actionSearchDialogMd) },
312
+ { slug: 'action-trigger', title: 'Action Trigger', render: pattern('Action Trigger', 'action-trigger', actionTriggerMd) },
313
+ { slug: 'action-view', title: 'Action View', render: pattern('Action View', 'action-view', actionViewMd) },
314
+ ],
315
+ },
316
+ {
317
+ label: 'IA',
318
+ pages: [
319
+ { slug: 'chat', title: 'Chat', render: comp('Chat', 'chat', chatMd) },
320
+ { slug: 'composer', title: 'Composer', render: comp('Composer', 'composer', composerMd) },
321
+ ],
322
+ },
323
+ {
324
+ label: 'Formulário',
325
+ pages: [
326
+ { slug: 'calendar', title: 'Calendar', render: comp('Calendar', 'calendar', calendarMd) },
327
+ { slug: 'checkbox', title: 'Checkbox', render: comp('Checkbox', 'checkbox', checkboxMd) },
328
+ { slug: 'field', title: 'Field', render: comp('Field', 'field', fieldMd) },
329
+ { slug: 'icon-picker', title: 'Icon Picker', render: comp('Icon Picker', 'icon-picker', iconPickerMd) },
330
+ { slug: 'input', title: 'Input', render: comp('Input', 'input', inputMd) },
331
+ { slug: 'input-group', title: 'Input Group', render: comp('Input Group', 'input-group', inputGroupMd) },
332
+ { slug: 'input-otp', title: 'Input OTP', render: comp('Input OTP', 'input-otp', inputOtpMd) },
333
+ { slug: 'label', title: 'Label', render: comp('Label', 'label', labelMd) },
334
+ { slug: 'radio-group', title: 'Radio Group', render: comp('Radio Group', 'radio-group', radioGroupMd) },
335
+ { slug: 'select', title: 'Select', render: comp('Select', 'select', selectMd) },
336
+ { slug: 'slider', title: 'Slider', render: comp('Slider', 'slider', sliderMd) },
337
+ { slug: 'switch', title: 'Switch', render: comp('Switch', 'switch', switchMd) },
338
+ { slug: 'textarea', title: 'Textarea', render: comp('Textarea', 'textarea', textareaMd) },
339
+ ],
340
+ },
341
+ {
342
+ label: 'Botões',
343
+ pages: [
344
+ { slug: 'button', title: 'Button', render: comp('Button', 'button', buttonMd) },
345
+ { slug: 'button-group', title: 'Button Group', render: comp('Button Group', 'button-group', buttonGroupMd) },
346
+ { slug: 'toggle', title: 'Toggle', render: comp('Toggle', 'toggle', toggleMd) },
347
+ { slug: 'toggle-group', title: 'Toggle Group', render: comp('Toggle Group', 'toggle-group', toggleGroupMd) },
348
+ ],
349
+ },
350
+ {
351
+ label: 'Navegação',
352
+ pages: [
353
+ { slug: 'breadcrumb', title: 'Breadcrumb', render: comp('Breadcrumb', 'breadcrumb', breadcrumbMd) },
354
+ { slug: 'command', title: 'Command', render: comp('Command', 'command', commandMd) },
355
+ { slug: 'pagination', title: 'Pagination', render: comp('Pagination', 'pagination', paginationMd) },
356
+ { slug: 'tabs', title: 'Tabs', render: comp('Tabs', 'tabs', tabsMd) },
357
+ ],
358
+ },
359
+ {
360
+ label: 'Exibição',
361
+ pages: [
362
+ { slug: 'avatar', title: 'Avatar', render: comp('Avatar', 'avatar', avatarMd) },
363
+ { slug: 'badge', title: 'Badge', render: comp('Badge', 'badge', badgeMd) },
364
+ { slug: 'carousel', title: 'Carousel', render: comp('Carousel', 'carousel', carouselMd) },
365
+ { slug: 'copyable', title: 'Copyable', render: comp('Copyable', 'copyable', copyableMd) },
366
+ { slug: 'item', title: 'Item', render: comp('Item', 'item', itemMd) },
367
+ { slug: 'kbd', title: 'Kbd', render: comp('Kbd', 'kbd', kbdMd) },
368
+ { slug: 'markdown', title: 'Markdown', render: comp('Markdown', 'markdown', markdownMd) },
369
+ { slug: 'table', title: 'Table', render: comp('Table', 'table', tableMd) },
370
+ { slug: 'truncate', title: 'Truncate', render: comp('Truncate', 'truncate', truncateMd) },
371
+ ],
372
+ },
373
+ {
374
+ label: 'Feedback',
375
+ pages: [
376
+ { slug: 'alert', title: 'Alert', render: comp('Alert', 'alert', alertMd) },
377
+ { slug: 'confirm', title: 'Dialog (imperativo)', render: comp('Dialog (imperativo)', 'confirm', confirmMd) },
378
+ { slug: 'data-state', title: 'Data State', render: comp('Data State', 'data-state', dataStateMd) },
379
+ { slug: 'empty', title: 'Empty', render: comp('Empty', 'empty', emptyMd) },
380
+ { slug: 'progress', title: 'Progress', render: comp('Progress', 'progress', progressMd) },
381
+ { slug: 'skeleton', title: 'Skeleton', render: comp('Skeleton', 'skeleton', skeletonMd) },
382
+ { slug: 'spinner', title: 'Spinner', render: comp('Spinner', 'spinner', spinnerMd) },
383
+ { slug: 'toast', title: 'Toast', render: comp('Toast', 'toast', toastMd) },
384
+ ],
385
+ },
386
+ {
387
+ label: 'Sobreposição',
388
+ pages: [
389
+ { slug: 'alert-dialog', title: 'Alert Dialog', render: comp('Alert Dialog', 'alert-dialog', alertDialogMd) },
390
+ { slug: 'dialog', title: 'Dialog', render: comp('Dialog', 'dialog', dialogMd) },
391
+ { slug: 'drawer', title: 'Drawer', render: comp('Drawer', 'drawer', drawerMd) },
392
+ { slug: 'menu', title: 'Menu', render: comp('Menu', 'menu', menuMd) },
393
+ { slug: 'popover', title: 'Popover', render: comp('Popover', 'popover', popoverMd) },
394
+ { slug: 'tooltip', title: 'Tooltip', render: comp('Tooltip', 'tooltip', tooltipMd) },
395
+ ],
396
+ },
397
+ ],
398
+ },
399
+ ]
400
+
401
+ /** O catálogo INTEIRO (compat): Maestro (lente) e `/__docs` mostram tudo numa nav só —
402
+ * inclusive o changelog, que no SITE é página do menu superior (fora destas seções);
403
+ * o site divide em duas superfícies de navegação do mesmo SDK (PROTOCOL_SECTIONS em /docs,
404
+ * UI_SECTIONS em /ui). */
405
+ export const DOC_SECTIONS: DocSection[] = [
406
+ {
407
+ ...PROTOCOL_SECTIONS[0]!,
408
+ groups: [
409
+ {
410
+ pages: [
411
+ ...PROTOCOL_SECTIONS[0]!.groups[0]!.pages,
412
+ { slug: 'changelog', title: 'Changelog', render: () => <OpusChangelog /> },
413
+ ],
414
+ },
415
+ ],
416
+ },
417
+ // A união (/__docs, lente do Maestro) mostra tudo numa nav só e NÃO tem a superfície /ui
418
+ // à parte — então tira a entrada-redirect 'ui' (o catálogo já entra via UI_SECTIONS).
419
+ ...PROTOCOL_SECTIONS.slice(1).map((s) => ({
420
+ ...s,
421
+ groups: s.groups.map((g) => ({ ...g, pages: g.pages.filter((p) => p.slug !== 'ui') })),
422
+ })),
423
+ ...UI_SECTIONS,
424
+ ]
@@ -0,0 +1,107 @@
1
+ /**
2
+ * standalone — monta a doc do DocBrowser como página inteira (providers + topo com toggle de
3
+ * tema). É o que a rota dev `/__docs` de cada app renderiza: a MESMA doc da base, porém
4
+ * dentro do dev server do projeto → veste o tema do projeto e a versão do Opus pinada
5
+ * ali (a "verdade por-projeto", complemento da "verdade da base" no Maestro).
6
+ *
7
+ * O tema (light/dark) NÃO vem daqui: o CSS é importado pelo entry do plugin (o
8
+ * index.css do app) antes de chamar `mountDocs` — por isso a identidade é a do projeto.
9
+ */
10
+ import { StrictMode, useState } from 'react'
11
+ import { createRoot } from 'react-dom/client'
12
+ import { Moon, Sun } from 'lucide-react'
13
+ import { Button, Toaster, TooltipProvider } from '../react.tsx'
14
+ import { DocBrowser } from './DocBrowser.tsx'
15
+ import type { DocSection } from './registry.tsx'
16
+
17
+ const THEME_KEY = 'opus-theme'
18
+
19
+ // Reexporta o adaptador de pasta: o entry do plugin `opusDocs({ source })` importa
20
+ // `mountDocs` + `docSectionsFromFolder` daqui (mesmo subpath), sem export novo no package.
21
+ export { docSectionsFromFolder } from './folder.tsx'
22
+ export type { FolderOptions } from './folder.tsx'
23
+
24
+ function StandaloneDocBrowser({
25
+ basePath,
26
+ title,
27
+ subtitle,
28
+ sections,
29
+ }: {
30
+ basePath: string
31
+ title: string
32
+ subtitle: string
33
+ sections?: DocSection[]
34
+ }): React.ReactElement {
35
+ const [dark, setDark] = useState<boolean>(() => document.documentElement.classList.contains('dark'))
36
+ const toggle = (): void => {
37
+ const next = !dark
38
+ setDark(next)
39
+ document.documentElement.classList.toggle('dark', next)
40
+ try {
41
+ localStorage.setItem(THEME_KEY, next ? 'dark' : 'light')
42
+ } catch {
43
+ /* localStorage indisponível (modo privado) — o toggle ainda vale na sessão. */
44
+ }
45
+ }
46
+ return (
47
+ <div className="flex h-screen flex-col bg-background text-foreground">
48
+ <header className="flex h-12 shrink-0 items-center justify-between border-b border-border px-4">
49
+ <div className="flex items-center gap-1.5 text-sm">
50
+ <span className="font-medium">{title}</span>
51
+ <span className="text-muted-foreground/60">· {subtitle}</span>
52
+ </div>
53
+ <Button variant="ghost" size="icon" aria-label="Alternar tema" onClick={toggle}>
54
+ {dark ? <Sun /> : <Moon />}
55
+ </Button>
56
+ </header>
57
+ <div className="min-h-0 flex-1">
58
+ <DocBrowser basePath={basePath} sections={sections} />
59
+ </div>
60
+ </div>
61
+ )
62
+ }
63
+
64
+ export interface MountDocsOptions {
65
+ /** Prefixo das rotas. Default `/__docs`. */
66
+ basePath?: string
67
+ /** Elemento alvo. Default: `#root` (cria um se faltar). */
68
+ el?: HTMLElement
69
+ /** Título no topo. Default `Components` (o site-base passa `opus`). */
70
+ title?: string
71
+ /** Legenda ao lado do título. Default `a verdade visual deste projeto`. */
72
+ subtitle?: string
73
+ /** Seções a exibir. Ausente = a doc do próprio Opus (`DOC_SECTIONS`). Passe o retorno de
74
+ * `docSectionsFromFolder(...)` pra montar a doc a partir da pasta `docs/` do projeto. */
75
+ sections?: DocSection[]
76
+ }
77
+
78
+ /** Monta a doc na página. Chamado pelo entry do plugin opusDocs (depois de importar o CSS do app). */
79
+ export function mountDocs(options: MountDocsOptions = {}): void {
80
+ const basePath = options.basePath ?? '/__docs'
81
+ const title = options.title ?? 'Components'
82
+ const subtitle = options.subtitle ?? 'a verdade visual deste projeto'
83
+ // Tema persistido pela própria doc; senão, a preferência do sistema.
84
+ let saved: string | null = null
85
+ try {
86
+ saved = localStorage.getItem(THEME_KEY)
87
+ } catch {
88
+ /* ignora */
89
+ }
90
+ const dark = saved !== null ? saved === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches
91
+ document.documentElement.classList.toggle('dark', dark)
92
+
93
+ let el = options.el ?? document.getElementById('root')
94
+ if (el === null || el === undefined) {
95
+ el = document.createElement('div')
96
+ el.id = 'root'
97
+ document.body.appendChild(el)
98
+ }
99
+ createRoot(el).render(
100
+ <StrictMode>
101
+ <TooltipProvider delayDuration={200}>
102
+ <StandaloneDocBrowser basePath={basePath} title={title} subtitle={subtitle} sections={options.sections} />
103
+ </TooltipProvider>
104
+ <Toaster />
105
+ </StrictMode>,
106
+ )
107
+ }