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
@@ -0,0 +1,302 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-US" dir="ltr">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Tutorial: Styled Confirmation Dialog | Brut RB</title>
7
+ <meta name="description" content="Documentation for the Brut.RB web framework.">
8
+ <meta name="generator" content="VitePress v1.6.3">
9
+ <link rel="preload stylesheet" href="/assets/style.B1z60PPQ.css" as="style">
10
+ <link rel="preload stylesheet" href="/vp-icons.css" as="style">
11
+
12
+ <script type="module" src="/assets/app.0-aKXKdt.js"></script>
13
+ <link rel="modulepreload" href="/assets/chunks/theme.a6feKWJO.js">
14
+ <link rel="modulepreload" href="/assets/chunks/framework.1L-BeKqY.js">
15
+ <link rel="modulepreload" href="/assets/tutorials_02-dialog.md.CIeg8R--.lean.js">
16
+ <link rel="icon" href="/favicon.ico">
17
+ <meta property="og:title" content="BrutRB Documentation">
18
+ <meta property="og:type" content="website">
19
+ <meta property="og:image" content="https://brutrb.com/SocialImage.png">
20
+ <script defer data-domain="brutrb.com" src="https://plausible.io/js/script.js"></script>
21
+ <script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
22
+ <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
23
+ </head>
24
+ <body>
25
+ <div id="app"><div class="Layout" data-v-d8b57b2d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-fcbfc0e0></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-fcbfc0e0>Skip to content</a><!--]--><!----><header class="VPNav" data-v-d8b57b2d data-v-7ad780c2><div class="VPNavBar" data-v-7ad780c2 data-v-9fd4d1dd><div class="wrapper" data-v-9fd4d1dd><div class="container" data-v-9fd4d1dd><div class="title" data-v-9fd4d1dd><div class="VPNavBarTitle has-sidebar" data-v-9fd4d1dd data-v-9f43907a><a class="title" href="/" data-v-9f43907a><!--[--><!--]--><!----><span data-v-9f43907a>Brut RB</span><!--[--><!--]--></a></div></div><div class="content" data-v-9fd4d1dd><div class="content-body" data-v-9fd4d1dd><!--[--><!--]--><div class="VPNavBarSearch search" data-v-9fd4d1dd><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-9fd4d1dd data-v-afb2845e><span id="main-nav-aria-label" class="visually-hidden" data-v-afb2845e> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/getting-started.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Getting Started</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/overview.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Overview</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/api/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Brut API</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/brut-js/api/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>BrutJS</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/brut-css/index.html" target="_self" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>BrutCSS</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-9fd4d1dd data-v-3f90c1a5><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-3f90c1a5 data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-9fd4d1dd data-v-ef6192dc data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/thirdtank/brut" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-9fd4d1dd data-v-f953d92f data-v-bfe7971f><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-bfe7971f><span class="vpi-more-horizontal icon" data-v-bfe7971f></span></button><div class="menu" data-v-bfe7971f><div class="VPMenu" data-v-bfe7971f data-v-20ed86d6><!----><!--[--><!--[--><!----><div class="group" data-v-f953d92f><div class="item appearance" data-v-f953d92f><p class="label" data-v-f953d92f>Appearance</p><div class="appearance-action" data-v-f953d92f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-f953d92f data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div></div></div><div class="group" data-v-f953d92f><div class="item social-links" data-v-f953d92f><div class="VPSocialLinks social-links-list" data-v-f953d92f data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/thirdtank/brut" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-9fd4d1dd data-v-6bee1efd><span class="container" data-v-6bee1efd><span class="top" data-v-6bee1efd></span><span class="middle" data-v-6bee1efd></span><span class="bottom" data-v-6bee1efd></span></span></button></div></div></div></div><div class="divider" data-v-9fd4d1dd><div class="divider-line" data-v-9fd4d1dd></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-d8b57b2d data-v-2488c25a><div class="container" data-v-2488c25a><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-2488c25a><span class="vpi-align-left menu-icon" data-v-2488c25a></span><span class="menu-text" data-v-2488c25a>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-2488c25a data-v-6b867909><button data-v-6b867909>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-d8b57b2d data-v-42c4c606><div class="curtain" data-v-42c4c606></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-42c4c606><span class="visually-hidden" id="sidebar-aria-label" data-v-42c4c606> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Overview</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/getting-started.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Getting Started</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/overview.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Concepts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/features.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Features</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/dir-structure.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Directory Structure</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/dev-environment.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Dev Environment</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/tutorial.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Tutorial</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/doc-conventions.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Documentation Conventions</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Front-End</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/routes.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Routes</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/pages.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Pages</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/layouts.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Layouts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/forms.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Forms</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/form-constraints.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Form Constraints</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/handlers.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Handlers and Actions</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/components.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Components</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/flash-and-session.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Flash and Session</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/space-time-continuum.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Space/Time Continuum</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/javascript.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>JavaScript</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/css.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>CSS</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/assets.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Assets</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/brut-js.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>BrutJS</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Back-End</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/database-schema.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Database Schema</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/database-access.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Database Access</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/seed-data.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Seed Data</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/jobs.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Jobs</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/business-logic.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Business Logic</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Framework</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/configuration.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Configuration</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/keyword-injection.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Keyword Injection</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/i18n.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>I18n</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/cli.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>CLI / Tasks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/deployment.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Deployment</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Testing</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/unit-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Unit Tests</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/end-to-end-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>End-to-End Tests</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/custom-element-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Testing Custom Elements</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible collapsed" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Advanced Topics</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/hooks.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Route Hooks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/middleware.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Middleware</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/instrumentation.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Instrumentation</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/security.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Security</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/lsp.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>LSP Support</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible collapsed" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Recipes</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/migrations.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Migration Basics</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/form-errors.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Styling Form Errors</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/authentication.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Authentication</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/alternate-layouts.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Alternate Layouts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/blank-layouts.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Blank Layouts</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/custom-flash.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Custom Flash Class</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/indexed-forms.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Indexed Form Elements</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/recipes/text-field-component.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Text Field Component</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 collapsible" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Meta</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-0009425e><span class="vpi-chevron-right caret-icon" data-v-0009425e></span></div></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/why.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Why?!</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/adrs.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>ADRs</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/roadmap.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Roadmap to 1.0</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/ai.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>AI Declaration</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-d8b57b2d data-v-9a6c75ad><div class="VPDoc has-sidebar has-aside" data-v-9a6c75ad data-v-e6f2a212><!--[--><!--]--><div class="container" data-v-e6f2a212><div class="aside" data-v-e6f2a212><div class="aside-curtain" data-v-e6f2a212></div><div class="aside-container" data-v-e6f2a212><div class="aside-content" data-v-e6f2a212><div class="VPDocAside" data-v-e6f2a212 data-v-cb998dce><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-cb998dce data-v-f610f197><div class="content" data-v-f610f197><div class="outline-marker" data-v-f610f197></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-f610f197>On this page</div><ul class="VPDocOutlineItem root" data-v-f610f197 data-v-53c99d69><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-cb998dce></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-e6f2a212><div class="content-container" data-v-e6f2a212><!--[--><!--]--><main class="main" data-v-e6f2a212><div style="position:relative;" class="vp-doc _tutorials_02-dialog" data-v-e6f2a212><div><h1 id="tutorial-styled-confirmation-dialog" tabindex="-1">Tutorial: Styled Confirmation Dialog <a class="header-anchor" href="#tutorial-styled-confirmation-dialog" aria-label="Permalink to &quot;Tutorial: Styled Confirmation Dialog&quot;">​</a></h1><p>For actions that can&#39;t be undone, it&#39;s customary to confirm with the visitor that they are sure they want to take that action. Brut provides support for this. You can use <code>window.confirm</code> or create your own styled <code>&lt;dialog&gt;</code> that Brut will use. Both approaches don&#39;t require writing any JavaScript yourself.</p><p><a href="https://video.hardlimit.com/w/4y8Pjd8VVPDK372mozCUdj" target="_blank" rel="noreferrer">You can watching this as a screencast instead</a>.</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>If you haven&#39;t followed the <a href="/tutorials/01-intro.html">initial tutorial</a>, you&#39;ll need to pull down the blog app so you have a place to work.</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>Clone the <code>blog-demo</code> repo (<strong>don&#39;t use Codespaces as it is not supported</strong>):</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-sAqA3" id="tab-3IT36g5" checked><label data-title="Terminal" for="tab-3IT36g5">Terminal</label><input type="radio" name="group-sAqA3" id="tab-ae4ZUeD"><label data-title="GitHub CLI" for="tab-ae4ZUeD">GitHub CLI</label></div><div class="blocks"><div class="language-bash vp-adaptive-theme active"><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;"> clone</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> git@github.com:thirdtank/blog-demo.git</span></span></code></pre></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">gh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> repo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> clone</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> thirdtank/blog-demo</span></span></code></pre></div></div></div></li><li><p><code>cd</code> to what you just cloned.</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;">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> blog-demo</span></span></code></pre></div></li><li><p>Create a branch named <code>confirmation-dialog</code> off of the <code>02-confirmation-dialog/start</code> branch:</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;"> checkout</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -b</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> confirmation-dialog</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 02-confirmation-dialog/start</span></span></code></pre></div></li><li><p>Build your development image.</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;">dx/build</span></span></code></pre></div></li><li><p>Start the environment, which will pull down Postgres and otel-desktop-viewer</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;">dx/start</span></span></code></pre></div></li><li><p>In another terminal window, &quot;log in&quot; to your dev environment (note that you can use your editor on your computer to edit code)</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dx/exec</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> bash</span></span></code></pre></div></li><li><p>Set up and run tests to make sure things are working before you start making changes. Note, this is <strong>inside the container</strong>, not directly on your computer.</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/setup</span></span>
26
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/ci</span></span></code></pre></div></li></ol><h2 id="what-we-re-doing" tabindex="-1">What We&#39;re Doing <a class="header-anchor" href="#what-we-re-doing" aria-label="Permalink to &quot;What We&#39;re Doing&quot;">​</a></h2><p>When writing a blog post, if the title and content satisfy all constraints, the post is saved and shown on the home page. Because this can&#39;t currently be undone, we want the user to confirm the posting, just to avoid any accidents.</p><p>Initially, we will use <code>window.confirm</code> to do this. After that, we&#39;ll create a nicely styled dialog to do the confirmation. While this will require that the browser execute JavaScript, we won&#39;t be writing any. We&#39;ll use Brut-provided Web Components to do this.</p><p><img src="/assets/02-confirmation-flow.D9gZ0S5U.png" alt="Diagram showing the flow, with a screenshot of the blog post editor on the left, and a pink arrow from
27
+ the &#39;Post it&#39; button going to the text &#39;Are You Sure?&#39;. From there, a pink line labeled &#39;No&#39; goes back
28
+ to the editor, while a pink line labeled &#39;Yes&#39; goes to a screenshot of the home page showing the blog
29
+ post."></p><h2 id="initial-version-using-window-confirm" tabindex="-1">Initial Version Using <code>window.confirm</code> <a class="header-anchor" href="#initial-version-using-window-confirm" aria-label="Permalink to &quot;Initial Version Using `window.confirm`&quot;">​</a></h2><p>Brut includes an <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements" target="_blank" rel="noreferrer">autonomous custom element</a> named <a href="/brut-js/api/ConfirmSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirm-submit&gt;</code></a>. This element wraps an existing submit button and intercepts its form submission to ask for confirmation. If confirmation is granted, the form is submitted. If not, it&#39;s not.</p><p>It is used on a per-button basis, which gives you flexibility in handling what buttons do what within the form. It <em>only</em> works on <code>&lt;button&gt;</code> and <code>&lt;input type=&quot;submit&quot;&gt;</code> elements.</p><div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</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;">&lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">form</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> ...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
30
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">input</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> ...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
31
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-confirm-submit</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> message</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;You sure?&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
32
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;Submit&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">&lt;!-- if clicked, confirmation is requested --&gt;</span></span>
33
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-confirm-submit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
34
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;Also Submit&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">&lt;!-- if clicked, form is submitted --&gt;</span></span>
35
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;/</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">form</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span></code></pre></div><h3 id="adding-confirmation-to-blog-posting" tabindex="-1">Adding Confirmation to Blog Posting <a class="header-anchor" href="#adding-confirmation-to-blog-posting" aria-label="Permalink to &quot;Adding Confirmation to Blog Posting&quot;">​</a></h3><p>We can use it on <code>BlogPostEditorPage</code>. Open up <code>app/src/front_end/pages/blog_post_editor_page.rb</code> and make this change toward the end of <code>page_template</code></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 highlighted"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">brut_confirm_submit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
36
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> message:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;This will post immediately to the home page&quot;</span></span>
37
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
38
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> button { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">t</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 style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:post</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]) }</span></span>
39
+ <span class="line highlighted"><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">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br></div></div><p>The method <code>brut_confirm_submit</code> is provided by Phlex due to a call to <a href="https://www.phlex.fun/sgml/html-elements.html#custom-elements" target="_blank" rel="noreferrer"><code>register_element</code></a> in <a href="/api/Brut/FrontEnd/Component.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Component</code></a>.</p><p>Now, start up your server using <code>bin/dev</code>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/dev</span></span></code></pre></div><div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
40
+ <span class="line"><span>« LOTS OF OUTPUT »</span></span>
41
+ <span class="line"><span>15:50:10 startup_message.1 | Your app is now running at</span></span>
42
+ <span class="line"><span>15:50:10 startup_message.1 | </span></span>
43
+ <span class="line"><span>15:50:10 startup_message.1 | http://localhost:6502</span></span>
44
+ <span class="line"><span>15:50:10 startup_message.1 |</span></span></code></pre></div><p>Open <code>http://localhost:6502</code> in your browser, then click &quot;Write New Blog Post&quot;, write a valid post and click &quot;Post It&quot;. You should see the browser&#39;s <code>window.confirm</code> show up with the value for <code>message:</code> as the message.</p><p><img src="/assets/02-confirmation-dialog-browser.DH8ALFO4.png" alt="Screenshot showing the browser&#39;s builtin confirmation dialog"></p><p>Click &quot;Cancel&quot; and the dialog goes away and nothing is posted. Click &quot;Post It&quot; again, then click &quot;OK&quot;, and the post goes through as normal.</p><p>Even though we are going to build our own dialog, let&#39;s keep our end-to-end test working.</p><h3 id="interacting-with-window-confirm-in-end-to-end-tests" tabindex="-1">Interacting with <code>window.confirm</code> in End-to-End Tests <a class="header-anchor" href="#interacting-with-window-confirm-in-end-to-end-tests" aria-label="Permalink to &quot;Interacting with `window.confirm` in End-to-End Tests&quot;">​</a></h3><p>Let&#39;s start by seeing how the test fails:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> e2e</span></span></code></pre></div><div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
45
+ <span class="line"><span>&gt; bin/test e2e</span></span>
46
+ <span class="line"><span>[ bin/test ] Rebuilding test database schema</span></span>
47
+ <span class="line"><span>[ bin/test ] Executing [&quot;bin/db rebuild --env=test&quot;]</span></span>
48
+ <span class="line"><span>[ bin/db ] Database exists. Dropping...</span></span>
49
+ <span class="line"><span>[ bin/db ] blog_test does not exit. Creating...</span></span>
50
+ <span class="line"><span>[ bin/db ] Migrations applied</span></span>
51
+ <span class="line"><span>[ bin/test ] [&quot;bin/db rebuild --env=test&quot;] succeeded</span></span>
52
+ <span class="line"><span>[ bin/test ] Running all tests</span></span>
53
+ <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/blog-demo/specs -I /Users/davec/Projects/ThirdTank/blog-demo/app/src -I lib/ --tag e2e -P \&quot;**/*.spec.rb\&quot; /Users/davec/Projects/ThirdTank/blog-demo/specs/&quot;]</span></span>
54
+ <span class="line"><span></span></span>
55
+ <span class="line"><span>«TONS OF OUTPUT»</span></span>
56
+ <span class="line"><span></span></span>
57
+ <span class="line"><span>Failures:</span></span>
58
+ <span class="line"><span></span></span>
59
+ <span class="line"><span> 1) We can post a new blog post allows posting a post</span></span>
60
+ <span class="line"><span> Failure/Error: expect(content_error_message).to have_text(&quot;This field does not have enough words&quot;)</span></span>
61
+ <span class="line"><span></span></span>
62
+ <span class="line"><span> /Users/davec/Projects/ThirdTank/blog-demo/local-gems/gem-home/gems/playwright-ruby-client-1.52.0/lib/playwright/locator_assertions_impl.rb:53:in &#39;Playwright::LocatorAssertionsImpl#expect_impl&#39;: (Playwright::AssertionError)</span></span>
63
+ <span class="line highlighted"><span> Locator expected to have text &#39;This field does not have enough words&#39;</span></span>
64
+ <span class="line highlighted"><span> Actual value &lt;element(s) not found&gt; </span></span>
65
+ <span class="line"><span> Call log:</span></span>
66
+ <span class="line"><span> - locator#Playwright::Locator#expect with timeout 5000ms</span></span>
67
+ <span class="line"><span> - waiting for locator(&quot;brut-cv-messages[input-name=&#39;content&#39;] brut-cv&quot;)</span></span>
68
+ <span class="line"><span> from /Users/davec/Projects/ThirdTank/blog-demo/local-gems/gem-home/gems/playwright-ruby-client-1.52.0/lib/playwright/locator_assertions_impl.rb:397:in &#39;Playwright::LocatorAssertionsImpl#to_have_text&#39;</span></span>
69
+ <span class="line"><span></span></span>
70
+ <span class="line"><span>«MASSIVE STACK TRACE»</span></span>
71
+ <span class="line"><span></span></span>
72
+ <span class="line"><span> from /Users/davec/Projects/ThirdTank/blog-demo/local-gems/gem-home/gems/rspec-core-3.13.5/lib/rspec/core/runner.rb:45:in &#39;RSpec::Core::Runner.invoke&#39;</span></span>
73
+ <span class="line"><span> from /Users/davec/Projects/ThirdTank/blog-demo/local-gems/gem-home/gems/rspec-core-3.13.5/exe/rspec:4:in &#39;&lt;top (required)&gt;&#39;</span></span>
74
+ <span class="line"><span> from bin/rspec:16:in &#39;Kernel#load&#39;</span></span>
75
+ <span class="line"><span> from bin/rspec:16:in &#39;&lt;main&gt;&#39;</span></span>
76
+ <span class="line highlighted"><span> # ./specs/e2e/home_page.spec.rb:34:in &#39;block (2 levels) in &lt;top (required)&gt;&#39;</span></span>
77
+ <span class="line"><span></span></span>
78
+ <span class="line"><span>«MASSIVE STACK TRACE»</span></span>
79
+ <span class="line"><span></span></span>
80
+ <span class="line"><span> # ./local-gems/gem-home/gems/brut-0.5.0/lib/brut/spec_support/rspec_setup.rb:129:in &#39;block in Brut::SpecSupport::RSpecSetup#setup!&#39;</span></span>
81
+ <span class="line"><span></span></span>
82
+ <span class="line"><span>Finished in 7.6 seconds (files took 0.7169 seconds to load)</span></span>
83
+ <span class="line"><span>1 example, 1 failure</span></span>
84
+ <span class="line"><span></span></span>
85
+ <span class="line"><span>Failed examples:</span></span>
86
+ <span class="line"><span></span></span>
87
+ <span class="line"><span>bin/test run ./specs/e2e/home_page.spec.rb:4 # We can post a new blog post allows posting a post</span></span>
88
+ <span class="line"><span></span></span>
89
+ <span class="line"><span>Randomized with seed 25427</span></span>
90
+ <span class="line"><span></span></span>
91
+ <span class="line"><span>[ bin/test ] error: [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/blog-demo/specs -I /Users/davec/Projects/ThirdTank/blog-demo/app/src -I lib/ --tag e2e -P \&quot;**/*.spec.rb\&quot; /Users/davec/Projects/ThirdTank/blog-demo/specs/&quot;] failed - exited 1</span></span></code></pre></div><p>I&#39;ve highlighted the relevant parts. Playwright loves stack traces and obtuse errors.</p><p>Let&#39;s look at line 34 of <code>specs/e2e/home_page.spec.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:#6F42C1;--shiki-dark:#B392F0;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(title_error_message).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> have_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This field is too short&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
92
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(content_error_message).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> have_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This field is required&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
93
+ <span class="line"></span>
94
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">title_field.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fill</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;New blog post&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
95
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">content_field.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fill</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Too short&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
96
+ <span class="line"></span>
97
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">submit_button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span>
98
+ <span class="line"></span>
99
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(page).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> be_page_for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">BlogPostEditorPage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
100
+ <span class="line"></span>
101
+ <span class="line highlighted"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(content_error_message).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> have_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This field does not have enough words&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
102
+ <span class="line"></span>
103
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">content_field.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fill</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This is a longer post, so we should be OK&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
104
+ <span class="line"></span>
105
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">submit_button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span>
106
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(page).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> be_page_for</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></code></pre></div><p>The test was expecting to hit the server and re-generate the page with a server-side error message. Although <a href="/brut-js/api/ConfirmSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirm-submit&gt;</code></a> did not pop up when there were client-side constraint violations, it doesn&#39;t know there are server-side ones, so it is waiting for us to confirm the submission.</p><p>Playwright will <a href="https://playwright.dev/docs/dialogs" target="_blank" rel="noreferrer">automatically dismiss any browser-based dialogs</a>. To handle them, our test will need to register a handler. To do this with Ruby, we&#39;ll call <code>page.on</code> and given it an event name and a block to handle the event.</p><p>The event name is &quot;dialog&quot; and a Playwright <code>Dialog</code> will be passed. We can call <code>accept</code> on that.</p><p>Here&#39;s the change. Note the line numbers for reference in the file. You want to set this up before <code>submit_button.click</code> is called.</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:#24292E;--shiki-dark:#E1E4E8;"> content_field.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fill</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Too short&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
107
+ <span class="line"></span>
108
+ <span class="line highlighted"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> accept_dialog</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">-&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(dialog) {</span></span>
109
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> dialog.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">accept</span></span>
110
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
111
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;dialog&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,accept_dialog)</span></span>
112
+ <span class="line"></span>
113
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> submit_button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br></div></div><p>Note that this configuration will stay in effect for the rest of the test. That means when we later save the blog post, it will accept the dialog.</p><p>Now, <code>bin/test e2e</code> should pass:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> e2e</span></span></code></pre></div><div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>#OUTPUT</span></span>
114
+ <span class="line"><span>[ bin/test ] Rebuilding test database schema</span></span>
115
+ <span class="line"><span>[ bin/test ] Executing [&quot;bin/db rebuild --env=test&quot;]</span></span>
116
+ <span class="line"><span>[ bin/db ] Database exists. Dropping...</span></span>
117
+ <span class="line"><span>[ bin/db ] blog_test does not exit. Creating...</span></span>
118
+ <span class="line"><span>[ bin/db ] Migrations applied</span></span>
119
+ <span class="line"><span>[ bin/test ] [&quot;bin/db rebuild --env=test&quot;] succeeded</span></span>
120
+ <span class="line"><span>[ bin/test ] Running all tests</span></span>
121
+ <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/blog-demo/specs -I /Users/davec/Projects/ThirdTank/blog-demo/app/src -I lib/ --tag e2e -P \&quot;**/*.spec.rb\&quot; /Users/davec/Projects/ThirdTank/blog-demo/specs/&quot;]</span></span>
122
+ <span class="line"><span></span></span>
123
+ <span class="line"><span>«TONS OF OUTPUT»</span></span>
124
+ <span class="line"><span></span></span>
125
+ <span class="line"><span>Finished in 3.57 seconds (files took 0.7341 seconds to load)</span></span>
126
+ <span class="line highlighted"><span>1 example, 0 failures</span></span>
127
+ <span class="line"><span></span></span>
128
+ <span class="line"><span>Randomized with seed 1445</span></span>
129
+ <span class="line"><span></span></span>
130
+ <span class="line"><span>[ bin/test ] [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/blog-demo/specs -I /Users/davec/Projects/ThirdTank/blog-demo/app/src -I lib/ --tag e2e -P \&quot;**/*.spec.rb\&quot; /Users/davec/Projects/ThirdTank/blog-demo/specs/&quot;] succeeded</span></span>
131
+ <span class="line"><span>[ bin/test ] Re-Rebuilding test database schema</span></span>
132
+ <span class="line"><span>[ bin/test ] Executing [&quot;bin/db rebuild --env=test&quot;]</span></span>
133
+ <span class="line"><span>[ bin/db ] Database exists. Dropping...</span></span>
134
+ <span class="line"><span>[ bin/db ] blog_test does not exit. Creating...</span></span>
135
+ <span class="line"><span>[ bin/db ] Migrations applied</span></span>
136
+ <span class="line"><span>[ bin/test ] [&quot;bin/db rebuild --env=test&quot;] succeeded</span></span></code></pre></div><p><code>window.confirm</code> is great in a pinch, but we&#39;d like to use our own styled dialog if possible.</p><h2 id="using-a-styled-dialog" tabindex="-1">Using a Styled Dialog <a class="header-anchor" href="#using-a-styled-dialog" aria-label="Permalink to &quot;Using a Styled Dialog&quot;">​</a></h2><p>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog" target="_blank" rel="noreferrer"><code>&lt;dialog&gt;</code></a> element has been available since 2022 and provides some of what we&#39;ll need to confirm a blog post. Brut can enhance a <code>&lt;dialog&gt;</code> to act as a confirmation dialog by using the <a href="/brut-js/api/ConfirmationDialog.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirmation-dialog&gt;</code></a> custom element.</p><p>Like <a href="/brut-js/api/ConfirmSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirm-submit&gt;</code></a>, it wraps an element and enhances it. To work, the <code>&lt;dialog&gt;</code> must include certain elements to represent the message, a button for consent, and a button for denial.</p><p>Let&#39;s see it in action.</p><h3 id="creating-a-styled-dialog" tabindex="-1">Creating a Styled Dialog <a class="header-anchor" href="#creating-a-styled-dialog" aria-label="Permalink to &quot;Creating a Styled Dialog&quot;">​</a></h3><p>Edit <code>app/src/front_end/pages/blog_post_editor_page.rb</code> and add the dialog at the end of <code>page_template</code>:</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;"> brut_confirm_submit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
137
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> message:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;This will post immediately to the home page&quot;</span></span>
138
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
139
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> button { </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">t</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 style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:post</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>
141
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
142
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span>
143
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">brut_confirmation_dialog </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
144
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> dialog </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
145
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h1</span></span>
146
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> div </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
147
+ <span class="line highlighted"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">value:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;ok&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
148
+ <span class="line highlighted"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">value:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;cancel&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Don&#39;t Publish&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
149
+ <span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
150
+ <span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
151
+ <span class="line highlighted"><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">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br></div></div><p>Your browser should provide a default visual style for the dialog (that is terrible), but you can see that <a href="/brut-js/api/ConfirmSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirm-submit&gt;</code></a> will now use it when you click &quot;Post It&quot;:</p><p><img src="/assets/02-confirmation-dialog-browser-element.DPsf0xUW.png" alt="Screenshot showing the browser&#39;s styling of a dialog element"></p><p><a href="/brut-js/api/ConfirmSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirm-submit&gt;</code></a> and <a href="/brut-js/api/ConfirmationDialog.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirmation-dialog&gt;</code></a> work together to allow you to style these dialog how you&#39;d like. It expects an <code>h1</code> element inside where the message will go. It expects a <code>&lt;button value=&quot;ok&quot;&gt;</code> that, when clicked, indicates the visitor is accepting the dialog. A <code>&lt;button value=&quot;cancel&quot;&gt;</code> should also be present that, when clicked, indicates the visitor wants to abort and not submit the form.</p><p>If you&#39;ve never worked with a <code>&lt;dialog&gt;</code> before, it can be handy to set <code>open</code> on the element so it shows up without having to click something to open it. It doesn&#39;t show exactly as it would when we use JavaScript to show it, but it&#39;s good enough to get your styling work done:</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:#6F42C1;--shiki-dark:#B392F0;">dialog</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">open:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
152
+ <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
153
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Here&#39;s the CSS I chose. Add this to <code>app/src/front_end/css/index.css</code>, inside the <code>.BlogPostEditorPage</code> block:</p><div class="language-css vp-adaptive-theme line-numbers-mode"><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;"> cursor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: pointer;</span></span>
154
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &amp;</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">:hover</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
155
+ <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;">#ACFFAC</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
156
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
157
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
158
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
159
+ <span class="line highlighted"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> brut-confirmation-dialog</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> dialog</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
160
+ <span class="line highlighted"><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>
161
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> border-width</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
162
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> box-shadow</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">rgb</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">200</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">200</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">200</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;">px</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">px</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 12.72</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">px</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 3.46892</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">px</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
163
+ <span class="line highlighted"><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;">white</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
164
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> padding</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>
165
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> h</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">1 {</span></span>
166
+ <span class="line highlighted"><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;">black</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
167
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> font-size</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">rem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
168
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
169
+ <span class="line highlighted"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
170
+ <span class="line highlighted"><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>
171
+ <span class="line highlighted"><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;">flex</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
172
+ <span class="line highlighted"><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.25</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">rem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
173
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> align-items</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">center</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
174
+ <span class="line highlighted"><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>
175
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
176
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> padding-left</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">rem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
177
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> padding-right</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">rem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
178
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> padding-top</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>
179
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> padding-bottom</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>
180
+ <span class="line highlighted"><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>
181
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> font-size</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">150</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">%</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
182
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> align-self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">end</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
183
+ <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> cursor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">pointer</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
184
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &amp;[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">value</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=&quot;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">ok</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&quot;] {</span></span>
185
+ <span class="line highlighted"><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;">#E5FFE5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
186
+ <span class="line highlighted"><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;"> #006300</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
187
+ <span class="line highlighted"><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;">#006300</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
188
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
189
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &amp;[</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">value</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;cancel&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">] {</span></span>
190
+ <span class="line highlighted"><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;">#FFE5E5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
191
+ <span class="line highlighted"><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;"> #630000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
192
+ <span class="line highlighted"><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;">#630000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
193
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
194
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
195
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
196
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
197
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
198
+ <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">brut-cv</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
199
+ <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>
200
+ <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;">#A60053</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br><span class="line-number">80</span><br><span class="line-number">81</span><br><span class="line-number">82</span><br><span class="line-number">83</span><br><span class="line-number">84</span><br><span class="line-number">85</span><br><span class="line-number">86</span><br><span class="line-number">87</span><br><span class="line-number">88</span><br><span class="line-number">89</span><br><span class="line-number">90</span><br><span class="line-number">91</span><br><span class="line-number">92</span><br><span class="line-number">93</span><br><span class="line-number">94</span><br><span class="line-number">95</span><br><span class="line-number">96</span><br><span class="line-number">97</span><br><span class="line-number">98</span><br><span class="line-number">99</span><br><span class="line-number">100</span><br><span class="line-number">101</span><br><span class="line-number">102</span><br><span class="line-number">103</span><br><span class="line-number">104</span><br><span class="line-number">105</span><br><span class="line-number">106</span><br></div></div><p>Now, reload the page and click &quot;Post It&quot;. You should see a somewhat nicer dialog:</p><p><img src="/assets/02-confirmation-dialog-browser-element-styled.3NEGM20-.png" alt="Screenshot showing the our styling of a dialog element"></p><p>And, sure enough if you click &quot;Don&#39;t Publish&quot;, the dialog clears and nothing happens. If you click &quot;Post It!&quot;, it submits the form.</p><p>A few notes on how this works:</p><ul><li>The contents of the <code>&lt;h1&gt;</code> come from the <code>message</code> attribute of the <strong><a href="/brut-js/api/ConfirmSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirm-submit&gt;</code></a></strong>. This allows you to re-use the confirmation dialog for other purposes.</li><li>The content of the <code>&lt;button value=&quot;ok&quot; ...&gt;</code> is the same as the button wrapped by <a href="/brut-js/api/ConfirmSubmit.html" target="_self" rel="noopener" data-no-router><code style="white-space:nowrap;">&lt;brut-confirm-submit&gt;</code></a>.</li></ul><p>Also note how the use of semantic and standard HTML allows us to style the elements without classes or <code>data-</code> tags.</p><p>Let&#39;s look back at our tests.</p><h3 id="interacting-with-our-dialog-in-tests" tabindex="-1">Interacting with Our Dialog in Tests <a class="header-anchor" href="#interacting-with-our-dialog-in-tests" aria-label="Permalink to &quot;Interacting with Our Dialog in Tests&quot;">​</a></h3><p>Run our end-to-end test:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> e2e</span></span></code></pre></div><p>It should fail:</p><div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>#OUTPUT</span></span>
201
+ <span class="line"><span>&gt; bin/test e2e</span></span>
202
+ <span class="line"><span>[ bin/test ] Rebuilding test database schema</span></span>
203
+ <span class="line"><span>[ bin/test ] Executing [&quot;bin/db rebuild --env=test&quot;]</span></span>
204
+ <span class="line"><span>[ bin/db ] Database exists. Dropping...</span></span>
205
+ <span class="line"><span>[ bin/db ] blog_test does not exit. Creating...</span></span>
206
+ <span class="line"><span>[ bin/db ] Migrations applied</span></span>
207
+ <span class="line"><span>[ bin/test ] [&quot;bin/db rebuild --env=test&quot;] succeeded</span></span>
208
+ <span class="line"><span></span></span>
209
+ <span class="line"><span>«TONS OF OUTPUT»</span></span>
210
+ <span class="line"><span></span></span>
211
+ <span class="line"><span>Failures:</span></span>
212
+ <span class="line"><span></span></span>
213
+ <span class="line"><span> 1) We can post a new blog post allows posting a post</span></span>
214
+ <span class="line"><span> Failure/Error: expect(content_error_message).to have_text(&quot;This field does not have enough words&quot;)</span></span>
215
+ <span class="line"><span></span></span>
216
+ <span class="line"><span> /Users/davec/Projects/ThirdTank/blog-demo/local-gems/gem-home/gems/playwright-ruby-client-1.52.0/lib/playwright/locator_assertions_impl.rb:53:in &#39;Playwright::LocatorAssertionsImpl#expect_impl&#39;: (Playwright::AssertionError)</span></span>
217
+ <span class="line highlighted"><span> Locator expected to have text &#39;This field does not have enough words&#39;</span></span>
218
+ <span class="line highlighted"><span> Actual value &lt;element(s) not found&gt; </span></span>
219
+ <span class="line"><span> Call log:</span></span>
220
+ <span class="line"><span> - locator#Playwright::Locator#expect with timeout 5000ms</span></span>
221
+ <span class="line"><span> - waiting for locator(&quot;brut-cv-messages[input-name=&#39;content&#39;] brut-cv&quot;)</span></span>
222
+ <span class="line"><span> from /Users/davec/Projects/ThirdTank/blog-demo/local-gems/gem-home/gems/playwright-ruby-client-1.52.0/lib/playwright/locator_assertions_impl.rb:397:in &#39;Playwright::LocatorAssertionsImpl#to_have_text&#39;</span></span>
223
+ <span class="line"><span> from /Users/davec/Projects/ThirdTank/blog-demo/local-gems/gem-home/gems/playwright-ruby-client-1.52.0/lib/playwright_api/locator_assertions.rb:642:in &#39;Playwright::LocatorAssertions#to_have_text&#39;</span></span>
224
+ <span class="line"><span></span></span>
225
+ <span class="line"><span>«HUGE STACK TRACE»</span></span>
226
+ <span class="line"><span></span></span>
227
+ <span class="line"><span> from /Users/davec/Projects/ThirdTank/blog-demo/local-gems/gem-home/gems/rspec-core-3.13.5/lib/rspec/core/runner.rb:45:in &#39;RSpec::Core::Runner.invoke&#39;</span></span>
228
+ <span class="line"><span> from /Users/davec/Projects/ThirdTank/blog-demo/local-gems/gem-home/gems/rspec-core-3.13.5/exe/rspec:4:in &#39;&lt;top (required)&gt;&#39;</span></span>
229
+ <span class="line"><span> from bin/rspec:16:in &#39;Kernel#load&#39;</span></span>
230
+ <span class="line"><span> from bin/rspec:16:in &#39;&lt;main&gt;&#39;</span></span>
231
+ <span class="line"><span> # ./specs/e2e/home_page.spec.rb:39:in &#39;block (2 levels) in &lt;top (required)&gt;&#39;</span></span>
232
+ <span class="line highlighted"><span></span></span>
233
+ <span class="line"><span>«HUGE STACK TRACE»</span></span>
234
+ <span class="line"><span></span></span>
235
+ <span class="line"><span> # ./local-gems/gem-home/gems/brut-0.5.0/lib/brut/spec_support/rspec_setup.rb:185:in &#39;Brut::SpecSupport::RSpecSetup::OptionalSidekiqSupport#disable_sidekiq_testing&#39;</span></span>
236
+ <span class="line"><span> # ./local-gems/gem-home/gems/brut-0.5.0/lib/brut/spec_support/rspec_setup.rb:129:in &#39;block in Brut::SpecSupport::RSpecSetup#setup!&#39;</span></span>
237
+ <span class="line"><span></span></span>
238
+ <span class="line"><span>Finished in 8.31 seconds (files took 0.66944 seconds to load)</span></span>
239
+ <span class="line"><span>1 example, 1 failure</span></span>
240
+ <span class="line"><span></span></span>
241
+ <span class="line"><span>Failed examples:</span></span>
242
+ <span class="line"><span></span></span>
243
+ <span class="line"><span>bin/test run ./specs/e2e/home_page.spec.rb:4 # We can post a new blog post allows posting a post</span></span>
244
+ <span class="line"><span></span></span>
245
+ <span class="line"><span>Randomized with seed 29349</span></span>
246
+ <span class="line"><span></span></span>
247
+ <span class="line"><span>[ bin/test ] error: [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/blog-demo/specs -I /Users/davec/Projects/ThirdTank/blog-demo/app/src -I lib/ --tag e2e -P \&quot;**/*.spec.rb\&quot; /Users/davec/Projects/ThirdTank/blog-demo/specs/&quot;] failed - exited 1</span></span></code></pre></div><p>Line 39 is the same line that failed when we first added the confirmation. Since Playwright interacts with browser dialogs via an event, the event listener we added is never fired, so our error is simply that the page didn&#39;t refresh.</p><p>Let&#39;s remove the listener and instead interact with the new dialog. We should click &quot;cancel&quot; to make sure it doens&#39;t do anything, then click &quot;ok&quot;.</p><p>One problem with Playwright (well, with web pages in general) is that it&#39;s not easy to assert that something didn&#39;t happen or isn&#39;t there. We can&#39;t click the cancel button, then assert that there is no error message.</p><p>Instead, we&#39;ll assert that the dialog is not being shown.</p><p>To do that, we&#39;ll locate the dialog, the ok button, and the cancel button. The assertion that the dialog isn&#39;t shown requires accessing the JavaScript <code>open</code> property and checking that it&#39;s false. The rest of the test works as before, punctuated with calls to <code>dialog_ok_button.click</code> to accept the dialog.</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;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(content_error_message).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> have_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This field is required&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
248
+ <span class="line"></span>
249
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">title_field.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fill</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;New blog post&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
250
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">content_field.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fill</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Too short&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
251
+ <span class="line"></span>
252
+ <span class="line highlighted"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">dialog</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">locator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;brut-confirmation-dialog dialog&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
253
+ <span class="line highlighted"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">dialog_ok_button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">locator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;brut-confirmation-dialog button[value=&#39;ok&#39;]&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
254
+ <span class="line highlighted"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">dialog_cancel_button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">locator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;brut-confirmation-dialog button[value=&#39;cancel&#39;]&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
255
+ <span class="line"></span>
256
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">submit_button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span>
257
+ <span class="line"></span>
258
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">dialog_cancel_button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span>
259
+ <span class="line highlighted"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(dialog).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> have_js_property</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:open</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
260
+ <span class="line"></span>
261
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">submit_button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span>
262
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">dialog_ok_button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span>
263
+ <span class="line"></span>
264
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(page).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> be_page_for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">BlogPostEditorPage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
265
+ <span class="line"></span>
266
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(content_error_message).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> have_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This field does not have enough words&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
267
+ <span class="line"></span>
268
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">content_field.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fill</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This is a longer post, so we should be OK&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
269
+ <span class="line"></span>
270
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">submit_button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span>
271
+ <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">dialog_ok_button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span>
272
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(page).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> be_page_for</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>
273
+ <span class="line"></span>
274
+ <span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">new_post</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">DB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">BlogPost</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">order</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">desc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:created_at</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">first</span></span></code></pre><div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br></div></div><p>The test should now pass:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> e2e</span></span></code></pre></div><div class="language-txt vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>#OUTPUT</span></span>
275
+ <span class="line"><span>[ bin/test ] Rebuilding test database schema</span></span>
276
+ <span class="line"><span>[ bin/test ] Executing [&quot;bin/db rebuild --env=test&quot;]</span></span>
277
+ <span class="line"><span>[ bin/db ] Database exists. Dropping...</span></span>
278
+ <span class="line"><span>[ bin/db ] blog_test does not exit. Creating...</span></span>
279
+ <span class="line"><span>[ bin/db ] Migrations applied</span></span>
280
+ <span class="line"><span>[ bin/test ] [&quot;bin/db rebuild --env=test&quot;] succeeded</span></span>
281
+ <span class="line"><span></span></span>
282
+ <span class="line"><span>«TONS OF OUTPUT»</span></span>
283
+ <span class="line"><span></span></span>
284
+ <span class="line"><span>[7215] - Goodbye!</span></span>
285
+ <span class="line"><span>[7215] - Gracefully shutting down workers...</span></span>
286
+ <span class="line"><span></span></span>
287
+ <span class="line"><span>Finished in 3.45 seconds (files took 0.71481 seconds to load)</span></span>
288
+ <span class="line highlighted"><span>1 example, 0 failures</span></span>
289
+ <span class="line"><span></span></span>
290
+ <span class="line"><span>Randomized with seed 30988</span></span>
291
+ <span class="line"><span></span></span>
292
+ <span class="line"><span>[ bin/test ] [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/blog-demo/specs -I /Users/davec/Projects/ThirdTank/blog-demo/app/src -I lib/ --tag e2e -P \&quot;**/*.spec.rb\&quot; /Users/davec/Projects/ThirdTank/blog-demo/specs/&quot;] succeeded</span></span>
293
+ <span class="line"><span>[ bin/test ] Re-Rebuilding test database schema</span></span>
294
+ <span class="line"><span>[ bin/test ] Executing [&quot;bin/db rebuild --env=test&quot;]</span></span>
295
+ <span class="line"><span>[ bin/db ] Database exists. Dropping...</span></span>
296
+ <span class="line"><span>[ bin/db ] blog_test does not exit. Creating...</span></span>
297
+ <span class="line"><span>[ bin/db ] Migrations applied</span></span>
298
+ <span class="line"><span>[ bin/test ] [&quot;bin/db rebuild --env=test&quot;] succeeded</span></span></code></pre></div><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><ul><li>Extract the dialog into its own component</li><li>Use Internationalization for all the dialog values</li></ul></div></div></main><footer class="VPDocFooter" data-v-e6f2a212 data-v-1bcd8184><!--[--><!--]--><!----><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-1bcd8184><span class="visually-hidden" id="doc-footer-aria-label" data-v-1bcd8184>Pager</span><div class="pager" data-v-1bcd8184><!----></div><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link next" href="/getting-started.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Next page</span><span class="title" data-v-1bcd8184>Getting Started</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
299
+ <script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"BxjHi9-8\",\"ai.md\":\"Cy9GWnER\",\"assets.md\":\"7C3HWkga\",\"brut-js.md\":\"B4GYxQVw\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"CjsktgFz\",\"components.md\":\"BzVRwegp\",\"configuration.md\":\"eM5wFVi5\",\"css.md\":\"CltvJqAa\",\"custom-element-tests.md\":\"B_rbta32\",\"database-access.md\":\"gnluu54N\",\"database-schema.md\":\"LpmBPVEU\",\"deployment.md\":\"BLseERGV\",\"dev-environment.md\":\"DRH2D2-O\",\"dir-structure.md\":\"CWir1pic\",\"doc-conventions.md\":\"DOkAuXlt\",\"end-to-end-tests.md\":\"DzqRpZ43\",\"features.md\":\"DPFXsy0z\",\"flash-and-session.md\":\"nPvUpnUx\",\"form-constraints.md\":\"KTv5cdR4\",\"forms.md\":\"B3BHvCV3\",\"getting-started.md\":\"BgR0ZHsl\",\"handlers.md\":\"h84MMB1R\",\"hooks.md\":\"Jmb5VOLA\",\"i18n.md\":\"BAm9t9JJ\",\"index.md\":\"Bn9e0sRJ\",\"instrumentation.md\":\"BgcaGVYH\",\"javascript.md\":\"DzrMxUmI\",\"jobs.md\":\"S-2amAYp\",\"keyword-injection.md\":\"95Zgh2eN\",\"layouts.md\":\"CVGl9xIO\",\"lsp.md\":\"Dn1rIiW0\",\"markdown-examples.md\":\"CCFEQO44\",\"middleware.md\":\"Czz_UlJN\",\"overview.md\":\"DlKiRRG_\",\"pages.md\":\"B7Hc-i6H\",\"recipes_alternate-layouts.md\":\"BwEytl59\",\"recipes_authentication.md\":\"Dzvi_g69\",\"recipes_blank-layouts.md\":\"fyAUJyJR\",\"recipes_custom-flash.md\":\"CrQbI5eH\",\"recipes_form-errors.md\":\"Bv5RCKqH\",\"recipes_indexed-forms.md\":\"CstYyOSo\",\"recipes_migrations.md\":\"CTcnWDJF\",\"recipes_text-field-component.md\":\"H4wLAK0Z\",\"roadmap.md\":\"C6PRi0DX\",\"routes.md\":\"BD6y2i-f\",\"security.md\":\"C0G_AZR-\",\"seed-data.md\":\"BvFZlqIk\",\"space-time-continuum.md\":\"xl44xDos\",\"tutorial.md\":\"BM40jnoq\",\"tutorials_01-intro.md\":\"BXvYWcO9\",\"tutorials_02-dialog.md\":\"CIeg8R--\",\"unit-tests.md\":\"DUGrnLj5\",\"why.md\":\"C-hk5xgJ\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Brut RB\",\"description\":\"Documentation for the Brut.RB web framework.\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\"},\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Overview\",\"link\":\"/overview\"},{\"text\":\"Brut API\",\"link\":\"/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutCSS\",\"link\":\"/brut-css/index.html\",\"target\":\"_self\"}],\"outline\":[2,3],\"sidebar\":[{\"text\":\"Overview\",\"collapsed\":false,\"items\":[{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Concepts\",\"link\":\"/overview\"},{\"text\":\"Features\",\"link\":\"/features\"},{\"text\":\"Directory Structure\",\"link\":\"/dir-structure\"},{\"text\":\"Dev Environment\",\"link\":\"/dev-environment\"},{\"text\":\"Tutorial\",\"link\":\"/tutorial\"},{\"text\":\"Documentation Conventions\",\"link\":\"/doc-conventions\"}]},{\"text\":\"Front-End\",\"collapsed\":false,\"items\":[{\"text\":\"Routes\",\"link\":\"/routes\"},{\"text\":\"Pages\",\"link\":\"/pages\"},{\"text\":\"Layouts\",\"link\":\"/layouts\"},{\"text\":\"Forms\",\"link\":\"/forms\"},{\"text\":\"Form Constraints\",\"link\":\"/form-constraints\"},{\"text\":\"Handlers and Actions\",\"link\":\"/handlers\"},{\"text\":\"Components\",\"link\":\"/components\"},{\"text\":\"Flash and Session\",\"link\":\"/flash-and-session\"},{\"text\":\"Space/Time Continuum\",\"link\":\"/space-time-continuum\"},{\"text\":\"JavaScript\",\"link\":\"/javascript\"},{\"text\":\"CSS\",\"link\":\"/css\"},{\"text\":\"Assets\",\"link\":\"/assets\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js\"}]},{\"text\":\"Back-End\",\"collapsed\":false,\"items\":[{\"text\":\"Database Schema\",\"link\":\"/database-schema\"},{\"text\":\"Database Access\",\"link\":\"/database-access\"},{\"text\":\"Seed Data\",\"link\":\"/seed-data\"},{\"text\":\"Jobs\",\"link\":\"/jobs\"},{\"text\":\"Business Logic\",\"link\":\"/business-logic\"}]},{\"text\":\"Framework\",\"collapsed\":false,\"items\":[{\"text\":\"Configuration\",\"link\":\"/configuration\"},{\"text\":\"Keyword Injection\",\"link\":\"/keyword-injection\"},{\"text\":\"I18n\",\"link\":\"/i18n\"},{\"text\":\"CLI / Tasks\",\"link\":\"/cli\"},{\"text\":\"Deployment\",\"link\":\"/deployment\"}]},{\"text\":\"Testing\",\"collapsed\":false,\"items\":[{\"text\":\"Unit Tests\",\"link\":\"/unit-tests\"},{\"text\":\"End-to-End Tests\",\"link\":\"/end-to-end-tests\"},{\"text\":\"Testing Custom Elements\",\"link\":\"/custom-element-tests\"}]},{\"text\":\"Advanced Topics\",\"collapsed\":true,\"items\":[{\"text\":\"Route Hooks\",\"link\":\"/hooks\"},{\"text\":\"Middleware\",\"link\":\"/middleware\"},{\"text\":\"Instrumentation\",\"link\":\"/instrumentation\"},{\"text\":\"Security\",\"link\":\"/security\"},{\"text\":\"LSP Support\",\"link\":\"/lsp\"}]},{\"text\":\"Recipes\",\"collapsed\":true,\"items\":[{\"text\":\"Migration Basics\",\"link\":\"/recipes/migrations\"},{\"text\":\"Styling Form Errors\",\"link\":\"/recipes/form-errors\"},{\"text\":\"Authentication\",\"link\":\"/recipes/authentication\"},{\"text\":\"Alternate Layouts\",\"link\":\"/recipes/alternate-layouts\"},{\"text\":\"Blank Layouts\",\"link\":\"/recipes/blank-layouts\"},{\"text\":\"Custom Flash Class\",\"link\":\"/recipes/custom-flash\"},{\"text\":\"Indexed Form Elements\",\"link\":\"/recipes/indexed-forms\"},{\"text\":\"Text Field Component\",\"link\":\"/recipes/text-field-component\"}]},{\"text\":\"Meta\",\"collapsed\":false,\"items\":[{\"text\":\"Why?!\",\"link\":\"/why\"},{\"text\":\"ADRs\",\"link\":\"/adrs\"},{\"text\":\"Roadmap to 1.0\",\"link\":\"/roadmap\"},{\"text\":\"AI Declaration\",\"link\":\"/ai\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/thirdtank/brut\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
300
+
301
+ </body>
302
+ </html>