brut 0.0.20 → 0.0.22

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 (728) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +24 -3
  3. data/.nvim.lua +1 -0
  4. data/Dockerfile.dx +12 -3
  5. data/Gemfile.lock +9 -7
  6. data/README.md +0 -7
  7. data/Rakefile +6 -4
  8. data/bin/dev +20 -0
  9. data/bin/docs +27 -0
  10. data/bin/setup +47 -1
  11. data/brut-css/.nvim.lua +1 -0
  12. data/brut-css/README.md +28 -0
  13. data/brut-css/bin/build +31 -0
  14. data/brut-css/bin/dev +1 -0
  15. data/brut-css/bin/docs +15 -0
  16. data/brut-css/bin/setup +5 -0
  17. data/brut-css/config/media-queries-all.css +15 -0
  18. data/brut-css/config/media-queries-minimal.css +5 -0
  19. data/brut-css/config/postcss.config.cjs +7 -0
  20. data/brut-css/config/pseudo-classes-all.css +9 -0
  21. data/brut-css/dx +1 -0
  22. data/brut-css/package-lock.json +3217 -0
  23. data/brut-css/package.json +36 -0
  24. data/brut-css/src/css/appearance.css +145 -0
  25. data/brut-css/src/css/border.css +522 -0
  26. data/brut-css/src/css/colors.css +3502 -0
  27. data/brut-css/src/css/dimensions.css +548 -0
  28. data/brut-css/src/css/flex.css +179 -0
  29. data/brut-css/src/css/index.css +13 -0
  30. data/brut-css/src/css/layout.css +120 -0
  31. data/brut-css/src/css/list.css +41 -0
  32. data/brut-css/src/css/positioning.css +354 -0
  33. data/brut-css/src/css/properties/colors.css +455 -0
  34. data/brut-css/src/css/properties/index.css +3 -0
  35. data/brut-css/src/css/properties/spacing.css +140 -0
  36. data/brut-css/src/css/properties/typography.css +224 -0
  37. data/brut-css/src/css/reset.css +107 -0
  38. data/brut-css/src/css/spacing.css +585 -0
  39. data/brut-css/src/css/typography.css +519 -0
  40. data/brut-css/src/css/utils.css +104 -0
  41. data/brut-css/src/docs/1_getting-started/1_overview.md +46 -0
  42. data/brut-css/src/docs/1_getting-started/2_installation.md +25 -0
  43. data/brut-css/src/docs/1_getting-started/3_core-concepts.md +75 -0
  44. data/brut-css/src/docs/1_getting-started/4_simple-example.md +132 -0
  45. data/brut-css/src/docs/1_getting-started/page.html.ejs +10 -0
  46. data/brut-css/src/docs/2_properties/page.html.ejs +71 -0
  47. data/brut-css/src/docs/3_classes/color-demo.html.ejs +31 -0
  48. data/brut-css/src/docs/3_classes/page.html.ejs +87 -0
  49. data/brut-css/src/docs/4_customization/1_design-system.md +36 -0
  50. data/brut-css/src/docs/4_customization/2_breakpoints.md +75 -0
  51. data/brut-css/src/docs/4_customization/3_pseudo-classes.md +74 -0
  52. data/brut-css/src/docs/4_customization/4_advanced-configuration.md +40 -0
  53. data/brut-css/src/docs/4_customization/page.html.ejs +10 -0
  54. data/brut-css/src/docs/docs.css +98 -0
  55. data/brut-css/src/docs/includes/body-and-header.html.ejs +30 -0
  56. data/brut-css/src/docs/includes/footer-and-rest.html.ejs +9 -0
  57. data/brut-css/src/docs/includes/head.html.ejs +5 -0
  58. data/brut-css/src/docs/includes/nav.html.ejs +10 -0
  59. data/brut-css/src/docs/index.html.ejs +32 -0
  60. data/brut-css/src/docs/prism-twilight.min.css +1 -0
  61. data/brut-css/src/js/Logger.js +71 -0
  62. data/brut-css/src/js/build.js +111 -0
  63. data/brut-css/src/js/cli/CLIArgError.js +7 -0
  64. data/brut-css/src/js/cli/Debug.js +27 -0
  65. data/brut-css/src/js/cli/DocsDir.js +16 -0
  66. data/brut-css/src/js/cli/DocsTemplateSourceDir.js +16 -0
  67. data/brut-css/src/js/cli/InputFile.js +31 -0
  68. data/brut-css/src/js/cli/MediaQueryConfigFile.js +10 -0
  69. data/brut-css/src/js/cli/OutputFile.js +22 -0
  70. data/brut-css/src/js/cli/ParsedArg.js +17 -0
  71. data/brut-css/src/js/cli/PathToBrutCSSRoot.js +19 -0
  72. data/brut-css/src/js/cli/PseudoClassConfigFile.js +11 -0
  73. data/brut-css/src/js/cli.js +108 -0
  74. data/brut-css/src/js/docGenerator.js +467 -0
  75. data/brut-css/src/js/mediaQueryConfigParser.js +98 -0
  76. data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +49 -0
  77. data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +42 -0
  78. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +9 -0
  79. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +185 -0
  80. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +8 -0
  81. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +7 -0
  82. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +73 -0
  83. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +9 -0
  84. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +4 -0
  85. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +8 -0
  86. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +12 -0
  87. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +4 -0
  88. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +8 -0
  89. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +5 -0
  90. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +9 -0
  91. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +49 -0
  92. data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +45 -0
  93. data/brut-css/src/js/pseudoClassConfigParser.js +145 -0
  94. data/brut-js/.projections.json +10 -0
  95. data/brut-js/README.md +118 -0
  96. data/brut-js/bin/build +10 -0
  97. data/brut-js/bin/ci +5 -0
  98. data/brut-js/bin/setup +5 -0
  99. data/brut-js/docs/README.md +8 -0
  100. data/brut-js/docs/jsdoc-plugins/customElementTag.js +8 -0
  101. data/brut-js/docs/jsdoc-theme/publish.js +692 -0
  102. data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +25 -0
  103. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +202 -0
  104. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +2 -0
  105. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +28 -0
  106. data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +327 -0
  107. data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +111 -0
  108. data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +132 -0
  109. data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +10 -0
  110. data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +199 -0
  111. data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +143 -0
  112. data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +2 -0
  113. data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +13 -0
  114. data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +32 -0
  115. data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +38 -0
  116. data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +14 -0
  117. data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +38 -0
  118. data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +131 -0
  119. data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +14 -0
  120. data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +131 -0
  121. data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +108 -0
  122. data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +19 -0
  123. data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +8 -0
  124. data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +19 -0
  125. data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +7 -0
  126. data/brut-js/docs/jsdoc.config.json +23 -0
  127. data/brut-js/docs/package-lock.json +343 -0
  128. data/brut-js/docs/package.json +7 -0
  129. data/brut-js/package-lock.json +2171 -0
  130. data/brut-js/package.json +32 -0
  131. data/brut-js/specs/AjaxSubmit.spec.js +256 -0
  132. data/brut-js/specs/Autosubmit.spec.js +127 -0
  133. data/brut-js/specs/ConfirmSubmit.spec.js +193 -0
  134. data/brut-js/specs/ConstraintViolationMessage.spec.js +33 -0
  135. data/brut-js/specs/ConstraintViolationMessages.spec.js +29 -0
  136. data/brut-js/specs/CopyToClipboard.spec.js +35 -0
  137. data/brut-js/specs/Form.spec.js +181 -0
  138. data/brut-js/specs/I18nTranslation.spec.js +19 -0
  139. data/brut-js/specs/LocaleDetection.spec.js +22 -0
  140. data/brut-js/specs/Message.spec.js +15 -0
  141. data/brut-js/specs/SpecHelper.js +23 -0
  142. data/brut-js/specs/Tabs.spec.js +41 -0
  143. data/brut-js/specs/config/asset_metadata.json +7 -0
  144. data/brut-js/src/AjaxSubmit.js +384 -0
  145. data/brut-js/src/Autosubmit.js +63 -0
  146. data/brut-js/src/BaseCustomElement.js +261 -0
  147. data/brut-js/src/ConfirmSubmit.js +116 -0
  148. data/brut-js/src/ConfirmationDialog.js +143 -0
  149. data/brut-js/src/ConstraintViolationMessage.js +125 -0
  150. data/brut-js/src/ConstraintViolationMessages.js +98 -0
  151. data/brut-js/src/CopyToClipboard.js +96 -0
  152. data/brut-js/src/Form.js +151 -0
  153. data/brut-js/src/I18nTranslation.js +61 -0
  154. data/brut-js/src/LocaleDetection.js +117 -0
  155. data/brut-js/src/Logger.js +90 -0
  156. data/brut-js/src/Message.js +56 -0
  157. data/brut-js/src/RichString.js +113 -0
  158. data/brut-js/src/Tabs.js +168 -0
  159. data/brut-js/src/Tracing.js +247 -0
  160. data/brut-js/src/appForTestingOnly.js +15 -0
  161. data/brut-js/src/index.js +130 -0
  162. data/brut-js/src/testing/AssetMetadata.js +35 -0
  163. data/brut-js/src/testing/AssetMetadataLoader.js +25 -0
  164. data/brut-js/src/testing/CustomElementTest.js +235 -0
  165. data/brut-js/src/testing/DOMCreator.js +45 -0
  166. data/brut-js/src/testing/index.js +48 -0
  167. data/brutrb.com/.vitepress/config.mjs +106 -0
  168. data/brutrb.com/.vitepress/plugins/jsdocLinker.js +34 -0
  169. data/brutrb.com/.vitepress/plugins/rdocLinker.js +18 -0
  170. data/brutrb.com/.vitepress/theme/custom.css +7 -0
  171. data/brutrb.com/.vitepress/theme/index.js +18 -0
  172. data/brutrb.com/.vitepress/theme/style.css +149 -0
  173. data/brutrb.com/ai.md +68 -0
  174. data/brutrb.com/assets.md +138 -0
  175. data/brutrb.com/bin/build +5 -0
  176. data/brutrb.com/bin/deploy +7 -0
  177. data/brutrb.com/bin/dev +5 -0
  178. data/brutrb.com/bin/setup +5 -0
  179. data/brutrb.com/brut-js.md +117 -0
  180. data/brutrb.com/business-logic.md +55 -0
  181. data/brutrb.com/cli.md +278 -0
  182. data/brutrb.com/components.md +243 -0
  183. data/brutrb.com/configuration.md +257 -0
  184. data/brutrb.com/css.md +103 -0
  185. data/brutrb.com/custom-element-tests.md +149 -0
  186. data/brutrb.com/database-access.md +201 -0
  187. data/brutrb.com/database-schema.md +312 -0
  188. data/brutrb.com/deployment.md +66 -0
  189. data/brutrb.com/dev-environment.md +179 -0
  190. data/brutrb.com/doc-conventions.md +39 -0
  191. data/brutrb.com/end-to-end-tests.md +174 -0
  192. data/brutrb.com/flash-and-session.md +224 -0
  193. data/brutrb.com/forms.md +866 -0
  194. data/brutrb.com/getting-started.md +66 -0
  195. data/brutrb.com/handlers.md +153 -0
  196. data/brutrb.com/hooks.md +178 -0
  197. data/brutrb.com/i18n.md +188 -0
  198. data/brutrb.com/images/Makefile +10 -0
  199. data/brutrb.com/images/dev-env-overview.dot +54 -0
  200. data/brutrb.com/images/dev-env-overview.png +0 -0
  201. data/brutrb.com/images/dev-env-protocol.dot +37 -0
  202. data/brutrb.com/images/dev-env-protocol.png +0 -0
  203. data/brutrb.com/images/logo-300.png +0 -0
  204. data/brutrb.com/images/logo.png +0 -0
  205. data/brutrb.com/images/overview.graffle +0 -0
  206. data/brutrb.com/images/overview.png +0 -0
  207. data/brutrb.com/images/spa.dot +19 -0
  208. data/brutrb.com/images/spa.png +0 -0
  209. data/brutrb.com/images/workspace-protocol.dot +44 -0
  210. data/brutrb.com/images/workspace-protocol.png +0 -0
  211. data/brutrb.com/index.md +36 -0
  212. data/brutrb.com/instrumentation.md +183 -0
  213. data/brutrb.com/javascript.md +122 -0
  214. data/brutrb.com/jobs.md +14 -0
  215. data/{doc-src → brutrb.com}/keyword-injection.md +122 -68
  216. data/brutrb.com/markdown-examples.md +85 -0
  217. data/brutrb.com/middleware.md +80 -0
  218. data/brutrb.com/not-released.md +5 -0
  219. data/brutrb.com/overview.md +404 -0
  220. data/brutrb.com/package-lock.json +2404 -0
  221. data/brutrb.com/package.json +11 -0
  222. data/brutrb.com/pages.md +378 -0
  223. data/brutrb.com/public/images/logo-300.png +0 -0
  224. data/brutrb.com/public/images/logo.png +0 -0
  225. data/brutrb.com/routes.md +215 -0
  226. data/brutrb.com/security.md +105 -0
  227. data/brutrb.com/seed-data.md +63 -0
  228. data/brutrb.com/space-time-continuum.md +85 -0
  229. data/brutrb.com/tutorial.md +3 -0
  230. data/brutrb.com/unit-tests.md +148 -0
  231. data/docker-compose.dx.yml +6 -3
  232. data/docs/404.html +21 -0
  233. data/docs/CNAME +1 -0
  234. data/docs/ai.html +24 -0
  235. data/docs/api/Brut/BackEnd/SeedData.html +493 -0
  236. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +214 -0
  237. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +125 -0
  238. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +125 -0
  239. data/docs/api/Brut/BackEnd/Sidekiq.html +125 -0
  240. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +414 -0
  241. data/docs/api/Brut/BackEnd/Validators.html +128 -0
  242. data/docs/api/Brut/BackEnd.html +132 -0
  243. data/docs/api/Brut/CLI/App.html +1576 -0
  244. data/docs/api/Brut/CLI/AppRunner.html +491 -0
  245. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +264 -0
  246. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +306 -0
  247. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +262 -0
  248. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +314 -0
  249. data/docs/api/Brut/CLI/Apps/BuildAssets.html +183 -0
  250. data/docs/api/Brut/CLI/Apps/DB/Create.html +365 -0
  251. data/docs/api/Brut/CLI/Apps/DB/Drop.html +357 -0
  252. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +383 -0
  253. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +335 -0
  254. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +329 -0
  255. data/docs/api/Brut/CLI/Apps/DB/Seed.html +347 -0
  256. data/docs/api/Brut/CLI/Apps/DB/Status.html +383 -0
  257. data/docs/api/Brut/CLI/Apps/DB.html +183 -0
  258. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +303 -0
  259. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +512 -0
  260. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +398 -0
  261. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +374 -0
  262. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +410 -0
  263. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +262 -0
  264. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +303 -0
  265. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +480 -0
  266. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +450 -0
  267. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +380 -0
  268. data/docs/api/Brut/CLI/Apps/Scaffold.html +253 -0
  269. data/docs/api/Brut/CLI/Apps/Test/Audit.html +464 -0
  270. data/docs/api/Brut/CLI/Apps/Test/E2e.html +407 -0
  271. data/docs/api/Brut/CLI/Apps/Test/JS.html +262 -0
  272. data/docs/api/Brut/CLI/Apps/Test/Run.html +578 -0
  273. data/docs/api/Brut/CLI/Apps/Test.html +253 -0
  274. data/docs/api/Brut/CLI/Apps.html +125 -0
  275. data/docs/api/Brut/CLI/Command.html +2342 -0
  276. data/docs/api/Brut/CLI/Error.html +139 -0
  277. data/docs/api/Brut/CLI/ExecutionResults/Result.html +664 -0
  278. data/docs/api/Brut/CLI/ExecutionResults.html +675 -0
  279. data/docs/api/Brut/CLI/Executor.html +430 -0
  280. data/docs/api/Brut/CLI/InvalidOption.html +245 -0
  281. data/docs/api/Brut/CLI/Options.html +753 -0
  282. data/docs/api/Brut/CLI/Output.html +699 -0
  283. data/docs/api/Brut/CLI/SystemExecError.html +451 -0
  284. data/docs/api/Brut/CLI.html +263 -0
  285. data/docs/api/Brut/FactoryBot.html +225 -0
  286. data/docs/api/Brut/Framework/App.html +1097 -0
  287. data/docs/api/Brut/Framework/Config.html +1045 -0
  288. data/docs/api/Brut/Framework/Container.html +1379 -0
  289. data/docs/api/Brut/Framework/Error.html +140 -0
  290. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +144 -0
  291. data/docs/api/Brut/Framework/Errors/Bug.html +234 -0
  292. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +257 -0
  293. data/docs/api/Brut/Framework/Errors/MissingParameter.html +273 -0
  294. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +471 -0
  295. data/docs/api/Brut/Framework/Errors/NotFound.html +308 -0
  296. data/docs/api/Brut/Framework/Errors/NotImplemented.html +234 -0
  297. data/docs/api/Brut/Framework/Errors.html +328 -0
  298. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +392 -0
  299. data/docs/api/Brut/Framework/MCP.html +861 -0
  300. data/docs/api/Brut/Framework/ProjectEnvironment.html +648 -0
  301. data/docs/api/Brut/Framework.html +129 -0
  302. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +317 -0
  303. data/docs/api/Brut/FrontEnd/Component/Helpers.html +326 -0
  304. data/docs/api/Brut/FrontEnd/Component.html +365 -0
  305. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +470 -0
  306. data/docs/api/Brut/FrontEnd/Components/FormTag.html +518 -0
  307. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +317 -0
  308. data/docs/api/Brut/FrontEnd/Components/Input.html +195 -0
  309. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +339 -0
  310. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +660 -0
  311. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +417 -0
  312. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +918 -0
  313. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +651 -0
  314. data/docs/api/Brut/FrontEnd/Components/Inputs.html +125 -0
  315. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +367 -0
  316. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +336 -0
  317. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +655 -0
  318. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +352 -0
  319. data/docs/api/Brut/FrontEnd/Components.html +135 -0
  320. data/docs/api/Brut/FrontEnd/Download.html +467 -0
  321. data/docs/api/Brut/FrontEnd/Flash.html +1150 -0
  322. data/docs/api/Brut/FrontEnd/Form.html +1157 -0
  323. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +634 -0
  324. data/docs/api/Brut/FrontEnd/Forms/Input.html +615 -0
  325. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +547 -0
  326. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1318 -0
  327. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +609 -0
  328. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +587 -0
  329. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +613 -0
  330. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +582 -0
  331. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +609 -0
  332. data/docs/api/Brut/FrontEnd/Forms.html +127 -0
  333. data/docs/api/Brut/FrontEnd/GenericResponse.html +377 -0
  334. data/docs/api/Brut/FrontEnd/Handler.html +442 -0
  335. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +318 -0
  336. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +336 -0
  337. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +399 -0
  338. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +354 -0
  339. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +151 -0
  340. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +315 -0
  341. data/docs/api/Brut/FrontEnd/Handlers.html +125 -0
  342. data/docs/api/Brut/FrontEnd/HandlingResults.html +339 -0
  343. data/docs/api/Brut/FrontEnd/HttpMethod.html +661 -0
  344. data/docs/api/Brut/FrontEnd/HttpStatus.html +496 -0
  345. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +284 -0
  346. data/docs/api/Brut/FrontEnd/Layout.html +318 -0
  347. data/docs/api/Brut/FrontEnd/Middleware.html +135 -0
  348. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +288 -0
  349. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +292 -0
  350. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +324 -0
  351. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +372 -0
  352. data/docs/api/Brut/FrontEnd/Middlewares.html +125 -0
  353. data/docs/api/Brut/FrontEnd/Page.html +773 -0
  354. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +797 -0
  355. data/docs/api/Brut/FrontEnd/Pages.html +125 -0
  356. data/docs/api/Brut/FrontEnd/RequestContext.html +1312 -0
  357. data/docs/api/Brut/FrontEnd/RouteHook.html +424 -0
  358. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +242 -0
  359. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +249 -0
  360. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +264 -0
  361. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +261 -0
  362. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +284 -0
  363. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +252 -0
  364. data/docs/api/Brut/FrontEnd/RouteHooks.html +115 -0
  365. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +227 -0
  366. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +305 -0
  367. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +324 -0
  368. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +319 -0
  369. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +315 -0
  370. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +315 -0
  371. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +327 -0
  372. data/docs/api/Brut/FrontEnd/Routing/Route.html +761 -0
  373. data/docs/api/Brut/FrontEnd/Routing.html +927 -0
  374. data/docs/api/Brut/FrontEnd/Session.html +1195 -0
  375. data/docs/api/Brut/FrontEnd.html +134 -0
  376. data/docs/api/Brut/I18n/BaseMethods.html +931 -0
  377. data/docs/api/Brut/I18n/ForBackEnd.html +302 -0
  378. data/docs/api/Brut/I18n/ForCLI.html +302 -0
  379. data/docs/api/Brut/I18n/ForHTML.html +296 -0
  380. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +316 -0
  381. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +930 -0
  382. data/docs/api/Brut/I18n.html +127 -0
  383. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +435 -0
  384. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +286 -0
  385. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +302 -0
  386. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +864 -0
  387. data/docs/api/Brut/Instrumentation.html +126 -0
  388. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +532 -0
  389. data/docs/api/Brut/SinatraHelpers.html +281 -0
  390. data/docs/api/Brut/SpecSupport/ClockSupport.html +383 -0
  391. data/docs/api/Brut/SpecSupport/ComponentSupport.html +502 -0
  392. data/docs/api/Brut/SpecSupport/E2ETestServer.html +503 -0
  393. data/docs/api/Brut/SpecSupport/E2eSupport.html +142 -0
  394. data/docs/api/Brut/SpecSupport/EnhancedNode.html +403 -0
  395. data/docs/api/Brut/SpecSupport/FlashSupport.html +278 -0
  396. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +401 -0
  397. data/docs/api/Brut/SpecSupport/GeneralSupport.html +195 -0
  398. data/docs/api/Brut/SpecSupport/HandlerSupport.html +160 -0
  399. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +553 -0
  400. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +439 -0
  401. data/docs/api/Brut/SpecSupport/Matchers.html +125 -0
  402. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +335 -0
  403. data/docs/api/Brut/SpecSupport/RSpecSetup.html +602 -0
  404. data/docs/api/Brut/SpecSupport/SessionSupport.html +196 -0
  405. data/docs/api/Brut/SpecSupport.html +129 -0
  406. data/docs/api/Brut.html +225 -0
  407. data/docs/api/Clock.html +603 -0
  408. data/docs/api/RichString.html +968 -0
  409. data/docs/api/SemanticLogger/Appender/Async.html +219 -0
  410. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +115 -0
  411. data/docs/api/Sequel/Extensions/BrutMigrations.html +533 -0
  412. data/docs/api/Sequel/Extensions.html +117 -0
  413. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +105 -0
  414. data/docs/api/Sequel/Plugins/CreatedAt.html +125 -0
  415. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +207 -0
  416. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +186 -0
  417. data/docs/api/Sequel/Plugins/ExternalId.html +218 -0
  418. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +202 -0
  419. data/docs/api/Sequel/Plugins/FindBang.html +125 -0
  420. data/docs/api/Sequel/Plugins.html +117 -0
  421. data/docs/api/Sequel.html +117 -0
  422. data/docs/api/_index.html +1553 -0
  423. data/docs/api/class_list.html +54 -0
  424. data/docs/api/css/common.css +1 -0
  425. data/docs/api/css/full_list.css +58 -0
  426. data/docs/api/css/style.css +503 -0
  427. data/docs/api/file.README.html +127 -0
  428. data/docs/api/file_list.html +59 -0
  429. data/docs/api/frames.html +22 -0
  430. data/docs/api/index.html +127 -0
  431. data/docs/api/js/app.js +344 -0
  432. data/docs/api/js/full_list.js +242 -0
  433. data/docs/api/js/jquery.js +4 -0
  434. data/docs/api/method_list.html +3998 -0
  435. data/docs/api/top-level-namespace.html +112 -0
  436. data/docs/assets/ai.md.tZrjP9im.js +1 -0
  437. data/docs/assets/ai.md.tZrjP9im.lean.js +1 -0
  438. data/docs/assets/app.D_yaTITQ.js +1 -0
  439. data/docs/assets/assets.md.D3wunzLx.js +19 -0
  440. data/docs/assets/assets.md.D3wunzLx.lean.js +1 -0
  441. data/docs/assets/brut-js.md.o2DAO2s2.js +12 -0
  442. data/docs/assets/brut-js.md.o2DAO2s2.lean.js +1 -0
  443. data/docs/assets/business-logic.md.BY4hGy0m.js +1 -0
  444. data/docs/assets/business-logic.md.BY4hGy0m.lean.js +1 -0
  445. data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +1 -0
  446. data/docs/assets/chunks/VPLocalSearchBox.B2-ZzyTY.js +8 -0
  447. data/docs/assets/chunks/framework.1L-BeKqY.js +18 -0
  448. data/docs/assets/chunks/theme.CfGFVRvE.js +2 -0
  449. data/docs/assets/cli.md.RmeA2b0i.js +127 -0
  450. data/docs/assets/cli.md.RmeA2b0i.lean.js +1 -0
  451. data/docs/assets/components.md.eCttGlN-.js +104 -0
  452. data/docs/assets/components.md.eCttGlN-.lean.js +1 -0
  453. data/docs/assets/configuration.md.BRriU0cL.js +78 -0
  454. data/docs/assets/configuration.md.BRriU0cL.lean.js +1 -0
  455. data/docs/assets/css.md.DJgj2clw.js +21 -0
  456. data/docs/assets/css.md.DJgj2clw.lean.js +1 -0
  457. data/docs/assets/custom-element-tests.md.BrYJQEl3.js +69 -0
  458. data/docs/assets/custom-element-tests.md.BrYJQEl3.lean.js +1 -0
  459. data/docs/assets/database-access.md.C7l-Vuvb.js +63 -0
  460. data/docs/assets/database-access.md.C7l-Vuvb.lean.js +1 -0
  461. data/docs/assets/database-schema.md.BUjR0VS1.js +63 -0
  462. data/docs/assets/database-schema.md.BUjR0VS1.lean.js +1 -0
  463. data/docs/assets/deployment.md.Dbka4OTr.js +1 -0
  464. data/docs/assets/deployment.md.Dbka4OTr.lean.js +1 -0
  465. data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
  466. data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
  467. data/docs/assets/dev-environment.md.BNc8AYiK.js +11 -0
  468. data/docs/assets/dev-environment.md.BNc8AYiK.lean.js +1 -0
  469. data/docs/assets/doc-conventions.md.DCfRXXi-.js +1 -0
  470. data/docs/assets/doc-conventions.md.DCfRXXi-.lean.js +1 -0
  471. data/docs/assets/end-to-end-tests.md.yfQHC0b5.js +26 -0
  472. data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +1 -0
  473. data/docs/assets/flash-and-session.md.BXY8RvT0.js +93 -0
  474. data/docs/assets/flash-and-session.md.BXY8RvT0.lean.js +1 -0
  475. data/docs/assets/forms.md.CBTYQ_Cz.js +379 -0
  476. data/docs/assets/forms.md.CBTYQ_Cz.lean.js +1 -0
  477. data/docs/assets/getting-started.md.Bz2s1Vjb.js +2 -0
  478. data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +1 -0
  479. data/docs/assets/handlers.md.089DVD3v.js +69 -0
  480. data/docs/assets/handlers.md.089DVD3v.lean.js +1 -0
  481. data/docs/assets/hooks.md.C4-moMny.js +80 -0
  482. data/docs/assets/hooks.md.C4-moMny.lean.js +1 -0
  483. data/docs/assets/i18n.md.Do9i1qWl.js +23 -0
  484. data/docs/assets/i18n.md.Do9i1qWl.lean.js +1 -0
  485. data/docs/assets/index.md.B28EwVpq.js +1 -0
  486. data/docs/assets/index.md.B28EwVpq.lean.js +1 -0
  487. data/docs/assets/instrumentation.md.CL6ax7nT.js +35 -0
  488. data/docs/assets/instrumentation.md.CL6ax7nT.lean.js +1 -0
  489. data/docs/assets/javascript.md.GWbhRS51.js +31 -0
  490. data/docs/assets/javascript.md.GWbhRS51.lean.js +1 -0
  491. data/docs/assets/jobs.md.S-2amAYp.js +1 -0
  492. data/docs/assets/jobs.md.S-2amAYp.lean.js +1 -0
  493. data/docs/assets/keyword-injection.md.Dt2tKREs.js +25 -0
  494. data/docs/assets/keyword-injection.md.Dt2tKREs.lean.js +1 -0
  495. data/docs/assets/markdown-examples.md.CCFEQO44.js +33 -0
  496. data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +1 -0
  497. data/docs/assets/middleware.md.Czz_UlJN.js +20 -0
  498. data/docs/assets/middleware.md.Czz_UlJN.lean.js +1 -0
  499. data/docs/assets/not-released.md.BBy28McC.js +1 -0
  500. data/docs/assets/not-released.md.BBy28McC.lean.js +1 -0
  501. data/docs/assets/overview.Da81cB9R.png +0 -0
  502. data/docs/assets/overview.md.CDalkuxV.js +133 -0
  503. data/docs/assets/overview.md.CDalkuxV.lean.js +1 -0
  504. data/docs/assets/pages.md.BE3kfOc5.js +122 -0
  505. data/docs/assets/pages.md.BE3kfOc5.lean.js +1 -0
  506. data/docs/assets/routes.md.BMM7peut.js +29 -0
  507. data/docs/assets/routes.md.BMM7peut.lean.js +1 -0
  508. data/docs/assets/security.md.C668yXCi.js +1 -0
  509. data/docs/assets/security.md.C668yXCi.lean.js +1 -0
  510. data/docs/assets/seed-data.md.BvFZlqIk.js +14 -0
  511. data/docs/assets/seed-data.md.BvFZlqIk.lean.js +1 -0
  512. data/docs/assets/spa.qejUdp-5.png +0 -0
  513. data/docs/assets/space-time-continuum.md.KPUIKysQ.js +1 -0
  514. data/docs/assets/space-time-continuum.md.KPUIKysQ.lean.js +1 -0
  515. data/docs/assets/style.D73IYGCX.css +1 -0
  516. data/docs/assets/tutorial.md.BnoGjrdK.js +1 -0
  517. data/docs/assets/tutorial.md.BnoGjrdK.lean.js +1 -0
  518. data/docs/assets/unit-tests.md.DUGrnLj5.js +13 -0
  519. data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +1 -0
  520. data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
  521. data/docs/assets.html +42 -0
  522. data/docs/brut-css/brut.css +1 -0
  523. data/docs/brut-css/brut.max.css +22372 -0
  524. data/docs/brut-css/classes/appearances.html +783 -0
  525. data/docs/brut-css/classes/background-colors.html +3529 -0
  526. data/docs/brut-css/classes/border-colors.html +3529 -0
  527. data/docs/brut-css/classes/borders.html +2293 -0
  528. data/docs/brut-css/classes/dimensions.html +2581 -0
  529. data/docs/brut-css/classes/flex.html +917 -0
  530. data/docs/brut-css/classes/foreground-colors.html +3261 -0
  531. data/docs/brut-css/classes/junk-drawer.html +431 -0
  532. data/docs/brut-css/classes/layout.html +668 -0
  533. data/docs/brut-css/classes/lists.html +331 -0
  534. data/docs/brut-css/classes/positioning.html +1751 -0
  535. data/docs/brut-css/classes/spacings.html +2633 -0
  536. data/docs/brut-css/classes/typography.html +2206 -0
  537. data/docs/brut-css/customization/advanced-configuration.html +204 -0
  538. data/docs/brut-css/customization/breakpoints.html +227 -0
  539. data/docs/brut-css/customization/design-system.html +197 -0
  540. data/docs/brut-css/customization/pseudo-classes.html +228 -0
  541. data/docs/brut-css/docs.css +98 -0
  542. data/docs/brut-css/getting-started/core-concepts.html +234 -0
  543. data/docs/brut-css/getting-started/installation.html +190 -0
  544. data/docs/brut-css/getting-started/overview.html +210 -0
  545. data/docs/brut-css/getting-started/simple-example.html +285 -0
  546. data/docs/brut-css/index.html +193 -0
  547. data/docs/brut-css/prism-twilight.min.css +1 -0
  548. data/docs/brut-css/properties/colors.html +1548 -0
  549. data/docs/brut-css/properties/spacings.html +614 -0
  550. data/docs/brut-css/properties/typography.html +777 -0
  551. data/docs/brut-js/api/AjaxSubmit.html +374 -0
  552. data/docs/brut-js/api/AjaxSubmit.js.html +435 -0
  553. data/docs/brut-js/api/Autosubmit.html +192 -0
  554. data/docs/brut-js/api/Autosubmit.js.html +114 -0
  555. data/docs/brut-js/api/BaseCustomElement.html +1091 -0
  556. data/docs/brut-js/api/BaseCustomElement.js.html +312 -0
  557. data/docs/brut-js/api/BrutCustomElements.html +172 -0
  558. data/docs/brut-js/api/BufferedLogger.html +173 -0
  559. data/docs/brut-js/api/ConfirmSubmit.html +278 -0
  560. data/docs/brut-js/api/ConfirmSubmit.js.html +167 -0
  561. data/docs/brut-js/api/ConfirmationDialog.html +425 -0
  562. data/docs/brut-js/api/ConfirmationDialog.js.html +194 -0
  563. data/docs/brut-js/api/ConstraintViolationMessage.html +448 -0
  564. data/docs/brut-js/api/ConstraintViolationMessage.js.html +176 -0
  565. data/docs/brut-js/api/ConstraintViolationMessages.html +590 -0
  566. data/docs/brut-js/api/ConstraintViolationMessages.js.html +149 -0
  567. data/docs/brut-js/api/CopyToClipboard.html +345 -0
  568. data/docs/brut-js/api/CopyToClipboard.js.html +147 -0
  569. data/docs/brut-js/api/Form.html +294 -0
  570. data/docs/brut-js/api/Form.js.html +202 -0
  571. data/docs/brut-js/api/I18nTranslation.html +409 -0
  572. data/docs/brut-js/api/I18nTranslation.js.html +112 -0
  573. data/docs/brut-js/api/LocaleDetection.html +312 -0
  574. data/docs/brut-js/api/LocaleDetection.js.html +168 -0
  575. data/docs/brut-js/api/Logger.html +702 -0
  576. data/docs/brut-js/api/Logger.js.html +141 -0
  577. data/docs/brut-js/api/Message.html +238 -0
  578. data/docs/brut-js/api/Message.js.html +107 -0
  579. data/docs/brut-js/api/PrefixedLogger.html +369 -0
  580. data/docs/brut-js/api/RichString.html +1049 -0
  581. data/docs/brut-js/api/RichString.js.html +164 -0
  582. data/docs/brut-js/api/Tabs.html +295 -0
  583. data/docs/brut-js/api/Tabs.js.html +219 -0
  584. data/docs/brut-js/api/Tracing.html +277 -0
  585. data/docs/brut-js/api/Tracing.js.html +298 -0
  586. data/docs/brut-js/api/external-CustomElementRegistry.html +140 -0
  587. data/docs/brut-js/api/external-Performance.html +138 -0
  588. data/docs/brut-js/api/external-Promise.html +138 -0
  589. data/docs/brut-js/api/external-ValidityState.html +138 -0
  590. data/docs/brut-js/api/external-Window.html +233 -0
  591. data/docs/brut-js/api/external-fetch.html +138 -0
  592. data/docs/brut-js/api/global.html +400 -0
  593. data/docs/brut-js/api/index.html +168 -0
  594. data/docs/brut-js/api/index.js.html +181 -0
  595. data/docs/brut-js/api/module-testing.html +383 -0
  596. data/docs/brut-js/api/scripts/linenumber.js +25 -0
  597. data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +202 -0
  598. data/docs/brut-js/api/scripts/prettify/lang-css.js +2 -0
  599. data/docs/brut-js/api/scripts/prettify/prettify.js +28 -0
  600. data/docs/brut-js/api/styles/jsdoc-default.css +327 -0
  601. data/docs/brut-js/api/styles/prettify-jsdoc.css +111 -0
  602. data/docs/brut-js/api/styles/prettify-tomorrow.css +132 -0
  603. data/docs/brut-js/api/testing.AssetMetadata.html +172 -0
  604. data/docs/brut-js/api/testing.AssetMetadataLoader.html +171 -0
  605. data/docs/brut-js/api/testing.CustomElementTest.html +679 -0
  606. data/docs/brut-js/api/testing.DOMCreator.html +171 -0
  607. data/docs/brut-js/api/testing_AssetMetadata.js.html +86 -0
  608. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +76 -0
  609. data/docs/brut-js/api/testing_CustomElementTest.js.html +286 -0
  610. data/docs/brut-js/api/testing_DOMCreator.js.html +96 -0
  611. data/docs/brut-js/api/testing_index.js.html +99 -0
  612. data/docs/brut-js.html +35 -0
  613. data/docs/business-logic.html +24 -0
  614. data/docs/cli.html +150 -0
  615. data/docs/components.html +127 -0
  616. data/docs/configuration.html +101 -0
  617. data/docs/css.html +44 -0
  618. data/docs/custom-element-tests.html +92 -0
  619. data/docs/database-access.html +86 -0
  620. data/docs/database-schema.html +86 -0
  621. data/docs/deployment.html +24 -0
  622. data/docs/dev-environment.html +34 -0
  623. data/docs/doc-conventions.html +24 -0
  624. data/docs/end-to-end-tests.html +49 -0
  625. data/docs/flash-and-session.html +116 -0
  626. data/docs/forms.html +402 -0
  627. data/docs/getting-started.html +25 -0
  628. data/docs/handlers.html +92 -0
  629. data/docs/hashmap.json +1 -0
  630. data/docs/hooks.html +103 -0
  631. data/docs/i18n.html +46 -0
  632. data/docs/images/logo-300.png +0 -0
  633. data/docs/images/logo.png +0 -0
  634. data/docs/index.html +24 -0
  635. data/docs/instrumentation.html +58 -0
  636. data/docs/javascript.html +54 -0
  637. data/docs/jobs.html +24 -0
  638. data/docs/keyword-injection.html +48 -0
  639. data/docs/markdown-examples.html +56 -0
  640. data/docs/middleware.html +43 -0
  641. data/docs/not-released.html +24 -0
  642. data/docs/overview.html +156 -0
  643. data/docs/pages.html +145 -0
  644. data/docs/routes.html +52 -0
  645. data/docs/security.html +24 -0
  646. data/docs/seed-data.html +37 -0
  647. data/docs/space-time-continuum.html +24 -0
  648. data/docs/tutorial.html +24 -0
  649. data/docs/unit-tests.html +36 -0
  650. data/docs/vp-icons.css +1 -0
  651. data/lib/brut/back_end/seed_data.rb +19 -2
  652. data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
  653. data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
  654. data/lib/brut/back_end/sidekiq.rb +2 -1
  655. data/lib/brut/back_end/validator.rb +5 -1
  656. data/lib/brut/back_end.rb +4 -2
  657. data/lib/brut/cli/app_runner.rb +1 -1
  658. data/lib/brut/cli/apps/test.rb +5 -0
  659. data/lib/brut/cli.rb +4 -3
  660. data/lib/brut/factory_bot.rb +0 -5
  661. data/lib/brut/framework/app.rb +70 -5
  662. data/lib/brut/framework/config.rb +5 -3
  663. data/lib/brut/framework/container.rb +3 -2
  664. data/lib/brut/framework/errors.rb +12 -4
  665. data/lib/brut/framework/mcp.rb +58 -1
  666. data/lib/brut/framework/project_environment.rb +6 -2
  667. data/lib/brut/framework.rb +1 -1
  668. data/lib/brut/front_end/component.rb +69 -71
  669. data/lib/brut/front_end/components/constraint_violations.rb +1 -4
  670. data/lib/brut/front_end/components/form_tag.rb +1 -1
  671. data/lib/brut/front_end/components/input.rb +3 -3
  672. data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
  673. data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +7 -9
  674. data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
  675. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
  676. data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
  677. data/lib/brut/front_end/components/time_tag.rb +2 -1
  678. data/lib/brut/front_end/form.rb +4 -4
  679. data/lib/brut/front_end/forms/input.rb +2 -1
  680. data/lib/brut/front_end/forms/input_definition.rb +5 -2
  681. data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
  682. data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
  683. data/lib/brut/front_end/forms/select_input.rb +2 -4
  684. data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
  685. data/lib/brut/front_end/handler.rb +28 -26
  686. data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
  687. data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
  688. data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
  689. data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
  690. data/lib/brut/front_end/layout.rb +16 -0
  691. data/lib/brut/front_end/page.rb +52 -29
  692. data/lib/brut/front_end/request_context.rb +3 -2
  693. data/lib/brut/front_end/routing.rb +5 -1
  694. data/lib/brut/front_end.rb +4 -13
  695. data/lib/brut/i18n/base_methods.rb +167 -79
  696. data/lib/brut/i18n/for_back_end.rb +4 -0
  697. data/lib/brut/i18n/for_cli.rb +4 -0
  698. data/lib/brut/i18n/for_html.rb +32 -4
  699. data/lib/brut/i18n/http_accept_language.rb +47 -0
  700. data/lib/brut/instrumentation/open_telemetry.rb +36 -1
  701. data/lib/brut/instrumentation.rb +3 -5
  702. data/lib/brut/sinatra_helpers.rb +11 -3
  703. data/lib/brut/spec_support/component_support.rb +30 -16
  704. data/lib/brut/spec_support/e2e_support.rb +1 -1
  705. data/lib/brut/spec_support/e2e_test_server.rb +3 -0
  706. data/lib/brut/spec_support/general_support.rb +3 -0
  707. data/lib/brut/spec_support/handler_support.rb +6 -1
  708. data/lib/brut/spec_support/matcher.rb +1 -0
  709. data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
  710. data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
  711. data/lib/brut/spec_support/matchers/have_i18n_string.rb +2 -5
  712. data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
  713. data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
  714. data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
  715. data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
  716. data/lib/brut/spec_support.rb +1 -1
  717. data/lib/brut/version.rb +1 -1
  718. data/lib/brut.rb +5 -4
  719. data/lib/sequel/extensions/brut_migrations.rb +1 -1
  720. metadata +648 -13
  721. data/doc-src/architecture.md +0 -102
  722. data/doc-src/assets.md +0 -98
  723. data/doc-src/forms.md +0 -214
  724. data/doc-src/handlers.md +0 -83
  725. data/doc-src/javascript.md +0 -265
  726. data/doc-src/pages.md +0 -210
  727. data/doc-src/route-hooks.md +0 -59
  728. data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -0,0 +1,31 @@
1
+ import{_ as i,c as a,o as e,ag as t}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"JavaScript","description":"","frontmatter":{},"headers":[],"relativePath":"javascript.md","filePath":"javascript.md"}'),n={name:"javascript.md"};function l(p,s,h,o,r,d){return e(),a("div",null,s[0]||(s[0]=[t(`<h1 id="javascript" tabindex="-1">JavaScript <a class="header-anchor" href="#javascript" aria-label="Permalink to &quot;JavaScript&quot;">​</a></h1><p>Brut provides basic bundling using <a href="https://esbuild.github.io/" target="_blank" rel="noreferrer">esbuild</a>. Brut does not support nor prevent the use of any front-end framework. Brut does, however, include <a href="/brut-js.html">BrutJS</a>, a lightweight library of HTML custom elements and utility code. These elements can provide a fair bit of front-end functionality using progressive enhancement without the need for a framework.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>All your app&#39;s JavaScript lives in <code>app/src/front_end/js</code>, or in modules you bring in via <code>package.json</code>. Brut will <em>bundle</em> all of that up into a single <code>.js</code> file that is served up with your app. Brut does this by using esbuild, a stable and standardized tool for bundling JavaScript.</p><p>The way esbuild works is to be given an <em>entry point</em> that requires, or transitively requires, all of your JavaScript by using ES6 modules. <code>app/src/front_end/js/index.js</code> is the entry point for your app.</p><p>For example, if you have a <code>Widget</code> class that uses a <code>Status</code> class, and you also use the third party library &quot;foobar&quot;, here is how all the files would look.</p><p>First, <code>package.json</code> (in your app&#39;s root) would include <code>&quot;foobar&quot;</code> (and it must set <code>&quot;type&quot;</code> to <code>&quot;module&quot;</code>):</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
2
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;name&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;your-app&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
3
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;type&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;module&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
4
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;license&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;UNLICENSED&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
5
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;dependencies&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
6
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;foobar&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^0.0.11&quot;</span></span>
7
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
8
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;devDependencies&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
9
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;chokidar-cli&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^3.0.0&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
10
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;esbuild&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^0.20.2&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
11
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;jsdom&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^25.0.1&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
12
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;mocha&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^10.7.3&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
13
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> &quot;playwright&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;^1.50.1&quot;</span></span>
14
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
15
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Next, <code>app/src/front_end/js/index.js</code> would import both <code>&quot;foobar&quot;</code> and <code>&quot;Widget&quot;</code>:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { foobar } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;foobar&quot;</span></span>
16
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Widget </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;./Widget&quot;</span></span>
17
+ <span class="line"></span>
18
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// ...</span></span></code></pre></div><p>Notice that &quot;foobar&quot;, since it&#39;s brought in as a third party dependency, is imported without a <code>./</code>. Be careful here! Every third party library has a different syntax for how to import whatever it is or does. Consult the documentation of each third party library you wish to import.</p><p>The second <code>import</code> uses a <code>./</code> because it&#39;s importing a file in <code>app/src/front_end/js</code> namely <code>Widget.js</code>. Be careful here, too, as you must be sure to <code>export</code> the right thing. Here&#39;s what <code>app/src/front_end/js/Widget.js</code> might look like:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Status </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;./extra/Status&quot;</span></span>
19
+ <span class="line"></span>
20
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Widget</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
21
+ <span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> status</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>
22
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
23
+ <span class="line"></span>
24
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Widget</span></span></code></pre></div><p>Note that we import <code>Status</code> here. Unlike Ruby, ES6 modules requires each class that references a class to import it explicitly. Also notice that we do <code>export default Widget</code>, which allows <code>import Widget</code> to work.</p><p>Finally, <code>app/src/front_end/extra/Status.js</code> looks like so:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> Status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
25
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
26
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Status</span></span></code></pre></div><p>When <code>bin/build-assets</code> runs, esbuild will use <code>app/src/front_end/js/index.js</code> as its <em>entry point</em>, and will bundle both <code>Widget.js</code> and the &quot;foobar&quot; library. When it bundles <code>Widget.js</code>, it will see that it imports <code>extra/Status.js</code> and bundle that, too.</p><p>This bundle can be included in your app by ensuring this is in your layout:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
27
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> doctype</span></span>
28
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> html</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">lang:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;en&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
29
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> head </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
30
+ <span class="line highlighted"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> script</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">defer:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">src:</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> asset_path</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;/js/app.js&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">))</span></span>
31
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span></code></pre></div><p>The <code>asset_path</code> helper takes a logical path—<code>/js/app.js</code>—and returns the actual path the browser can use. More details on this can be found in <a href="/assets.html">assets</a>.</p><h2 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to &quot;Testing&quot;">​</a></h2><p>Client-side behavior is best tested with end-to-end tests, however you can simplify your end-to-end tests by creating unit tests of your custom elements. BrutJS provides support for this. TBD LINK.</p><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to &quot;Recommended Practices&quot;">​</a></h2><p>Brut encourages you to use HTML custom elements as progressive enhancements over server-generated views. This sort of client-side code will age well. The toolchain and dependencies are minimal, so you will not have to worry too much about code written this way.</p><p>It <em>will</em> be lower level and more verbose than existing frameworks. We would argue that it is not significantly more difficult and the sustainability is worth it.</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to &quot;Technical Notes&quot;">​</a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut&#39;s internals, the source code is always more correct.</p></div><p><em>Last Updated May 7, 2025</em></p><p>Currently, Brut only supports a single entry point and bundle. This could be easily made more flexible if there is a desire to finely tweak the JavaScript loaded on specific pages.</p><p>Brut also does not expose any esbuild configuration. This could be provided in the future, but for now, it is hard-coded.</p>`,30)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default};
@@ -0,0 +1 @@
1
+ import{_ as i,c as a,o as e,ag as t}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"JavaScript","description":"","frontmatter":{},"headers":[],"relativePath":"javascript.md","filePath":"javascript.md"}'),n={name:"javascript.md"};function l(p,s,h,o,r,d){return e(),a("div",null,s[0]||(s[0]=[t("",30)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as r,o as s,j as e,a as t}from"./chunks/framework.1L-BeKqY.js";const m=JSON.parse('{"title":"Background Jobs","description":"","frontmatter":{},"headers":[],"relativePath":"jobs.md","filePath":"jobs.md"}'),n={name:"jobs.md"};function i(l,o,c,d,u,p){return s(),r("div",null,o[0]||(o[0]=[e("h1",{id:"background-jobs",tabindex:"-1"},[t("Background Jobs "),e("a",{class:"header-anchor",href:"#background-jobs","aria-label":'Permalink to "Background Jobs"'},"​")],-1),e("p",null,"Brut provides little direct support for background jobs. Currently, Brut recommends Sidekiq, since it is battle-tested, well-supported, and open source.",-1),e("p",null,"When you set up your Brut app, it should ask if you want Sidekiq support and add the necessary configuraiton.",-1),e("p",null,[t("It will expect jobs in "),e("code",null,"app/src/back_end/jobs"),t(".")],-1),e("div",{class:"warning custom-block github-alert"},[e("p",{class:"custom-block-title"},"WARNING"),e("p",null,"The way Sidekiq is configured with Brut is effective and reliable, but it is complex. It currently involves several moving parts to make it work properly. This will be an area for improvement.")],-1)]))}const k=a(n,[["render",i]]);export{m as __pageData,k as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as r,o as s,j as e,a as t}from"./chunks/framework.1L-BeKqY.js";const m=JSON.parse('{"title":"Background Jobs","description":"","frontmatter":{},"headers":[],"relativePath":"jobs.md","filePath":"jobs.md"}'),n={name:"jobs.md"};function i(l,o,c,d,u,p){return s(),r("div",null,o[0]||(o[0]=[e("h1",{id:"background-jobs",tabindex:"-1"},[t("Background Jobs "),e("a",{class:"header-anchor",href:"#background-jobs","aria-label":'Permalink to "Background Jobs"'},"​")],-1),e("p",null,"Brut provides little direct support for background jobs. Currently, Brut recommends Sidekiq, since it is battle-tested, well-supported, and open source.",-1),e("p",null,"When you set up your Brut app, it should ask if you want Sidekiq support and add the necessary configuraiton.",-1),e("p",null,[t("It will expect jobs in "),e("code",null,"app/src/back_end/jobs"),t(".")],-1),e("div",{class:"warning custom-block github-alert"},[e("p",{class:"custom-block-title"},"WARNING"),e("p",null,"The way Sidekiq is configured with Brut is effective and reliable, but it is complex. It currently involves several moving parts to make it work properly. This will be an area for improvement.")],-1)]))}const k=a(n,[["render",i]]);export{m as __pageData,k as default};
@@ -0,0 +1,25 @@
1
+ import{_ as i,c as s,o as a,ag as t}from"./chunks/framework.1L-BeKqY.js";const u=JSON.parse('{"title":"Keyword Injection","description":"","frontmatter":{},"headers":[],"relativePath":"keyword-injection.md","filePath":"keyword-injection.md"}'),n={name:"keyword-injection.md"};function o(l,e,h,r,d,p){return a(),s("div",null,e[0]||(e[0]=[t(`<h1 id="keyword-injection" tabindex="-1">Keyword Injection <a class="header-anchor" href="#keyword-injection" aria-label="Permalink to &quot;Keyword Injection&quot;">​</a></h1><p>Brut is desiged around classes and objects, as compared to modules and DSLs. Almost everything you do when building your app is to create a class that has an initializer and implements one or more methods. But, these initalizers and methods often need information from the request that Brut is managing.</p><p>In a basic Rack or Sinatra app, you would access this information via Rack&#39;s API, which is essentially a <code>Hash</code> of whatever. It&#39;s error-prone and requires consulting documentation, source code, or runtime information to figure out what&#39;s stored where.</p><p>Brut can instead inject these values explicitly into the classes of yours it creates. It does this based on the names of keyword arguments declared by your class&#39; intializer or a template method Brut will call.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>For example, a <a href="/pages.html">Page</a> requires you to implement an initializer. That initializer&#39;s keyword arguments define what information is needed. Brut provides that information when it creates the object. This is a form of dependency injection and it can simplify your code if used effectively.</p><p>Consider this route:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">page </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;/widgets/:id&quot;</span></span></code></pre></div><p>Brut will expect to find <code>WidgetsByIdPage</code>. Your initializer can declare <code>id:</code> as a keyword arg and this will be passed when the class is created:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> WidgetsByIdPage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppPage</span></span>
2
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">id:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
3
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @widget </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> DB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Widget</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">find</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(id)</span></span>
4
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
5
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>If the page requires access to the session, it can declare that:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> WidgetsByIdPage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppPage</span></span>
6
+ <span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">id:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">session:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
7
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @widget </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> DB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Widget</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">find</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(id)</span></span>
8
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @current_user </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> session.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">current_user</span></span>
9
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
10
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Because <code>session:</code> is a required argument, Brut cannot instantiate the page without it, so it will always be passed in and availbale.</p><h3 id="standard-injectible-information" tabindex="-1">Standard Injectible Information <a class="header-anchor" href="#standard-injectible-information" aria-label="Permalink to &quot;Standard Injectible Information&quot;">​</a></h3><p>In any request, the following information is available to be injected:</p><ul><li><code>session:</code> - An instance of your app&#39;s <a href="/api/Brut/FrontEnd/Session.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Session</code></a> subclass for the current visitor&#39;s session.</li><li><code>flash:</code> - An instance of your app&#39;s <a href="/api/Brut/FrontEnd/Flash.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Flash</code></a> subclass.</li><li><code>xhr:</code> - true if this was an Ajax request.</li><li><code>body:</code> - the body submitted, if any.</li><li><code>csrf_token:</code> - The current CSRF token.</li><li><code>clock:</code> - A <code>Clock</code> to be used to access the current time in the visitor&#39;s time zone.</li><li><code>http_*</code> - any parameter that starts with <code>http_</code> is assumed to be for an HTTP header. For example, <code>http_accept_language</code> would be given the value for the &quot;Accept-Language&quot; header. See the section on HTTP headers below.</li><li><code>env:</code> - The Rack env. This is discouraged, but available if you can&#39;t get what you want directly</li></ul><p>Depending on the context, other information is available:</p><ul><li><code>form:</code> - If a form was submitted, this is the <a href="/api/Brut/FrontEnd/Form.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Form</code></a> subclass containing the data. See <a href="/forms.html">Forms</a>.</li><li>Any query string paramter - Note that if these conflict with existing Brut values, the behavior is undefined. Name your query string parameters carefully. These should have default values or your page won&#39;t work if they are omitted.</li><li>Any route parameter - These should not have default values, since they are required for Brut to match the route.</li></ul><p>A <a href="/api/Brut/FrontEnd/RouteHook.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::RouteHook</code></a> is slightly different. Only the following data is available to be injected:</p><ul><li><code>request_context:</code> - The current request context, thought it may be <code>nil</code> depending on when the hook runs</li><li><code>session:</code> - An instance of your app&#39;s <a href="/api/Brut/FrontEnd/Session.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Session</code></a> subclass for the current visitor&#39;s session.</li><li><code>request:</code> - The Rack request</li><li><code>response:</code> - The Rack response</li><li><code>env:</code> - The Rack env.</li></ul><h3 id="http-headers" tabindex="-1">HTTP Headers <a class="header-anchor" href="#http-headers" aria-label="Permalink to &quot;HTTP Headers&quot;">​</a></h3><p>Since any header can be sent with a request, Brut allows you to access them, including non-standard ones. Rack (which is based on CGI), provides access to all HTTP headers in the <code>env</code> by taking the header name, replacing dashes (&quot;-&quot;) with underscores (&quot;_&quot;), and prepending <code>http_</code> to the name, then uppercasing it. Thus, &quot;User-Agent&quot; becomes <code>HTTP_USER_AGENT</code>.</p><p>Because Ruby parameters and variables must start with a lower-case letter, Brut uses the lowercased version of the Rack/CGI variable. Thus, to receive the &quot;User-Agent&quot;, you would declare the keyword parameter <code>http_user_agent</code>.</p><p>Further, because headers come from the client and may not be under your control, the value that is actually injected depends on a few things:</p><ul><li>If your keyword arg is required, i.e. there is no default value: <ul><li>If the header was not provided, <code>nil</code> is injected.</li><li>If the header <em>was</em> provided, it&#39;s value is injected, even if it&#39;s the empty string.</li></ul></li><li>If your keyword arg is optional, i.e. it has a default value <ul><li>If the header was not provided, no value is injected, and your code will receive the default value.</li><li>If the header <em>was</em> provided, it&#39;s value is injected, even if it&#39;s the empty string.</li></ul></li></ul><h3 id="ordering-and-disambiguation" tabindex="-1">Ordering and Disambiguation <a class="header-anchor" href="#ordering-and-disambiguation" aria-label="Permalink to &quot;Ordering and Disambiguation&quot;">​</a></h3><p>You are discouraged from using builtin keys for your own data or request parameters. For example, you should not have a query string parameter named <code>env</code> as this conflicts with the builtin <code>env</code> that Brut will inject.</p><p>Since you can inject your own data (see below), you are free to corrupt the request context. Please don&#39;t do this. Brut may actively prevent this in the future.</p><p>You can also use the request context to put your own data that can be injected.</p><h3 id="injecting-custom-data" tabindex="-1">Injecting Custom Data <a class="header-anchor" href="#injecting-custom-data" aria-label="Permalink to &quot;Injecting Custom Data&quot;">​</a></h3><p>The correct place to inject your own data into the request is in a <a href="/hooks.html">before hook</a>. When you configure a before hook, it will run after Brut&#39;s internal <a href="/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::RouteHooks::SetupRequestContext</code></a>, which ensures the request context exists and is ready for use.</p><p>For example, here is how you might inject the currently logged-in account based on the session:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AuthBeforeHook</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">FrontEnd</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">RouteHook</span></span>
11
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> before</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">request_context:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">session:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
12
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> session.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">authenticated_account</span></span>
13
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> request_context[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:authenticated_account</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> session.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">authenticated_account</span></span>
14
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
15
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> continue</span></span>
16
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
17
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Note that the value is only injected if it exists. It&#39;s important not to inject <code>nil</code> for values that don&#39;t exist.</p><p>You may be thinking that this particular example is unnecessary. You could simply inject <code>session:</code> and call <code>session.authenticated_account</code>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> DashboardPage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppPage</span></span>
18
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">session:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
19
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @widgets </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> session.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">authenticated_account</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">widgets</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # e.g.</span></span>
20
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
21
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>If <code>DashboardPage</code> requires an authenticated account, by only injecting the session, you&#39;ll need to handle the case where <code>session.authenticated_account</code> is <code>nil</code>. Instead, if you configure the <code>AuthBeforeHook</code> as above, then inject <code>authenticated_account</code>, you avoid the need for this logic:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> DashboardPage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppPage</span></span>
22
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">authenticated_account:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
23
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @widgets </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> authenticated_account.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">widgets</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # e.g.</span></span>
24
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
25
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Because <code>AuthBeforeHook</code> never injects <code>nil</code>, <code>DashboardPage</code> can rely on <code>authenticated_account</code> always being present. Further, if a visitor tried to access <code>/dashboard_page</code> without having been authenticated, Brut would be unable to create an instance of <code>DashboardPage</code> and generate an error.</p><h3 id="nil-and-empty-strings" tabindex="-1"><code>nil</code> and Empty Strings <a class="header-anchor" href="#nil-and-empty-strings" aria-label="Permalink to &quot;\`nil\` and Empty Strings&quot;">​</a></h3><p>When a keyword argument has no default value, Brut will require that value to exist and be available for injection. If the keyword is not one of the canned always-available values, it will look in the request context, then in the query string.</p><p>If the request has the keyword as a key, <em>it will inject whatever value it finds, including <code>nil</code></em>. In general, you should avoid injecting <code>nil</code> when you actually intend to not have a value.</p><p>For example, the <code>AuthBeforeHook</code> above, you could implement it like so:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">request_context[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:authenticated_account</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> session.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">authenticated_account</span></span></code></pre></div><p>The problem is that if the visitor is not logged in, the <code>:authenticated_account</code> <em>will</em> have a value, and that value will be <code>nil</code>. This is almost certainly not what you want.</p><p>For query string parameters, the HTTP spec says that they are strings. Thus, if a query string parameter is present in ther request URL, it will <em>always</em> have a value and <em>never</em> be <code>nil</code>. If the paramter doesn&#39;t have a value after the <code>=</code> (e.g. for <code>foo</code> in <code>?foo=&amp;bar=quux</code>), the value will be the empty string.</p><p>This means you must write code to explicitly handle the cases you care about.</p><h3 id="when-values-aren-t-available" tabindex="-1">When Values Aren&#39;t Available <a class="header-anchor" href="#when-values-aren-t-available" aria-label="Permalink to &quot;When Values Aren&#39;t Available&quot;">​</a></h3><p>When a value is not available for injection, and the keyword doesn&#39;t provide a default, Brut will raise an error. This is because such a situation represents a design error.</p><p>For example, the <code>DashboardPage</code> above requires an <code>authenticated_account</code>. Your app should never route a logged-out visitor to that page. This allows the <code>DashboardPage</code> to avoid having to check for <code>nil</code> and figure out what to do.</p><p>This is most relevant for query string parameters, since they can be easily manipulated by the visitor in their browser. Query string parameters should always have a default value, even if it&#39;s <code>nil</code>.</p><p><em>Path</em> parameters (like <code>:id</code> in <code>WidgetsByIdPage</code>) should <em>never</em> have a default value as their absence means a different URL was requested. For example, <code>/widgets</code> would trigger a <code>WidgetsPage</code>. <em>Only</em> if the <code>:id</code> path parameter is present would the <code>WidgetsByIdPage</code> be triggered, so it&#39;s safe to omit the default value for <code>id:</code> (and pointless to include one).</p><p>See <a href="/hooks.html">route hooks</a>.</p><h3 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to &quot;Testing&quot;">​</a></h3><p>Brut will not create your classes in a test. Instead, you must pass in the values you want. There are various helpers in <a href="/api/Brut/SpecSupport.html" target="_self" rel="noopener" data-no-router><code>Brut::SpecSupport</code></a> to create blank or empty versions of the special classes.</p><p>In particular, A basic <code>request_context</code> is setup per test and injected into the Thread local storage. This means that if your test should trigger a codepath that <em>does</em> cause Brut to use keyword injection, useful values will be injected.</p><p>For your tests, however, you should pass in directly what you need:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">page</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">WidgetsByIdPage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">id:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> widget.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">session:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> empty_session)</span></span></code></pre></div><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to &quot;Recommended Practices&quot;">​</a></h2><p>Consider a method like so:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> create_widget</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">name:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">organization:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> nil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">quantity:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><p>Outside of Brut, the way to interpret this arguments is as follows:</p><ul><li><code>name</code> is required</li><li><code>organization</code> is optional</li><li><code>quantity</code> has a default value of 10 if not provided</li></ul><p>Any method or intializer that will be keyword-injected should be designed with this in mind. Thus, the following guidelines will be helpful in managing your app:</p><ul><li><strong>Choose arguments based on the needs of the class:</strong><ul><li>If a value is optional, default it to either <code>nil</code> or a symbol that indicates what happens when the value is omitted</li><li>If an optional value has a default, use that (this should be rare for pages, handlers, components, and hooks)</li><li>Otherwise, do not provide a default for the keyword</li></ul></li><li><strong>Design for non-<code>nil</code> values instead of allowing <code>nil</code> and checking for it</strong><ul><li>If a page needs, say, the currently logged-in user, set that up as injectible with no default.</li><li>If a codepath creates that page without the logged-in user, you will get a very obvious error and can figure out how it happened. Your page&#39;s code doesn&#39;t need to figure out what to do with <code>nil</code></li></ul></li><li><strong>Do not inject <code>nil</code> into the request context.</strong> When your code requires a value for a keyword, you want to rely on that value being non-nil. Thus, avoid injecting <code>nil</code> into the request context. Brut will allow it as a sort-of escape hatch, but you should design your app to avoid it</li><li><strong>Be careful injecting global data.</strong> The request context instance is per request, but you could certainly put global data into it. For example, you may put an initialized API client into the request context as a convieniece. <strong>Be careful</strong> because your app is multi-threaded. Any object that is not scoped to the request must be thread-safe.</li></ul><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to &quot;Technical Notes&quot;">​</a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut&#39;s internals, the source code is always more correct.</p></div><p><em>Last Updated May 7, 2025</em></p><p>Keyword injection is currently implemented in a few places and not available via public API. It could be useful as an API and it will be exposed at some point. For now, it&#39;s only available for Brut-managed classes as documented here.</p>`,69)]))}const k=i(n,[["render",o]]);export{u as __pageData,k as default};
@@ -0,0 +1 @@
1
+ import{_ as i,c as s,o as a,ag as t}from"./chunks/framework.1L-BeKqY.js";const u=JSON.parse('{"title":"Keyword Injection","description":"","frontmatter":{},"headers":[],"relativePath":"keyword-injection.md","filePath":"keyword-injection.md"}'),n={name:"keyword-injection.md"};function o(l,e,h,r,d,p){return a(),s("div",null,e[0]||(e[0]=[t("",69)]))}const k=i(n,[["render",o]]);export{u as __pageData,k as default};
@@ -0,0 +1,33 @@
1
+ import{_ as a,c as i,o as n,ag as t}from"./chunks/framework.1L-BeKqY.js";const E=JSON.parse('{"title":"Markdown Extension Examples","description":"","frontmatter":{},"headers":[],"relativePath":"markdown-examples.md","filePath":"markdown-examples.md"}'),e={name:"markdown-examples.md"};function l(p,s,h,k,r,d){return n(),i("div",null,s[0]||(s[0]=[t(`<h1 id="markdown-extension-examples" tabindex="-1">Markdown Extension Examples <a class="header-anchor" href="#markdown-extension-examples" aria-label="Permalink to &quot;Markdown Extension Examples&quot;">​</a></h1><p>This page demonstrates some of the built-in markdown extensions provided by VitePress.</p><h2 id="syntax-highlighting" tabindex="-1">Syntax Highlighting <a class="header-anchor" href="#syntax-highlighting" aria-label="Permalink to &quot;Syntax Highlighting&quot;">​</a></h2><p>VitePress provides Syntax Highlighting powered by <a href="https://github.com/shikijs/shiki" target="_blank" rel="noreferrer">Shiki</a>, with additional features like line-highlighting:</p><p><strong>Input</strong></p><div class="language-md vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">md</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">\`\`\`js{4}</span></span>
2
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
3
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> () {</span></span>
4
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
5
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> msg: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;Highlighted!&#39;</span></span>
6
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
7
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
8
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
9
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">\`\`\`</span></span></code></pre></div><p><strong>Output</strong></p><div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
10
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> () {</span></span>
11
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
12
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> msg: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;Highlighted!&#39;</span></span>
13
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
14
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
15
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><h2 id="custom-containers" tabindex="-1">Custom Containers <a class="header-anchor" href="#custom-containers" aria-label="Permalink to &quot;Custom Containers&quot;">​</a></h2><p><strong>Input</strong></p><div class="language-md vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">md</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::: info</span></span>
16
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">This is an info box.</span></span>
17
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:::</span></span>
18
+ <span class="line"></span>
19
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::: tip</span></span>
20
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">This is a tip.</span></span>
21
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:::</span></span>
22
+ <span class="line"></span>
23
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::: warning</span></span>
24
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">This is a warning.</span></span>
25
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:::</span></span>
26
+ <span class="line"></span>
27
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::: danger</span></span>
28
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">This is a dangerous warning.</span></span>
29
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:::</span></span>
30
+ <span class="line"></span>
31
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::: details</span></span>
32
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">This is a details block.</span></span>
33
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:::</span></span></code></pre></div><p><strong>Output</strong></p><div class="info custom-block"><p class="custom-block-title">INFO</p><p>This is an info box.</p></div><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>This is a tip.</p></div><div class="warning custom-block"><p class="custom-block-title">WARNING</p><p>This is a warning.</p></div><div class="danger custom-block"><p class="custom-block-title">DANGER</p><p>This is a dangerous warning.</p></div><details class="details custom-block"><summary>Details</summary><p>This is a details block.</p></details><h2 id="more" tabindex="-1">More <a class="header-anchor" href="#more" aria-label="Permalink to &quot;More&quot;">​</a></h2><p>Check out the documentation for the <a href="https://vitepress.dev/guide/markdown" target="_blank" rel="noreferrer">full list of markdown extensions</a>.</p>`,19)]))}const c=a(e,[["render",l]]);export{E as __pageData,c as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as i,o as n,ag as t}from"./chunks/framework.1L-BeKqY.js";const E=JSON.parse('{"title":"Markdown Extension Examples","description":"","frontmatter":{},"headers":[],"relativePath":"markdown-examples.md","filePath":"markdown-examples.md"}'),e={name:"markdown-examples.md"};function l(p,s,h,k,r,d){return n(),i("div",null,s[0]||(s[0]=[t("",19)]))}const c=a(e,[["render",l]]);export{E as __pageData,c as default};
@@ -0,0 +1,20 @@
1
+ import{_ as a,c as i,o as e,ag as t}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"Middleware","description":"","frontmatter":{},"headers":[],"relativePath":"middleware.md","filePath":"middleware.md"}'),n={name:"middleware.md"};function l(h,s,p,d,r,o){return e(),i("div",null,s[0]||(s[0]=[t(`<h1 id="middleware" tabindex="-1">Middleware <a class="header-anchor" href="#middleware" aria-label="Permalink to &quot;Middleware&quot;">​</a></h1><p>Brut supports Rack Middleware.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>Similar to <a href="/hooks.html">route hooks</a>, Brut supports Rack Middleware, which is a lower-level way of modifying a request or changing behavior.</p><p>Middleware is recommended if what you want to do is not dependent on your application&#39;s code or classes and is relatively simple.</p><p>To use a middleware, create the class in <code>app/src/front_end/middleware/</code>. You are encouraged to extend <a href="/api/Brut/FrontEnd/Middleware.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Middleware</code></a>, however this is an empty class currently. It could grow to have helper methods you&#39;ll find useful.</p><p>The class itself should conform to Rack&#39;s specification, which is typically that it will be given the Rack &quot;app&quot; in the initializer, and then have a method <code>call</code> which will be given the Rack environment.</p><p>Here&#39;s a middleware that adds a tag to the environment for paths that are &quot;special&quot; to our app, which in this case means they start with <code>/special</code>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> TagSpecialPathsMiddleware</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">FrontEnd</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Middleware</span></span>
2
+ <span class="line"></span>
3
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initializer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(app)</span></span>
4
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @app </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> app</span></span>
5
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
6
+ <span class="line"></span>
7
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> call</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(env)</span></span>
8
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> env[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;PATH_INFO&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=~</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF;"> /^</span><span style="--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold;">\\/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF;">special</span><span style="--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold;">\\/</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF;">/</span></span>
9
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> env[</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;app.special_path&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span></span>
10
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
11
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @app.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">call</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(env)</span></span>
12
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
13
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>To use this middleware, call <code>use</code> with the class name as a string inside <code>App</code>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> App</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Framework</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">App</span></span>
14
+ <span class="line"></span>
15
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
16
+ <span class="line"></span>
17
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> use </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:TagSpecialPathsMiddleware</span></span>
18
+ <span class="line"></span>
19
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
20
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Don&#39;t use the actual class as this can create load order issues.</p><h2 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to &quot;Testing&quot;">​</a></h2><p>Like hooks, Rack middleware can be tested as a normal class. That said, you are encouraged to test the middleware as part of an end-to-end test if possible, since this will ensure it&#39;s configured properly in the context of your app.</p><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to &quot;Recommended Practices&quot;">​</a></h2><p>Middleware should be used when its logic can be entirely based on the Rack environment passed-in. While your database models and other classes should be available, excessive use of your domain logic in a middleware can create a confusing situation.</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to &quot;Technical Notes&quot;">​</a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut&#39;s internals, the source code is always more correct.</p></div><p><em>Last Updated June 12, 2025</em></p><p>Route hooks and Middlewares do not share implementations, however they are similar in concept. These concepts may be unified in the future.</p><p><code>use</code> and the way Middleware behaves follows Sinatra&#39;s implementation as Brut is currently based on Sinatra. This may not always be the case, however as things change, we will do our best to ensure the semantics remain the same. Nevertheless, it&#39;s advisable to have end to end tests assert the behavior of your configured middleware and not just a unit test of the class itself.</p>`,21)]))}const g=a(n,[["render",l]]);export{c as __pageData,g as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as i,o as e,ag as t}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"Middleware","description":"","frontmatter":{},"headers":[],"relativePath":"middleware.md","filePath":"middleware.md"}'),n={name:"middleware.md"};function l(h,s,p,d,r,o){return e(),i("div",null,s[0]||(s[0]=[t("",21)]))}const g=a(n,[["render",l]]);export{c as __pageData,g as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as s,o as r,j as e,a as o}from"./chunks/framework.1L-BeKqY.js";const p=JSON.parse('{"title":"Brut is Not Released Software","description":"","frontmatter":{},"headers":[],"relativePath":"not-released.md","filePath":"not-released.md"}'),n={name:"not-released.md"};function i(l,t,d,c,u,f){return r(),s("div",null,t[0]||(t[0]=[e("h1",{id:"brut-is-not-released-software",tabindex:"-1"},[o("Brut is Not Released Software "),e("a",{class:"header-anchor",href:"#brut-is-not-released-software","aria-label":'Permalink to "Brut is Not Released Software"'},"​")],-1),e("div",{class:"warning custom-block github-alert"},[e("p",{class:"custom-block-title"},"WARNING"),e("p",null,"Brut is not considered released. It is still in development and some of what is in this doc site is inaccurate.")],-1)]))}const h=a(n,[["render",i]]);export{p as __pageData,h as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as s,o as r,j as e,a as o}from"./chunks/framework.1L-BeKqY.js";const p=JSON.parse('{"title":"Brut is Not Released Software","description":"","frontmatter":{},"headers":[],"relativePath":"not-released.md","filePath":"not-released.md"}'),n={name:"not-released.md"};function i(l,t,d,c,u,f){return r(),s("div",null,t[0]||(t[0]=[e("h1",{id:"brut-is-not-released-software",tabindex:"-1"},[o("Brut is Not Released Software "),e("a",{class:"header-anchor",href:"#brut-is-not-released-software","aria-label":'Permalink to "Brut is Not Released Software"'},"​")],-1),e("div",{class:"warning custom-block github-alert"},[e("p",{class:"custom-block-title"},"WARNING"),e("p",null,"Brut is not considered released. It is still in development and some of what is in this doc site is inaccurate.")],-1)]))}const h=a(n,[["render",i]]);export{p as __pageData,h as default};
Binary file