brut 0.14.0 → 0.16.0.pre

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 (569) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +10 -0
  4. data/Dockerfile.dx +3 -6
  5. data/Gemfile.lock +1 -1
  6. data/brut-css/package-lock.json +2 -2
  7. data/brut-css/package.json +1 -1
  8. data/brut-js/package-lock.json +2 -2
  9. data/brut-js/package.json +1 -1
  10. data/brut-js/specs/Toast.spec.js +34 -0
  11. data/brut-js/src/I18nTranslation.js +3 -0
  12. data/brut-js/src/Message.js +9 -3
  13. data/brut-js/src/RichString.js +4 -1
  14. data/brut-js/src/Toast.js +102 -0
  15. data/brut-js/src/index.js +3 -0
  16. data/brutrb.com/.vitepress/config.mjs +4 -3
  17. data/brutrb.com/brut-js.md +1 -0
  18. data/brutrb.com/deployment.md +23 -9
  19. data/brutrb.com/jobs.md +107 -7
  20. data/brutrb.com/recipes/dev-env-secrets.md +87 -0
  21. data/brutrb.com/roadmap.md +2 -7
  22. data/docs/404.html +3 -3
  23. data/docs/adrs.html +7 -7
  24. data/docs/ai.html +7 -7
  25. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  26. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  27. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  28. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  29. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  30. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  31. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  32. data/docs/api/Brut/BackEnd.html +1 -1
  33. data/docs/api/Brut/CLI/App.html +1 -1
  34. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  67. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  69. data/docs/api/Brut/CLI/Apps.html +1 -1
  70. data/docs/api/Brut/CLI/Command.html +1 -1
  71. data/docs/api/Brut/CLI/Error.html +1 -1
  72. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  73. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  74. data/docs/api/Brut/CLI/Executor.html +1 -1
  75. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  76. data/docs/api/Brut/CLI/Options.html +1 -1
  77. data/docs/api/Brut/CLI/Output.html +1 -1
  78. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  79. data/docs/api/Brut/CLI.html +1 -1
  80. data/docs/api/Brut/FactoryBot.html +1 -1
  81. data/docs/api/Brut/Framework/App.html +1 -1
  82. data/docs/api/Brut/Framework/Config.html +1 -1
  83. data/docs/api/Brut/Framework/Container.html +1 -1
  84. data/docs/api/Brut/Framework/Error.html +1 -1
  85. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  86. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  87. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  88. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  89. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  90. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  91. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  92. data/docs/api/Brut/Framework/Errors.html +1 -1
  93. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  94. data/docs/api/Brut/Framework/MCP.html +1 -1
  95. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  96. data/docs/api/Brut/Framework.html +1 -1
  97. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +37 -18
  113. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  116. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  134. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  143. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  144. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  145. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  146. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Layout.html +171 -3
  148. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  158. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  160. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  161. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  162. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  163. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  164. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  165. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  172. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  175. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  176. data/docs/api/Brut/FrontEnd.html +1 -1
  177. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  178. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  179. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  180. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  181. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  182. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  183. data/docs/api/Brut/I18n.html +1 -1
  184. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  185. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
  186. data/docs/api/Brut/Instrumentation/Methods.html +1 -1
  187. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  188. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  189. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  190. data/docs/api/Brut/Instrumentation.html +1 -1
  191. data/docs/api/Brut/RubocopConfig.html +1 -1
  192. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  193. data/docs/api/Brut/SinatraHelpers.html +1 -1
  194. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  195. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  197. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  199. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  200. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  201. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  202. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  210. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  211. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  212. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  213. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  214. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  215. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  216. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  217. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  218. data/docs/api/Brut/SpecSupport.html +1 -1
  219. data/docs/api/Brut.html +1 -1
  220. data/docs/api/Clock.html +1 -1
  221. data/docs/api/ModuleName.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 +1 -1
  225. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  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/_index.html +1 -1
  237. data/docs/api/file.README.html +1 -1
  238. data/docs/api/index.html +1 -1
  239. data/docs/api/method_list.html +157 -141
  240. data/docs/api/top-level-namespace.html +1 -1
  241. data/docs/assets/adrs.md.YglbWtQe.js +1 -0
  242. data/docs/assets/adrs.md.YglbWtQe.lean.js +1 -0
  243. data/docs/assets/ai.md.ChLnvDAX.js +1 -0
  244. data/docs/assets/ai.md.ChLnvDAX.lean.js +1 -0
  245. data/docs/assets/{app.BDtsVxyd.js → app.Dm7v_ouO.js} +1 -1
  246. data/docs/assets/{assets.md.7C3HWkga.js → assets.md.BEF6Oz6K.js} +2 -2
  247. data/docs/assets/assets.md.BEF6Oz6K.lean.js +1 -0
  248. data/docs/assets/{brut-js.md.B4GYxQVw.js → brut-js.md.BMz0X1Rz.js} +2 -2
  249. data/docs/assets/brut-js.md.BMz0X1Rz.lean.js +1 -0
  250. data/docs/assets/business-logic.md.DbuaOYGU.js +1 -0
  251. data/docs/assets/business-logic.md.DbuaOYGU.lean.js +1 -0
  252. data/docs/assets/chunks/@localSearchIndexroot.BiNFswvo.js +1 -0
  253. data/docs/assets/chunks/VPLocalSearchBox.DQK6jQou.js +8 -0
  254. data/docs/assets/chunks/framework.C4nOkCZI.js +18 -0
  255. data/docs/assets/chunks/{theme.DZKmijwi.js → theme.BuExsdM9.js} +2 -2
  256. data/docs/assets/{cli.md.CjsktgFz.js → cli.md.DDMar_51.js} +2 -2
  257. data/docs/assets/cli.md.DDMar_51.lean.js +1 -0
  258. data/docs/assets/{components.md.rMhQ0WdZ.js → components.md.Dfd3w6UW.js} +5 -5
  259. data/docs/assets/components.md.Dfd3w6UW.lean.js +1 -0
  260. data/docs/assets/{configuration.md.BK42Yjp_.js → configuration.md.DTYoV2Ea.js} +2 -2
  261. data/docs/assets/configuration.md.DTYoV2Ea.lean.js +1 -0
  262. data/docs/assets/{css.md.CltvJqAa.js → css.md.K5rOCOQY.js} +2 -2
  263. data/docs/assets/css.md.K5rOCOQY.lean.js +1 -0
  264. data/docs/assets/{custom-element-tests.md.B_rbta32.js → custom-element-tests.md.DiLe-eFw.js} +2 -2
  265. data/docs/assets/custom-element-tests.md.DiLe-eFw.lean.js +1 -0
  266. data/docs/assets/{database-access.md.gnluu54N.js → database-access.md.Dc8l2Plf.js} +2 -2
  267. data/docs/assets/database-access.md.Dc8l2Plf.lean.js +1 -0
  268. data/docs/assets/{database-schema.md.LpmBPVEU.js → database-schema.md.BJ_JhXmO.js} +2 -2
  269. data/docs/assets/database-schema.md.BJ_JhXmO.lean.js +1 -0
  270. data/docs/assets/{deployment.md.BLseERGV.js → deployment.md.C1u5ep0g.js} +2 -2
  271. data/docs/assets/deployment.md.C1u5ep0g.lean.js +1 -0
  272. data/docs/assets/{dev-environment.md.DRH2D2-O.js → dev-environment.md.B1S9p5ZK.js} +2 -2
  273. data/docs/assets/{dev-environment.md.DRH2D2-O.lean.js → dev-environment.md.B1S9p5ZK.lean.js} +1 -1
  274. data/docs/assets/{dir-structure.md.CWir1pic.js → dir-structure.md.D1T2kGwj.js} +2 -2
  275. data/docs/assets/dir-structure.md.D1T2kGwj.lean.js +1 -0
  276. data/docs/assets/doc-conventions.md.CDnWaEFg.js +1 -0
  277. data/docs/assets/doc-conventions.md.CDnWaEFg.lean.js +1 -0
  278. data/docs/assets/{end-to-end-tests.md.DzqRpZ43.js → end-to-end-tests.md.BJJdNDYL.js} +2 -2
  279. data/docs/assets/end-to-end-tests.md.BJJdNDYL.lean.js +1 -0
  280. data/docs/assets/{features.md.DPFXsy0z.js → features.md.BDWxnyNO.js} +2 -2
  281. data/docs/assets/features.md.BDWxnyNO.lean.js +1 -0
  282. data/docs/assets/{flash-and-session.md.nPvUpnUx.js → flash-and-session.md.CUsMxoNl.js} +2 -2
  283. data/docs/assets/flash-and-session.md.CUsMxoNl.lean.js +1 -0
  284. data/docs/assets/{form-constraints.md.KTv5cdR4.js → form-constraints.md.KlfXSKm2.js} +2 -2
  285. data/docs/assets/form-constraints.md.KlfXSKm2.lean.js +1 -0
  286. data/docs/assets/{forms.md.v9qIbmUM.js → forms.md.DEkmJUvb.js} +3 -3
  287. data/docs/assets/forms.md.DEkmJUvb.lean.js +1 -0
  288. data/docs/assets/{getting-started.md.DTOl4c2g.js → getting-started.md.DO-4eoGW.js} +4 -4
  289. data/docs/assets/getting-started.md.DO-4eoGW.lean.js +1 -0
  290. data/docs/assets/{handlers.md.h84MMB1R.js → handlers.md.C5tUwmmo.js} +2 -2
  291. data/docs/assets/handlers.md.C5tUwmmo.lean.js +1 -0
  292. data/docs/assets/{hooks.md.Jmb5VOLA.js → hooks.md.CoiYCKRc.js} +2 -2
  293. data/docs/assets/hooks.md.CoiYCKRc.lean.js +1 -0
  294. data/docs/assets/{i18n.md.BAm9t9JJ.js → i18n.md.DxkCKhUw.js} +2 -2
  295. data/docs/assets/i18n.md.DxkCKhUw.lean.js +1 -0
  296. data/docs/assets/{index.md.Bn9e0sRJ.js → index.md.DnphWyQd.js} +1 -1
  297. data/docs/assets/{index.md.Bn9e0sRJ.lean.js → index.md.DnphWyQd.lean.js} +1 -1
  298. data/docs/assets/{instrumentation.md._lNSriEZ.js → instrumentation.md.BcxjC4jd.js} +2 -2
  299. data/docs/assets/instrumentation.md.BcxjC4jd.lean.js +1 -0
  300. data/docs/assets/{javascript.md.DzrMxUmI.js → javascript.md.D6fxhaQb.js} +2 -2
  301. data/docs/assets/javascript.md.D6fxhaQb.lean.js +1 -0
  302. data/docs/assets/jobs.md.Bc7Y1YpK.js +1 -0
  303. data/docs/assets/jobs.md.Bc7Y1YpK.lean.js +1 -0
  304. data/docs/assets/{keyword-injection.md.95Zgh2eN.js → keyword-injection.md.CqLnnzIz.js} +2 -2
  305. data/docs/assets/keyword-injection.md.CqLnnzIz.lean.js +1 -0
  306. data/docs/assets/layouts.md.HEbeK7Jr.js +68 -0
  307. data/docs/assets/layouts.md.HEbeK7Jr.lean.js +1 -0
  308. data/docs/assets/lsp.md.bE9dW8n9.js +1 -0
  309. data/docs/assets/lsp.md.bE9dW8n9.lean.js +1 -0
  310. data/docs/assets/{markdown-examples.md.CCFEQO44.js → markdown-examples.md.BPmtHlc-.js} +2 -2
  311. data/docs/assets/markdown-examples.md.BPmtHlc-.lean.js +1 -0
  312. data/docs/assets/{middleware.md.Czz_UlJN.js → middleware.md.BhOIsg59.js} +2 -2
  313. data/docs/assets/middleware.md.BhOIsg59.lean.js +1 -0
  314. data/docs/assets/overview.md.BpWAgPFH.js +1 -0
  315. data/docs/assets/overview.md.BpWAgPFH.lean.js +1 -0
  316. data/docs/assets/{pages.md.B7Hc-i6H.js → pages.md.B3sQXpEd.js} +2 -2
  317. data/docs/assets/pages.md.B3sQXpEd.lean.js +1 -0
  318. data/docs/assets/{recipes_alternate-layouts.md.BwEytl59.js → recipes_alternate-layouts.md.C1QzVkA7.js} +2 -2
  319. data/docs/assets/recipes_alternate-layouts.md.C1QzVkA7.lean.js +1 -0
  320. data/docs/assets/{recipes_authentication.md.nwO6F7Ou.js → recipes_authentication.md.CyvoIW82.js} +2 -2
  321. data/docs/assets/recipes_authentication.md.CyvoIW82.lean.js +1 -0
  322. data/docs/assets/{recipes_custom-flash.md.CrQbI5eH.js → recipes_custom-flash.md.6gFqf2uL.js} +2 -2
  323. data/docs/assets/recipes_custom-flash.md.6gFqf2uL.lean.js +1 -0
  324. data/docs/assets/{recipes_form-errors.md.Bv5RCKqH.js → recipes_form-errors.md.B5ptSzMO.js} +2 -2
  325. data/docs/assets/recipes_form-errors.md.B5ptSzMO.lean.js +1 -0
  326. data/docs/assets/{recipes_indexed-forms.md.CstYyOSo.js → recipes_indexed-forms.md.BYYQGW2C.js} +2 -2
  327. data/docs/assets/recipes_indexed-forms.md.BYYQGW2C.lean.js +1 -0
  328. data/docs/assets/{recipes_migrations.md.CTcnWDJF.js → recipes_migrations.md.Cid7-3cu.js} +2 -2
  329. data/docs/assets/recipes_migrations.md.Cid7-3cu.lean.js +1 -0
  330. data/docs/assets/{recipes_text-field-component.md.H4wLAK0Z.js → recipes_text-field-component.md.VhOsCtKI.js} +2 -2
  331. data/docs/assets/recipes_text-field-component.md.VhOsCtKI.lean.js +1 -0
  332. data/docs/assets/roadmap.md.CJsbUmK_.js +1 -0
  333. data/docs/assets/roadmap.md.CJsbUmK_.lean.js +1 -0
  334. data/docs/assets/{routes.md.BD6y2i-f.js → routes.md.C1dgIBtD.js} +2 -2
  335. data/docs/assets/routes.md.C1dgIBtD.lean.js +1 -0
  336. data/docs/assets/security.md.Jn4SY1uK.js +1 -0
  337. data/docs/assets/security.md.Jn4SY1uK.lean.js +1 -0
  338. data/docs/assets/{seed-data.md.BvFZlqIk.js → seed-data.md.UZW0WxYN.js} +2 -2
  339. data/docs/assets/seed-data.md.UZW0WxYN.lean.js +1 -0
  340. data/docs/assets/space-time-continuum.md.D9rYGDFH.js +1 -0
  341. data/docs/assets/space-time-continuum.md.D9rYGDFH.lean.js +1 -0
  342. data/docs/assets/{tutorial.md.BM40jnoq.js → tutorial.md.BX6f6l00.js} +2 -2
  343. data/docs/assets/tutorial.md.BX6f6l00.lean.js +1 -0
  344. data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.js → tutorials_01-intro.md.CzZ3kpF_.js} +2 -2
  345. data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.lean.js → tutorials_01-intro.md.CzZ3kpF_.lean.js} +1 -1
  346. data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.js → tutorials_02-dialog.md.D2vSjDVf.js} +2 -2
  347. data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.lean.js → tutorials_02-dialog.md.D2vSjDVf.lean.js} +1 -1
  348. data/docs/assets/{unit-tests.md.DUGrnLj5.js → unit-tests.md.vDsdBbO_.js} +2 -2
  349. data/docs/assets/unit-tests.md.vDsdBbO_.lean.js +1 -0
  350. data/docs/assets/why.md.4WpxdrQ2.js +1 -0
  351. data/docs/assets/why.md.4WpxdrQ2.lean.js +1 -0
  352. data/docs/assets.html +7 -7
  353. data/docs/brut-js/api/AjaxSubmit.html +2 -2
  354. data/docs/brut-js/api/AjaxSubmit.js.html +2 -2
  355. data/docs/brut-js/api/Autosubmit.html +2 -2
  356. data/docs/brut-js/api/Autosubmit.js.html +2 -2
  357. data/docs/brut-js/api/BaseCustomElement.html +2 -2
  358. data/docs/brut-js/api/BaseCustomElement.js.html +2 -2
  359. data/docs/brut-js/api/BrutCustomElements.html +3 -3
  360. data/docs/brut-js/api/BufferedLogger.html +2 -2
  361. data/docs/brut-js/api/ConfirmSubmit.html +2 -2
  362. data/docs/brut-js/api/ConfirmSubmit.js.html +2 -2
  363. data/docs/brut-js/api/ConfirmationDialog.html +2 -2
  364. data/docs/brut-js/api/ConfirmationDialog.js.html +2 -2
  365. data/docs/brut-js/api/ConstraintViolationMessage.html +2 -2
  366. data/docs/brut-js/api/ConstraintViolationMessage.js.html +2 -2
  367. data/docs/brut-js/api/ConstraintViolationMessages.html +2 -2
  368. data/docs/brut-js/api/ConstraintViolationMessages.js.html +2 -2
  369. data/docs/brut-js/api/CopyToClipboard.html +2 -2
  370. data/docs/brut-js/api/CopyToClipboard.js.html +2 -2
  371. data/docs/brut-js/api/Form.html +2 -2
  372. data/docs/brut-js/api/Form.js.html +2 -2
  373. data/docs/brut-js/api/I18nTranslation.html +2 -2
  374. data/docs/brut-js/api/I18nTranslation.js.html +5 -2
  375. data/docs/brut-js/api/LocaleDetection.html +2 -2
  376. data/docs/brut-js/api/LocaleDetection.js.html +2 -2
  377. data/docs/brut-js/api/Logger.html +2 -2
  378. data/docs/brut-js/api/Logger.js.html +2 -2
  379. data/docs/brut-js/api/Message.html +2 -2
  380. data/docs/brut-js/api/Message.js.html +11 -5
  381. data/docs/brut-js/api/PrefixedLogger.html +2 -2
  382. data/docs/brut-js/api/RichString.html +9 -9
  383. data/docs/brut-js/api/RichString.js.html +6 -3
  384. data/docs/brut-js/api/Tabs.html +2 -2
  385. data/docs/brut-js/api/Tabs.js.html +2 -2
  386. data/docs/brut-js/api/Toast.html +270 -0
  387. data/docs/brut-js/api/Toast.js.html +153 -0
  388. data/docs/brut-js/api/Tracing.html +2 -2
  389. data/docs/brut-js/api/Tracing.js.html +2 -2
  390. data/docs/brut-js/api/external-CustomElementRegistry.html +3 -3
  391. data/docs/brut-js/api/external-Performance.html +3 -3
  392. data/docs/brut-js/api/external-Promise.html +3 -3
  393. data/docs/brut-js/api/external-ValidityState.html +3 -3
  394. data/docs/brut-js/api/external-Window.html +4 -4
  395. data/docs/brut-js/api/external-fetch.html +3 -3
  396. data/docs/brut-js/api/global.html +3 -3
  397. data/docs/brut-js/api/index.html +2 -2
  398. data/docs/brut-js/api/index.js.html +5 -2
  399. data/docs/brut-js/api/module-testing.html +2 -2
  400. data/docs/brut-js/api/testing.AssetMetadata.html +2 -2
  401. data/docs/brut-js/api/testing.AssetMetadataLoader.html +2 -2
  402. data/docs/brut-js/api/testing.CustomElementTest.html +2 -2
  403. data/docs/brut-js/api/testing.DOMCreator.html +2 -2
  404. data/docs/brut-js/api/testing_AssetMetadata.js.html +2 -2
  405. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +2 -2
  406. data/docs/brut-js/api/testing_CustomElementTest.js.html +2 -2
  407. data/docs/brut-js/api/testing_DOMCreator.js.html +2 -2
  408. data/docs/brut-js/api/testing_index.js.html +2 -2
  409. data/docs/brut-js.html +8 -8
  410. data/docs/business-logic.html +7 -7
  411. data/docs/cli.html +7 -7
  412. data/docs/components.html +10 -10
  413. data/docs/configuration.html +7 -7
  414. data/docs/css.html +7 -7
  415. data/docs/custom-element-tests.html +7 -7
  416. data/docs/database-access.html +7 -7
  417. data/docs/database-schema.html +7 -7
  418. data/docs/deployment.html +7 -7
  419. data/docs/dev-environment.html +7 -7
  420. data/docs/dir-structure.html +7 -7
  421. data/docs/doc-conventions.html +7 -7
  422. data/docs/end-to-end-tests.html +7 -7
  423. data/docs/features.html +7 -7
  424. data/docs/flash-and-session.html +7 -7
  425. data/docs/form-constraints.html +7 -7
  426. data/docs/forms.html +8 -8
  427. data/docs/getting-started.html +9 -9
  428. data/docs/handlers.html +7 -7
  429. data/docs/hashmap.json +1 -1
  430. data/docs/hooks.html +7 -7
  431. data/docs/i18n.html +7 -7
  432. data/docs/index.html +6 -6
  433. data/docs/instrumentation.html +7 -7
  434. data/docs/javascript.html +7 -7
  435. data/docs/jobs.html +7 -7
  436. data/docs/keyword-injection.html +7 -7
  437. data/docs/layouts.html +42 -12
  438. data/docs/lsp.html +7 -7
  439. data/docs/markdown-examples.html +7 -7
  440. data/docs/middleware.html +7 -7
  441. data/docs/overview.html +7 -7
  442. data/docs/pages.html +7 -7
  443. data/docs/recipes/alternate-layouts.html +8 -8
  444. data/docs/recipes/authentication.html +7 -7
  445. data/docs/recipes/custom-flash.html +8 -8
  446. data/docs/recipes/form-errors.html +7 -7
  447. data/docs/recipes/indexed-forms.html +7 -7
  448. data/docs/recipes/migrations.html +7 -7
  449. data/docs/recipes/text-field-component.html +7 -7
  450. data/docs/roadmap.html +7 -7
  451. data/docs/routes.html +7 -7
  452. data/docs/security.html +7 -7
  453. data/docs/seed-data.html +7 -7
  454. data/docs/space-time-continuum.html +7 -7
  455. data/docs/tutorial.html +7 -7
  456. data/docs/tutorials/01-intro.html +7 -7
  457. data/docs/tutorials/02-dialog.html +7 -7
  458. data/docs/unit-tests.html +7 -7
  459. data/docs/why.html +7 -7
  460. data/dx/bash_customizations +3 -4
  461. data/dx/build.pre +15 -0
  462. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +41 -15
  463. data/lib/brut/cli/apps/test.rb +2 -0
  464. data/lib/brut/framework/container.rb +6 -4
  465. data/lib/brut/framework/mcp.rb +1 -1
  466. data/lib/brut/version.rb +1 -1
  467. data/mkbrut/Gemfile.lock +2 -2
  468. data/mkbrut/lib/mkbrut/add_segment.rb +38 -0
  469. data/mkbrut/lib/mkbrut/add_segment_options.rb +22 -0
  470. data/mkbrut/lib/mkbrut/app.rb +7 -2
  471. data/mkbrut/lib/mkbrut/base.rb +1 -0
  472. data/mkbrut/lib/mkbrut/cli.rb +90 -8
  473. data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +19 -7
  474. data/mkbrut/lib/mkbrut/ops/insert_into_file.rb +36 -0
  475. data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +14 -2
  476. data/mkbrut/lib/mkbrut/ops.rb +1 -0
  477. data/mkbrut/lib/mkbrut/segments/bare_bones.rb +8 -0
  478. data/mkbrut/lib/mkbrut/segments/demo.rb +8 -0
  479. data/mkbrut/lib/mkbrut/segments/heroku.rb +23 -3
  480. data/mkbrut/lib/mkbrut/segments/sidekiq.rb +136 -1
  481. data/mkbrut/lib/mkbrut/segments.rb +1 -1
  482. data/mkbrut/lib/mkbrut/version.rb +1 -1
  483. data/mkbrut/lib/mkbrut.rb +2 -0
  484. data/mkbrut/templates/Base/.gitignore +3 -0
  485. data/mkbrut/templates/Base/Dockerfile.dx +18 -21
  486. data/mkbrut/templates/Base/Gemfile.erb +1 -1
  487. data/mkbrut/templates/Base/bin/run +107 -67
  488. data/mkbrut/templates/Base/bin/run.run +4 -0
  489. data/mkbrut/templates/Base/bin/setup +32 -1
  490. data/mkbrut/templates/Base/dx/bash_customizations +0 -4
  491. data/mkbrut/templates/Base/package.json.erb +1 -1
  492. data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +15 -15
  493. data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +5 -4
  494. data/mkbrut/templates/segments/Sidekiq/app/boot_sidekiq.rb +2 -0
  495. data/mkbrut/templates/segments/Sidekiq/app/config/sidekiq.yml +4 -0
  496. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/app_job.rb +3 -0
  497. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/example_job.rb +12 -0
  498. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/segments/sidekiq_segment.rb +56 -0
  499. data/mkbrut/templates/segments/Sidekiq/bin/run.sidekiq +4 -0
  500. data/mkbrut/templates/segments/Sidekiq/specs/back_end/jobs/example_job.spec.rb +5 -0
  501. data/mkbrut/templates/segments/Sidekiq/specs/integration/sidekiq_works.spec.rb +38 -0
  502. metadata +131 -116
  503. data/docs/assets/adrs.md.BxjHi9-8.js +0 -1
  504. data/docs/assets/adrs.md.BxjHi9-8.lean.js +0 -1
  505. data/docs/assets/ai.md.Cy9GWnER.js +0 -1
  506. data/docs/assets/ai.md.Cy9GWnER.lean.js +0 -1
  507. data/docs/assets/assets.md.7C3HWkga.lean.js +0 -1
  508. data/docs/assets/brut-js.md.B4GYxQVw.lean.js +0 -1
  509. data/docs/assets/business-logic.md.BY4hGy0m.js +0 -1
  510. data/docs/assets/business-logic.md.BY4hGy0m.lean.js +0 -1
  511. data/docs/assets/chunks/@localSearchIndexroot.BWVzhs5N.js +0 -1
  512. data/docs/assets/chunks/VPLocalSearchBox.DCJk5nAW.js +0 -8
  513. data/docs/assets/chunks/framework.1L-BeKqY.js +0 -18
  514. data/docs/assets/cli.md.CjsktgFz.lean.js +0 -1
  515. data/docs/assets/components.md.rMhQ0WdZ.lean.js +0 -1
  516. data/docs/assets/configuration.md.BK42Yjp_.lean.js +0 -1
  517. data/docs/assets/css.md.CltvJqAa.lean.js +0 -1
  518. data/docs/assets/custom-element-tests.md.B_rbta32.lean.js +0 -1
  519. data/docs/assets/database-access.md.gnluu54N.lean.js +0 -1
  520. data/docs/assets/database-schema.md.LpmBPVEU.lean.js +0 -1
  521. data/docs/assets/deployment.md.BLseERGV.lean.js +0 -1
  522. data/docs/assets/dir-structure.md.CWir1pic.lean.js +0 -1
  523. data/docs/assets/doc-conventions.md.DOkAuXlt.js +0 -1
  524. data/docs/assets/doc-conventions.md.DOkAuXlt.lean.js +0 -1
  525. data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +0 -1
  526. data/docs/assets/features.md.DPFXsy0z.lean.js +0 -1
  527. data/docs/assets/flash-and-session.md.nPvUpnUx.lean.js +0 -1
  528. data/docs/assets/form-constraints.md.KTv5cdR4.lean.js +0 -1
  529. data/docs/assets/forms.md.v9qIbmUM.lean.js +0 -1
  530. data/docs/assets/getting-started.md.DTOl4c2g.lean.js +0 -1
  531. data/docs/assets/handlers.md.h84MMB1R.lean.js +0 -1
  532. data/docs/assets/hooks.md.Jmb5VOLA.lean.js +0 -1
  533. data/docs/assets/i18n.md.BAm9t9JJ.lean.js +0 -1
  534. data/docs/assets/instrumentation.md._lNSriEZ.lean.js +0 -1
  535. data/docs/assets/javascript.md.DzrMxUmI.lean.js +0 -1
  536. data/docs/assets/jobs.md.S-2amAYp.js +0 -1
  537. data/docs/assets/jobs.md.S-2amAYp.lean.js +0 -1
  538. data/docs/assets/keyword-injection.md.95Zgh2eN.lean.js +0 -1
  539. data/docs/assets/layouts.md.CVGl9xIO.js +0 -38
  540. data/docs/assets/layouts.md.CVGl9xIO.lean.js +0 -1
  541. data/docs/assets/lsp.md.Dn1rIiW0.js +0 -1
  542. data/docs/assets/lsp.md.Dn1rIiW0.lean.js +0 -1
  543. data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +0 -1
  544. data/docs/assets/middleware.md.Czz_UlJN.lean.js +0 -1
  545. data/docs/assets/overview.md.DlKiRRG_.js +0 -1
  546. data/docs/assets/overview.md.DlKiRRG_.lean.js +0 -1
  547. data/docs/assets/pages.md.B7Hc-i6H.lean.js +0 -1
  548. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +0 -1
  549. data/docs/assets/recipes_authentication.md.nwO6F7Ou.lean.js +0 -1
  550. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +0 -15
  551. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +0 -1
  552. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +0 -1
  553. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +0 -1
  554. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +0 -1
  555. data/docs/assets/recipes_migrations.md.CTcnWDJF.lean.js +0 -1
  556. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +0 -1
  557. data/docs/assets/roadmap.md.C6PRi0DX.js +0 -1
  558. data/docs/assets/roadmap.md.C6PRi0DX.lean.js +0 -1
  559. data/docs/assets/routes.md.BD6y2i-f.lean.js +0 -1
  560. data/docs/assets/security.md.C0G_AZR-.js +0 -1
  561. data/docs/assets/security.md.C0G_AZR-.lean.js +0 -1
  562. data/docs/assets/seed-data.md.BvFZlqIk.lean.js +0 -1
  563. data/docs/assets/space-time-continuum.md.xl44xDos.js +0 -1
  564. data/docs/assets/space-time-continuum.md.xl44xDos.lean.js +0 -1
  565. data/docs/assets/tutorial.md.BM40jnoq.lean.js +0 -1
  566. data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +0 -1
  567. data/docs/assets/why.md.C-hk5xgJ.js +0 -1
  568. data/docs/assets/why.md.C-hk5xgJ.lean.js +0 -1
  569. data/docs/recipes/blank-layouts.html +0 -43
@@ -1,4 +1,4 @@
1
- import{_ as a,c as s,o as t,ag as i}from"./chunks/framework.1L-BeKqY.js";const u=JSON.parse('{"title":"Unit Tests","description":"","frontmatter":{},"headers":[],"relativePath":"unit-tests.md","filePath":"unit-tests.md"}'),n={name:"unit-tests.md"};function o(r,e,l,h,c,d){return t(),s("div",null,e[0]||(e[0]=[i(`<h1 id="unit-tests" tabindex="-1">Unit Tests <a class="header-anchor" href="#unit-tests" aria-label="Permalink to &quot;Unit Tests&quot;">​</a></h1><p>Tests in Brut use RSpec and given that most of your Brut-powered classes are simple Ruby classes, you can test them in conventional ways.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>When you scaffold something like a page or component, Brut will create an empty test file in <code>specs/</code>, whose path mirrors the class in <code>app/src</code>. For example, <code>specs/front_end/handlers/login_handler.spec.rb</code> will test the class defined in <code>app/src/front_end/handlers/login_handler.rb</code>.</p><p>Each page of Brut&#39;s documentation includes a &quot;Testing&quot; section that outlines additional features avialable to make testing work more easily. This section will talk about general features and behavior.</p><h3 id="tests-run-in-a-database-transaction" tabindex="-1">Tests run in a Database Transaction <a class="header-anchor" href="#tests-run-in-a-database-transaction" aria-label="Permalink to &quot;Tests run in a Database Transaction&quot;">​</a></h3><p>At the start of each test (<code>it</code> block in RSpec), a database transaction is opened. At the end, the transaction is rolled back. This means that none of the changes you make to the database have any effect outside the context of the test.</p><p>The downside of this approach is that you cannot test anything that involves database transactions. For example, if you want to ensure that a piece of business logic runs inside a database transaction, you will have to assure that another way, such as spying.</p><h3 id="a-usable-requestcontext-is-created-for-front-end-tests" tabindex="-1">A Usable <code>RequestContext</code> is Created for Front End Tests <a class="header-anchor" href="#a-usable-requestcontext-is-created-for-front-end-tests" aria-label="Permalink to &quot;A Usable \`RequestContext\` is Created for Front End Tests&quot;">​</a></h3><p>Although your tests of pages, components, and handlers are generally isolated, it&#39;s possible to trigger codepaths where Brut will use <a href="/keyword-injection.html">keyword injection</a>, such as a global component.</p><p>To make sure this doesn&#39;t fail, Brut sets up a reasonable <code>RequestContext</code> that will be used for any such injections.</p><p>Brut will also <code>let</code> that instance, named <code>request_context</code>. This means you can access it and modify it in your test as needed. It will be recreated new for each test, so you are safe making changes to it.</p><h3 id="bin-test-audit-and-managing-tests" tabindex="-1"><code>bin/test audit</code> and Managing Tests <a class="header-anchor" href="#bin-test-audit-and-managing-tests" aria-label="Permalink to &quot;\`bin/test audit\` and Managing Tests&quot;">​</a></h3><p><code>bin/test audit</code> will fail if any file in <code>app/src</code> does not have a corresponding test. This is handy when you are moving fast to make sure you don&#39;t forget to add test coverage.</p><p>That said, sometimes classes are simple and won&#39;t benefit from a test, or a class&#39; behavior may be adequately covered by another test. It&#39;s helpful to record this information so it&#39;s clear that you&#39;ve given consideration to tests and not just forgotten them.</p><p>Every Brut test has <a href="/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html" target="_self" rel="noopener" data-no-router><code>Brut::SpecSupport::GeneralSupport::ClassMethods</code></a> included and it provides three methods to help record your intent with respect to omitting tests. These methods should be called in a <code>describe</code> or <code>context</code> block.</p><ul><li><p><code>implementation_is_covered_by_other_tests(description)</code> used to explain where the coverage for this class is.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">RSpec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> TaxCalculator</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
1
+ import{_ as a,c as s,o as t,ag as i}from"./chunks/framework.C4nOkCZI.js";const u=JSON.parse('{"title":"Unit Tests","description":"","frontmatter":{},"headers":[],"relativePath":"unit-tests.md","filePath":"unit-tests.md"}'),n={name:"unit-tests.md"};function o(r,e,l,h,c,d){return t(),s("div",null,[...e[0]||(e[0]=[i(`<h1 id="unit-tests" tabindex="-1">Unit Tests <a class="header-anchor" href="#unit-tests" aria-label="Permalink to &quot;Unit Tests&quot;">​</a></h1><p>Tests in Brut use RSpec and given that most of your Brut-powered classes are simple Ruby classes, you can test them in conventional ways.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>When you scaffold something like a page or component, Brut will create an empty test file in <code>specs/</code>, whose path mirrors the class in <code>app/src</code>. For example, <code>specs/front_end/handlers/login_handler.spec.rb</code> will test the class defined in <code>app/src/front_end/handlers/login_handler.rb</code>.</p><p>Each page of Brut&#39;s documentation includes a &quot;Testing&quot; section that outlines additional features avialable to make testing work more easily. This section will talk about general features and behavior.</p><h3 id="tests-run-in-a-database-transaction" tabindex="-1">Tests run in a Database Transaction <a class="header-anchor" href="#tests-run-in-a-database-transaction" aria-label="Permalink to &quot;Tests run in a Database Transaction&quot;">​</a></h3><p>At the start of each test (<code>it</code> block in RSpec), a database transaction is opened. At the end, the transaction is rolled back. This means that none of the changes you make to the database have any effect outside the context of the test.</p><p>The downside of this approach is that you cannot test anything that involves database transactions. For example, if you want to ensure that a piece of business logic runs inside a database transaction, you will have to assure that another way, such as spying.</p><h3 id="a-usable-requestcontext-is-created-for-front-end-tests" tabindex="-1">A Usable <code>RequestContext</code> is Created for Front End Tests <a class="header-anchor" href="#a-usable-requestcontext-is-created-for-front-end-tests" aria-label="Permalink to &quot;A Usable \`RequestContext\` is Created for Front End Tests&quot;">​</a></h3><p>Although your tests of pages, components, and handlers are generally isolated, it&#39;s possible to trigger codepaths where Brut will use <a href="/keyword-injection.html">keyword injection</a>, such as a global component.</p><p>To make sure this doesn&#39;t fail, Brut sets up a reasonable <code>RequestContext</code> that will be used for any such injections.</p><p>Brut will also <code>let</code> that instance, named <code>request_context</code>. This means you can access it and modify it in your test as needed. It will be recreated new for each test, so you are safe making changes to it.</p><h3 id="bin-test-audit-and-managing-tests" tabindex="-1"><code>bin/test audit</code> and Managing Tests <a class="header-anchor" href="#bin-test-audit-and-managing-tests" aria-label="Permalink to &quot;\`bin/test audit\` and Managing Tests&quot;">​</a></h3><p><code>bin/test audit</code> will fail if any file in <code>app/src</code> does not have a corresponding test. This is handy when you are moving fast to make sure you don&#39;t forget to add test coverage.</p><p>That said, sometimes classes are simple and won&#39;t benefit from a test, or a class&#39; behavior may be adequately covered by another test. It&#39;s helpful to record this information so it&#39;s clear that you&#39;ve given consideration to tests and not just forgotten them.</p><p>Every Brut test has <a href="/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html" target="_self" rel="noopener" data-no-router><code>Brut::SpecSupport::GeneralSupport::ClassMethods</code></a> included and it provides three methods to help record your intent with respect to omitting tests. These methods should be called in a <code>describe</code> or <code>context</code> block.</p><ul><li><p><code>implementation_is_covered_by_other_tests(description)</code> used to explain where the coverage for this class is.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">RSpec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> TaxCalculator</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
2
2
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> implementation_is_covered_by_other_tests </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;e2e tests for checkout&quot;</span></span>
3
3
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div></li><li><p><code>implementation_is_needed(check_again_at:)</code> used when you want to acknowledge that a test is required, but for whatever reason you cannot provide it now. This will create a test that fails after the date/time given to <code>check_again_at:</code>.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">RSpec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> TaxCalculator</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
4
4
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> implementation_is_needed</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">check_again_at:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;2025-06-13&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
@@ -10,4 +10,4 @@ import{_ as a,c as s,o as t,ag as i}from"./chunks/framework.1L-BeKqY.js";const u
10
10
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> it </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;should be possible to create them all&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
11
11
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> FactoryBot</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">lint</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> traits:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span></span>
12
12
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
13
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This implies that each factory and each trait of that factory can be created without providing any additional attributes. This is <em>critical</em> to sustainable tests over time. If any factory can be created at any time without dependencies, your tests will be easy to write and maintain.</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to &quot;Technical Notes&quot;">​</a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut&#39;s internals, the source code is always more correct.</p></div><p><em>Last Updated May 9, 2025</em></p>`,32)]))}const k=a(n,[["render",o]]);export{u as __pageData,k as default};
13
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This implies that each factory and each trait of that factory can be created without providing any additional attributes. This is <em>critical</em> to sustainable tests over time. If any factory can be created at any time without dependencies, your tests will be easy to write and maintain.</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to &quot;Technical Notes&quot;">​</a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut&#39;s internals, the source code is always more correct.</p></div><p><em>Last Updated May 9, 2025</em></p>`,32)])])}const k=a(n,[["render",o]]);export{u as __pageData,k as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as s,o as t,ag as i}from"./chunks/framework.C4nOkCZI.js";const u=JSON.parse('{"title":"Unit Tests","description":"","frontmatter":{},"headers":[],"relativePath":"unit-tests.md","filePath":"unit-tests.md"}'),n={name:"unit-tests.md"};function o(r,e,l,h,c,d){return t(),s("div",null,[...e[0]||(e[0]=[i("",32)])])}const k=a(n,[["render",o]]);export{u as __pageData,k as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as e,o as i,ag as o}from"./chunks/framework.C4nOkCZI.js";const c=JSON.parse('{"title":"Why Does Brut Exist?","description":"","frontmatter":{},"headers":[],"relativePath":"why.md","filePath":"why.md"}'),r={name:"why.md"};function s(n,t,h,d,u,l){return i(),e("div",null,[...t[0]||(t[0]=[o('<h1 id="why-does-brut-exist" tabindex="-1">Why Does Brut Exist? <a class="header-anchor" href="#why-does-brut-exist" aria-label="Permalink to &quot;Why Does Brut Exist?&quot;">​</a></h1><p>I love writing Ruby, but grew tired of writing Rails. Rails is great, and has been great to me over the years. I&#39;ve written a lot of books about it! But the churn and increasing configuration burden made me think: what if we had another way to build web apps in Ruby?</p><p>What if it was totally different, but still focused on being straightforward and simple? What if it had <em>fewer</em> abstractions, <em>less</em> configuration, and not as much <em>stuff</em>?</p><p>My thinking is, you need to know HTML, JavaScript, CSS, SQL, Ruby, HTTP, and a few other things to make a web app. What if we tried to limit the additional abstractions you&#39;d have to learn?</p><p>That&#39;s what Brut is trying to be. Straightfoward, direct abstractions or translations of stuff you already know. The raw web…or at least as raw as it can be.</p>',5)])])}const w=a(r,[["render",s]]);export{c as __pageData,w as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as e,o as i,ag as o}from"./chunks/framework.C4nOkCZI.js";const c=JSON.parse('{"title":"Why Does Brut Exist?","description":"","frontmatter":{},"headers":[],"relativePath":"why.md","filePath":"why.md"}'),r={name:"why.md"};function s(n,t,h,d,u,l){return i(),e("div",null,[...t[0]||(t[0]=[o("",5)])])}const w=a(r,[["render",s]]);export{c as __pageData,w as default};
data/docs/assets.html CHANGED
@@ -5,14 +5,14 @@
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <title>Assets | Brut RB</title>
7
7
  <meta name="description" content="Documentation for the Brut.RB web framework.">
8
- <meta name="generator" content="VitePress v1.6.3">
8
+ <meta name="generator" content="VitePress v1.6.4">
9
9
  <link rel="preload stylesheet" href="/assets/style.B1z60PPQ.css" as="style">
10
10
  <link rel="preload stylesheet" href="/vp-icons.css" as="style">
11
11
 
12
- <script type="module" src="/assets/app.BDtsVxyd.js"></script>
13
- <link rel="modulepreload" href="/assets/chunks/theme.DZKmijwi.js">
14
- <link rel="modulepreload" href="/assets/chunks/framework.1L-BeKqY.js">
15
- <link rel="modulepreload" href="/assets/assets.md.7C3HWkga.lean.js">
12
+ <script type="module" src="/assets/app.Dm7v_ouO.js"></script>
13
+ <link rel="modulepreload" href="/assets/chunks/theme.BuExsdM9.js">
14
+ <link rel="modulepreload" href="/assets/chunks/framework.C4nOkCZI.js">
15
+ <link rel="modulepreload" href="/assets/assets.md.BEF6Oz6K.lean.js">
16
16
  <link rel="icon" href="/favicon.ico">
17
17
  <meta property="og:title" content="BrutRB Documentation">
18
18
  <meta property="og:type" content="website">
@@ -22,7 +22,7 @@
22
22
  <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
23
23
  </head>
24
24
  <body>
25
- <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="/features.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Features</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="/dir-structure.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Directory Structure</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="/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="/doc-conventions.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Documentation Conventions</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="/form-constraints.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Form Constraints</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/migrations.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Migration Basics</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-errors.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Styling Form Errors</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/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/alternate-layouts.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Alternate 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="/recipes/blank-layouts.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Blank 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="/recipes/custom-flash.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Custom Flash Class</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/indexed-forms.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Indexed Form Elements</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/text-field-component.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Text Field Component</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>Meta</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="/why.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Why?!</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="/adrs.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>ADRs</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="/roadmap.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Roadmap to 1.0</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><!--]--><!--[--><!--]--></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 _assets" data-v-e6f2a212><div><h1 id="assets" tabindex="-1">Assets <a class="header-anchor" href="#assets" aria-label="Permalink to &quot;Assets&quot;">​</a></h1><p>As mentioned in <a href="/javascript.html">Javascript</a> and <a href="/css.html">CSS</a>, esbuild is used to bundle JavaScript and CSS. Brut also provides support for managing images.</p><h2 id="javascript-and-css" tabindex="-1">JavaScript and CSS <a class="header-anchor" href="#javascript-and-css" aria-label="Permalink to &quot;JavaScript and CSS&quot;">​</a></h2><p>Both JavaScript and CSS are managed largely the same way: esbuild is given <code>app/src/front_end/js/index.js</code> or <code>app/src/front_end/css/index.css</code> and a bundle is produced.</p><p>For both JS and CSS, the bundles are <em>hashed</em>, even in development. This is to reduce differences in production and development. The <code>asset_path</code> helper can translate the logical path (<code>/js/app.js</code> or <code>/css/styles.css</code>) into the specific hashed path.</p><p>Sourcemaps are provided as well, for both development and production.</p><h3 id="what-is-hashing-and-why-do-it" tabindex="-1">What is Hashing and Why Do It? <a class="header-anchor" href="#what-is-hashing-and-why-do-it" aria-label="Permalink to &quot;What is Hashing and Why Do It?&quot;">​</a></h3><p>In production, while your pages produce dynamic data, the CSS and JavaScript bundles themselves are not dynamic. They are the same for every single request until you change them. Because of this, it&#39;s common to configure a cache for these files. Often, that cache is a <em>content delivery network</em> or CDN.</p><p>When a page is rendered, the browser will ask for the CSS and JS bundles. The CDN will tell the browser it&#39;s OK to cache the file, potentially for a very long time (years). On subsequent requests for those files, the browser will re-use its cached copy, saving bandwidth and time.</p><p>A downside of this approach is when you <em>do</em> want to change something. While most CDNs allow you to invalidate their cached values, there are many layers of caching whose behavior can be hard to control. It turns out to be much simpler to rename the file each you change it, thus &quot;breaking the cache&quot;.</p><p>A common way to do this is to create a hash of the file&#39;s contents and append that value to its name, so instead of <code>/static/css/styles.css</code>, the file would <code>/static/css/styles-98724fhjkjk.css</code>. When you make a change to your CSS, it&#39;ll get new name, say <code>/static/css/styles-3yjgdrjksrfdws.css</code>.</p><p>To keep you from having to deal with this directly, Brut&#39;s <code>asset_path</code> helper will translate a logical name like <code>/css/styles.css</code> to the actual name, like <code>/static/css/styles-3yjgdrjksrfdws.css</code>.</p><h3 id="what-are-sourcemaps-and-why-create-them" tabindex="-1">What are SourceMaps and Why Create Them? <a class="header-anchor" href="#what-are-sourcemaps-and-why-create-them" aria-label="Permalink to &quot;What are SourceMaps and Why Create Them?&quot;">​</a></h3><p>Bundled JavaScript and CSS will have been <em>minified</em>. This means removing whitespace, line breaks and, in the case of JavaScript, potentially changing the actual names of classes and variables. This is all to reduce the size of the file as much as possible without changing its meaning.</p><p>In your browser&#39;s dev tools, all your CSS is one the first line of <code>styles.css</code> and every stack trace from your JavaScript is on line 1 of <code>app.js</code>. This is not helpful for diagnosing issues.</p><p><em>SourceMaps</em> are separate files that translate the minified files back to normal ones, so you can see a normal stack trace with the actual line numbers of your source files.</p><p>Brut&#39;s configuration of esbuild is to produce sourcemaps.</p><h2 id="fonts" tabindex="-1">Fonts <a class="header-anchor" href="#fonts" aria-label="Permalink to &quot;Fonts&quot;">​</a></h2><p>Custom fonts are managed implicitly by esbuild&#39;s managing of CSS. In your CSS, you should reference fonts as relative to the CSS file. For example, if you have the font <code>app/src/front_end/fonts/monaspace-xenon.ttf</code>, then your <code>app/src/front_end/css/index.css</code> should look like so:</p><div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</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;">@font-face</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
25
+ <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="/features.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Features</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="/dir-structure.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Directory Structure</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="/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="/doc-conventions.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Documentation Conventions</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="/form-constraints.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Form Constraints</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/migrations.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Migration Basics</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-errors.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Styling Form Errors</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/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/alternate-layouts.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Alternate 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="/recipes/custom-flash.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Custom Flash Class</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/indexed-forms.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Indexed Form Elements</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/text-field-component.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Text Field Component</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>Meta</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="/why.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Why?!</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="/adrs.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>ADRs</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="/roadmap.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Roadmap to 1.0</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><!--]--><!--[--><!--]--></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 _assets" data-v-e6f2a212><div><h1 id="assets" tabindex="-1">Assets <a class="header-anchor" href="#assets" aria-label="Permalink to &quot;Assets&quot;">​</a></h1><p>As mentioned in <a href="/javascript.html">Javascript</a> and <a href="/css.html">CSS</a>, esbuild is used to bundle JavaScript and CSS. Brut also provides support for managing images.</p><h2 id="javascript-and-css" tabindex="-1">JavaScript and CSS <a class="header-anchor" href="#javascript-and-css" aria-label="Permalink to &quot;JavaScript and CSS&quot;">​</a></h2><p>Both JavaScript and CSS are managed largely the same way: esbuild is given <code>app/src/front_end/js/index.js</code> or <code>app/src/front_end/css/index.css</code> and a bundle is produced.</p><p>For both JS and CSS, the bundles are <em>hashed</em>, even in development. This is to reduce differences in production and development. The <code>asset_path</code> helper can translate the logical path (<code>/js/app.js</code> or <code>/css/styles.css</code>) into the specific hashed path.</p><p>Sourcemaps are provided as well, for both development and production.</p><h3 id="what-is-hashing-and-why-do-it" tabindex="-1">What is Hashing and Why Do It? <a class="header-anchor" href="#what-is-hashing-and-why-do-it" aria-label="Permalink to &quot;What is Hashing and Why Do It?&quot;">​</a></h3><p>In production, while your pages produce dynamic data, the CSS and JavaScript bundles themselves are not dynamic. They are the same for every single request until you change them. Because of this, it&#39;s common to configure a cache for these files. Often, that cache is a <em>content delivery network</em> or CDN.</p><p>When a page is rendered, the browser will ask for the CSS and JS bundles. The CDN will tell the browser it&#39;s OK to cache the file, potentially for a very long time (years). On subsequent requests for those files, the browser will re-use its cached copy, saving bandwidth and time.</p><p>A downside of this approach is when you <em>do</em> want to change something. While most CDNs allow you to invalidate their cached values, there are many layers of caching whose behavior can be hard to control. It turns out to be much simpler to rename the file each you change it, thus &quot;breaking the cache&quot;.</p><p>A common way to do this is to create a hash of the file&#39;s contents and append that value to its name, so instead of <code>/static/css/styles.css</code>, the file would <code>/static/css/styles-98724fhjkjk.css</code>. When you make a change to your CSS, it&#39;ll get new name, say <code>/static/css/styles-3yjgdrjksrfdws.css</code>.</p><p>To keep you from having to deal with this directly, Brut&#39;s <code>asset_path</code> helper will translate a logical name like <code>/css/styles.css</code> to the actual name, like <code>/static/css/styles-3yjgdrjksrfdws.css</code>.</p><h3 id="what-are-sourcemaps-and-why-create-them" tabindex="-1">What are SourceMaps and Why Create Them? <a class="header-anchor" href="#what-are-sourcemaps-and-why-create-them" aria-label="Permalink to &quot;What are SourceMaps and Why Create Them?&quot;">​</a></h3><p>Bundled JavaScript and CSS will have been <em>minified</em>. This means removing whitespace, line breaks and, in the case of JavaScript, potentially changing the actual names of classes and variables. This is all to reduce the size of the file as much as possible without changing its meaning.</p><p>In your browser&#39;s dev tools, all your CSS is one the first line of <code>styles.css</code> and every stack trace from your JavaScript is on line 1 of <code>app.js</code>. This is not helpful for diagnosing issues.</p><p><em>SourceMaps</em> are separate files that translate the minified files back to normal ones, so you can see a normal stack trace with the actual line numbers of your source files.</p><p>Brut&#39;s configuration of esbuild is to produce sourcemaps.</p><h2 id="fonts" tabindex="-1">Fonts <a class="header-anchor" href="#fonts" aria-label="Permalink to &quot;Fonts&quot;">​</a></h2><p>Custom fonts are managed implicitly by esbuild&#39;s managing of CSS. In your CSS, you should reference fonts as relative to the CSS file. For example, if you have the font <code>app/src/front_end/fonts/monaspace-xenon.ttf</code>, then your <code>app/src/front_end/css/index.css</code> should look like so:</p><div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</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;">@font-face</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
26
26
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> font-family</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Monaspace Xenon&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
27
27
  <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> src</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">url</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;../fonts/monaspace-xenon.ttf&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">format</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;truetype&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span>
28
28
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> font-display</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">swap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
@@ -41,7 +41,7 @@
41
41
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
42
42
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
43
43
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>As you can see, this format could support multiple bundles and additional file types.</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="/css.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Previous page</span><span class="title" data-v-1bcd8184>CSS</span><!--]--></a></div><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link next" href="/brut-js.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Next page</span><span class="title" data-v-1bcd8184>BrutJS</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
44
- <script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"BxjHi9-8\",\"ai.md\":\"Cy9GWnER\",\"assets.md\":\"7C3HWkga\",\"brut-js.md\":\"B4GYxQVw\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"CjsktgFz\",\"components.md\":\"rMhQ0WdZ\",\"configuration.md\":\"BK42Yjp_\",\"css.md\":\"CltvJqAa\",\"custom-element-tests.md\":\"B_rbta32\",\"database-access.md\":\"gnluu54N\",\"database-schema.md\":\"LpmBPVEU\",\"deployment.md\":\"BLseERGV\",\"dev-environment.md\":\"DRH2D2-O\",\"dir-structure.md\":\"CWir1pic\",\"doc-conventions.md\":\"DOkAuXlt\",\"end-to-end-tests.md\":\"DzqRpZ43\",\"features.md\":\"DPFXsy0z\",\"flash-and-session.md\":\"nPvUpnUx\",\"form-constraints.md\":\"KTv5cdR4\",\"forms.md\":\"v9qIbmUM\",\"getting-started.md\":\"DTOl4c2g\",\"handlers.md\":\"h84MMB1R\",\"hooks.md\":\"Jmb5VOLA\",\"i18n.md\":\"BAm9t9JJ\",\"index.md\":\"Bn9e0sRJ\",\"instrumentation.md\":\"_lNSriEZ\",\"javascript.md\":\"DzrMxUmI\",\"jobs.md\":\"S-2amAYp\",\"keyword-injection.md\":\"95Zgh2eN\",\"layouts.md\":\"CVGl9xIO\",\"lsp.md\":\"Dn1rIiW0\",\"markdown-examples.md\":\"CCFEQO44\",\"middleware.md\":\"Czz_UlJN\",\"overview.md\":\"DlKiRRG_\",\"pages.md\":\"B7Hc-i6H\",\"recipes_alternate-layouts.md\":\"BwEytl59\",\"recipes_authentication.md\":\"nwO6F7Ou\",\"recipes_blank-layouts.md\":\"fyAUJyJR\",\"recipes_custom-flash.md\":\"CrQbI5eH\",\"recipes_form-errors.md\":\"Bv5RCKqH\",\"recipes_indexed-forms.md\":\"CstYyOSo\",\"recipes_migrations.md\":\"CTcnWDJF\",\"recipes_text-field-component.md\":\"H4wLAK0Z\",\"roadmap.md\":\"C6PRi0DX\",\"routes.md\":\"BD6y2i-f\",\"security.md\":\"C0G_AZR-\",\"seed-data.md\":\"BvFZlqIk\",\"space-time-continuum.md\":\"xl44xDos\",\"tutorial.md\":\"BM40jnoq\",\"tutorials_01-intro.md\":\"B4sUBY3X\",\"tutorials_02-dialog.md\":\"CPNK1SC_\",\"unit-tests.md\":\"DUGrnLj5\",\"why.md\":\"C-hk5xgJ\"}");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\":\"Features\",\"link\":\"/features\"},{\"text\":\"Directory Structure\",\"link\":\"/dir-structure\"},{\"text\":\"Dev Environment\",\"link\":\"/dev-environment\"},{\"text\":\"Tutorial\",\"link\":\"/tutorial\"},{\"text\":\"Documentation Conventions\",\"link\":\"/doc-conventions\"}]},{\"text\":\"Front-End\",\"collapsed\":false,\"items\":[{\"text\":\"Routes\",\"link\":\"/routes\"},{\"text\":\"Pages\",\"link\":\"/pages\"},{\"text\":\"Layouts\",\"link\":\"/layouts\"},{\"text\":\"Forms\",\"link\":\"/forms\"},{\"text\":\"Form Constraints\",\"link\":\"/form-constraints\"},{\"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\":\"Migration Basics\",\"link\":\"/recipes/migrations\"},{\"text\":\"Styling Form Errors\",\"link\":\"/recipes/form-errors\"},{\"text\":\"Authentication\",\"link\":\"/recipes/authentication\"},{\"text\":\"Alternate Layouts\",\"link\":\"/recipes/alternate-layouts\"},{\"text\":\"Blank Layouts\",\"link\":\"/recipes/blank-layouts\"},{\"text\":\"Custom Flash Class\",\"link\":\"/recipes/custom-flash\"},{\"text\":\"Indexed Form Elements\",\"link\":\"/recipes/indexed-forms\"},{\"text\":\"Text Field Component\",\"link\":\"/recipes/text-field-component\"}]},{\"text\":\"Meta\",\"collapsed\":false,\"items\":[{\"text\":\"Why?!\",\"link\":\"/why\"},{\"text\":\"ADRs\",\"link\":\"/adrs\"},{\"text\":\"Roadmap to 1.0\",\"link\":\"/roadmap\"},{\"text\":\"AI Declaration\",\"link\":\"/ai\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/thirdtank/brut\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
44
+ <script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"YglbWtQe\",\"ai.md\":\"ChLnvDAX\",\"assets.md\":\"BEF6Oz6K\",\"brut-js.md\":\"BMz0X1Rz\",\"business-logic.md\":\"DbuaOYGU\",\"cli.md\":\"DDMar_51\",\"components.md\":\"Dfd3w6UW\",\"configuration.md\":\"DTYoV2Ea\",\"css.md\":\"K5rOCOQY\",\"custom-element-tests.md\":\"DiLe-eFw\",\"database-access.md\":\"Dc8l2Plf\",\"database-schema.md\":\"BJ_JhXmO\",\"deployment.md\":\"C1u5ep0g\",\"dev-environment.md\":\"B1S9p5ZK\",\"dir-structure.md\":\"D1T2kGwj\",\"doc-conventions.md\":\"CDnWaEFg\",\"end-to-end-tests.md\":\"BJJdNDYL\",\"features.md\":\"BDWxnyNO\",\"flash-and-session.md\":\"CUsMxoNl\",\"form-constraints.md\":\"KlfXSKm2\",\"forms.md\":\"DEkmJUvb\",\"getting-started.md\":\"DO-4eoGW\",\"handlers.md\":\"C5tUwmmo\",\"hooks.md\":\"CoiYCKRc\",\"i18n.md\":\"DxkCKhUw\",\"index.md\":\"DnphWyQd\",\"instrumentation.md\":\"BcxjC4jd\",\"javascript.md\":\"D6fxhaQb\",\"jobs.md\":\"Bc7Y1YpK\",\"keyword-injection.md\":\"CqLnnzIz\",\"layouts.md\":\"HEbeK7Jr\",\"lsp.md\":\"bE9dW8n9\",\"markdown-examples.md\":\"BPmtHlc-\",\"middleware.md\":\"BhOIsg59\",\"overview.md\":\"BpWAgPFH\",\"pages.md\":\"B3sQXpEd\",\"recipes_alternate-layouts.md\":\"C1QzVkA7\",\"recipes_authentication.md\":\"CyvoIW82\",\"recipes_custom-flash.md\":\"6gFqf2uL\",\"recipes_form-errors.md\":\"B5ptSzMO\",\"recipes_indexed-forms.md\":\"BYYQGW2C\",\"recipes_migrations.md\":\"Cid7-3cu\",\"recipes_text-field-component.md\":\"VhOsCtKI\",\"roadmap.md\":\"CJsbUmK_\",\"routes.md\":\"C1dgIBtD\",\"security.md\":\"Jn4SY1uK\",\"seed-data.md\":\"UZW0WxYN\",\"space-time-continuum.md\":\"D9rYGDFH\",\"tutorial.md\":\"BX6f6l00\",\"tutorials_01-intro.md\":\"CzZ3kpF_\",\"tutorials_02-dialog.md\":\"D2vSjDVf\",\"unit-tests.md\":\"vDsdBbO_\",\"why.md\":\"4WpxdrQ2\"}");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\":\"Features\",\"link\":\"/features\"},{\"text\":\"Directory Structure\",\"link\":\"/dir-structure\"},{\"text\":\"Dev Environment\",\"link\":\"/dev-environment\"},{\"text\":\"Tutorial\",\"link\":\"/tutorial\"},{\"text\":\"Documentation Conventions\",\"link\":\"/doc-conventions\"}]},{\"text\":\"Front-End\",\"collapsed\":false,\"items\":[{\"text\":\"Routes\",\"link\":\"/routes\"},{\"text\":\"Pages\",\"link\":\"/pages\"},{\"text\":\"Layouts\",\"link\":\"/layouts\"},{\"text\":\"Forms\",\"link\":\"/forms\"},{\"text\":\"Form Constraints\",\"link\":\"/form-constraints\"},{\"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\":\"Migration Basics\",\"link\":\"/recipes/migrations\"},{\"text\":\"Styling Form Errors\",\"link\":\"/recipes/form-errors\"},{\"text\":\"Authentication\",\"link\":\"/recipes/authentication\"},{\"text\":\"Alternate Layouts\",\"link\":\"/recipes/alternate-layouts\"},{\"text\":\"Custom Flash Class\",\"link\":\"/recipes/custom-flash\"},{\"text\":\"Indexed Form Elements\",\"link\":\"/recipes/indexed-forms\"},{\"text\":\"Text Field Component\",\"link\":\"/recipes/text-field-component\"}]},{\"text\":\"Meta\",\"collapsed\":false,\"items\":[{\"text\":\"Why?!\",\"link\":\"/why\"},{\"text\":\"ADRs\",\"link\":\"/adrs\"},{\"text\":\"Roadmap to 1.0\",\"link\":\"/roadmap\"},{\"text\":\"AI Declaration\",\"link\":\"/ai\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/thirdtank/brut\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
45
45
 
46
46
  </body>
47
47
  </html>
@@ -437,13 +437,13 @@ controller.abort(AjaxSubmit.doNotSubmitThroughBrowser)</code></pre>
437
437
  </div>
438
438
 
439
439
  <nav>
440
- <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
440
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Toast.html">Toast</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
441
441
  </nav>
442
442
 
443
443
  <br class="clear">
444
444
 
445
445
  <footer>
446
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Mon Sep 08 2025 22:18:07 GMT+0000 (Coordinated Universal Time)
446
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Tue Sep 16 2025 13:24:01 GMT+0000 (Coordinated Universal Time)
447
447
  </footer>
448
448
 
449
449
  <script> prettyPrint(); </script>
@@ -535,13 +535,13 @@ export default AjaxSubmit
535
535
  </div>
536
536
 
537
537
  <nav>
538
- <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
538
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Toast.html">Toast</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
539
539
  </nav>
540
540
 
541
541
  <br class="clear">
542
542
 
543
543
  <footer>
544
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Mon Sep 08 2025 22:18:07 GMT+0000 (Coordinated Universal Time)
544
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Tue Sep 16 2025 13:24:01 GMT+0000 (Coordinated Universal Time)
545
545
  </footer>
546
546
 
547
547
  <script> prettyPrint(); </script>
@@ -177,13 +177,13 @@ That means if your input/textarea/select uses the <code>form</code> attribute, t
177
177
  </div>
178
178
 
179
179
  <nav>
180
- <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
180
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Toast.html">Toast</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
181
181
  </nav>
182
182
 
183
183
  <br class="clear">
184
184
 
185
185
  <footer>
186
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Mon Sep 08 2025 22:18:07 GMT+0000 (Coordinated Universal Time)
186
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Tue Sep 16 2025 13:24:01 GMT+0000 (Coordinated Universal Time)
187
187
  </footer>
188
188
 
189
189
  <script> prettyPrint(); </script>
@@ -99,13 +99,13 @@ export default Autosubmit
99
99
  </div>
100
100
 
101
101
  <nav>
102
- <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
102
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Toast.html">Toast</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
103
103
  </nav>
104
104
 
105
105
  <br class="clear">
106
106
 
107
107
  <footer>
108
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Mon Sep 08 2025 22:18:07 GMT+0000 (Coordinated Universal Time)
108
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Tue Sep 16 2025 13:24:01 GMT+0000 (Coordinated Universal Time)
109
109
  </footer>
110
110
 
111
111
  <script> prettyPrint(); </script>
@@ -1076,13 +1076,13 @@ this inside a <code>DOMContentLoaded</code> event, or after the page's HTML has
1076
1076
  </div>
1077
1077
 
1078
1078
  <nav>
1079
- <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
1079
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Toast.html">Toast</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
1080
1080
  </nav>
1081
1081
 
1082
1082
  <br class="clear">
1083
1083
 
1084
1084
  <footer>
1085
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Mon Sep 08 2025 22:18:07 GMT+0000 (Coordinated Universal Time)
1085
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Tue Sep 16 2025 13:24:01 GMT+0000 (Coordinated Universal Time)
1086
1086
  </footer>
1087
1087
 
1088
1088
  <script> prettyPrint(); </script>
@@ -297,13 +297,13 @@ export default BaseCustomElement
297
297
  </div>
298
298
 
299
299
  <nav>
300
- <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
300
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-testing.html">testing</a></li></ul><h3>Externals</h3><ul><li><a href="external-CustomElementRegistry.html">CustomElementRegistry</a></li><li><a href="external-Performance.html">Performance</a></li><li><a href="external-Promise.html">Promise</a></li><li><a href="external-ValidityState.html">ValidityState</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-fetch.html">fetch</a></li></ul><h3>Classes</h3><ul><li><a href="AjaxSubmit.html">AjaxSubmit</a></li><li><a href="Autosubmit.html">Autosubmit</a></li><li><a href="BaseCustomElement.html">BaseCustomElement</a></li><li><a href="BrutCustomElements.html">BrutCustomElements</a></li><li><a href="BufferedLogger.html">BufferedLogger</a></li><li><a href="ConfirmSubmit.html">ConfirmSubmit</a></li><li><a href="ConfirmationDialog.html">ConfirmationDialog</a></li><li><a href="ConstraintViolationMessage.html">ConstraintViolationMessage</a></li><li><a href="ConstraintViolationMessages.html">ConstraintViolationMessages</a></li><li><a href="CopyToClipboard.html">CopyToClipboard</a></li><li><a href="Form.html">Form</a></li><li><a href="I18nTranslation.html">I18nTranslation</a></li><li><a href="LocaleDetection.html">LocaleDetection</a></li><li><a href="Logger.html">Logger</a></li><li><a href="Message.html">Message</a></li><li><a href="PrefixedLogger.html">PrefixedLogger</a></li><li><a href="RichString.html">RichString</a></li><li><a href="Tabs.html">Tabs</a></li><li><a href="Toast.html">Toast</a></li><li><a href="Tracing.html">Tracing</a></li><li><a href="testing.AssetMetadata.html">AssetMetadata</a></li><li><a href="testing.AssetMetadataLoader.html">AssetMetadataLoader</a></li><li><a href="testing.CustomElementTest.html">CustomElementTest</a></li><li><a href="testing.DOMCreator.html">DOMCreator</a></li></ul><h3><a href="global.html">Global</a></h3>
301
301
  </nav>
302
302
 
303
303
  <br class="clear">
304
304
 
305
305
  <footer>
306
- Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Mon Sep 08 2025 22:18:07 GMT+0000 (Coordinated Universal Time)
306
+ Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Tue Sep 16 2025 13:24:01 GMT+0000 (Coordinated Universal Time)
307
307
  </footer>
308
308
 
309
309
  <script> prettyPrint(); </script>