brut 0.12.1 → 0.14.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 (365) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/Gemfile.lock +1 -1
  4. data/brut-css/package-lock.json +57 -106
  5. data/brut-css/package.json +1 -1
  6. data/brut-js/package-lock.json +61 -331
  7. data/brut-js/package.json +2 -2
  8. data/brut-js/specs/AjaxSubmit.spec.js +48 -0
  9. data/brut-js/src/AjaxSubmit.js +77 -18
  10. data/brutrb.com/.vitepress/config.mjs +0 -1
  11. data/brutrb.com/layouts.md +70 -12
  12. data/brutrb.com/package-lock.json +428 -381
  13. data/brutrb.com/recipes/authentication.md +6 -6
  14. data/docs/404.html +2 -2
  15. data/docs/adrs.html +3 -3
  16. data/docs/ai.html +3 -3
  17. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  18. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  19. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  20. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  21. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  22. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  23. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  24. data/docs/api/Brut/BackEnd.html +1 -1
  25. data/docs/api/Brut/CLI/App.html +1 -1
  26. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  27. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  28. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  29. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  30. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  31. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  32. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  33. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  34. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  61. data/docs/api/Brut/CLI/Apps.html +1 -1
  62. data/docs/api/Brut/CLI/Command.html +1 -1
  63. data/docs/api/Brut/CLI/Error.html +1 -1
  64. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  65. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  66. data/docs/api/Brut/CLI/Executor.html +1 -1
  67. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  68. data/docs/api/Brut/CLI/Options.html +1 -1
  69. data/docs/api/Brut/CLI/Output.html +1 -1
  70. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  71. data/docs/api/Brut/CLI.html +1 -1
  72. data/docs/api/Brut/FactoryBot.html +1 -1
  73. data/docs/api/Brut/Framework/App.html +1 -1
  74. data/docs/api/Brut/Framework/Config.html +1 -1
  75. data/docs/api/Brut/Framework/Container.html +1 -1
  76. data/docs/api/Brut/Framework/Error.html +1 -1
  77. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  78. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  79. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  80. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  81. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  82. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  83. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  84. data/docs/api/Brut/Framework/Errors.html +1 -1
  85. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  86. data/docs/api/Brut/Framework/MCP.html +1 -1
  87. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  88. data/docs/api/Brut/Framework.html +1 -1
  89. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  90. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  91. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  92. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  93. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  94. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  95. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  96. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
  97. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  108. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  126. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  135. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  136. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  137. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  138. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  149. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  150. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  151. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  152. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  153. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  154. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  155. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  156. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  168. data/docs/api/Brut/FrontEnd.html +1 -1
  169. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  170. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  171. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  172. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  173. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  174. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  175. data/docs/api/Brut/I18n.html +1 -1
  176. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  177. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
  178. data/docs/api/Brut/Instrumentation/Methods.html +1 -1
  179. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  180. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  181. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  182. data/docs/api/Brut/Instrumentation.html +1 -1
  183. data/docs/api/Brut/RubocopConfig.html +1 -1
  184. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  185. data/docs/api/Brut/SinatraHelpers.html +1 -1
  186. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  187. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  188. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  189. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  190. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  191. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  192. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  193. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  194. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  195. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  196. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  197. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  198. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  199. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  200. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  201. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  207. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  208. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  209. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  210. data/docs/api/Brut/SpecSupport.html +1 -1
  211. data/docs/api/Brut.html +1 -1
  212. data/docs/api/Clock.html +1 -1
  213. data/docs/api/ModuleName.html +1 -1
  214. data/docs/api/RichString.html +1 -1
  215. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  216. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  217. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  218. data/docs/api/Sequel/Extensions.html +1 -1
  219. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  220. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  221. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  222. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  223. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  224. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  225. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  226. data/docs/api/Sequel/Plugins.html +1 -1
  227. data/docs/api/Sequel.html +1 -1
  228. data/docs/api/_index.html +1 -1
  229. data/docs/api/file.README.html +1 -1
  230. data/docs/api/index.html +1 -1
  231. data/docs/api/top-level-namespace.html +1 -1
  232. data/docs/assets/{app.CGHFI-MA.js → app.BDtsVxyd.js} +1 -1
  233. data/docs/assets/chunks/@localSearchIndexroot.BWVzhs5N.js +1 -0
  234. data/docs/assets/chunks/{VPLocalSearchBox.-c-Kihwo.js → VPLocalSearchBox.DCJk5nAW.js} +1 -1
  235. data/docs/assets/chunks/{theme.DpqrgXG8.js → theme.DZKmijwi.js} +2 -2
  236. data/docs/assets/{components.md.X4h_TEG-.js → components.md.rMhQ0WdZ.js} +3 -3
  237. data/docs/assets/{configuration.md.CPCyUo5A.js → configuration.md.BK42Yjp_.js} +1 -1
  238. data/docs/assets/{forms.md.BwmfAZ6Z.js → forms.md.v9qIbmUM.js} +2 -2
  239. data/docs/assets/{forms.md.BwmfAZ6Z.lean.js → forms.md.v9qIbmUM.lean.js} +1 -1
  240. data/docs/assets/{getting-started.md.mPgbkbBv.js → getting-started.md.DTOl4c2g.js} +2 -2
  241. data/docs/assets/{recipes_authentication.md.BAISoxmN.js → recipes_authentication.md.nwO6F7Ou.js} +6 -6
  242. data/docs/assets/{tutorials_02-dialog.md.DOonTBrE.js → tutorials_02-dialog.md.CPNK1SC_.js} +2 -2
  243. data/docs/assets/{tutorials_02-dialog.md.DOonTBrE.lean.js → tutorials_02-dialog.md.CPNK1SC_.lean.js} +1 -1
  244. data/docs/assets.html +3 -3
  245. data/docs/brut-js/api/AjaxSubmit.html +29 -10
  246. data/docs/brut-js/api/AjaxSubmit.js.html +78 -19
  247. data/docs/brut-js/api/Autosubmit.html +1 -1
  248. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  249. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  250. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  251. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  252. data/docs/brut-js/api/BufferedLogger.html +1 -1
  253. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  254. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  255. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  256. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  257. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  258. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  259. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  260. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  261. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  262. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  263. data/docs/brut-js/api/Form.html +1 -1
  264. data/docs/brut-js/api/Form.js.html +1 -1
  265. data/docs/brut-js/api/I18nTranslation.html +1 -1
  266. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  267. data/docs/brut-js/api/LocaleDetection.html +1 -1
  268. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  269. data/docs/brut-js/api/Logger.html +1 -1
  270. data/docs/brut-js/api/Logger.js.html +1 -1
  271. data/docs/brut-js/api/Message.html +1 -1
  272. data/docs/brut-js/api/Message.js.html +1 -1
  273. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  274. data/docs/brut-js/api/RichString.html +1 -1
  275. data/docs/brut-js/api/RichString.js.html +1 -1
  276. data/docs/brut-js/api/Tabs.html +1 -1
  277. data/docs/brut-js/api/Tabs.js.html +1 -1
  278. data/docs/brut-js/api/Tracing.html +1 -1
  279. data/docs/brut-js/api/Tracing.js.html +1 -1
  280. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  281. data/docs/brut-js/api/external-Performance.html +1 -1
  282. data/docs/brut-js/api/external-Promise.html +1 -1
  283. data/docs/brut-js/api/external-ValidityState.html +1 -1
  284. data/docs/brut-js/api/external-Window.html +1 -1
  285. data/docs/brut-js/api/external-fetch.html +1 -1
  286. data/docs/brut-js/api/global.html +1 -1
  287. data/docs/brut-js/api/index.html +1 -1
  288. data/docs/brut-js/api/index.js.html +1 -1
  289. data/docs/brut-js/api/module-testing.html +1 -1
  290. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  291. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  292. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  293. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  294. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  295. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  296. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  297. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  298. data/docs/brut-js/api/testing_index.js.html +1 -1
  299. data/docs/brut-js.html +3 -3
  300. data/docs/business-logic.html +3 -3
  301. data/docs/cli.html +3 -3
  302. data/docs/components.html +7 -7
  303. data/docs/configuration.html +5 -5
  304. data/docs/css.html +3 -3
  305. data/docs/custom-element-tests.html +3 -3
  306. data/docs/database-access.html +3 -3
  307. data/docs/database-schema.html +3 -3
  308. data/docs/deployment.html +3 -3
  309. data/docs/dev-environment.html +3 -3
  310. data/docs/dir-structure.html +3 -3
  311. data/docs/doc-conventions.html +3 -3
  312. data/docs/end-to-end-tests.html +3 -3
  313. data/docs/features.html +3 -3
  314. data/docs/flash-and-session.html +3 -3
  315. data/docs/form-constraints.html +3 -3
  316. data/docs/forms.html +5 -5
  317. data/docs/getting-started.html +6 -6
  318. data/docs/handlers.html +3 -3
  319. data/docs/hashmap.json +1 -1
  320. data/docs/hooks.html +3 -3
  321. data/docs/i18n.html +3 -3
  322. data/docs/index.html +3 -3
  323. data/docs/instrumentation.html +3 -3
  324. data/docs/javascript.html +3 -3
  325. data/docs/jobs.html +3 -3
  326. data/docs/keyword-injection.html +3 -3
  327. data/docs/layouts.html +3 -3
  328. data/docs/lsp.html +3 -3
  329. data/docs/markdown-examples.html +3 -3
  330. data/docs/middleware.html +3 -3
  331. data/docs/overview.html +3 -3
  332. data/docs/pages.html +3 -3
  333. data/docs/recipes/alternate-layouts.html +3 -3
  334. data/docs/recipes/authentication.html +10 -10
  335. data/docs/recipes/blank-layouts.html +3 -3
  336. data/docs/recipes/custom-flash.html +3 -3
  337. data/docs/recipes/form-errors.html +3 -3
  338. data/docs/recipes/indexed-forms.html +3 -3
  339. data/docs/recipes/migrations.html +3 -3
  340. data/docs/recipes/text-field-component.html +3 -3
  341. data/docs/roadmap.html +3 -3
  342. data/docs/routes.html +3 -3
  343. data/docs/security.html +3 -3
  344. data/docs/seed-data.html +3 -3
  345. data/docs/space-time-continuum.html +3 -3
  346. data/docs/tutorial.html +3 -3
  347. data/docs/tutorials/01-intro.html +3 -3
  348. data/docs/tutorials/02-dialog.html +5 -5
  349. data/docs/unit-tests.html +3 -3
  350. data/docs/why.html +3 -3
  351. data/lib/brut/front_end/components/page_identifier.rb +7 -4
  352. data/lib/brut/front_end/layout.rb +11 -0
  353. data/lib/brut/front_end/page.rb +1 -1
  354. data/lib/brut/version.rb +1 -1
  355. data/mkbrut/Gemfile.lock +1 -1
  356. data/mkbrut/lib/mkbrut/version.rb +1 -1
  357. data/mkbrut/templates/Base/app/src/front_end/layouts/blank_layout.rb +11 -0
  358. data/mkbrut/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +3 -6
  359. metadata +18 -18
  360. data/brutrb.com/recipes/blank-layouts.md +0 -22
  361. data/docs/assets/chunks/@localSearchIndexroot.DcMsFVrO.js +0 -1
  362. /data/docs/assets/{components.md.X4h_TEG-.lean.js → components.md.rMhQ0WdZ.lean.js} +0 -0
  363. /data/docs/assets/{configuration.md.CPCyUo5A.lean.js → configuration.md.BK42Yjp_.lean.js} +0 -0
  364. /data/docs/assets/{getting-started.md.mPgbkbBv.lean.js → getting-started.md.DTOl4c2g.lean.js} +0 -0
  365. /data/docs/assets/{recipes_authentication.md.BAISoxmN.lean.js → recipes_authentication.md.nwO6F7Ou.lean.js} +0 -0
@@ -9,7 +9,8 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
9
9
  *
10
10
  * 1. When the button is clicked, the form's validity is checked. If it's not valid, nothing happens.
11
11
  * 2. If the form is valid, this element will be given the `requesting` attribute.
12
- * 3. The request will be initiated, set to abort after `request-timeout` ms (see below).
12
+ * 3. The request will be initiated, set to abort after `request-timeout` ms as well
13
+ * as to abort on an externally-provided AbortSignal (see below).
13
14
  * The data submitted will be the contents of `new FormData(form)`, along with the
14
15
  * name/value of the button that was clicked, if it has a name and value.
15
16
  * 4. If the request returns OK:
@@ -28,8 +29,7 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
28
29
  * - otherwise, the operation is aborted.
29
30
  * 7. If fetch throws an error, the operation is aborted.
30
31
  *
31
- * Aborting the operation will submit the form in the normal way, allowing the browser to deal with whatever the issue is. You can set
32
- * `log-request-errors` to introspect this process.
32
+ * ## 422 Responses
33
33
  *
34
34
  * For a 422 response (where `no-server-side-error-parsing` is *not* set),
35
35
  * this element assumes the response is `text/html` and contains one or more `<brut-cv>`
@@ -56,6 +56,20 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
56
56
  * - the response body will be `text/html`
57
57
  * - the response body will contain one or more `<brut-cv>` elements
58
58
  *
59
+ * ## Aborting the `fetch` Request
60
+ *
61
+ * By default, the call to `fetch` will be aborted after the value of `request-timeout` ms (default is 5,000).
62
+ * When this happens, the **form is submitted through the browser** without Ajax. This is currently
63
+ * not configurable.
64
+ *
65
+ * You *can* set an additional `AbortSignal` by setting the `abortSignal` property. When this is done, then
66
+ * either a timeout or your custom abort will abort the request and **submit the form through the browser**.
67
+ *
68
+ * For your custom abort signal, you can prevent browser submission by providing a reason
69
+ * with the value `AjaxSubmit.doNotSubmitThroughBrowser`. This can be useful when you are debouncing
70
+ * requests. See the examples. You will also find it useful to set `log-request-errors` on the element
71
+ * so you can see what is happening.
72
+ *
59
73
  * @property {boolean} no-server-side-error-parsing - if set, the response body for a 422 will not be parsed and inserted into the DOM. Instead, the body will be part of the detail of the `brut:submitinvalid` event.
60
74
  * @property {number} request-timeout - number of ms that the entire operation is expected to complete within. Default is 5000
61
75
  * @property {number} submitted-lifetime - number of ms that "submitted" should remain on the element after the form has completed. Default is 2000
@@ -67,23 +81,29 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
67
81
  * @fires brut:submitok Fired when the AJAX request initated by this returns OK and all processing has completed. The detail will include the *parsed document* of the HTML returned in the response.
68
82
  * @fires brut:submitinvalid Fired when the AJAX request initated by this returns a 422 and all logic around managing the reponse has completed. The detail will be null unless `no-server-side-error-parsing` is set, in which case it will be the parsed document of the HTML returned in the response.
69
83
  *
70
- * @example
84
+ * @example <caption>Typical use</caption>
71
85
  * <form action="/widgets" method="post">
72
86
  * <input type="text" name="name">
73
87
  *
74
88
  * <brut-ajax-submit>
75
89
  * <button name="button" value="save">Save</button>
76
- * </brut-ajax-submit>
90
+ * </brut-ajax-submit>
77
91
  * <brut-ajax-submit>
78
92
  * <button name="button" value="analyze">Analyze</button>
79
- * </brut-ajax-submit>
93
+ * </brut-ajax-submit>
80
94
  * </form>
81
- *
82
95
  * <!-- When "Save" is clicked, "name" will have the value from the text field,
83
96
  * and "button" will have the value "save".
84
97
  * When "Analyze" is clicked, "name" will have the value from the text
85
98
  * field, and "button" will have the value "analyze". -->
86
99
  *
100
+ * @example <caption>Using a custom abort signal</caption>
101
+ * const ajaxSubmit = document.querySelector("brut-ajax-submit")
102
+ * const controller = new AbortController()
103
+ * ajaxSubmit.abortSignal = controller.signal
104
+ * // later, when you want to abort the request
105
+ * controller.abort(AjaxSubmit.doNotSubmitThroughBrowser)
106
+ *
87
107
  * @customelement brut-ajax-submit
88
108
  */
89
109
  class AjaxSubmit extends BaseCustomElement {
@@ -99,12 +119,28 @@ class AjaxSubmit extends BaseCustomElement {
99
119
  "no-server-side-error-parsing",
100
120
  ]
101
121
 
102
- #requestErrorLogger = () => {}
103
- #formSubmitDelay = 0
104
- #submittedLifetime = 2000
105
- #requestTimeout = 5000
106
- #maxRetryAttempts = 25
122
+ /* Use this when calling abort() to indicate that the form
123
+ * should be sub submitted through the browser.
124
+ */
125
+ static doNotSubmitThroughBrowser = "doNotSubmitThroughBrowser"
126
+
127
+ #requestErrorLogger = () => {}
128
+ #formSubmitDelay = 0
129
+ #submittedLifetime = 2000
130
+ #requestTimeout = 5000
131
+ #maxRetryAttempts = 25
107
132
  #serverSideErrorParsing = true
133
+ #abortSignal = null
134
+
135
+ /* Set an additional abort signal to be used when
136
+ * the form is submitted via Ajax. This allows you to
137
+ * control the fetch request beyond the built-in timeout.
138
+ *
139
+ * @param {AbortSignal} value the AbortSignal to add to the fetch request.
140
+ */
141
+ set abortSignal(value) {
142
+ this.#abortSignal = value
143
+ }
108
144
 
109
145
  constructor() {
110
146
  super()
@@ -205,18 +241,36 @@ class AjaxSubmit extends BaseCustomElement {
205
241
  }
206
242
  const urlSearchParams = new URLSearchParams(formData)
207
243
 
208
- const timeoutSignal = AbortSignal.timeout(this.#requestTimeout)
244
+ const signals = [
245
+ AbortSignal.timeout(this.#requestTimeout),
246
+ ]
247
+ if (this.#abortSignal) {
248
+ signals.push(this.#abortSignal)
249
+ }
250
+ const abortSignals = AbortSignal.any(signals)
251
+
252
+ let url = form.action
253
+ let body = null
254
+
255
+ if (form.method.toLowerCase() == "get") {
256
+ const sep = url.includes("?") ? "&" : "?"
257
+ url += sep + urlSearchParams.toString()
258
+ }
259
+ else {
260
+ body = urlSearchParams
261
+ }
209
262
 
210
263
  const request = new Request(
211
- form.action,
264
+ url,
212
265
  {
213
266
  headers: headers,
214
267
  method: form.method,
215
- body: urlSearchParams,
216
- signal: timeoutSignal,
268
+ body: body,
269
+ signal: abortSignals,
217
270
  }
218
271
  )
219
272
 
273
+
220
274
  if (numAttempts > this.#maxRetryAttempts) {
221
275
  this.#requestErrorLogger("%d attempts. Giving up",numAttempts)
222
276
  this.#submitFormThroughBrowser(form)
@@ -273,8 +327,13 @@ class AjaxSubmit extends BaseCustomElement {
273
327
  }
274
328
  }
275
329
  }).catch( (error) => {
276
- this.#requestErrorLogger("Got %o, which cannot be retried",error)
277
- this.#submitFormThroughBrowser(form)
330
+ if (error == AjaxSubmit.doNotSubmitThroughBrowser) {
331
+ this.#requestErrorLogger("Error indicates we should not submit through browser: %o",error)
332
+ }
333
+ else {
334
+ this.#requestErrorLogger("Got %o, which cannot be retried",error)
335
+ this.#submitFormThroughBrowser(form)
336
+ }
278
337
  })
279
338
  }
280
339
 
@@ -133,7 +133,6 @@ export default defineConfig({
133
133
  { text: "Styling Form Errors", link: "/recipes/form-errors" },
134
134
  { text: "Authentication", link: "/recipes/authentication" },
135
135
  { text: "Alternate Layouts", link: "/recipes/alternate-layouts" },
136
- { text: "Blank Layouts", link: "/recipes/blank-layouts" },
137
136
  { text: "Custom Flash Class", link: "/recipes/custom-flash" },
138
137
  { text: "Indexed Form Elements", link: "/recipes/indexed-forms" },
139
138
  { text: "Text Field Component", link: "/recipes/text-field-component" },
@@ -12,14 +12,16 @@ Your app should include `app/src/front_end/layouts/default_layout.rb`. The name
12
12
  A layout is a Phlex component that's expected to have a single call to `yield` in
13
13
  its `view_template` method.
14
14
 
15
+ ### Default Layout and Common Layout Needs
16
+
15
17
  Here is the `DefaultLayout` provided to new Brut apps:
16
18
 
17
19
  ```ruby {33}
18
20
  class DefaultLayout < Brut::FrontEnd::Layout
19
21
  include Brut::FrontEnd::Components
20
22
 
21
- def initialize(page_name:)
22
- @page_name = page_name
23
+ def initialize(page:)
24
+ @page = page
23
25
  end
24
26
 
25
27
  def view_template
@@ -34,7 +36,7 @@ class DefaultLayout < Brut::FrontEnd::Layout
34
36
  link(rel: "stylesheet", href: asset_path("/css/styles.css"))
35
37
  script(defer: true, src: asset_path("/js/app.js"))
36
38
  title { app_name }
37
- PageIdentifier(@page_name)
39
+ PageIdentifier(page)
38
40
  I18nTranslations("cv.cs")
39
41
  I18nTranslations("cv.this_field")
40
42
  Traceparent()
@@ -46,7 +48,7 @@ class DefaultLayout < Brut::FrontEnd::Layout
46
48
  end
47
49
  body do
48
50
  brut_tracing url: "/__brut/instrumentation", show_warnings: true
49
- main class: @page_name do
51
+ main class: page.page_name do
50
52
  yield
51
53
  end
52
54
  end
@@ -65,8 +67,69 @@ included by default are important for other features of Brut:
65
67
  | `Brut::FrontEnd::Components::Traceparent` | Includes the OpenTelemetry *traceparent* on the page so that client-side telemetry is reported back to the server. See `<brut-tracing>` and [observability](/instrumentation) |
66
68
  | `<brut-tracing>` / `brut_tracing` | Custom element that collects the client-side telemetry and sends it back to the server. See [observability](/instrumentation) |
67
69
 
68
- See [creating alternate layouts](/recipes/alternate-layouts) and [blank
69
- layouts](/recipes/blank-layouts) for customization options.
70
+ ### Adding Logic/Dynamic Behavior to Layouts
71
+
72
+ Often, your pages will need to make slight tweaks to the layout that don't apply to all pages. For example, you may wish for a certain page to refresh on a schedule and you want to do that with a [meta refresh](https://en.wikipedia.org/wiki/Meta_refresh), which must appear in the `<head>` of the page.
73
+
74
+ Unlike Rails, which uses named blocks to render optional or dynamic content, Brut allows you to use methods and normal Ruby-based flow logic. Since your layouts have access to the page they are laying out, you can use your pages' APIs to do whatever it is you need.
75
+
76
+ Taking the meta refresh example, suppose your `AppPage` defines a method, `auto_refresh_seconds` that, if non-`nil` means your page should automatically reload itself after that many seconds. By default, you don't refresh, so it returns `nil`:
77
+
78
+ ```ruby
79
+ class AppPage < Brut::FrontEnd::page
80
+
81
+ # ...
82
+
83
+ def auto_refresh_seconds = nil
84
+
85
+ # ...
86
+ end
87
+ ```
88
+
89
+ Your layout can refrence this API, since it's just a method on a class:
90
+
91
+ ```ruby
92
+ class DefaultLayout < Brut::FrontEnd::Layout
93
+
94
+ # ...
95
+
96
+ def view_template
97
+ doctype
98
+ html(lang: "en") do
99
+ head do
100
+ if page.auto_refresh_seconds
101
+ meta(http_equiv: safe("refresh"), content: page.auto_refresh_seconds)
102
+ end
103
+
104
+ # ...
105
+ end
106
+ # ...
107
+ end
108
+ ```
109
+
110
+ Since your pages are a class hierarchy, you can override `auto_refresh_seconds` in any page, and that page will automatically refresh itself:
111
+
112
+ ```ruby
113
+ class DashboardPage < AppPage
114
+
115
+ def auto_refresh_seconds = 60 * 60
116
+
117
+ # ...
118
+
119
+ end
120
+ ```
121
+
122
+ ### Alternate Layouts
123
+
124
+ If you used `mkbrut`, you should have access to a `BlankLayout` that is useful for allowing a page to respond to Ajax requests:
125
+
126
+ ```ruby
127
+ class SomePage < AppPage
128
+ def layout = "blank"
129
+ end
130
+ ```
131
+
132
+ See [creating alternate layouts](/recipes/alternate-layouts) for more information on creating alternate layouts based on your needs.
70
133
 
71
134
  ## Testing
72
135
 
@@ -87,12 +150,7 @@ be [global components](/components#global-components)
87
150
  > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
88
151
  > internals, the source code is always more correct.
89
152
 
90
- _Last Updated July 1, 2025_
153
+ _Last Updated Sep 9, 2025_
91
154
 
92
155
  Layouts work due to the implementation of the method `view_template` in `Brut::FrontEnd::Page`. This is why a page class must provide `page_template` instead.
93
156
 
94
- While you could override `view_template` in your page to provide a "blank layout",
95
- this is discouraged, as the use of `view_template` should be considered a
96
- private implementation detail.
97
-
98
-