brut 0.11.0 → 0.12.0

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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +8 -8
  4. data/brut-css/package-lock.json +94 -97
  5. data/brut-css/package.json +2 -2
  6. data/brut-js/package-lock.json +3 -3
  7. data/brut-js/package.json +6 -3
  8. data/brut-js/specs/AjaxSubmit.spec.js +62 -6
  9. data/brut-js/src/AjaxSubmit.js +26 -6
  10. data/brutrb.com/forms.md +1 -0
  11. data/brutrb.com/recipes/authentication.md +1 -0
  12. data/brutrb.com/tutorials/01-intro.md +26 -2
  13. data/docs/404.html +2 -2
  14. data/docs/adrs.html +3 -3
  15. data/docs/ai.html +3 -3
  16. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  17. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  18. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  19. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  20. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  21. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  22. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  23. data/docs/api/Brut/BackEnd.html +1 -1
  24. data/docs/api/Brut/CLI/App.html +1 -1
  25. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  26. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  27. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  28. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  29. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  30. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  31. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  32. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  33. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  34. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  60. data/docs/api/Brut/CLI/Apps.html +1 -1
  61. data/docs/api/Brut/CLI/Command.html +1 -1
  62. data/docs/api/Brut/CLI/Error.html +1 -1
  63. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  64. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  65. data/docs/api/Brut/CLI/Executor.html +1 -1
  66. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  67. data/docs/api/Brut/CLI/Options.html +1 -1
  68. data/docs/api/Brut/CLI/Output.html +1 -1
  69. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  70. data/docs/api/Brut/CLI.html +1 -1
  71. data/docs/api/Brut/FactoryBot.html +1 -1
  72. data/docs/api/Brut/Framework/App.html +1 -1
  73. data/docs/api/Brut/Framework/Config.html +1 -1
  74. data/docs/api/Brut/Framework/Container.html +1 -1
  75. data/docs/api/Brut/Framework/Error.html +1 -1
  76. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  77. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  78. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  79. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  80. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  81. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  82. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  83. data/docs/api/Brut/Framework/Errors.html +1 -1
  84. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +2 -2
  85. data/docs/api/Brut/Framework/MCP.html +1 -1
  86. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  87. data/docs/api/Brut/Framework.html +1 -1
  88. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  89. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  90. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  91. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  92. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  93. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  94. data/docs/api/Brut/FrontEnd/Components/Input.html +2 -2
  95. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +443 -0
  96. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  97. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +14 -9
  98. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +8 -9
  100. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +10 -11
  101. data/docs/api/Brut/FrontEnd/Components/Inputs.html +2 -2
  102. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  107. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Form.html +39 -39
  111. data/docs/api/Brut/FrontEnd/Forms/Button.html +331 -0
  112. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +544 -0
  113. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Forms/Input.html +6 -2
  117. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +111 -23
  118. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +20 -12
  119. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms.html +2 -2
  125. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +2 -2
  132. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  134. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  135. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  136. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  137. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  148. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  149. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  150. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  151. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  152. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  153. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  154. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  155. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  156. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  157. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  167. data/docs/api/Brut/FrontEnd.html +1 -1
  168. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  169. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  170. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  171. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  172. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  173. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  174. data/docs/api/Brut/I18n.html +1 -1
  175. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  176. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +596 -0
  177. data/docs/api/Brut/Instrumentation/Methods.html +173 -0
  178. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +7 -7
  179. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +7 -7
  180. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +19 -17
  181. data/docs/api/Brut/Instrumentation.html +3 -1
  182. data/docs/api/Brut/RubocopConfig.html +1 -1
  183. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  184. data/docs/api/Brut/SinatraHelpers.html +1 -1
  185. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  186. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  187. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  188. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  189. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  190. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  191. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  192. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  193. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  194. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  195. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  196. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  197. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  198. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  199. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  200. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  201. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  206. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  207. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  208. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  209. data/docs/api/Brut/SpecSupport.html +1 -1
  210. data/docs/api/Brut.html +1 -1
  211. data/docs/api/Clock.html +1 -1
  212. data/docs/api/ModuleName.html +1 -1
  213. data/docs/api/RichString.html +1 -1
  214. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  215. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  216. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  217. data/docs/api/Sequel/Extensions.html +1 -1
  218. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  219. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  220. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  221. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  222. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  223. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  224. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  225. data/docs/api/Sequel/Plugins.html +1 -1
  226. data/docs/api/Sequel.html +1 -1
  227. data/docs/api/_index.html +36 -1
  228. data/docs/api/class_list.html +1 -1
  229. data/docs/api/file.README.html +1 -1
  230. data/docs/api/index.html +1 -1
  231. data/docs/api/method_list.html +364 -252
  232. data/docs/api/top-level-namespace.html +1 -1
  233. data/docs/assets/{app.0-aKXKdt.js → app.V2GOcOrg.js} +1 -1
  234. data/docs/assets/chunks/@localSearchIndexroot.3Lsq4QTb.js +1 -0
  235. data/docs/assets/chunks/{VPLocalSearchBox.CW-UBkNA.js → VPLocalSearchBox.CZTadcAy.js} +1 -1
  236. data/docs/assets/chunks/{theme.a6feKWJO.js → theme.FeuYNxyp.js} +2 -2
  237. data/docs/assets/{components.md.BzVRwegp.js → components.md.f4cdTyvV.js} +3 -3
  238. data/docs/assets/{configuration.md.eM5wFVi5.js → configuration.md.Bs4-rxnS.js} +1 -1
  239. data/docs/assets/{forms.md.B3BHvCV3.js → forms.md.Sys-XxVf.js} +3 -3
  240. data/docs/assets/{forms.md.B3BHvCV3.lean.js → forms.md.Sys-XxVf.lean.js} +1 -1
  241. data/docs/assets/{getting-started.md.BgR0ZHsl.js → getting-started.md.CFIW0bcE.js} +2 -2
  242. data/docs/assets/instrumentation.md._lNSriEZ.js +90 -0
  243. data/docs/assets/instrumentation.md._lNSriEZ.lean.js +1 -0
  244. data/docs/assets/{recipes_authentication.md.Dzvi_g69.js → recipes_authentication.md.BAISoxmN.js} +1 -0
  245. data/docs/assets/{tutorials_01-intro.md.BXvYWcO9.js → tutorials_01-intro.md.B4sUBY3X.js} +10 -10
  246. data/docs/assets/{tutorials_01-intro.md.BXvYWcO9.lean.js → tutorials_01-intro.md.B4sUBY3X.lean.js} +1 -1
  247. data/docs/assets/{tutorials_02-dialog.md.CIeg8R--.js → tutorials_02-dialog.md.QTFeHdiA.js} +1 -1
  248. data/docs/assets.html +3 -3
  249. data/docs/brut-js/api/AjaxSubmit.html +16 -6
  250. data/docs/brut-js/api/AjaxSubmit.js.html +27 -7
  251. data/docs/brut-js/api/Autosubmit.html +1 -1
  252. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  253. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  254. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  255. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  256. data/docs/brut-js/api/BufferedLogger.html +1 -1
  257. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  258. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  259. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  260. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  261. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  262. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  263. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  264. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  265. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  266. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  267. data/docs/brut-js/api/Form.html +1 -1
  268. data/docs/brut-js/api/Form.js.html +1 -1
  269. data/docs/brut-js/api/I18nTranslation.html +1 -1
  270. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  271. data/docs/brut-js/api/LocaleDetection.html +1 -1
  272. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  273. data/docs/brut-js/api/Logger.html +1 -1
  274. data/docs/brut-js/api/Logger.js.html +1 -1
  275. data/docs/brut-js/api/Message.html +1 -1
  276. data/docs/brut-js/api/Message.js.html +1 -1
  277. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  278. data/docs/brut-js/api/RichString.html +1 -1
  279. data/docs/brut-js/api/RichString.js.html +1 -1
  280. data/docs/brut-js/api/Tabs.html +1 -1
  281. data/docs/brut-js/api/Tabs.js.html +1 -1
  282. data/docs/brut-js/api/Tracing.html +1 -1
  283. data/docs/brut-js/api/Tracing.js.html +1 -1
  284. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  285. data/docs/brut-js/api/external-Performance.html +1 -1
  286. data/docs/brut-js/api/external-Promise.html +1 -1
  287. data/docs/brut-js/api/external-ValidityState.html +1 -1
  288. data/docs/brut-js/api/external-Window.html +1 -1
  289. data/docs/brut-js/api/external-fetch.html +1 -1
  290. data/docs/brut-js/api/global.html +1 -1
  291. data/docs/brut-js/api/index.html +1 -1
  292. data/docs/brut-js/api/index.js.html +1 -1
  293. data/docs/brut-js/api/module-testing.html +1 -1
  294. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  295. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  296. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  297. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  298. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  299. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  300. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  301. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  302. data/docs/brut-js/api/testing_index.js.html +1 -1
  303. data/docs/brut-js.html +3 -3
  304. data/docs/business-logic.html +3 -3
  305. data/docs/cli.html +3 -3
  306. data/docs/components.html +7 -7
  307. data/docs/configuration.html +5 -5
  308. data/docs/css.html +3 -3
  309. data/docs/custom-element-tests.html +3 -3
  310. data/docs/database-access.html +3 -3
  311. data/docs/database-schema.html +3 -3
  312. data/docs/deployment.html +3 -3
  313. data/docs/dev-environment.html +3 -3
  314. data/docs/dir-structure.html +3 -3
  315. data/docs/doc-conventions.html +3 -3
  316. data/docs/end-to-end-tests.html +3 -3
  317. data/docs/features.html +3 -3
  318. data/docs/flash-and-session.html +3 -3
  319. data/docs/form-constraints.html +3 -3
  320. data/docs/forms.html +6 -6
  321. data/docs/getting-started.html +6 -6
  322. data/docs/handlers.html +3 -3
  323. data/docs/hashmap.json +1 -1
  324. data/docs/hooks.html +3 -3
  325. data/docs/i18n.html +3 -3
  326. data/docs/index.html +3 -3
  327. data/docs/instrumentation.html +61 -6
  328. data/docs/javascript.html +3 -3
  329. data/docs/jobs.html +3 -3
  330. data/docs/keyword-injection.html +3 -3
  331. data/docs/layouts.html +3 -3
  332. data/docs/lsp.html +3 -3
  333. data/docs/markdown-examples.html +3 -3
  334. data/docs/middleware.html +3 -3
  335. data/docs/overview.html +3 -3
  336. data/docs/pages.html +3 -3
  337. data/docs/recipes/alternate-layouts.html +3 -3
  338. data/docs/recipes/authentication.html +5 -4
  339. data/docs/recipes/blank-layouts.html +3 -3
  340. data/docs/recipes/custom-flash.html +3 -3
  341. data/docs/recipes/form-errors.html +3 -3
  342. data/docs/recipes/indexed-forms.html +3 -3
  343. data/docs/recipes/migrations.html +3 -3
  344. data/docs/recipes/text-field-component.html +3 -3
  345. data/docs/roadmap.html +3 -3
  346. data/docs/routes.html +3 -3
  347. data/docs/security.html +3 -3
  348. data/docs/seed-data.html +3 -3
  349. data/docs/space-time-continuum.html +3 -3
  350. data/docs/tutorial.html +3 -3
  351. data/docs/tutorials/01-intro.html +12 -12
  352. data/docs/tutorials/02-dialog.html +5 -5
  353. data/docs/unit-tests.html +3 -3
  354. data/docs/why.html +3 -3
  355. data/lib/brut/front_end/components/input.rb +1 -0
  356. data/lib/brut/front_end/components/inputs/button_tag.rb +40 -0
  357. data/lib/brut/front_end/components/inputs/input_tag.rb +3 -1
  358. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +0 -1
  359. data/lib/brut/front_end/components/inputs/textarea_tag.rb +0 -1
  360. data/lib/brut/front_end/form.rb +7 -5
  361. data/lib/brut/front_end/forms/button.rb +21 -0
  362. data/lib/brut/front_end/forms/button_input_definition.rb +37 -0
  363. data/lib/brut/front_end/forms/input_declarations.rb +10 -0
  364. data/lib/brut/front_end/forms/input_definition.rb +7 -2
  365. data/lib/brut/version.rb +1 -1
  366. data/mkbrut/Gemfile.lock +1 -1
  367. data/mkbrut/lib/mkbrut/version.rb +1 -1
  368. metadata +29 -21
  369. data/docs/assets/chunks/@localSearchIndexroot.DPhqaz1b.js +0 -1
  370. data/docs/assets/instrumentation.md.BgcaGVYH.js +0 -35
  371. data/docs/assets/instrumentation.md.BgcaGVYH.lean.js +0 -1
  372. /data/docs/assets/{components.md.BzVRwegp.lean.js → components.md.f4cdTyvV.lean.js} +0 -0
  373. /data/docs/assets/{configuration.md.eM5wFVi5.lean.js → configuration.md.Bs4-rxnS.lean.js} +0 -0
  374. /data/docs/assets/{getting-started.md.BgR0ZHsl.lean.js → getting-started.md.CFIW0bcE.lean.js} +0 -0
  375. /data/docs/assets/{recipes_authentication.md.Dzvi_g69.lean.js → recipes_authentication.md.BAISoxmN.lean.js} +0 -0
  376. /data/docs/assets/{tutorials_02-dialog.md.CIeg8R--.lean.js → tutorials_02-dialog.md.QTFeHdiA.lean.js} +0 -0
data/docs/tutorial.html CHANGED
@@ -9,8 +9,8 @@
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.0-aKXKdt.js"></script>
13
- <link rel="modulepreload" href="/assets/chunks/theme.a6feKWJO.js">
12
+ <script type="module" src="/assets/app.V2GOcOrg.js"></script>
13
+ <link rel="modulepreload" href="/assets/chunks/theme.FeuYNxyp.js">
14
14
  <link rel="modulepreload" href="/assets/chunks/framework.1L-BeKqY.js">
15
15
  <link rel="modulepreload" href="/assets/tutorial.md.BM40jnoq.lean.js">
16
16
  <link rel="icon" href="/favicon.ico">
@@ -49,7 +49,7 @@
49
49
  <span class="line highlighted"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> inline_svg</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;edit_icon&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
50
50
  <span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
51
51
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><p>This says to find the line that looks like the first one (preceded with a <code>-</code> and shown in red) and replace it with the second one (preceded with a <code>+</code> and shown in green). <strong>Do not use the <code>+</code> or <code>-</code> in your code</strong>, that is just to indicate which line is which.</p><p>Lastly, we&#39;ll try to mention the path to the file either in the preceding text or as a comment in the code.</p><h2 id="tutorials-1" tabindex="-1">Tutorials <a class="header-anchor" href="#tutorials-1" aria-label="Permalink to &quot;Tutorials&quot;">​</a></h2><p>These go mostly in order, each building on the last, but you can start anywhere by using the tutorial on GitHub. The only one that starts from nothing is the first one.</p><table tabindex="0"><thead><tr><th>Index</th><th>Title</th><th>Tutorial</th><th>Screencast</th></tr></thead><tbody><tr><td>1</td><td>Build a Blog in 15 Minutes</td><td><a href="./tutorials/01-intro.html">Tutorial</a></td><td><a href="https://video.hardlimit.com/w/ae7EMhwjDq9kSH5dqQ9swV" target="_blank" rel="noreferrer">Screencast</a></td></tr><tr><td>2</td><td>Adding a Styled Confirmation Dialog</td><td><a href="./tutorials/02-dialog.html">Tutorial</a></td><td><a href="https://video.hardlimit.com/w/4y8Pjd8VVPDK372mozCUdj" target="_blank" rel="noreferrer">Screencast</a></td></tr><tr><td>3</td><td>Leveraging Externalizable IDs (coming soon)</td><td></td><td></td></tr><tr><td>4</td><td>Form Basics (coming soon)</td><td></td><td></td></tr><tr><td>5</td><td>Advanced Forms (coming soon)</td><td></td><td></td></tr><tr><td>6</td><td>AJax Form Submissions (coming soon)</td><td></td><td></td></tr><tr><td>7</td><td>Authentication (coming soon)</td><td></td><td></td></tr><tr><td>8</td><td>Background Jobs with Sidekiq (coming soon)</td><td></td><td></td></tr><tr><td>9</td><td>How to Leverage BrutJS and Custom Elements (coming soon)</td><td></td><td></td></tr></tbody></table></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="/dev-environment.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Previous page</span><span class="title" data-v-1bcd8184>Dev Environment</span><!--]--></a></div><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link next" href="/doc-conventions.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Next page</span><span class="title" data-v-1bcd8184>Documentation Conventions</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
52
- <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\":\"BzVRwegp\",\"configuration.md\":\"eM5wFVi5\",\"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\":\"B3BHvCV3\",\"getting-started.md\":\"BgR0ZHsl\",\"handlers.md\":\"h84MMB1R\",\"hooks.md\":\"Jmb5VOLA\",\"i18n.md\":\"BAm9t9JJ\",\"index.md\":\"Bn9e0sRJ\",\"instrumentation.md\":\"BgcaGVYH\",\"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\":\"Dzvi_g69\",\"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\":\"BXvYWcO9\",\"tutorials_02-dialog.md\":\"CIeg8R--\",\"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>
52
+ <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\":\"f4cdTyvV\",\"configuration.md\":\"Bs4-rxnS\",\"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\":\"Sys-XxVf\",\"getting-started.md\":\"CFIW0bcE\",\"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\":\"BAISoxmN\",\"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\":\"QTFeHdiA\",\"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>
53
53
 
54
54
  </body>
55
55
  </html>
@@ -9,10 +9,10 @@
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.0-aKXKdt.js"></script>
13
- <link rel="modulepreload" href="/assets/chunks/theme.a6feKWJO.js">
12
+ <script type="module" src="/assets/app.V2GOcOrg.js"></script>
13
+ <link rel="modulepreload" href="/assets/chunks/theme.FeuYNxyp.js">
14
14
  <link rel="modulepreload" href="/assets/chunks/framework.1L-BeKqY.js">
15
- <link rel="modulepreload" href="/assets/tutorials_01-intro.md.BXvYWcO9.lean.js">
15
+ <link rel="modulepreload" href="/assets/tutorials_01-intro.md.B4sUBY3X.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">
@@ -405,7 +405,7 @@
405
405
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">\n\r</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
406
406
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
407
407
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
408
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Brut includes a test to make sure your factories are valid and will work. It&#39;s in <code>specs/lint_factories.spec.rb</code>. Run it now to make sure this factory works:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/lint_factories.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
408
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Brut includes a test to make sure your factories are valid and will work. It&#39;s in <code>specs/lint_factories.spec.rb</code>. Run it now to make sure this factory works:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/lint_factories.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
409
409
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \&quot;**/*.spec.rb\&quot; \&quot;specs/lint_factories.spec.rb\&quot;&quot;]</span></span>
410
410
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
411
411
  <span class="line"><span></span></span>
@@ -430,7 +430,7 @@
430
430
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p><code>create</code> is a method provided by Factory Bot that is brought in via <code>FactoryBot::Syntax::Methods</code>.</p><p>Now, load the seed data into the development database with <code>bin/db seed</code>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/db</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> seed</span></span></code></pre></div><p>We can now show this data on the home page.</p><h2 id="accessing-the-database" tabindex="-1">Accessing the Database <a class="header-anchor" href="#accessing-the-database" aria-label="Permalink to &quot;Accessing the Database&quot;">​</a></h2><p>On <code>HomePage</code>, we put in a <code>&lt;p&gt;</code> as a placeholder for blog posts. Let&#39;s replace that with code to fetch and display the blog posts.</p><p>In Brut, since HTML is generated by Phlex and thus by Ruby code, we can structure our HTML generation however we like, including by accessing the database directly. This may not scale as our app gets large, but for now, it&#39;s the simplest thing to do.</p><p>Sequel&#39;s database models are similar to Rails&#39; Active Record&#39;s in that we can call class methods to access data. In this case, <code>DB::BlogPost</code> has a method <code>order</code> that will fetch all records sorted by the field we give it in the order we decide. The sort field and order is specified via <code>Sequel.desc</code> for descending or <code>Sequel.asc</code> for ascending. We want posts in reverse-chronological order, so <code>Sequel.desc(:created_at)</code> will achieve this.</p><p>We can call <code>.each</code> on the result and iterate over each blog post. For the content, we&#39;ll split by <code>\n\r</code> to create paragraphs.</p><p>Here&#39;s what <code>HomePage</code>&#39;s <code>page_template</code> should look like now:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> page_template</span></span>
431
431
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> header </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
432
432
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h1 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;My Amazing Blog&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
433
- <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">href:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Write New Blog Post&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
433
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">href:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> BlogPostEditorPage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">routing</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Write New Blog Post&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
434
434
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
435
435
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> main </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
436
436
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> DB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">BlogPost</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">order</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">desc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:created_at</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">each</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> |blog_post|</span></span>
@@ -462,7 +462,7 @@
462
462
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> else</span></span>
463
463
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> NewBlogPostPage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">form:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @form)</span></span>
464
464
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
465
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>The form object provides access to the values of any field we&#39;ve declared via a method call.</p><p>Now, create a new blog post, provide valid data, and submit it.</p><p><img src="/assets/new-post-editor.DrHr-5oh.png" alt="Screenshot of the blog post editor, with a new post filled in"></p><p>Once you submit it, you should see the homage page with your new post at the top:</p><p><img src="/assets/new-post-home-page.Bm34lyMg.png" alt="Screenshot of the home page, showing the new blog post"></p><p>Our work isn&#39;t quite done. We need tests.</p><h2 id="testing-brut-apps" tabindex="-1">Testing Brut Apps <a class="header-anchor" href="#testing-brut-apps" aria-label="Permalink to &quot;Testing Brut Apps&quot;">​</a></h2><p>We&#39;ll create the following tests:</p><ul><li>Check that the logic in the handler is sound</li><li>Check that blog posts show up on the home page</li><li>Check that the entire workflow of create a blog post and seeing it show up on the home page works in a real web browser</li></ul><p>Let&#39;s test our handler first, as that is where the main logic is.</p><h3 id="testing-handlers" tabindex="-1">Testing Handlers <a class="header-anchor" href="#testing-handlers" aria-label="Permalink to &quot;Testing Handlers&quot;">​</a></h3><p>Our handler will need three tests:</p><ul><li>If the form was submitted without client-side validations happening, we should not create a new blog post and re-generate the blog post editor page, showing the errors.</li><li>If client-side validations pass, but the blog post isn&#39;t five words or more, we should not create a new blog post and re-generate the blog post editor page, showing the errors.</li><li>If everything looks good, we save the new blog post and redirect to the home page.</li></ul><p>Brut apps are tested with RSpec, and Brut provides several convenience methods and matchers to make testing as painless as possible.</p><p>When testing a handler, the public method is <code>handle!</code>, not <code>handle</code>, so we want to call that (Brut implements <code>handle!</code> to call <code>handle</code>).</p><p>First, we&#39;ll test client-side validations. Open up <code>specs/front_end/handlers/new_blog_post_handler.spec.rb</code> and replace the code there with this:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;spec_helper&quot;</span></span>
465
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>The form object provides access to the values of any field we&#39;ve declared via a method call.</p><p>Now, create a new blog post, provide valid data, and submit it.</p><p><img src="/assets/new-post-editor.DrHr-5oh.png" alt="Screenshot of the blog post editor, with a new post filled in"></p><p>Once you submit it, you should see the home page with your new post at the top:</p><p><img src="/assets/new-post-home-page.Bm34lyMg.png" alt="Screenshot of the home page, showing the new blog post"></p><p>Our work isn&#39;t quite done. We need tests.</p><h2 id="testing-brut-apps" tabindex="-1">Testing Brut Apps <a class="header-anchor" href="#testing-brut-apps" aria-label="Permalink to &quot;Testing Brut Apps&quot;">​</a></h2><p>We&#39;ll create the following tests:</p><ul><li>Check that the logic in the handler is sound</li><li>Check that blog posts show up on the home page</li><li>Check that the entire workflow of create a blog post and seeing it show up on the home page works in a real web browser</li></ul><p>Let&#39;s test our handler first, as that is where the main logic is.</p><h3 id="testing-handlers" tabindex="-1">Testing Handlers <a class="header-anchor" href="#testing-handlers" aria-label="Permalink to &quot;Testing Handlers&quot;">​</a></h3><p>Our handler will need three tests:</p><ul><li>If the form was submitted without client-side validations happening, we should not create a new blog post and re-generate the blog post editor page, showing the errors.</li><li>If client-side validations pass, but the blog post isn&#39;t five words or more, we should not create a new blog post and re-generate the blog post editor page, showing the errors.</li><li>If everything looks good, we save the new blog post and redirect to the home page.</li></ul><p>Brut apps are tested with RSpec, and Brut provides several convenience methods and matchers to make testing as painless as possible.</p><p>When testing a handler, the public method is <code>handle!</code>, not <code>handle</code>, so we want to call that (Brut implements <code>handle!</code> to call <code>handle</code>).</p><p>First, we&#39;ll test client-side validations. Open up <code>specs/front_end/handlers/new_blog_post_handler.spec.rb</code> and replace the code there with this:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;spec_helper&quot;</span></span>
466
466
  <span class="line"></span>
467
467
  <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;"> NewBlogPostHandler</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
468
468
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> describe </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;#handle!&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
@@ -513,7 +513,7 @@
513
513
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(blog_post.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">content</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> eq</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This post is the best post that has been written in the history of posts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
514
514
  <span class="line"></span>
515
515
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
516
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This is using RSpec&#39;s <code>expect { ... }.to change { ... }.by(N)</code> to make sure that our handler created a row in the database. We then use the matcher <code>have_redirected_to</code> to assert that <code>result</code> is a URI to <code>HomePage</code>. We also check that the blog post we created in the database is correct.</p><p>Let&#39;s run the test with <code>bin/test run</code></p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/handlers/new_blog_post_handler.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
516
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This is using RSpec&#39;s <code>expect { ... }.to change { ... }.by(N)</code> to make sure that our handler created a row in the database. We then use the matcher <code>have_redirected_to</code> to assert that <code>result</code> is a URI to <code>HomePage</code>. We also check that the blog post we created in the database is correct.</p><p>Let&#39;s run the test with <code>bin/test run</code></p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/handlers/new_blog_post_handler.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
517
517
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \&quot;**/*.spec.rb\&quot; \&quot;specs/front_end/handlers/new_blog_post_handler.spec.rb\&quot;&quot;]</span></span>
518
518
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
519
519
  <span class="line"><span></span></span>
@@ -552,7 +552,7 @@
552
552
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
553
553
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
554
554
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
555
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Let&#39;s run the test, which should fail:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/pages/home_page.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
555
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Let&#39;s run the test, which should fail:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/pages/home_page.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
556
556
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \&quot;**/*.spec.rb\&quot; \&quot;specs/front_end/pages/home_page.spec.rb\&quot;&quot;]</span></span>
557
557
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
558
558
  <span class="line"><span></span></span>
@@ -592,7 +592,7 @@
592
592
  <span class="line"><span>Randomized with seed 44491</span></span>
593
593
  <span class="line"><span></span></span>
594
594
  <span class="line"><span>[ bin/test ] error: [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \&quot;**/*.spec.rb\&quot; \&quot;specs/front_end/pages/home_page.spec.rb\&quot;&quot;] failed - exited 1</span></span></code></pre></div><p>Brut obviously errs on the side of being verbose. But, you can see that the problem is that it cannot find an <code>&lt;article&gt;</code> with the <code>id=</code> of <code>blbl_6f04feaefb9520d86b19c3ac4ad22c4f</code>, the <code>external_id</code> of the first blog post.</p><p>To make it pass, we&#39;ll need to add <code>id:</code> to each <code>&lt;article&gt;</code>. Make this one-line change in <code>HomePage</code>:</p><div class="language-diff vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">diff</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#B31D28;--shiki-dark:#FDAEB7;">- article do</span></span>
595
- <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">+ article(id: blog_post.external_id) do</span></span></code></pre></div><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>This shows a useful feature of the <code>external_id</code>: Because it&#39;s not only unique to the database table, but also across <em>all</em> database tables, it makes a pretty good <code>ID</code> inside an HTML page, since it&#39;s highly unlikely any other part of the page would use that value for the <code>id=</code> of an element.</p></div><p>Now, the test should pass:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/pages/home_page.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
595
+ <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">+ article(id: blog_post.external_id) do</span></span></code></pre></div><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>This shows a useful feature of the <code>external_id</code>: Because it&#39;s not only unique to the database table, but also across <em>all</em> database tables, it makes a pretty good <code>ID</code> inside an HTML page, since it&#39;s highly unlikely any other part of the page would use that value for the <code>id=</code> of an element.</p></div><p>Now, the test should pass:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/pages/home_page.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
596
596
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \&quot;**/*.spec.rb\&quot; \&quot;specs/front_end/pages/home_page.spec.rb\&quot;&quot;]</span></span>
597
597
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
598
598
  <span class="line"><span></span></span>
@@ -610,7 +610,7 @@
610
610
  <span class="line"></span>
611
611
  <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;"> BlogPostEditorPage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
612
612
  <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;end-to-end test&quot;</span></span>
613
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Now, all unit tests should pass, which we can check via <code>bin/test run</code>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
613
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Now, all unit tests should pass, which we can check via <code>bin/test run</code>:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
614
614
  <span class="line"><span>[ bin/test ] Running all tests</span></span>
615
615
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \&quot;**/*.spec.rb\&quot; /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs/&quot;]</span></span>
616
616
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
@@ -676,7 +676,7 @@
676
676
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(article).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> have_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This is a longer post, so we should be OK&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
677
677
  <span class="line"></span>
678
678
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
679
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Run it now with <code>bin/test e2e</code>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> e2e</span></span></code></pre></div><p>It should pass:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
679
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Run it now with <code>bin/test e2e</code>:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> e2e</span></span></code></pre></div><p>It should pass:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
680
680
  <span class="line"><span>[ bin/test ] Rebuilding test database schema</span></span>
681
681
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/db rebuild --env=test&quot;]</span></span>
682
682
  <span class="line"><span>[ bin/db ] Database exists. Dropping...</span></span>
@@ -730,7 +730,7 @@
730
730
  <span class="line"><span>[ bin/ci ] Checking to see that all classes have tests</span></span>
731
731
  <span class="line"><span>[ bin/test ] All tests exists!</span></span>
732
732
  <span class="line"><span>[ bin/ci ] Done</span></span></code></pre></div><p>That&#39;s it!</p><h2 id="areas-for-self-exploration" tabindex="-1">Areas for Self-Exploration <a class="header-anchor" href="#areas-for-self-exploration" aria-label="Permalink to &quot;Areas for Self-Exploration&quot;">​</a></h2><p>Here are a few enhancement you can try to make:</p><ul><li>Create a client-side constraint requiring the title to match a certain regexp.</li><li>Add a server-side constraint requiring at least two paragraphs.</li><li>Allow editing the blog post creation date</li><li>Add an author field to allow entering the author&#39;s name</li><li>Add pagination to the home page</li></ul></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><!----></div><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link next" href="/getting-started.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Next page</span><span class="title" data-v-1bcd8184>Getting Started</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
733
- <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\":\"BzVRwegp\",\"configuration.md\":\"eM5wFVi5\",\"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\":\"B3BHvCV3\",\"getting-started.md\":\"BgR0ZHsl\",\"handlers.md\":\"h84MMB1R\",\"hooks.md\":\"Jmb5VOLA\",\"i18n.md\":\"BAm9t9JJ\",\"index.md\":\"Bn9e0sRJ\",\"instrumentation.md\":\"BgcaGVYH\",\"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\":\"Dzvi_g69\",\"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\":\"BXvYWcO9\",\"tutorials_02-dialog.md\":\"CIeg8R--\",\"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>
733
+ <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\":\"f4cdTyvV\",\"configuration.md\":\"Bs4-rxnS\",\"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\":\"Sys-XxVf\",\"getting-started.md\":\"CFIW0bcE\",\"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\":\"BAISoxmN\",\"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\":\"QTFeHdiA\",\"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>
734
734
 
735
735
  </body>
736
736
  </html>