brut 0.0.27 → 0.0.29

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 (411) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.projections.json +10 -0
  4. data/.rspec +3 -0
  5. data/Dockerfile.dx +32 -14
  6. data/Gemfile.lock +1 -1
  7. data/assets/Logo-Square.pxd +0 -0
  8. data/assets/LogoPylon.pxd +0 -0
  9. data/assets/LogoStop.pxd +0 -0
  10. data/assets/LogoTall.pxd +0 -0
  11. data/bin/docs +24 -2
  12. data/bin/rspec +27 -0
  13. data/bin/setup +3 -3
  14. data/brutrb.com/.vitepress/config.mjs +1 -0
  15. data/brutrb.com/.vitepress/theme/custom.css +7 -0
  16. data/brutrb.com/.vitepress/theme/style.css +26 -15
  17. data/brutrb.com/deployment.md +123 -45
  18. data/brutrb.com/dev-environment.md +6 -5
  19. data/brutrb.com/doc-conventions.md +1 -1
  20. data/brutrb.com/getting-started.md +64 -28
  21. data/brutrb.com/images/LogoPylon.png +0 -0
  22. data/brutrb.com/images/LogoSquare.png +0 -0
  23. data/brutrb.com/images/LogoStop.png +0 -0
  24. data/brutrb.com/images/LogoTall.png +0 -0
  25. data/brutrb.com/images/OverviewMetro.graffle +0 -0
  26. data/brutrb.com/images/OverviewMetro.png +0 -0
  27. data/brutrb.com/index.md +4 -3
  28. data/brutrb.com/instrumentation.md +12 -0
  29. data/brutrb.com/layouts.md +130 -0
  30. data/brutrb.com/overview.md +6 -6
  31. data/docker-compose.dx.yml +5 -2
  32. data/docs/404.html +3 -3
  33. data/docs/ai.html +6 -6
  34. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  35. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  36. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  37. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  38. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  39. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  40. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  41. data/docs/api/Brut/BackEnd.html +1 -1
  42. data/docs/api/Brut/CLI/App.html +38 -13
  43. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +2 -2
  45. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +2 -2
  46. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +2 -2
  47. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +2 -2
  48. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/DB/Create.html +2 -2
  50. data/docs/api/Brut/CLI/Apps/DB/Drop.html +2 -2
  51. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +9 -3
  52. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +11 -11
  53. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +2 -2
  54. data/docs/api/Brut/CLI/Apps/DB/Seed.html +2 -2
  55. data/docs/api/Brut/CLI/Apps/DB/Status.html +12 -12
  56. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +270 -0
  58. data/docs/api/Brut/CLI/Apps/DeployBase.html +257 -0
  59. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +585 -0
  60. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +196 -0
  61. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +2 -2
  63. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +2 -2
  64. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +2 -2
  65. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +2 -2
  66. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +2 -2
  67. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +2 -2
  69. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  70. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +2 -2
  71. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  72. data/docs/api/Brut/CLI/Apps/Test/Audit.html +12 -10
  73. data/docs/api/Brut/CLI/Apps/Test/E2e.html +8 -8
  74. data/docs/api/Brut/CLI/Apps/Test/JS.html +9 -9
  75. data/docs/api/Brut/CLI/Apps/Test/Run.html +18 -18
  76. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  77. data/docs/api/Brut/CLI/Apps.html +2 -2
  78. data/docs/api/Brut/CLI/Command.html +113 -28
  79. data/docs/api/Brut/CLI/Error.html +1 -1
  80. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  81. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  82. data/docs/api/Brut/CLI/Executor.html +169 -38
  83. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  84. data/docs/api/Brut/CLI/Options.html +68 -19
  85. data/docs/api/Brut/CLI/Output.html +1 -1
  86. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  87. data/docs/api/Brut/CLI.html +1 -1
  88. data/docs/api/Brut/FactoryBot.html +1 -1
  89. data/docs/api/Brut/Framework/App.html +1 -1
  90. data/docs/api/Brut/Framework/Config.html +1 -1
  91. data/docs/api/Brut/Framework/Container.html +110 -29
  92. data/docs/api/Brut/Framework/Error.html +1 -1
  93. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +89 -1
  94. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  95. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  96. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  97. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  98. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  99. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  100. data/docs/api/Brut/Framework/Errors.html +31 -8
  101. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  102. data/docs/api/Brut/Framework/MCP.html +1 -1
  103. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  104. data/docs/api/Brut/Framework.html +1 -1
  105. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Component/Helpers.html +36 -26
  107. data/docs/api/Brut/FrontEnd/Component.html +7 -7
  108. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Components/FormTag.html +37 -29
  110. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +20 -117
  114. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +25 -23
  115. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +73 -380
  116. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +22 -138
  117. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Components.html +23 -2
  123. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +24 -68
  127. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +201 -0
  128. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +535 -0
  129. data/docs/api/Brut/FrontEnd/Forms/Input.html +983 -35
  130. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +4 -4
  131. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +29 -19
  132. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +9 -3
  133. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +9 -3
  135. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +72 -22
  137. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  138. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  147. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  148. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  149. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  150. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +6 -2
  157. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  161. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  162. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  163. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  164. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  165. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  166. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  167. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  168. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  169. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  172. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  175. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  176. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  177. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  178. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  179. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  180. data/docs/api/Brut/FrontEnd.html +1 -1
  181. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  182. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  183. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  184. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  185. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  186. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  187. data/docs/api/Brut/I18n.html +1 -1
  188. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  189. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  190. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  191. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  192. data/docs/api/Brut/Instrumentation.html +1 -1
  193. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  194. data/docs/api/Brut/SinatraHelpers.html +1 -1
  195. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  197. data/docs/api/Brut/SpecSupport/E2ETestServer.html +20 -20
  198. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  199. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  200. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  201. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  202. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  203. data/docs/api/Brut/SpecSupport/HandlerSupport.html +2 -2
  204. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +142 -0
  205. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +142 -0
  206. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +155 -0
  207. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +55 -25
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +149 -0
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +46 -19
  210. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +149 -0
  211. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +149 -0
  212. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +165 -0
  213. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +158 -0
  214. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +156 -0
  215. data/docs/api/Brut/SpecSupport/Matchers.html +2 -2
  216. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +24 -24
  217. data/docs/api/Brut/SpecSupport/RSpecSetup.html +55 -20
  218. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  219. data/docs/api/Brut/SpecSupport.html +1 -1
  220. data/docs/api/Brut.html +1 -1
  221. data/docs/api/Clock.html +1 -1
  222. data/docs/api/RichString.html +1 -1
  223. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  224. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +5 -1
  225. data/docs/api/Sequel/Extensions/BrutMigrations.html +36 -28
  226. data/docs/api/Sequel/Extensions.html +1 -1
  227. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  228. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  229. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  230. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  231. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  232. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  233. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  234. data/docs/api/Sequel/Plugins.html +1 -1
  235. data/docs/api/Sequel.html +1 -1
  236. data/docs/api/SpecSupport/Matchers/BeABug.html +143 -0
  237. data/docs/api/_index.html +106 -1
  238. data/docs/api/class_list.html +1 -1
  239. data/docs/api/css/full_list.css +2 -1
  240. data/docs/api/css/style.css +14 -13
  241. data/docs/api/file.README.html +1 -1
  242. data/docs/api/index.html +1 -1
  243. data/docs/api/method_list.html +530 -330
  244. data/docs/api/top-level-namespace.html +1 -1
  245. data/docs/assets/LogoStop.X8x-4riz.png +0 -0
  246. data/docs/assets/OverviewMetro.DUS-5fUZ.png +0 -0
  247. data/docs/assets/{ai.md.tZrjP9im.js → ai.md._6HCDL6d.js} +1 -1
  248. data/docs/assets/ai.md._6HCDL6d.lean.js +1 -0
  249. data/docs/assets/{app.D_yaTITQ.js → app.BhrfSt68.js} +1 -1
  250. data/docs/assets/chunks/@localSearchIndexroot.CeRAdP1K.js +1 -0
  251. data/docs/assets/chunks/{VPLocalSearchBox.B2-ZzyTY.js → VPLocalSearchBox.Dpot_2H4.js} +1 -1
  252. data/docs/assets/chunks/{theme.CfGFVRvE.js → theme.N2SNVLgU.js} +2 -2
  253. data/docs/assets/{components.md.eCttGlN-.js → components.md.CRUMdRoN.js} +1 -1
  254. data/docs/assets/{configuration.md.BRriU0cL.js → configuration.md.LG-zIBww.js} +1 -1
  255. data/docs/assets/deployment.md.BLseERGV.js +48 -0
  256. data/docs/assets/deployment.md.BLseERGV.lean.js +1 -0
  257. data/docs/assets/{dev-environment.md.BNc8AYiK.js → dev-environment.md.GZv6xvi9.js} +1 -1
  258. data/docs/assets/doc-conventions.md.-kN3Xo5C.js +1 -0
  259. data/docs/assets/{doc-conventions.md.DCfRXXi-.lean.js → doc-conventions.md.-kN3Xo5C.lean.js} +1 -1
  260. data/docs/assets/{forms.md.CBTYQ_Cz.js → forms.md.B-koVgyw.js} +23 -23
  261. data/docs/assets/{forms.md.CBTYQ_Cz.lean.js → forms.md.B-koVgyw.lean.js} +1 -1
  262. data/docs/assets/getting-started.md.Dj0qtZI2.js +25 -0
  263. data/docs/assets/getting-started.md.Dj0qtZI2.lean.js +1 -0
  264. data/docs/assets/index.md.CuBB-BdM.js +1 -0
  265. data/docs/assets/index.md.CuBB-BdM.lean.js +1 -0
  266. data/docs/assets/{instrumentation.md.CL6ax7nT.js → instrumentation.md.a9Pjps4P.js} +2 -2
  267. data/docs/assets/{instrumentation.md.CL6ax7nT.lean.js → instrumentation.md.a9Pjps4P.lean.js} +1 -1
  268. data/docs/assets/layouts.md.cPnh3NId.js +51 -0
  269. data/docs/assets/layouts.md.cPnh3NId.lean.js +1 -0
  270. data/docs/assets/lsp.md.Bsu-f6VU.js +1 -0
  271. data/docs/assets/lsp.md.Bsu-f6VU.lean.js +1 -0
  272. data/docs/assets/{overview.md.CDalkuxV.js → overview.md.DVKRM8zl.js} +4 -4
  273. data/docs/assets/overview.md.DVKRM8zl.lean.js +1 -0
  274. data/docs/assets/recipes_authentication.md.CAsXf7hk.js +1 -0
  275. data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +1 -0
  276. data/docs/assets/{style.D73IYGCX.css → style.B2o1L9eN.css} +1 -1
  277. data/docs/assets.html +5 -5
  278. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  279. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  280. data/docs/brut-js/api/Autosubmit.html +1 -1
  281. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  282. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  283. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  284. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  285. data/docs/brut-js/api/BufferedLogger.html +1 -1
  286. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  287. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  288. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  289. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  290. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  291. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  292. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  293. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  294. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  295. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  296. data/docs/brut-js/api/Form.html +1 -1
  297. data/docs/brut-js/api/Form.js.html +1 -1
  298. data/docs/brut-js/api/I18nTranslation.html +1 -1
  299. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  300. data/docs/brut-js/api/LocaleDetection.html +1 -1
  301. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  302. data/docs/brut-js/api/Logger.html +1 -1
  303. data/docs/brut-js/api/Logger.js.html +1 -1
  304. data/docs/brut-js/api/Message.html +1 -1
  305. data/docs/brut-js/api/Message.js.html +1 -1
  306. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  307. data/docs/brut-js/api/RichString.html +1 -1
  308. data/docs/brut-js/api/RichString.js.html +1 -1
  309. data/docs/brut-js/api/Tabs.html +1 -1
  310. data/docs/brut-js/api/Tabs.js.html +1 -1
  311. data/docs/brut-js/api/Tracing.html +1 -1
  312. data/docs/brut-js/api/Tracing.js.html +1 -1
  313. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  314. data/docs/brut-js/api/external-Performance.html +1 -1
  315. data/docs/brut-js/api/external-Promise.html +1 -1
  316. data/docs/brut-js/api/external-ValidityState.html +1 -1
  317. data/docs/brut-js/api/external-Window.html +1 -1
  318. data/docs/brut-js/api/external-fetch.html +1 -1
  319. data/docs/brut-js/api/global.html +1 -1
  320. data/docs/brut-js/api/index.html +1 -1
  321. data/docs/brut-js/api/index.js.html +1 -1
  322. data/docs/brut-js/api/module-testing.html +1 -1
  323. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  324. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  325. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  326. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  327. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  328. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  329. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  330. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  331. data/docs/brut-js/api/testing_index.js.html +1 -1
  332. data/docs/brut-js.html +5 -5
  333. data/docs/business-logic.html +5 -5
  334. data/docs/cli.html +5 -5
  335. data/docs/components.html +7 -7
  336. data/docs/configuration.html +6 -6
  337. data/docs/css.html +5 -5
  338. data/docs/custom-element-tests.html +5 -5
  339. data/docs/database-access.html +5 -5
  340. data/docs/database-schema.html +5 -5
  341. data/docs/deployment.html +53 -6
  342. data/docs/dev-environment.html +6 -6
  343. data/docs/doc-conventions.html +6 -6
  344. data/docs/end-to-end-tests.html +5 -5
  345. data/docs/flash-and-session.html +5 -5
  346. data/docs/forms.html +28 -28
  347. data/docs/getting-started.html +30 -7
  348. data/docs/handlers.html +5 -5
  349. data/docs/hashmap.json +1 -1
  350. data/docs/hooks.html +5 -5
  351. data/docs/i18n.html +5 -5
  352. data/docs/index.html +6 -6
  353. data/docs/instrumentation.html +6 -6
  354. data/docs/javascript.html +5 -5
  355. data/docs/jobs.html +5 -5
  356. data/docs/keyword-injection.html +5 -5
  357. data/docs/layouts.html +74 -0
  358. data/docs/lsp.html +24 -0
  359. data/docs/markdown-examples.html +5 -5
  360. data/docs/middleware.html +5 -5
  361. data/docs/not-released.html +5 -5
  362. data/docs/overview.html +9 -9
  363. data/docs/pages.html +6 -6
  364. data/docs/recipes/authentication.html +24 -0
  365. data/docs/routes.html +5 -5
  366. data/docs/security.html +5 -5
  367. data/docs/seed-data.html +5 -5
  368. data/docs/space-time-continuum.html +5 -5
  369. data/docs/tutorial.html +5 -5
  370. data/docs/unit-tests.html +5 -5
  371. data/dx/bash_customizations +7 -0
  372. data/dx/build +13 -2
  373. data/dx/docker-compose.env +1 -1
  374. data/dx/exec +25 -8
  375. data/lib/brut/cli/app.rb +7 -2
  376. data/lib/brut/cli/apps/deploy_base.rb +86 -0
  377. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +194 -0
  378. data/lib/brut/cli/command.rb +7 -13
  379. data/lib/brut/cli/executor.rb +31 -5
  380. data/lib/brut/cli/options.rb +4 -0
  381. data/lib/brut/cli.rb +4 -3
  382. data/lib/brut/framework/container.rb +25 -7
  383. data/lib/brut/framework/errors/abstract_method.rb +7 -0
  384. data/lib/brut/framework/errors.rb +4 -2
  385. data/lib/brut/front_end/forms/input.rb +253 -20
  386. data/lib/brut/front_end/forms/input_definition.rb +15 -12
  387. data/lib/brut/front_end/middlewares/reload_app.rb +2 -0
  388. data/lib/brut/front_end.rb +1 -0
  389. data/lib/brut/spec_support/rspec_setup.rb +42 -2
  390. data/lib/brut/version.rb +1 -1
  391. data/lib/sequel/extensions/brut_instrumentation.rb +4 -0
  392. data/specs/brut/front_end/forms/input.spec.rb +978 -0
  393. data/specs/spec_helper.rb +27 -0
  394. data/specs/support/matchers/have_constraint_violation.rb +23 -0
  395. data/specs/support/matchers.rb +5 -0
  396. data/specs/support.rb +3 -0
  397. metadata +77 -29
  398. data/docs/assets/ai.md.tZrjP9im.lean.js +0 -1
  399. data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +0 -1
  400. data/docs/assets/deployment.md.Dbka4OTr.js +0 -1
  401. data/docs/assets/deployment.md.Dbka4OTr.lean.js +0 -1
  402. data/docs/assets/doc-conventions.md.DCfRXXi-.js +0 -1
  403. data/docs/assets/getting-started.md.Bz2s1Vjb.js +0 -2
  404. data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +0 -1
  405. data/docs/assets/index.md.B28EwVpq.js +0 -1
  406. data/docs/assets/index.md.B28EwVpq.lean.js +0 -1
  407. data/docs/assets/overview.Da81cB9R.png +0 -0
  408. data/docs/assets/overview.md.CDalkuxV.lean.js +0 -1
  409. /data/docs/assets/{components.md.eCttGlN-.lean.js → components.md.CRUMdRoN.lean.js} +0 -0
  410. /data/docs/assets/{configuration.md.BRriU0cL.lean.js → configuration.md.LG-zIBww.lean.js} +0 -0
  411. /data/docs/assets/{dev-environment.md.BNc8AYiK.lean.js → dev-environment.md.GZv6xvi9.lean.js} +0 -0
@@ -286,7 +286,7 @@ style UI.</p></td>
286
286
  <br class="clear">
287
287
 
288
288
  <footer>
289
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
289
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
290
290
  </footer>
291
291
 
292
292
  <script> prettyPrint(); </script>
@@ -210,7 +210,7 @@ export default Tabs
210
210
  <br class="clear">
211
211
 
212
212
  <footer>
213
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
213
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
214
214
  </footer>
215
215
 
216
216
  <script> prettyPrint(); </script>
@@ -268,7 +268,7 @@ Of course, this element is many magnitudes smaller in size than Open Telemetry's
268
268
  <br class="clear">
269
269
 
270
270
  <footer>
271
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
271
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
272
272
  </footer>
273
273
 
274
274
  <script> prettyPrint(); </script>
@@ -289,7 +289,7 @@ export default Tracing
289
289
  <br class="clear">
290
290
 
291
291
  <footer>
292
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
292
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
293
293
  </footer>
294
294
 
295
295
  <script> prettyPrint(); </script>
@@ -131,7 +131,7 @@
131
131
  <br class="clear">
132
132
 
133
133
  <footer>
134
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
134
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
135
135
  </footer>
136
136
 
137
137
  <script> prettyPrint(); </script>
@@ -129,7 +129,7 @@
129
129
  <br class="clear">
130
130
 
131
131
  <footer>
132
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
132
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
133
133
  </footer>
134
134
 
135
135
  <script> prettyPrint(); </script>
@@ -129,7 +129,7 @@
129
129
  <br class="clear">
130
130
 
131
131
  <footer>
132
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
132
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
133
133
  </footer>
134
134
 
135
135
  <script> prettyPrint(); </script>
@@ -129,7 +129,7 @@
129
129
  <br class="clear">
130
130
 
131
131
  <footer>
132
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
132
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
133
133
  </footer>
134
134
 
135
135
  <script> prettyPrint(); </script>
@@ -224,7 +224,7 @@
224
224
  <br class="clear">
225
225
 
226
226
  <footer>
227
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
227
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
228
228
  </footer>
229
229
 
230
230
  <script> prettyPrint(); </script>
@@ -129,7 +129,7 @@
129
129
  <br class="clear">
130
130
 
131
131
  <footer>
132
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
132
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
133
133
  </footer>
134
134
 
135
135
  <script> prettyPrint(); </script>
@@ -391,7 +391,7 @@ called and can be useful to assert the contents of what was requested via <code>
391
391
  <br class="clear">
392
392
 
393
393
  <footer>
394
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
394
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
395
395
  </footer>
396
396
 
397
397
  <script> prettyPrint(); </script>
@@ -159,7 +159,7 @@ task.</p></article>
159
159
  <br class="clear">
160
160
 
161
161
  <footer>
162
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
162
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
163
163
  </footer>
164
164
 
165
165
  <script> prettyPrint(); </script>
@@ -172,7 +172,7 @@ export {
172
172
  <br class="clear">
173
173
 
174
174
  <footer>
175
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
175
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
176
176
  </footer>
177
177
 
178
178
  <script> prettyPrint(); </script>
@@ -374,7 +374,7 @@ files are placed for serving.</p></td>
374
374
  <br class="clear">
375
375
 
376
376
  <footer>
377
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
377
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
378
378
  </footer>
379
379
 
380
380
  <script> prettyPrint(); </script>
@@ -163,7 +163,7 @@ to be defined in JSDOM the same way they are in a real browser.</p></div>
163
163
  <br class="clear">
164
164
 
165
165
  <footer>
166
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
166
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
167
167
  </footer>
168
168
 
169
169
  <script> prettyPrint(); </script>
@@ -162,7 +162,7 @@
162
162
  <br class="clear">
163
163
 
164
164
  <footer>
165
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
165
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
166
166
  </footer>
167
167
 
168
168
  <script> prettyPrint(); </script>
@@ -670,7 +670,7 @@ text.</li>
670
670
  <br class="clear">
671
671
 
672
672
  <footer>
673
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
673
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
674
674
  </footer>
675
675
 
676
676
  <script> prettyPrint(); </script>
@@ -162,7 +162,7 @@
162
162
  <br class="clear">
163
163
 
164
164
  <footer>
165
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
165
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
166
166
  </footer>
167
167
 
168
168
  <script> prettyPrint(); </script>
@@ -77,7 +77,7 @@ export default AssetMetadata
77
77
  <br class="clear">
78
78
 
79
79
  <footer>
80
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
80
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
81
81
  </footer>
82
82
 
83
83
  <script> prettyPrint(); </script>
@@ -67,7 +67,7 @@ export default AssetMetadataLoader
67
67
  <br class="clear">
68
68
 
69
69
  <footer>
70
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
70
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
71
71
  </footer>
72
72
 
73
73
  <script> prettyPrint(); </script>
@@ -277,7 +277,7 @@ export default CustomElementTest
277
277
  <br class="clear">
278
278
 
279
279
  <footer>
280
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
280
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
281
281
  </footer>
282
282
 
283
283
  <script> prettyPrint(); </script>
@@ -87,7 +87,7 @@ export default DOMCreator
87
87
  <br class="clear">
88
88
 
89
89
  <footer>
90
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
90
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
91
91
  </footer>
92
92
 
93
93
  <script> prettyPrint(); </script>
@@ -90,7 +90,7 @@ export {
90
90
  <br class="clear">
91
91
 
92
92
  <footer>
93
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Jun 25 2025 22:57:21 GMT+0000 (Coordinated Universal Time)
93
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Jul 04 2025 21:27:29 GMT+0000 (Coordinated Universal Time)
94
94
  </footer>
95
95
 
96
96
  <script> prettyPrint(); </script>
data/docs/brut-js.html CHANGED
@@ -6,18 +6,18 @@
6
6
  <title>BrutJS | Brut RB</title>
7
7
  <meta name="description" content="Documentation for the Brut.RB web framework.">
8
8
  <meta name="generator" content="VitePress v1.6.3">
9
- <link rel="preload stylesheet" href="/assets/style.D73IYGCX.css" as="style">
9
+ <link rel="preload stylesheet" href="/assets/style.B2o1L9eN.css" as="style">
10
10
  <link rel="preload stylesheet" href="/vp-icons.css" as="style">
11
11
 
12
- <script type="module" src="/assets/app.D_yaTITQ.js"></script>
13
- <link rel="modulepreload" href="/assets/chunks/theme.CfGFVRvE.js">
12
+ <script type="module" src="/assets/app.BhrfSt68.js"></script>
13
+ <link rel="modulepreload" href="/assets/chunks/theme.N2SNVLgU.js">
14
14
  <link rel="modulepreload" href="/assets/chunks/framework.1L-BeKqY.js">
15
15
  <link rel="modulepreload" href="/assets/brut-js.md.o2DAO2s2.lean.js">
16
16
  <script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
17
17
  <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
18
18
  </head>
19
19
  <body>
20
- <div id="app"><div class="Layout" data-v-d8b57b2d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-fcbfc0e0></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-fcbfc0e0>Skip to content</a><!--]--><!----><header class="VPNav" data-v-d8b57b2d data-v-7ad780c2><div class="VPNavBar" data-v-7ad780c2 data-v-9fd4d1dd><div class="wrapper" data-v-9fd4d1dd><div class="container" data-v-9fd4d1dd><div class="title" data-v-9fd4d1dd><div class="VPNavBarTitle has-sidebar" data-v-9fd4d1dd data-v-9f43907a><a class="title" href="/" data-v-9f43907a><!--[--><!--]--><!----><span data-v-9f43907a>Brut RB</span><!--[--><!--]--></a></div></div><div class="content" data-v-9fd4d1dd><div class="content-body" data-v-9fd4d1dd><!--[--><!--]--><div class="VPNavBarSearch search" data-v-9fd4d1dd><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-9fd4d1dd data-v-afb2845e><span id="main-nav-aria-label" class="visually-hidden" data-v-afb2845e> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/getting-started.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Getting Started</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/overview.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Overview</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/api/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Brut API</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/brut-js/api/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>BrutJS</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/brut-css/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>BrutCSS</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-9fd4d1dd data-v-3f90c1a5><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-3f90c1a5 data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-9fd4d1dd data-v-ef6192dc data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/thirdtank/brut" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-9fd4d1dd data-v-f953d92f data-v-bfe7971f><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-bfe7971f><span class="vpi-more-horizontal icon" data-v-bfe7971f></span></button><div class="menu" data-v-bfe7971f><div class="VPMenu" data-v-bfe7971f data-v-20ed86d6><!----><!--[--><!--[--><!----><div class="group" data-v-f953d92f><div class="item appearance" data-v-f953d92f><p class="label" data-v-f953d92f>Appearance</p><div class="appearance-action" data-v-f953d92f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-f953d92f data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div></div></div><div class="group" data-v-f953d92f><div class="item social-links" data-v-f953d92f><div class="VPSocialLinks social-links-list" data-v-f953d92f data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/thirdtank/brut" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-9fd4d1dd data-v-6bee1efd><span class="container" data-v-6bee1efd><span class="top" data-v-6bee1efd></span><span class="middle" data-v-6bee1efd></span><span class="bottom" data-v-6bee1efd></span></span></button></div></div></div></div><div class="divider" data-v-9fd4d1dd><div class="divider-line" data-v-9fd4d1dd></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-d8b57b2d data-v-2488c25a><div class="container" data-v-2488c25a><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-2488c25a><span class="vpi-align-left menu-icon" data-v-2488c25a></span><span class="menu-text" data-v-2488c25a>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-2488c25a data-v-6b867909><button data-v-6b867909>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-d8b57b2d data-v-42c4c606><div class="curtain" data-v-42c4c606></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-42c4c606><span class="visually-hidden" id="sidebar-aria-label" data-v-42c4c606> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Overview</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/getting-started.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Getting Started</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/overview.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Concepts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/doc-conventions.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Documentation Conventions</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/tutorial.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Tutorial</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/dev-environment.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Dev Environment</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/ai.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>AI Declaration</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible has-active" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Front-End</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/routes.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Routes</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/pages.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Pages</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/forms.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Forms</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/handlers.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Handlers and Actions</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/components.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Components</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/flash-and-session.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Flash and Session</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/space-time-continuum.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Space/Time Continuum</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/javascript.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>JavaScript</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/css.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>CSS</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/assets.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Assets</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/brut-js.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>BrutJS</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Back-End</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/database-schema.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Database Schema</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/database-access.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Database Access</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/seed-data.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Seed Data</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/jobs.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Jobs</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/business-logic.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Business Logic</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Framework</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/configuration.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Configuration</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/keyword-injection.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Keyword Injection</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/i18n.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>I18n</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/cli.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>CLI / Tasks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/deployment.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Deployment</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Testing</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/unit-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Unit Tests</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/end-to-end-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>End-to-End Tests</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/custom-element-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Testing Custom Elements</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Advanced Topics</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/hooks.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Route Hooks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/middleware.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Middleware</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/instrumentation.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Instrumentation</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/security.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Security</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-d8b57b2d data-v-9a6c75ad><div class="VPDoc has-sidebar has-aside" data-v-9a6c75ad data-v-e6f2a212><!--[--><!--]--><div class="container" data-v-e6f2a212><div class="aside" data-v-e6f2a212><div class="aside-curtain" data-v-e6f2a212></div><div class="aside-container" data-v-e6f2a212><div class="aside-content" data-v-e6f2a212><div class="VPDocAside" data-v-e6f2a212 data-v-cb998dce><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-cb998dce data-v-f610f197><div class="content" data-v-f610f197><div class="outline-marker" data-v-f610f197></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-f610f197>On this page</div><ul class="VPDocOutlineItem root" data-v-f610f197 data-v-53c99d69><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-cb998dce></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-e6f2a212><div class="content-container" data-v-e6f2a212><!--[--><!--]--><main class="main" data-v-e6f2a212><div style="position:relative;" class="vp-doc _brut-js" data-v-e6f2a212><div><h1 id="brutjs" tabindex="-1">BrutJS <a class="header-anchor" href="#brutjs" aria-label="Permalink to &quot;BrutJS&quot;">​</a></h1><p>Brut includes the JavaScript library <em>BrutJS</em> which provides HTML custom elements that are useful for progressively enhancing your HTML.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>By default, your app is set up to use BrutJS. This is done by defining all the custom elements it provides. Their source code is included in your JavaScript bundle, but they do not do anything until you use one of the custom elements.</p><p>Here&#39;s what <code>app/src/front_end/js/index.js</code> looks like initially:</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;"> { BrutCustomElements } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;brut-js&quot;</span></span>
20
+ <div id="app"><div class="Layout" data-v-d8b57b2d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-fcbfc0e0></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-fcbfc0e0>Skip to content</a><!--]--><!----><header class="VPNav" data-v-d8b57b2d data-v-7ad780c2><div class="VPNavBar" data-v-7ad780c2 data-v-9fd4d1dd><div class="wrapper" data-v-9fd4d1dd><div class="container" data-v-9fd4d1dd><div class="title" data-v-9fd4d1dd><div class="VPNavBarTitle has-sidebar" data-v-9fd4d1dd data-v-9f43907a><a class="title" href="/" data-v-9f43907a><!--[--><!--]--><!----><span data-v-9f43907a>Brut RB</span><!--[--><!--]--></a></div></div><div class="content" data-v-9fd4d1dd><div class="content-body" data-v-9fd4d1dd><!--[--><!--]--><div class="VPNavBarSearch search" data-v-9fd4d1dd><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-9fd4d1dd data-v-afb2845e><span id="main-nav-aria-label" class="visually-hidden" data-v-afb2845e> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/getting-started.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Getting Started</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/overview.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Overview</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/api/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Brut API</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/brut-js/api/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>BrutJS</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/brut-css/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>BrutCSS</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-9fd4d1dd data-v-3f90c1a5><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-3f90c1a5 data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-9fd4d1dd data-v-ef6192dc data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/thirdtank/brut" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-9fd4d1dd data-v-f953d92f data-v-bfe7971f><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-bfe7971f><span class="vpi-more-horizontal icon" data-v-bfe7971f></span></button><div class="menu" data-v-bfe7971f><div class="VPMenu" data-v-bfe7971f data-v-20ed86d6><!----><!--[--><!--[--><!----><div class="group" data-v-f953d92f><div class="item appearance" data-v-f953d92f><p class="label" data-v-f953d92f>Appearance</p><div class="appearance-action" data-v-f953d92f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-f953d92f data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div></div></div><div class="group" data-v-f953d92f><div class="item social-links" data-v-f953d92f><div class="VPSocialLinks social-links-list" data-v-f953d92f data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/thirdtank/brut" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-9fd4d1dd data-v-6bee1efd><span class="container" data-v-6bee1efd><span class="top" data-v-6bee1efd></span><span class="middle" data-v-6bee1efd></span><span class="bottom" data-v-6bee1efd></span></span></button></div></div></div></div><div class="divider" data-v-9fd4d1dd><div class="divider-line" data-v-9fd4d1dd></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-d8b57b2d data-v-2488c25a><div class="container" data-v-2488c25a><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-2488c25a><span class="vpi-align-left menu-icon" data-v-2488c25a></span><span class="menu-text" data-v-2488c25a>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-2488c25a data-v-6b867909><button data-v-6b867909>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-d8b57b2d data-v-42c4c606><div class="curtain" data-v-42c4c606></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-42c4c606><span class="visually-hidden" id="sidebar-aria-label" data-v-42c4c606> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Overview</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/getting-started.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Getting Started</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/overview.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Concepts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/doc-conventions.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Documentation Conventions</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/tutorial.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Tutorial</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/dev-environment.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Dev Environment</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/ai.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>AI Declaration</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible has-active" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Front-End</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/routes.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Routes</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/pages.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Pages</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/layouts.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Layouts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/forms.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Forms</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/handlers.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Handlers and Actions</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/components.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Components</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/flash-and-session.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Flash and Session</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/space-time-continuum.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Space/Time Continuum</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/javascript.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>JavaScript</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/css.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>CSS</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/assets.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Assets</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/brut-js.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>BrutJS</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Back-End</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/database-schema.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Database Schema</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/database-access.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Database Access</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/seed-data.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Seed Data</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/jobs.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Jobs</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/business-logic.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Business Logic</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Framework</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/configuration.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Configuration</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/keyword-injection.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Keyword Injection</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/i18n.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>I18n</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/cli.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>CLI / Tasks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/deployment.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Deployment</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Testing</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/unit-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Unit Tests</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/end-to-end-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>End-to-End Tests</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/custom-element-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Testing Custom Elements</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible collapsed" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Advanced Topics</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/hooks.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Route Hooks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/middleware.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Middleware</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/instrumentation.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Instrumentation</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/security.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Security</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/lsp.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>LSP Support</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible collapsed" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Recipes</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/authentication.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Authentication</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/form-validations.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Form Validations</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/database-migrations.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Database Migrations</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/ajax-form.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Ajax Form Submission</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/telemetry.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Custom Telemetry</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/cli-app.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>CLI App/Task</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-d8b57b2d data-v-9a6c75ad><div class="VPDoc has-sidebar has-aside" data-v-9a6c75ad data-v-e6f2a212><!--[--><!--]--><div class="container" data-v-e6f2a212><div class="aside" data-v-e6f2a212><div class="aside-curtain" data-v-e6f2a212></div><div class="aside-container" data-v-e6f2a212><div class="aside-content" data-v-e6f2a212><div class="VPDocAside" data-v-e6f2a212 data-v-cb998dce><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-cb998dce data-v-f610f197><div class="content" data-v-f610f197><div class="outline-marker" data-v-f610f197></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-f610f197>On this page</div><ul class="VPDocOutlineItem root" data-v-f610f197 data-v-53c99d69><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-cb998dce></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-e6f2a212><div class="content-container" data-v-e6f2a212><!--[--><!--]--><main class="main" data-v-e6f2a212><div style="position:relative;" class="vp-doc _brut-js" data-v-e6f2a212><div><h1 id="brutjs" tabindex="-1">BrutJS <a class="header-anchor" href="#brutjs" aria-label="Permalink to &quot;BrutJS&quot;">​</a></h1><p>Brut includes the JavaScript library <em>BrutJS</em> which provides HTML custom elements that are useful for progressively enhancing your HTML.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>By default, your app is set up to use BrutJS. This is done by defining all the custom elements it provides. Their source code is included in your JavaScript bundle, but they do not do anything until you use one of the custom elements.</p><p>Here&#39;s what <code>app/src/front_end/js/index.js</code> looks like initially:</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;"> { BrutCustomElements } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;brut-js&quot;</span></span>
21
21
  <span class="line"></span>
22
22
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">addEventListener</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;DOMContentLoaded&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
23
23
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> BrutCustomElements.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">define</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()</span></span>
@@ -29,7 +29,7 @@
29
29
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
30
30
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
31
31
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><h3 id="custom-elements" tabindex="-1">Custom Elements <a class="header-anchor" href="#custom-elements" aria-label="Permalink to &quot;Custom Elements&quot;">​</a></h3><p>The JSDoc for these elements&#39; classes should provide complete documentation, however this is an overview of what each one does.</p><table tabindex="0"><thead><tr><th>Element</th><th>Purpose</th></tr></thead><tbody><tr><td><a href="/brut-js/api/AjaxSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-ajax-submit&gt;</code></a></td><td>Allows submitting a form via Ajax. Handles the use of <code>fetch</code> and all possible cases, but you still provide the logic for what to do with the response.</td></tr><tr><td><a href="/brut-js/api/Autosubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-autosubmit&gt;</code></a></td><td>Auto submits a form when a <code>&lt;select&gt;</code>&#39;s option is chosen.</td></tr><tr><td><a href="/brut-js/api/ConfirmationDialog.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirmation-dialog&gt;</code></a></td><td>Enhances a <code>&lt;dialog&gt;</code> to make it easier to use as a generic confirmation with <a href="/brut-js/api/ConfirmSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirm-submit&gt;</code></a></td></tr><tr><td><a href="/brut-js/api/ConfirmSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirm-submit&gt;</code></a></td><td>Uses <code>window.confirm</code> or your owned styled <code>&lt;dialog&gt;</code> to confirm a button click.</td></tr><tr><td><a href="/brut-js/api/ConstraintViolationMessage.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-cv&gt;</code></a></td><td>Like <a href="/brut-js/api/Message.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-message&gt;</code></a> but specific to constraint violations, namely having additional logic for subsituting the field name in the message.</td></tr><tr><td><a href="/brut-js/api/ConstraintViolationMessages.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-cv-messages&gt;</code></a></td><td>Wraps <a href="/brut-js/api/ConstraintViolationMessage.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-cv&gt;</code></a> elements related to a single form input.</td></tr><tr><td><a href="/brut-js/api/CopyToClipboard.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-copy-to-clipboard&gt;</code></a></td><td>Allows the button inside it to copy text from another element onto the clipboard.</td></tr><tr><td><a href="/brut-js/api/Form.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-form&gt;</code></a></td><td>Manages client-side constraint violation UX unified with the server-side, as well as a few quality-of-life improvements for client-side violations and styling. See <a href="/forms.html#forms-and-constraint-violations">Forms</a>.</td></tr><tr><td><a href="/brut-js/api/I18nTranslation.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-i18n-translation&gt;</code></a></td><td>Holds the translated value for a single key in the web site visitor&#39;s locale.</td></tr><tr><td><a href="/brut-js/api/LocaleDetection.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-locale-detection&gt;</code></a></td><td>Sends an Ajax request to the server with the browser&#39;s reported locale and timezone. See <a href="/space-time-continuum.html#getting-timezone-from-the-browser">space-time continuum</a> for more details.</td></tr><tr><td><a href="/brut-js/api/Message.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-message&gt;</code></a></td><td>Shows a message using an <a href="/i18n.html">i18n</a> key to dynamically pull a localized message for client-side constraint violations.</td></tr><tr><td><a href="/brut-js/api/Tabs.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-tabs&gt;</code></a></td><td>Uses ARIA roles related to a tab control and implements it client-side.</td></tr><tr><td><a href="/brut-js/api/Tracing.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-tracing&gt;</code></a></td><td>Sends observability data back to the server to unify a server-side request with client-side tracing.</td></tr></tbody></table><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p>BrutJS&#39;s elements were created only to solve specific issues in the apps Brut was initially used for. It&#39;s hoped that more elements will be added to provide a more feature-complete set of primitives to create client-side enhancements.</p></div><h3 id="creating-your-own-custom-elements" tabindex="-1">Creating Your Own Custom Elements <a class="header-anchor" href="#creating-your-own-custom-elements" aria-label="Permalink to &quot;Creating Your Own Custom Elements&quot;">​</a></h3><p>BrutJS includes a base class, <a href="/brut-js/api/BaseCustomElement.html"><code>BaseCustomElement</code></a>, you can use to create your own custom elements with a bit more help, but not too much.</p><p>The documentation for <code>BaseCustomElement</code> has an example, but here are the features you get (noting that you aren&#39;t abandoning the web platform&#39;s API, merely gaining a few additional quality-of-life improvements):</p><ul><li>The ability to add debugging statements that are disabled via markup, not commenting-out <code>console.log</code></li><li>Per-attribute change callbacks so you don&#39;t have to create <code>attributeChangedCallback</code> as a giant <code>if/else</code> block.</li><li>Default implementations of <code>connectedCallback</code> and <code>attributeChangedCallback</code> that call the template method <code>update</code>, thus allowing your element to centralize its logic in one place, regardless of how a state change was triggered.</li><li>Static <code>define()</code> method that defines your element based on its static <code>tagName</code> field. This allows richer interaction of elements, as you can do e.g. <code>document.querySelector(SomeOtherElement.tagName)</code> and better navigate changes to your code over time.</li></ul><p>If you are familiary with the API for autonomous custom elements, <code>BaseCustomElement</code> doesn&#39;t require learning much more. What you know already will be leveraged.</p><h3 id="removing-brutjs" tabindex="-1">Removing BrutJS <a class="header-anchor" href="#removing-brutjs" aria-label="Permalink to &quot;Removing BrutJS&quot;">​</a></h3><p>To remove BrutJS from your app, modify <code>app/src/front_end/js/index.js</code> to remove the <code>import</code> and call to <code>define()</code>. You can then remove it from your <code>package.json</code>.</p><p><strong>Note</strong> If you remove it like this, several features will not work, including locale detection, client-side observability, and client-side form validation UX.</p><h2 id="recommnded-practices" tabindex="-1">Recommnded Practices <a class="header-anchor" href="#recommnded-practices" aria-label="Permalink to &quot;Recommnded Practices&quot;">​</a></h2><p>Consider this decision tree from Alex Russell&#39;s <a href="https://infrequently.org/2024/11/if-not-react-then-what/" target="_blank" rel="noreferrer">If Not React, Then What?</a>:</p><p><img src="/assets/spa.qejUdp-5.png" alt="Tree showing an SPA decision"></p><p>This is how Brut wants you to consider your app&#39;s architecture. <em>Many</em> apps do not have long-running sessions where visitors make lots of updates to data. Most so-called &quot;CRUD&quot; apps do not fall into this category. The visitor would be better served by a traditional app with server-side HTML generation and minimal interactivity. Visitors would also be better served with progressively enhanced features instead of massive JS payloads that show white screens on low bandwidth/low performance devices.</p><p>Thus, Brut recommends you design your app to work in a tranditional multi-page app sort of way, then <em>enhance</em> as needed using autonomous custom elements.</p><p>You can, of course, bring in whatever framework you like and use that in the normal way. BrutJS&#39;s custom elements should work with any framework.</p><h2 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to &quot;Testing&quot;">​</a></h2><p>See <a href="/custom-element-tests.html">Testing Custom Elements</a>.</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 15, 2025</em></p><p>None.</p></div></div></main><footer class="VPDocFooter" data-v-e6f2a212 data-v-1bcd8184><!--[--><!--]--><!----><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-1bcd8184><span class="visually-hidden" id="doc-footer-aria-label" data-v-1bcd8184>Pager</span><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link prev" href="/assets.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Previous page</span><span class="title" data-v-1bcd8184>Assets</span><!--]--></a></div><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link next" href="/database-schema.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Next page</span><span class="title" data-v-1bcd8184>Database Schema</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
32
- <script>window.__VP_HASH_MAP__=JSON.parse("{\"ai.md\":\"tZrjP9im\",\"assets.md\":\"D3wunzLx\",\"brut-js.md\":\"o2DAO2s2\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"RmeA2b0i\",\"components.md\":\"eCttGlN-\",\"configuration.md\":\"BRriU0cL\",\"css.md\":\"DJgj2clw\",\"custom-element-tests.md\":\"BrYJQEl3\",\"database-access.md\":\"C7l-Vuvb\",\"database-schema.md\":\"BUjR0VS1\",\"deployment.md\":\"Dbka4OTr\",\"dev-environment.md\":\"BNc8AYiK\",\"doc-conventions.md\":\"DCfRXXi-\",\"end-to-end-tests.md\":\"yfQHC0b5\",\"flash-and-session.md\":\"BXY8RvT0\",\"forms.md\":\"CBTYQ_Cz\",\"getting-started.md\":\"Bz2s1Vjb\",\"handlers.md\":\"089DVD3v\",\"hooks.md\":\"C4-moMny\",\"i18n.md\":\"Do9i1qWl\",\"index.md\":\"B28EwVpq\",\"instrumentation.md\":\"CL6ax7nT\",\"javascript.md\":\"GWbhRS51\",\"jobs.md\":\"S-2amAYp\",\"keyword-injection.md\":\"Dt2tKREs\",\"markdown-examples.md\":\"CCFEQO44\",\"middleware.md\":\"Czz_UlJN\",\"not-released.md\":\"BBy28McC\",\"overview.md\":\"CDalkuxV\",\"pages.md\":\"BE3kfOc5\",\"routes.md\":\"BMM7peut\",\"security.md\":\"C668yXCi\",\"seed-data.md\":\"BvFZlqIk\",\"space-time-continuum.md\":\"KPUIKysQ\",\"tutorial.md\":\"BnoGjrdK\",\"unit-tests.md\":\"DUGrnLj5\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Brut RB\",\"description\":\"Documentation for the Brut.RB web framework.\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\"},\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Overview\",\"link\":\"/overview\"},{\"text\":\"Brut API\",\"link\":\"/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutCSS\",\"link\":\"/brut-css/index.html\",\"target\":\"_self\"}],\"outline\":[2,3],\"sidebar\":[{\"text\":\"Overview\",\"collapsed\":false,\"items\":[{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Concepts\",\"link\":\"/overview\"},{\"text\":\"Documentation Conventions\",\"link\":\"/doc-conventions\"},{\"text\":\"Tutorial\",\"link\":\"/tutorial\"},{\"text\":\"Dev Environment\",\"link\":\"/dev-environment\"},{\"text\":\"AI Declaration\",\"link\":\"/ai\"}]},{\"text\":\"Front-End\",\"collapsed\":false,\"items\":[{\"text\":\"Routes\",\"link\":\"/routes\"},{\"text\":\"Pages\",\"link\":\"/pages\"},{\"text\":\"Forms\",\"link\":\"/forms\"},{\"text\":\"Handlers and Actions\",\"link\":\"/handlers\"},{\"text\":\"Components\",\"link\":\"/components\"},{\"text\":\"Flash and Session\",\"link\":\"/flash-and-session\"},{\"text\":\"Space/Time Continuum\",\"link\":\"/space-time-continuum\"},{\"text\":\"JavaScript\",\"link\":\"/javascript\"},{\"text\":\"CSS\",\"link\":\"/css\"},{\"text\":\"Assets\",\"link\":\"/assets\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js\"}]},{\"text\":\"Back-End\",\"collapsed\":false,\"items\":[{\"text\":\"Database Schema\",\"link\":\"/database-schema\"},{\"text\":\"Database Access\",\"link\":\"/database-access\"},{\"text\":\"Seed Data\",\"link\":\"/seed-data\"},{\"text\":\"Jobs\",\"link\":\"/jobs\"},{\"text\":\"Business Logic\",\"link\":\"/business-logic\"}]},{\"text\":\"Framework\",\"collapsed\":false,\"items\":[{\"text\":\"Configuration\",\"link\":\"/configuration\"},{\"text\":\"Keyword Injection\",\"link\":\"/keyword-injection\"},{\"text\":\"I18n\",\"link\":\"/i18n\"},{\"text\":\"CLI / Tasks\",\"link\":\"/cli\"},{\"text\":\"Deployment\",\"link\":\"/deployment\"}]},{\"text\":\"Testing\",\"collapsed\":false,\"items\":[{\"text\":\"Unit Tests\",\"link\":\"/unit-tests\"},{\"text\":\"End-to-End Tests\",\"link\":\"/end-to-end-tests\"},{\"text\":\"Testing Custom Elements\",\"link\":\"/custom-element-tests\"}]},{\"text\":\"Advanced Topics\",\"collapsed\":false,\"items\":[{\"text\":\"Route Hooks\",\"link\":\"/hooks\"},{\"text\":\"Middleware\",\"link\":\"/middleware\"},{\"text\":\"Instrumentation\",\"link\":\"/instrumentation\"},{\"text\":\"Security\",\"link\":\"/security\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/thirdtank/brut\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
32
+ <script>window.__VP_HASH_MAP__=JSON.parse("{\"ai.md\":\"_6HCDL6d\",\"assets.md\":\"D3wunzLx\",\"brut-js.md\":\"o2DAO2s2\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"RmeA2b0i\",\"components.md\":\"CRUMdRoN\",\"configuration.md\":\"LG-zIBww\",\"css.md\":\"DJgj2clw\",\"custom-element-tests.md\":\"BrYJQEl3\",\"database-access.md\":\"C7l-Vuvb\",\"database-schema.md\":\"BUjR0VS1\",\"deployment.md\":\"BLseERGV\",\"dev-environment.md\":\"GZv6xvi9\",\"doc-conventions.md\":\"-kN3Xo5C\",\"end-to-end-tests.md\":\"yfQHC0b5\",\"flash-and-session.md\":\"BXY8RvT0\",\"forms.md\":\"B-koVgyw\",\"getting-started.md\":\"Dj0qtZI2\",\"handlers.md\":\"089DVD3v\",\"hooks.md\":\"C4-moMny\",\"i18n.md\":\"Do9i1qWl\",\"index.md\":\"CuBB-BdM\",\"instrumentation.md\":\"a9Pjps4P\",\"javascript.md\":\"GWbhRS51\",\"jobs.md\":\"S-2amAYp\",\"keyword-injection.md\":\"Dt2tKREs\",\"layouts.md\":\"cPnh3NId\",\"lsp.md\":\"Bsu-f6VU\",\"markdown-examples.md\":\"CCFEQO44\",\"middleware.md\":\"Czz_UlJN\",\"not-released.md\":\"BBy28McC\",\"overview.md\":\"DVKRM8zl\",\"pages.md\":\"BE3kfOc5\",\"recipes_authentication.md\":\"CAsXf7hk\",\"routes.md\":\"BMM7peut\",\"security.md\":\"C668yXCi\",\"seed-data.md\":\"BvFZlqIk\",\"space-time-continuum.md\":\"KPUIKysQ\",\"tutorial.md\":\"BnoGjrdK\",\"unit-tests.md\":\"DUGrnLj5\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Brut RB\",\"description\":\"Documentation for the Brut.RB web framework.\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\"},\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Overview\",\"link\":\"/overview\"},{\"text\":\"Brut API\",\"link\":\"/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutCSS\",\"link\":\"/brut-css/index.html\",\"target\":\"_self\"}],\"outline\":[2,3],\"sidebar\":[{\"text\":\"Overview\",\"collapsed\":false,\"items\":[{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Concepts\",\"link\":\"/overview\"},{\"text\":\"Documentation Conventions\",\"link\":\"/doc-conventions\"},{\"text\":\"Tutorial\",\"link\":\"/tutorial\"},{\"text\":\"Dev Environment\",\"link\":\"/dev-environment\"},{\"text\":\"AI Declaration\",\"link\":\"/ai\"}]},{\"text\":\"Front-End\",\"collapsed\":false,\"items\":[{\"text\":\"Routes\",\"link\":\"/routes\"},{\"text\":\"Pages\",\"link\":\"/pages\"},{\"text\":\"Layouts\",\"link\":\"/layouts\"},{\"text\":\"Forms\",\"link\":\"/forms\"},{\"text\":\"Handlers and Actions\",\"link\":\"/handlers\"},{\"text\":\"Components\",\"link\":\"/components\"},{\"text\":\"Flash and Session\",\"link\":\"/flash-and-session\"},{\"text\":\"Space/Time Continuum\",\"link\":\"/space-time-continuum\"},{\"text\":\"JavaScript\",\"link\":\"/javascript\"},{\"text\":\"CSS\",\"link\":\"/css\"},{\"text\":\"Assets\",\"link\":\"/assets\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js\"}]},{\"text\":\"Back-End\",\"collapsed\":false,\"items\":[{\"text\":\"Database Schema\",\"link\":\"/database-schema\"},{\"text\":\"Database Access\",\"link\":\"/database-access\"},{\"text\":\"Seed Data\",\"link\":\"/seed-data\"},{\"text\":\"Jobs\",\"link\":\"/jobs\"},{\"text\":\"Business Logic\",\"link\":\"/business-logic\"}]},{\"text\":\"Framework\",\"collapsed\":false,\"items\":[{\"text\":\"Configuration\",\"link\":\"/configuration\"},{\"text\":\"Keyword Injection\",\"link\":\"/keyword-injection\"},{\"text\":\"I18n\",\"link\":\"/i18n\"},{\"text\":\"CLI / Tasks\",\"link\":\"/cli\"},{\"text\":\"Deployment\",\"link\":\"/deployment\"}]},{\"text\":\"Testing\",\"collapsed\":false,\"items\":[{\"text\":\"Unit Tests\",\"link\":\"/unit-tests\"},{\"text\":\"End-to-End Tests\",\"link\":\"/end-to-end-tests\"},{\"text\":\"Testing Custom Elements\",\"link\":\"/custom-element-tests\"}]},{\"text\":\"Advanced Topics\",\"collapsed\":true,\"items\":[{\"text\":\"Route Hooks\",\"link\":\"/hooks\"},{\"text\":\"Middleware\",\"link\":\"/middleware\"},{\"text\":\"Instrumentation\",\"link\":\"/instrumentation\"},{\"text\":\"Security\",\"link\":\"/security\"},{\"text\":\"LSP Support\",\"link\":\"/lsp\"}]},{\"text\":\"Recipes\",\"collapsed\":true,\"items\":[{\"text\":\"Authentication\",\"link\":\"/recipes/authentication\"},{\"text\":\"Form Validations\",\"link\":\"/recipes/form-validations\"},{\"text\":\"Database Migrations\",\"link\":\"/recipes/database-migrations\"},{\"text\":\"Ajax Form Submission\",\"link\":\"/recipes/ajax-form\"},{\"text\":\"Custom Telemetry\",\"link\":\"/recipes/telemetry\"},{\"text\":\"CLI App/Task\",\"link\":\"/recipes/cli-app\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/thirdtank/brut\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
33
33
 
34
34
  </body>
35
35
  </html>