brut 0.10.0 → 0.11.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 +14 -0
  3. data/Gemfile.lock +15 -15
  4. data/assets/YouTubeThumb.pxd +0 -0
  5. data/bin/new-version +3 -3
  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/brutrb.com/getting-started.md +3 -0
  11. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element-styled.png +0 -0
  12. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element.png +0 -0
  13. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser.png +0 -0
  14. data/brutrb.com/images/tutorial/02-confirmation-flow.graffle +0 -0
  15. data/brutrb.com/images/tutorial/02-confirmation-flow.png +0 -0
  16. data/brutrb.com/instrumentation.md +142 -3
  17. data/brutrb.com/tutorial.md +29 -1627
  18. data/brutrb.com/tutorials/01-intro.md +1630 -0
  19. data/brutrb.com/tutorials/02-dialog.md +569 -0
  20. data/docs/404.html +2 -2
  21. data/docs/adrs.html +4 -4
  22. data/docs/ai.html +4 -4
  23. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  24. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  25. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  26. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  27. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  28. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  29. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  30. data/docs/api/Brut/BackEnd.html +1 -1
  31. data/docs/api/Brut/CLI/App.html +1 -1
  32. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  33. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  34. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  67. data/docs/api/Brut/CLI/Apps.html +1 -1
  68. data/docs/api/Brut/CLI/Command.html +1 -1
  69. data/docs/api/Brut/CLI/Error.html +1 -1
  70. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  71. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  72. data/docs/api/Brut/CLI/Executor.html +1 -1
  73. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  74. data/docs/api/Brut/CLI/Options.html +1 -1
  75. data/docs/api/Brut/CLI/Output.html +1 -1
  76. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  77. data/docs/api/Brut/CLI.html +1 -1
  78. data/docs/api/Brut/FactoryBot.html +1 -1
  79. data/docs/api/Brut/Framework/App.html +1 -1
  80. data/docs/api/Brut/Framework/Config.html +1 -1
  81. data/docs/api/Brut/Framework/Container.html +1 -1
  82. data/docs/api/Brut/Framework/Error.html +1 -1
  83. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  84. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  85. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  86. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  87. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  88. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  89. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  90. data/docs/api/Brut/Framework/Errors.html +1 -1
  91. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  92. data/docs/api/Brut/Framework/MCP.html +1 -1
  93. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  94. data/docs/api/Brut/Framework.html +1 -1
  95. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  96. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  97. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +48 -27
  99. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  113. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  129. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  138. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  139. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  140. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  141. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  152. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  153. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  154. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  155. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  156. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  158. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  160. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  171. data/docs/api/Brut/FrontEnd.html +1 -1
  172. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  173. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  174. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  175. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  176. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  177. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  178. data/docs/api/Brut/I18n.html +1 -1
  179. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  180. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  181. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  182. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  183. data/docs/api/Brut/Instrumentation.html +1 -1
  184. data/docs/api/Brut/RubocopConfig.html +1 -1
  185. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  186. data/docs/api/Brut/SinatraHelpers.html +1 -1
  187. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  188. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  189. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  190. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  191. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  192. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  193. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  194. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  195. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  197. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  198. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  199. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  200. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  201. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  208. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  209. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  210. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  211. data/docs/api/Brut/SpecSupport.html +1 -1
  212. data/docs/api/Brut.html +1 -1
  213. data/docs/api/Clock.html +1 -1
  214. data/docs/api/ModuleName.html +1 -1
  215. data/docs/api/RichString.html +1 -1
  216. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  217. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  218. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  219. data/docs/api/Sequel/Extensions.html +1 -1
  220. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  221. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  222. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  223. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  224. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  225. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  226. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  227. data/docs/api/Sequel/Plugins.html +1 -1
  228. data/docs/api/Sequel.html +1 -1
  229. data/docs/api/_index.html +1 -1
  230. data/docs/api/file.README.html +1 -1
  231. data/docs/api/index.html +1 -1
  232. data/docs/api/top-level-namespace.html +1 -1
  233. data/docs/assets/02-confirmation-dialog-browser-element-styled.3NEGM20-.png +0 -0
  234. data/docs/assets/02-confirmation-dialog-browser-element.DPsf0xUW.png +0 -0
  235. data/docs/assets/02-confirmation-dialog-browser.DH8ALFO4.png +0 -0
  236. data/docs/assets/02-confirmation-flow.D9gZ0S5U.png +0 -0
  237. data/docs/assets/{app.vjGWMSnJ.js → app.0-aKXKdt.js} +1 -1
  238. data/docs/assets/chunks/@localSearchIndexroot.DPhqaz1b.js +1 -0
  239. data/docs/assets/chunks/{VPLocalSearchBox.C-ymMW2k.js → VPLocalSearchBox.CW-UBkNA.js} +1 -1
  240. data/docs/assets/chunks/{theme.pJUosGlI.js → theme.a6feKWJO.js} +2 -2
  241. data/docs/assets/{components.md.B543a3Lm.js → components.md.BzVRwegp.js} +3 -3
  242. data/docs/assets/{configuration.md.-foE_iVv.js → configuration.md.eM5wFVi5.js} +1 -1
  243. data/docs/assets/{form-constraints.md.DK5adCgM.js → form-constraints.md.KTv5cdR4.js} +6 -6
  244. data/docs/assets/{forms.md.D5-2rgHh.js → forms.md.B3BHvCV3.js} +1 -1
  245. data/docs/assets/{getting-started.md.Cd4XSZb_.js → getting-started.md.BgR0ZHsl.js} +6 -3
  246. data/docs/assets/{getting-started.md.Cd4XSZb_.lean.js → getting-started.md.BgR0ZHsl.lean.js} +1 -1
  247. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.js +66 -0
  248. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +1 -0
  249. data/docs/assets/tutorial.md.BM40jnoq.js +27 -0
  250. data/docs/assets/tutorial.md.BM40jnoq.lean.js +1 -0
  251. data/docs/assets/{tutorial.md.C4zR5XPG.js → tutorials_01-intro.md.BXvYWcO9.js} +5 -25
  252. data/docs/assets/tutorials_01-intro.md.BXvYWcO9.lean.js +1 -0
  253. data/docs/assets/tutorials_02-dialog.md.CIeg8R--.js +274 -0
  254. data/docs/assets/tutorials_02-dialog.md.CIeg8R--.lean.js +1 -0
  255. data/docs/assets.html +4 -4
  256. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  257. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  258. data/docs/brut-js/api/Autosubmit.html +1 -1
  259. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  260. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  261. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  262. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  263. data/docs/brut-js/api/BufferedLogger.html +1 -1
  264. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  265. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  266. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  267. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  268. data/docs/brut-js/api/ConstraintViolationMessage.html +55 -5
  269. data/docs/brut-js/api/ConstraintViolationMessage.js.html +18 -3
  270. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  271. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  272. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  273. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  274. data/docs/brut-js/api/Form.html +7 -10
  275. data/docs/brut-js/api/Form.js.html +20 -24
  276. data/docs/brut-js/api/I18nTranslation.html +1 -1
  277. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  278. data/docs/brut-js/api/LocaleDetection.html +1 -1
  279. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  280. data/docs/brut-js/api/Logger.html +1 -1
  281. data/docs/brut-js/api/Logger.js.html +1 -1
  282. data/docs/brut-js/api/Message.html +1 -1
  283. data/docs/brut-js/api/Message.js.html +1 -1
  284. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  285. data/docs/brut-js/api/RichString.html +1 -1
  286. data/docs/brut-js/api/RichString.js.html +1 -1
  287. data/docs/brut-js/api/Tabs.html +1 -1
  288. data/docs/brut-js/api/Tabs.js.html +1 -1
  289. data/docs/brut-js/api/Tracing.html +1 -1
  290. data/docs/brut-js/api/Tracing.js.html +1 -1
  291. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  292. data/docs/brut-js/api/external-Performance.html +1 -1
  293. data/docs/brut-js/api/external-Promise.html +1 -1
  294. data/docs/brut-js/api/external-ValidityState.html +1 -1
  295. data/docs/brut-js/api/external-Window.html +1 -1
  296. data/docs/brut-js/api/external-fetch.html +1 -1
  297. data/docs/brut-js/api/global.html +1 -1
  298. data/docs/brut-js/api/index.html +1 -1
  299. data/docs/brut-js/api/index.js.html +1 -1
  300. data/docs/brut-js/api/module-testing.html +1 -1
  301. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  302. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  303. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  304. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  305. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  306. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  307. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  308. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  309. data/docs/brut-js/api/testing_index.js.html +1 -1
  310. data/docs/brut-js.html +4 -4
  311. data/docs/business-logic.html +4 -4
  312. data/docs/cli.html +4 -4
  313. data/docs/components.html +8 -8
  314. data/docs/configuration.html +5 -5
  315. data/docs/css.html +4 -4
  316. data/docs/custom-element-tests.html +4 -4
  317. data/docs/database-access.html +4 -4
  318. data/docs/database-schema.html +4 -4
  319. data/docs/deployment.html +4 -4
  320. data/docs/dev-environment.html +4 -4
  321. data/docs/dir-structure.html +4 -4
  322. data/docs/doc-conventions.html +4 -4
  323. data/docs/end-to-end-tests.html +4 -4
  324. data/docs/features.html +4 -4
  325. data/docs/flash-and-session.html +4 -4
  326. data/docs/form-constraints.html +11 -11
  327. data/docs/forms.html +6 -6
  328. data/docs/getting-started.html +10 -7
  329. data/docs/handlers.html +4 -4
  330. data/docs/hashmap.json +1 -1
  331. data/docs/hooks.html +4 -4
  332. data/docs/i18n.html +4 -4
  333. data/docs/index.html +3 -3
  334. data/docs/instrumentation.html +4 -4
  335. data/docs/javascript.html +4 -4
  336. data/docs/jobs.html +4 -4
  337. data/docs/keyword-injection.html +4 -4
  338. data/docs/layouts.html +4 -4
  339. data/docs/lsp.html +4 -4
  340. data/docs/markdown-examples.html +4 -4
  341. data/docs/middleware.html +4 -4
  342. data/docs/overview.html +4 -4
  343. data/docs/pages.html +4 -4
  344. data/docs/recipes/alternate-layouts.html +4 -4
  345. data/docs/recipes/authentication.html +5 -5
  346. data/docs/recipes/blank-layouts.html +4 -4
  347. data/docs/recipes/custom-flash.html +4 -4
  348. data/docs/recipes/form-errors.html +94 -0
  349. data/docs/recipes/indexed-forms.html +4 -4
  350. data/docs/recipes/migrations.html +5 -5
  351. data/docs/recipes/text-field-component.html +4 -4
  352. data/docs/roadmap.html +4 -4
  353. data/docs/routes.html +4 -4
  354. data/docs/security.html +4 -4
  355. data/docs/seed-data.html +4 -4
  356. data/docs/space-time-continuum.html +4 -4
  357. data/docs/tutorial.html +12 -713
  358. data/docs/tutorials/01-intro.html +736 -0
  359. data/docs/tutorials/02-dialog.html +302 -0
  360. data/docs/unit-tests.html +4 -4
  361. data/docs/why.html +4 -4
  362. data/lib/brut/instrumentation/methods.rb +153 -0
  363. data/lib/brut/instrumentation/open_telemetry.rb +1 -0
  364. data/lib/brut/instrumentation.rb +1 -0
  365. data/lib/brut/version.rb +1 -1
  366. data/mkbrut/Gemfile.lock +1 -1
  367. data/mkbrut/bin/publish +1 -1
  368. data/mkbrut/lib/mkbrut/version.rb +1 -1
  369. data/specs/brut/instrumentation/methods.spec.rb +399 -0
  370. metadata +39 -17
  371. data/docs/assets/chunks/@localSearchIndexroot.Dn1xGMv_.js +0 -1
  372. data/docs/assets/tutorial.md.C4zR5XPG.lean.js +0 -1
  373. /data/docs/assets/{components.md.B543a3Lm.lean.js → components.md.BzVRwegp.lean.js} +0 -0
  374. /data/docs/assets/{configuration.md.-foE_iVv.lean.js → configuration.md.eM5wFVi5.lean.js} +0 -0
  375. /data/docs/assets/{form-constraints.md.DK5adCgM.lean.js → form-constraints.md.KTv5cdR4.lean.js} +0 -0
  376. /data/docs/assets/{forms.md.D5-2rgHh.lean.js → forms.md.B3BHvCV3.lean.js} +0 -0
@@ -1,17 +1,20 @@
1
- import{_ as s,c as e,o as t,ag as n}from"./chunks/framework.1L-BeKqY.js";const u=JSON.parse('{"title":"Getting Started","description":"","frontmatter":{},"headers":[],"relativePath":"getting-started.md","filePath":"getting-started.md"}'),i={name:"getting-started.md"};function p(l,a,o,d,r,h){return t(),e("div",null,a[0]||(a[0]=[n(`<h1 id="getting-started" tabindex="-1">Getting Started <a class="header-anchor" href="#getting-started" aria-label="Permalink to &quot;Getting Started&quot;">​</a></h1><p>Brut is developed alongside a separate gem called <code>mkbrut</code>, which allows you to create a new Brut app. It will set up your dev environment as well.</p><h2 id="get-mkbrut" tabindex="-1">Get <code>mkbrut</code> <a class="header-anchor" href="#get-mkbrut" aria-label="Permalink to &quot;Get \`mkbrut\`&quot;">​</a></h2><p>The simplest way to use <code>mkbrut</code> is to use an existing <a href="https://hub.docker.com/repository/docker/thirdtank/mkbrut/general" target="_blank" rel="noreferrer">Docker image</a>. You don&#39;t have to install or configure Ruby:</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>docker run \\</span></span>
1
+ import{_ as s,c as e,o as t,ag as n}from"./chunks/framework.1L-BeKqY.js";const u=JSON.parse('{"title":"Getting Started","description":"","frontmatter":{},"headers":[],"relativePath":"getting-started.md","filePath":"getting-started.md"}'),i={name:"getting-started.md"};function p(l,a,o,r,d,h){return t(),e("div",null,a[0]||(a[0]=[n(`<h1 id="getting-started" tabindex="-1">Getting Started <a class="header-anchor" href="#getting-started" aria-label="Permalink to &quot;Getting Started&quot;">​</a></h1><p>Brut is developed alongside a separate gem called <code>mkbrut</code>, which allows you to create a new Brut app. It will set up your dev environment as well.</p><h2 id="get-mkbrut" tabindex="-1">Get <code>mkbrut</code> <a class="header-anchor" href="#get-mkbrut" aria-label="Permalink to &quot;Get \`mkbrut\`&quot;">​</a></h2><p>The simplest way to use <code>mkbrut</code> is to use an existing <a href="https://hub.docker.com/repository/docker/thirdtank/mkbrut/general" target="_blank" rel="noreferrer">Docker image</a>. You don&#39;t have to install or configure Ruby:</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>docker run \\</span></span>
2
+ <span class="line"><span> --pull always \\</span></span>
2
3
  <span class="line"><span> -v &quot;$PWD&quot;:&quot;$PWD&quot; \\</span></span>
3
4
  <span class="line"><span> -w &quot;$PWD&quot; \\</span></span>
4
5
  <span class="line"><span> -u $(id -u):$(id -g) \\</span></span>
5
6
  <span class="line"><span> -it \\</span></span>
6
7
  <span class="line"><span> thirdtank/mkbrut \\</span></span>
7
8
  <span class="line"><span> mkbrut my-new-app</span></span></code></pre></div><p>If you already have Ruby 3.4 installed, you can install <code>mkbrut</code> directly:</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>&gt; gem install mkbrut</span></span>
8
- <span class="line"><span>&gt; mkbrut my-new-app</span></span></code></pre></div><h2 id="init-your-app" tabindex="-1">Init Your App <a class="header-anchor" href="#init-your-app" aria-label="Permalink to &quot;Init Your App&quot;">​</a></h2><p>A Brut app just needs a name, which will be used to derive a few more useful values. For now:</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-CFm39" id="tab-7ecxuU-" checked><label data-title="Docker-based" for="tab-7ecxuU-">Docker-based</label><input type="radio" name="group-CFm39" id="tab-YdXMyd3"><label data-title="RubyGems-based" for="tab-YdXMyd3">RubyGems-based</label></div><div class="blocks"><div class="language- vp-adaptive-theme active"><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>docker run \\</span></span>
9
+ <span class="line"><span>&gt; mkbrut my-new-app</span></span></code></pre></div><h2 id="init-your-app" tabindex="-1">Init Your App <a class="header-anchor" href="#init-your-app" aria-label="Permalink to &quot;Init Your App&quot;">​</a></h2><p>A Brut app just needs a name, which will be used to derive a few more useful values. For now:</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-0LfUU" id="tab-0OPZlKb" checked><label data-title="Docker-based" for="tab-0OPZlKb">Docker-based</label><input type="radio" name="group-0LfUU" id="tab-GVLwqsZ"><label data-title="RubyGems-based" for="tab-GVLwqsZ">RubyGems-based</label></div><div class="blocks"><div class="language- vp-adaptive-theme active"><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>docker run \\</span></span>
10
+ <span class="line"><span> --pull always \\</span></span>
9
11
  <span class="line"><span> -v &quot;$PWD&quot;:&quot;$PWD&quot; \\</span></span>
10
12
  <span class="line"><span> -w &quot;$PWD&quot; \\</span></span>
11
13
  <span class="line"><span> -u $(id -u):$(id -g) \\</span></span>
12
14
  <span class="line"><span> -it \\</span></span>
13
15
  <span class="line"><span> thirdtank/mkbrut \\</span></span>
14
- <span class="line"><span> mkbrut my-new-app</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>mkbrut my-new-app</span></span></code></pre></div></div></div><p>This will create your new app, along with some demo routes, components, handlers, and tests. If this is your first time using Brut, we recommend you examine these demo components.</p><p>To create your app without the demo components:</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-tSbsb" id="tab-WuhE_gL" checked><label data-title="Docker-based" for="tab-WuhE_gL">Docker-based</label><input type="radio" name="group-tSbsb" id="tab-YuZNjLY"><label data-title="RubyGems-based" for="tab-YuZNjLY">RubyGems-based</label></div><div class="blocks"><div class="language- vp-adaptive-theme active"><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>docker run \\</span></span>
16
+ <span class="line"><span> mkbrut my-new-app</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>mkbrut my-new-app</span></span></code></pre></div></div></div><p>This will create your new app, along with some demo routes, components, handlers, and tests. If this is your first time using Brut, we recommend you examine these demo components.</p><p>To create your app without the demo components:</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-B7oqe" id="tab-xSNUSQa" checked><label data-title="Docker-based" for="tab-xSNUSQa">Docker-based</label><input type="radio" name="group-B7oqe" id="tab-qn3bnjv"><label data-title="RubyGems-based" for="tab-qn3bnjv">RubyGems-based</label></div><div class="blocks"><div class="language- vp-adaptive-theme active"><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>docker run \\</span></span>
17
+ <span class="line"><span> --pull always \\</span></span>
15
18
  <span class="line"><span> -v &quot;$PWD&quot;:&quot;$PWD&quot; \\</span></span>
16
19
  <span class="line"><span> -w &quot;$PWD&quot; \\</span></span>
17
20
  <span class="line"><span> -u $(id -u):$(id -g) \\</span></span>
@@ -1 +1 @@
1
- import{_ as s,c as e,o as t,ag as n}from"./chunks/framework.1L-BeKqY.js";const u=JSON.parse('{"title":"Getting Started","description":"","frontmatter":{},"headers":[],"relativePath":"getting-started.md","filePath":"getting-started.md"}'),i={name:"getting-started.md"};function p(l,a,o,d,r,h){return t(),e("div",null,a[0]||(a[0]=[n("",29)]))}const k=s(i,[["render",p]]);export{u as __pageData,k as default};
1
+ import{_ as s,c as e,o as t,ag as n}from"./chunks/framework.1L-BeKqY.js";const u=JSON.parse('{"title":"Getting Started","description":"","frontmatter":{},"headers":[],"relativePath":"getting-started.md","filePath":"getting-started.md"}'),i={name:"getting-started.md"};function p(l,a,o,r,d,h){return t(),e("div",null,a[0]||(a[0]=[n("",29)]))}const k=s(i,[["render",p]]);export{u as __pageData,k as default};
@@ -0,0 +1,66 @@
1
+ import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const E=JSON.parse('{"title":"Styling Form Errors","description":"","frontmatter":{},"headers":[],"relativePath":"recipes/form-errors.md","filePath":"recipes/form-errors.md"}'),t={name:"recipes/form-errors.md"};function h(l,s,p,k,r,d){return n(),a("div",null,s[0]||(s[0]=[e(`<h1 id="styling-form-errors" tabindex="-1">Styling Form Errors <a class="header-anchor" href="#styling-form-errors" aria-label="Permalink to &quot;Styling Form Errors&quot;">​</a></h1><p>Brut makes it as easy as possible to unify client-side and server-side constraint violation handling, including how you style those messages.</p><h2 id="requirements" tabindex="-1">Requirements <a class="header-anchor" href="#requirements" aria-label="Permalink to &quot;Requirements&quot;">​</a></h2><p>What you want:</p><ul><li>When a form is rendered for the first time, there should be errors shown.</li><li>When a visitor interacts with a form before submissions, no errors are shown.</li><li>When the form is submitted, client-side constraint violations should be shown.</li><li>When JavaScript is circumvented and the form is submitted with client-side constraint violations, the form should be re-generated, showing those violations the same is if JavaScripts was <em>not</em> circumvented</li><li>When there are no client-side constraint violations, but there <em>are</em> server-side violations, the form should be re-generated, showing those violations the same is if JavaScripts was <em>not</em> circumvented</li></ul><p>This can be achieved through CSS.</p><h2 id="recipe" tabindex="-1">Recipe <a class="header-anchor" href="#recipe" aria-label="Permalink to &quot;Recipe&quot;">​</a></h2><h3 id="create-pages-and-html" tabindex="-1">Create Pages and HTML <a class="header-anchor" href="#create-pages-and-html" aria-label="Permalink to &quot;Create Pages and HTML&quot;">​</a></h3><p>First, create a form and handler:</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>bin/scaffold form /new_widget</span></span></code></pre></div><p>Edit <code>app/src/front_end/forms/new_widget_form.rb</code></p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> NewWidgetForm</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppForm</span></span>
2
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> input </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">minlength:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 3</span></span>
3
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> input </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:description</span></span>
4
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Now, implement the handler in <code>app/src/front_end/handlers/new_widget_handler.rb</code> to check for client-side violations <em>and</em> require that the description have at least 5 words in it.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> NewWidgetHandler</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppHandler</span></span>
5
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">form:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
6
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @form </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> form</span></span>
7
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
8
+ <span class="line"></span>
9
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> handle</span></span>
10
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @form.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">valid?</span></span>
11
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @form.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">split</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF;">/</span><span style="--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold;">\\s</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF;">+/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">length</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 5</span></span>
12
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @form.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">server_side_constraint_violation</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
13
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> input_name:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
14
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> key:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :not_enough_words</span></span>
15
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> )</span></span>
16
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
17
+ <span class="line"></span>
18
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @form.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">constraint_violations?</span></span>
19
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> NewWidgetPage</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>
20
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> else</span></span>
21
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> redirect_to</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">HomePage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
22
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
23
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
24
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Add the new error message to <code>app/config/i18n/en/2_app.rb</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:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
25
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> en:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
26
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> nevermind:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;Nevermind&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
27
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> cv:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
28
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> ss:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
29
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> not_enough_words:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;%{field} must have at least %{minwords} words&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
30
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
31
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span></code></pre></div><p>Now, build a minimal <code>NewWidgetPage</code>:</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>bin/scaffold page /new_widget\`</span></span></code></pre></div><p>We&#39;ll create the bare minimum in <code>app/src/front_end/pages/new_widget_page.rb</code></p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> NewWidgetPage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppPage</span></span>
32
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">form:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> nil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
33
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @form </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> form </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">||</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> NewWidgetForm</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span></span>
34
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
35
+ <span class="line"></span>
36
+ <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>
37
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> brut_form </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
38
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> FormTag</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">for:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @form) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
39
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> label </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
40
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Inputs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">InputTag</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 style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">input_name:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
41
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> ConstraintViolations</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 style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">input_name:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
42
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> span { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Name&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
43
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
44
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> label </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
45
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Inputs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">TextareaTag</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 style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">input_name:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
46
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> ConstraintViolations</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 style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">input_name:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
47
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> span { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Description&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
48
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
49
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
50
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
51
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
52
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><h3 id="create-css" tabindex="-1">Create CSS <a class="header-anchor" href="#create-css" aria-label="Permalink to &quot;Create CSS&quot;">​</a></h3><p>The most minimal CSS would be as followed, which you can place in <code>app/src/front_end/css/index.css</code></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:#22863A;--shiki-dark:#85E89D;">brut-cv</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
53
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> display</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">none</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
54
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
55
+ <span class="line"></span>
56
+ <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-form</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">submitted-invalid</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-cv</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
57
+ <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-cv</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">server-generated</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] {</span></span>
58
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> display</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">block</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
59
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> color</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">red</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
60
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>This will show the messages in <a href="/brut-js/api/ConstraintViolationMessage.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-cv&gt;</code></a> <em>only</em> if:</p><ul><li>Form submission was attempted (<code>submitted-invalid</code> would be set on <a href="/brut-js/api/Form.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-form&gt;</code></a>)</li><li>The server generated via <code>ConstraintViolations</code> (only if the handler was triggered)</li></ul><p>Because more than one <a href="/brut-js/api/ConstraintViolationMessage.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-cv&gt;</code></a> could be generated or inserted, you may want to style the <a href="/brut-js/api/ConstraintViolationMessages.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-cv-messages&gt;</code></a> that contains them, but you only want it to show up if it has contents.</p><p>You can&#39;t just do <code>brut-cv-messages:has(brut-cv)</code>, because your container would show up before the form submission was attempted.</p><p>Here is what you want instead. This will put the error messages in a red box:</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:#22863A;--shiki-dark:#85E89D;">brut-form</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">submitted-invalid</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-cv-messages</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">:has</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-cv</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
61
+ <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-cv-messages</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">:has</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-cv</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">[</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">server-generated</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]) {</span></span>
62
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> color</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">red</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
63
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> background-color</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">pink</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
64
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> border</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">solid</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> thin</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> red</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
65
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> border-radius</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">rem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
66
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div>`,29)]))}const g=i(t,[["render",h]]);export{E as __pageData,g as default};
@@ -0,0 +1 @@
1
+ import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const E=JSON.parse('{"title":"Styling Form Errors","description":"","frontmatter":{},"headers":[],"relativePath":"recipes/form-errors.md","filePath":"recipes/form-errors.md"}'),t={name:"recipes/form-errors.md"};function h(l,s,p,k,r,d){return n(),a("div",null,s[0]||(s[0]=[e("",29)]))}const g=i(t,[["render",h]]);export{E as __pageData,g as default};
@@ -0,0 +1,27 @@
1
+ import{_ as a,c as t,o as i,ag as e}from"./chunks/framework.1L-BeKqY.js";const k=JSON.parse('{"title":"Tutorials","description":"","frontmatter":{},"headers":[],"relativePath":"tutorial.md","filePath":"tutorial.md"}'),n={name:"tutorial.md"};function l(o,s,h,p,d,r){return i(),t("div",null,s[0]||(s[0]=[e(`<h1 id="tutorials" tabindex="-1">Tutorials <a class="header-anchor" href="#tutorials" aria-label="Permalink to &quot;Tutorials&quot;">​</a></h1><p>Below are several tutorials, along with screencasts showing the tutorial steps as a video. The first one is to <a href="https://video.hardlimit.com/w/ae7EMhwjDq9kSH5dqQ9swV" target="_blank" rel="noreferrer">build a blog in 15 minutes</a>. The remainder of the tutorials assumey you are going in order, however code for each starting point is available, so you can skip around.</p><p>If you&#39;d just like to read source code, there are two apps you can check out:</p><ul><li><a href="https://github.com/thirdtank/blog-demo" target="_blank" rel="noreferrer">The blog we&#39;ll build here</a></li><li><a href="https://github.com/thirdtank/adrs.cloud" target="_blank" rel="noreferrer">ADRs.cloud</a>, which is a more realistic app that has mulitple database tables, progressively-enhanced UI, and background jobs.</li></ul><p>You can be running either of these locally in minutes as long as you have Docker installed.</p><h2 id="understanding-these-tutorials" tabindex="-1">Understanding These Tutorials <a class="header-anchor" href="#understanding-these-tutorials" aria-label="Permalink to &quot;Understanding These Tutorials&quot;">​</a></h2><p>These tutorials will show you command line invocations and code. You should be able to follow along and just type what we say and it should work.</p><p>That said, it&#39;s not always clear what we are talking about.</p><h3 id="understanding-command-line-invocations" tabindex="-1">Understanding Command Line Invocations <a class="header-anchor" href="#understanding-command-line-invocations" aria-label="Permalink to &quot;Understanding Command Line Invocations&quot;">​</a></h3><p>If you aren&#39;t comfortable on the command line, it can be hard to understand what parts of this tutorial represent stuff you should type/paste and what is output from those commands. Here is how that works.</p><p>When we want you to run a command, the preceding text will tell you something like &quot;run this command&quot;, and then you&#39;ll see a codeblock that has the label &quot;bash&quot; in the upper right corner, like so:</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;">ls</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -l</span></span></code></pre></div><p>If you hover over it, an icon with the tooltip &quot;Copy Code&quot; will appear on the right, and you can click that to copy the command-line invocation. Or, you can select it and copy it, or you can type it in manually.</p><p>In any case, you are expected to type/paste/execute the entire thing. Other parts of this documentation site may precede command lines with <code>&gt;</code> to indicate it&#39;s a shell command. For this tutorial, we aren&#39;t doing that.</p><p>Sometimes, commands are long. They can be split up by entering a backslash (<code>\\</code>) as the last character of a line, hitting return, and continuing the command. For example this command:</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;">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> push</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> origin</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> main</span></span></code></pre></div><p>Could be executed like so:</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;">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> push</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
2
+ <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> origin</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
3
+ <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> main</span></span></code></pre></div><p>In both cases, you can copy/type these as written and they will work.</p><p>To show output of a command, a separate code block will be used, and the first line of the output will be the string <code># OUTPUT:</code>, and there should <strong>not</strong> be a &quot;bash&quot; label in the upper right corner:</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>
4
+ <span class="line"><span>app dx puma.config.rb</span></span>
5
+ <span class="line"><span>bin Gemfile README.md</span></span>
6
+ <span class="line"><span>config.ru package.json specs</span></span>
7
+ <span class="line"><span>docker-compose.dx.yml Procfile.development</span></span>
8
+ <span class="line"><span>Dockerfile.dx Procfile.test</span></span></code></pre></div><p>Sometimes, output is very long and very irrelevant. In that case, the string <code>«LOTS OF OUTPUT»</code> will be used as a placeholder:</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>
9
+ <span class="line"><span>app</span></span>
10
+ <span class="line"><span>dx</span></span>
11
+ <span class="line"><span>puma.config.rb</span></span>
12
+ <span class="line"><span>«LOTS OF OUTPUTS»</span></span></code></pre></div><h3 id="understanding-code-changes" tabindex="-1">Understanding Code Changes <a class="header-anchor" href="#understanding-code-changes" aria-label="Permalink to &quot;Understanding Code Changes&quot;">​</a></h3><p>In most cases, we&#39;ll show you the entire code for a file/class, and you should make your copy look like it. Suppose you have 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;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> SomeComponent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
13
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>We might say &quot;add the <code>view_template</code> to your component so it looks like this:&quot;</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> SomeComponent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
14
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
15
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h3 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;My component&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
16
+ <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:HelpPage</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;Would You Like to Know More?&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
17
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
18
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>That means you can replace the file with this code. Other times, we may only focus on one method. We might write &quot;Change <code>view_template</code> in <code>SomeComponent</code> so it looks like so:&quot;</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
19
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h3 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;My component&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
20
+ <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:HelpPage</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;Would You Like to Know More?&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
21
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>In this case, you&#39;d replace the method, but the leave the rest of the class as-is.</p><p>On occasion we&#39;ll want to only change a few lines and, in that case, we&#39;ll use a diff format like so:</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;">- a(href: &quot;&quot;) { &quot;Write New Blog Post&quot; }</span></span>
22
+ <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">+ a(href: BlogPostEditorPage.routing) { &quot;Write New Blog Post&quot; }</span></span></code></pre></div><p>If a change is more complex, sometimes we&#39;ll show line numbers and highlight the changes. Below, we&#39;re showing a change to lines 14,15, and 16 of the file.</p><div class="language-ruby vp-adaptive-theme line-numbers-mode"><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:#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;BlogPostEditorPage.routing&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
23
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> plain { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Edit&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
24
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> span </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
25
+ <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>
26
+ <span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
27
+ <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>`,40)]))}const u=a(n,[["render",l]]);export{k as __pageData,u as default};
@@ -0,0 +1 @@
1
+ import{_ as a,c as t,o as i,ag as e}from"./chunks/framework.1L-BeKqY.js";const k=JSON.parse('{"title":"Tutorials","description":"","frontmatter":{},"headers":[],"relativePath":"tutorial.md","filePath":"tutorial.md"}'),n={name:"tutorial.md"};function l(o,s,h,p,d,r){return i(),t("div",null,s[0]||(s[0]=[e("",40)]))}const u=a(n,[["render",l]]);export{k as __pageData,u as default};
@@ -1,29 +1,9 @@
1
- import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t="/assets/welcome-to-brut.VSWzl17-.png",l="/assets/initial-home-page.DNIaYmgP.png",p="/assets/styled-home-page.BzdI7dWz.png",h="/assets/basic-form.DbHnu0oW.png",o="/assets/basic-form-with-violations.Cv6Y9-Q_.png",k="/assets/styled-form-with-violations.Bv_sa9tg.png",d="/assets/styled-form-with-server-side-violations.Bjxd8Dpv.png",r="/assets/styled-home-page-with-posts.Dd4kG89D.png",c="/assets/new-post-editor.DrHr-5oh.png",g="/assets/new-post-home-page.Bm34lyMg.png",B=JSON.parse('{"title":"Tutorial","description":"","frontmatter":{},"headers":[],"relativePath":"tutorial.md","filePath":"tutorial.md"}'),E={name:"tutorial.md"};function u(y,s,F,b,m,C){return n(),i("div",null,s[0]||(s[0]=[e(`<h1 id="tutorial" tabindex="-1">Tutorial <a class="header-anchor" href="#tutorial" aria-label="Permalink to &quot;Tutorial&quot;">​</a></h1><p>Below, you&#39;ll build a blog that has a few pages, a form, a database table, and tests. It&#39;s the same steps I took in the <a href="https://video.hardlimit.com/w/ae7EMhwjDq9kSH5dqQ9swV" target="_blank" rel="noreferrer">15-minute blog video</a>.</p><p>If you&#39;d just like to read source code, there are two apps you can check out:</p><ul><li><a href="https://github.com/thirdtank/blog-demo" target="_blank" rel="noreferrer">The blog we&#39;ll build here</a></li><li><a href="https://github.com/thirdtank/adrs.cloud" target="_blank" rel="noreferrer">ADRs.cloud</a>, which is a more realistic app that has mulitple database tables, progressively-enhanced UI, and background jobs.</li></ul><p>You can be running either of these locally in minutes as long as you have Docker installed.</p><h2 id="understanding-this-tutorial" tabindex="-1">Understanding This Tutorial <a class="header-anchor" href="#understanding-this-tutorial" aria-label="Permalink to &quot;Understanding This Tutorial&quot;">​</a></h2><p>This tutorial will show you command line invocations and code. You should be able to follow along and just type what we say and it should work.</p><p>That said, it&#39;s not always clera what we are talking about.</p><h3 id="understanding-command-line-invocations" tabindex="-1">Understanding Command Line Invocations <a class="header-anchor" href="#understanding-command-line-invocations" aria-label="Permalink to &quot;Understanding Command Line Invocations&quot;">​</a></h3><p>If you aren&#39;t comfortable on the command line, it can be hard to understand what parts of this tutorial represent stuff you should type/paste and what is output from those commands. Here is how that works.</p><p>When we want you to run a command, the preceding text will tell you something like &quot;run this command&quot;, and then you&#39;ll see a codeblock that has the label &quot;bash&quot; in the upper right corner, like so:</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;">ls</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -l</span></span></code></pre></div><p>If you hover over it, an icon with the tooltip &quot;Copy Code&quot; will appear on the right, and you can click that to copy the command-line invocation. Or, you can select it and copy it, or you can type it in manually.</p><p>In any case, you are expected to type/paste/execute the entire thing. Other parts of this documentation site may precede command lines with <code>&gt;</code> to indicate it&#39;s a shell command. For this tutorial, we aren&#39;t doing that.</p><p>Sometimes, commands are long. They can be split up by entering a backslash (<code>\\</code>) as the last character of a line, hitting return, and continuing the command. For example this command:</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;">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> push</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> origin</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> main</span></span></code></pre></div><p>Could be executed like so:</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;">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> push</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
2
- <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> origin</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
3
- <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> main</span></span></code></pre></div><p>In both cases, you can copy/type these as written and they will work.</p><p>To show output of a command, a separate code block will be used, and the first line of the output will be the string <code># OUTPUT:</code>, and there should <strong>not</strong> be a &quot;bash&quot; label in the upper right corner:</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>
4
- <span class="line"><span>app dx puma.config.rb</span></span>
5
- <span class="line"><span>bin Gemfile README.md</span></span>
6
- <span class="line"><span>config.ru package.json specs</span></span>
7
- <span class="line"><span>docker-compose.dx.yml Procfile.development</span></span>
8
- <span class="line"><span>Dockerfile.dx Procfile.test</span></span></code></pre></div><p>Sometimes, output is very long and very irrelevant. In that case, the string <code>«LOTS OF OUTPUT»</code> will be used as a placeholder:</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>
9
- <span class="line"><span>app</span></span>
10
- <span class="line"><span>dx</span></span>
11
- <span class="line"><span>puma.config.rb</span></span>
12
- <span class="line"><span>«LOTS OF OUTPUTS»</span></span></code></pre></div><h3 id="understanding-code-changes" tabindex="-1">Understanding Code Changes <a class="header-anchor" href="#understanding-code-changes" aria-label="Permalink to &quot;Understanding Code Changes&quot;">​</a></h3><p>In most cases, we&#39;ll show you the entire code for a file/class, and you should make your copy look like it. Suppose you have 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;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> SomeComponent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
13
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>We might say &quot;add the <code>view_template</code> to your component so it looks like this:&quot;</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> SomeComponent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
14
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
15
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h3 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;My component&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
16
- <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:HelpPage</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;Would You Like to Know More?&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
17
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
18
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>That means you can replace the file with this code. Other times, we may only focus on one method. We might write &quot;Change <code>view_template</code> in <code>SomeComponent</code> so it looks like so:&quot;</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
19
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h3 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;My component&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
20
- <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:HelpPage</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;Would You Like to Know More?&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
21
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>In this case, you&#39;d replace the method, but the leave the rest of the class as-is.</p><p>On occasion we&#39;ll want to only change a few lines and, in that case, we&#39;ll use a diff format like so:</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;">- a(href: &quot;&quot;) { &quot;Write New Blog Post&quot; }</span></span>
22
- <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">+ a(href: BlogPostEditorPage.routing) { &quot;Write New Blog Post&quot; }</span></span></code></pre></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><p>OK, back to your regularly-scheduled tutorial</p><h2 id="set-up" tabindex="-1">Set Up <a class="header-anchor" href="#set-up" aria-label="Permalink to &quot;Set Up&quot;">​</a></h2><p>The only two pieces of software you need are Docker and a code editor:</p><ol><li><p><a href="https://docker.com" target="_blank" rel="noreferrer">Install Docker</a></p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you are on Windows, we <em>highly</em> recommend you use the Windows Subystem for Linux (WSL2), as this makes Brut, web developement, and, honestly, your entire life as you know it, far easier than trying to get things working natively in Windows.</p></div></li><li><p>If you are new to programming or new to Ruby and don&#39;t know what editor to get, use VSCode. If you are a vim or emacs person, those will be far better, but if you are used to an IDE, VSCode will be the easiest to get set up and learn to use.</p></li></ol><p>To check that docker is installed, open up a terminal and run:</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;">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> info</span></span></code></pre></div><p>This should produce a ton of output:</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>
1
+ import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t="/assets/welcome-to-brut.VSWzl17-.png",p="/assets/initial-home-page.DNIaYmgP.png",l="/assets/styled-home-page.BzdI7dWz.png",h="/assets/basic-form.DbHnu0oW.png",o="/assets/basic-form-with-violations.Cv6Y9-Q_.png",k="/assets/styled-form-with-violations.Bv_sa9tg.png",d="/assets/styled-form-with-server-side-violations.Bjxd8Dpv.png",r="/assets/styled-home-page-with-posts.Dd4kG89D.png",c="/assets/new-post-editor.DrHr-5oh.png",g="/assets/new-post-home-page.Bm34lyMg.png",v=JSON.parse('{"title":"Build a Blog in 15 Minutes","description":"","frontmatter":{},"headers":[],"relativePath":"tutorials/01-intro.md","filePath":"tutorials/01-intro.md"}'),E={name:"tutorials/01-intro.md"};function y(u,s,F,b,C,m){return n(),a("div",null,s[0]||(s[0]=[e(`<h1 id="build-a-blog-in-15-minutes" tabindex="-1">Build a Blog in 15 Minutes <a class="header-anchor" href="#build-a-blog-in-15-minutes" aria-label="Permalink to &quot;Build a Blog in 15 Minutes&quot;">​</a></h1><p>This will start from nothing and show you the main features of Brut by building a very basic blog. You&#39;ll learn how to make a new Brut app, how to build pages, submit forms, validate data, and access data in a database. You&#39;ll also learn how to test it all.</p><h2 id="set-up" tabindex="-1">Set Up <a class="header-anchor" href="#set-up" aria-label="Permalink to &quot;Set Up&quot;">​</a></h2><p>The only two pieces of software you need are Docker and a code editor:</p><ol><li><p><a href="https://docker.com" target="_blank" rel="noreferrer">Install Docker</a></p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you are on Windows, we <em>highly</em> recommend you use the Windows Subystem for Linux (WSL2), as this makes Brut, web developement, and, honestly, your entire life as you know it, far easier than trying to get things working natively in Windows.</p></div></li><li><p>If you are new to programming or new to Ruby and don&#39;t know what editor to get, use VSCode. If you are a vim or emacs person, those will be far better, but if you are used to an IDE, VSCode will be the easiest to get set up and learn to use.</p></li></ol><p>To check that docker is installed, open up a terminal and run:</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;">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> info</span></span></code></pre></div><p>This should produce a ton of output:</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>
23
2
  <span class="line"><span>Client:</span></span>
24
3
  <span class="line"><span> Version: 28.2.2</span></span>
25
4
  <span class="line"><span>«LOTS OF OUTPUT»</span></span></code></pre></div><p>To be extra sure, <strong>right after you ran <code>docker info</code></strong>, check <code>$?</code>, the exit code, to make sure it&#39;s a 0, which means the command ran successfully:</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:#005CC5;--shiki-dark:#79B8FF;">echo</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> $?</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>
26
5
  <span class="line"><span>0</span></span></code></pre></div><p>Now, let&#39;s create the app by first initializing it.</p><h2 id="initialize-your-app" tabindex="-1">Initialize Your App <a class="header-anchor" href="#initialize-your-app" aria-label="Permalink to &quot;Initialize Your App&quot;">​</a></h2><p><code>mkbrut</code> is a command line app that will initialize your new app. It&#39;s available as a RubyGem or a Docker image. We&#39;ll use the Docker image since that doesn&#39;t require installing anything.</p><p>We&#39;ll call the blog simply &quot;blog&quot;. <code>mkbrut</code> will insert some demo features in new apps to show you have to use Brut. Since you&#39;re following this tutorial, you don&#39;t need that, so we&#39;ll use the <code>--no-demo</code> flag.</p><p><code>cd</code> to a folder where you&#39;d like to work. <code>mkbrut</code> will create a folder called <code>blog</code> in there and in <em>that</em> folder, your app will be initialized.</p><p>The command to do this is pretty long, because it downloads <code>mkbrut</code> and then runs it inside a Docker container, meaning you don&#39;t have to install anything new. Here it is:</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>docker run \\</span></span>
6
+ <span class="line"><span> --pull always \\</span></span>
27
7
  <span class="line"><span> -v &quot;$PWD&quot;:&quot;$PWD&quot; \\</span></span>
28
8
  <span class="line"><span> -w &quot;$PWD&quot; \\</span></span>
29
9
  <span class="line"><span> -u $(id -u):$(id -g) \\</span></span>
@@ -157,7 +137,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
157
137
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> main </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
158
138
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Posts go here&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
159
139
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
160
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>If you&#39;ve never used Phlex before, it&#39;s a Ruby API that defines one method for each HTML element (along with any custom elements you tell it about). You then call these methods to build up markup. As you can see, it&#39;s structurally identical to HTML, but it&#39;s Ruby.</p><p>If your server is still running, refresh the page and you&#39;ll see this wonderful UI (otherwise, start your server with <code>bin/dev</code>):</p><p><img src="`+l+`" alt="Screenshot of the page we built"></p><p>Let&#39;s make it a bit nicer.</p><h3 id="using-css" tabindex="-1">Using CSS <a class="header-anchor" href="#using-css" aria-label="Permalink to &quot;Using CSS&quot;">​</a></h3><p>Open up <code>app/src/front_end/css/index.css</code> in your editor. You should see this:</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;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;brut-css/dist/brut.css&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
140
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>If you&#39;ve never used Phlex before, it&#39;s a Ruby API that defines one method for each HTML element (along with any custom elements you tell it about). You then call these methods to build up markup. As you can see, it&#39;s structurally identical to HTML, but it&#39;s Ruby.</p><p>If your server is still running, refresh the page and you&#39;ll see this wonderful UI (otherwise, start your server with <code>bin/dev</code>):</p><p><img src="`+p+`" alt="Screenshot of the page we built"></p><p>Let&#39;s make it a bit nicer.</p><h3 id="using-css" tabindex="-1">Using CSS <a class="header-anchor" href="#using-css" aria-label="Permalink to &quot;Using CSS&quot;">​</a></h3><p>Open up <code>app/src/front_end/css/index.css</code> in your editor. You should see this:</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;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;brut-css/dist/brut.css&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
161
141
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;svgs.css&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span></code></pre></div><p>Brut uses esbuild to bundle CSS. esbuild makes use of the standard <code>@import</code> directive. All <code>@imports</code> are relative to the current file or to <code>node_modules</code>. <code>brut-css/dist/brut.css</code> is the BrutCSS library that comes with Brut. We aren&#39;t going to use it, just to keep things focused. <code>svgs.css</code> is located in <code>app/src/front_end/css/svgs.css</code> and sets up a few classes for inline SVGs.</p><p>We&#39;ll add some CSS for the home page right here. We&#39;ll use vanilla CSS to avoid going on a deep dive on CSS frameworks.</p><p>Add this below <code>@import &quot;svgs.css&quot;;</code></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:#22863A;--shiki-dark:#85E89D;">body</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
162
142
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> width</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">50</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">%</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
163
143
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> margin-left</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">auto</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
@@ -171,7 +151,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
171
151
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> justify-content</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">space-between</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
172
152
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> width</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">100</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">%</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
173
153
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> gap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0.5</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">rem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
174
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>If you reload the home page in your browser, it now looks at least a little bit respectible:</p><p><img src="`+p+`" alt="Screenshot of the styled home page"></p><p>Now, let&#39;s build the blog post editor.</p><h2 id="creating-forms" tabindex="-1">Creating Forms <a class="header-anchor" href="#creating-forms" aria-label="Permalink to &quot;Creating Forms&quot;">​</a></h2><p>To create blog posts, we need three things:</p><ul><li>A page where the creation happens, which will host an HTML <code>&lt;form&gt;</code></li><li>A URL where that <code>&lt;form&gt;</code> will be submitted</li><li>Some code to handle the submissions</li></ul><h3 id="creating-a-new-page" tabindex="-1">Creating a New Page <a class="header-anchor" href="#creating-a-new-page" aria-label="Permalink to &quot;Creating a New Page&quot;">​</a></h3><p>To make a new page in Brut, we&#39;ll need to declare a route, and Brut will choose the class name. We&#39;ll use <code>/blog_post_editor</code>, meaning Brut will expect <code>BlogPostEditorPage</code> to exist. We can do all this at once with <code>bin/scaffold page</code>. <code>bin/scaffold page</code> accepts the URL of the page we want to build. Brut will use that URL to figure out the page class&#39; name and generate it, along with a failing test. It will also insert the route into <code>app.rb</code>. Run it now, like so:</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/scaffold</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> page</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /blog_post_editor</span></span></code></pre></div><p>Your output should look like so:</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>
154
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>If you reload the home page in your browser, it now looks at least a little bit respectible:</p><p><img src="`+l+`" alt="Screenshot of the styled home page"></p><p>Now, let&#39;s build the blog post editor.</p><h2 id="creating-forms" tabindex="-1">Creating Forms <a class="header-anchor" href="#creating-forms" aria-label="Permalink to &quot;Creating Forms&quot;">​</a></h2><p>To create blog posts, we need three things:</p><ul><li>A page where the creation happens, which will host an HTML <code>&lt;form&gt;</code></li><li>A URL where that <code>&lt;form&gt;</code> will be submitted</li><li>Some code to handle the submissions</li></ul><h3 id="creating-a-new-page" tabindex="-1">Creating a New Page <a class="header-anchor" href="#creating-a-new-page" aria-label="Permalink to &quot;Creating a New Page&quot;">​</a></h3><p>To make a new page in Brut, we&#39;ll need to declare a route, and Brut will choose the class name. We&#39;ll use <code>/blog_post_editor</code>, meaning Brut will expect <code>BlogPostEditorPage</code> to exist. We can do all this at once with <code>bin/scaffold page</code>. <code>bin/scaffold page</code> accepts the URL of the page we want to build. Brut will use that URL to figure out the page class&#39; name and generate it, along with a failing test. It will also insert the route into <code>app.rb</code>. Run it now, like so:</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/scaffold</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> page</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /blog_post_editor</span></span></code></pre></div><p>Your output should look like so:</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>
175
155
  <span class="line"><span>[ bin/scaffold ] Inserted route into app/src/app.rb</span></span>
176
156
  <span class="line"><span>[ bin/scaffold ] Page source is in app/src/front_end/pages/blog_post_editor_page.rb</span></span>
177
157
  <span class="line"><span>[ bin/scaffold ] Page test is in specs/front_end/pages/blog_post_editor_page.spec.rb</span></span>
@@ -725,4 +705,4 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
725
705
  <span class="line"><span>No vulnerabilities found</span></span>
726
706
  <span class="line"><span>[ bin/ci ] Checking to see that all classes have tests</span></span>
727
707
  <span class="line"><span>[ bin/test ] All tests exists!</span></span>
728
- <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>`,358)]))}const f=a(E,[["render",u]]);export{B as __pageData,f as default};
708
+ <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>`,324)]))}const f=i(E,[["render",y]]);export{v as __pageData,f as default};
@@ -0,0 +1 @@
1
+ import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t="/assets/welcome-to-brut.VSWzl17-.png",p="/assets/initial-home-page.DNIaYmgP.png",l="/assets/styled-home-page.BzdI7dWz.png",h="/assets/basic-form.DbHnu0oW.png",o="/assets/basic-form-with-violations.Cv6Y9-Q_.png",k="/assets/styled-form-with-violations.Bv_sa9tg.png",d="/assets/styled-form-with-server-side-violations.Bjxd8Dpv.png",r="/assets/styled-home-page-with-posts.Dd4kG89D.png",c="/assets/new-post-editor.DrHr-5oh.png",g="/assets/new-post-home-page.Bm34lyMg.png",v=JSON.parse('{"title":"Build a Blog in 15 Minutes","description":"","frontmatter":{},"headers":[],"relativePath":"tutorials/01-intro.md","filePath":"tutorials/01-intro.md"}'),E={name:"tutorials/01-intro.md"};function y(u,s,F,b,C,m){return n(),a("div",null,s[0]||(s[0]=[e("",324)]))}const f=i(E,[["render",y]]);export{v as __pageData,f as default};