brut 0.13.0 → 0.15.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 (530) 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 +50 -50
  7. data/brut-js/package.json +1 -1
  8. data/brut-js/specs/Toast.spec.js +34 -0
  9. data/brut-js/src/I18nTranslation.js +3 -0
  10. data/brut-js/src/Message.js +9 -3
  11. data/brut-js/src/RichString.js +4 -1
  12. data/brut-js/src/Toast.js +102 -0
  13. data/brut-js/src/index.js +3 -0
  14. data/brutrb.com/.vitepress/config.mjs +0 -1
  15. data/brutrb.com/brut-js.md +1 -0
  16. data/brutrb.com/layouts.md +70 -12
  17. data/brutrb.com/package-lock.json +428 -381
  18. data/docs/404.html +3 -3
  19. data/docs/adrs.html +7 -7
  20. data/docs/ai.html +7 -7
  21. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  22. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  23. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  24. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  25. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  26. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  27. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  28. data/docs/api/Brut/BackEnd.html +1 -1
  29. data/docs/api/Brut/CLI/App.html +1 -1
  30. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  31. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  32. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  33. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  34. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  65. data/docs/api/Brut/CLI/Apps.html +1 -1
  66. data/docs/api/Brut/CLI/Command.html +1 -1
  67. data/docs/api/Brut/CLI/Error.html +1 -1
  68. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  69. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  70. data/docs/api/Brut/CLI/Executor.html +1 -1
  71. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  72. data/docs/api/Brut/CLI/Options.html +1 -1
  73. data/docs/api/Brut/CLI/Output.html +1 -1
  74. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  75. data/docs/api/Brut/CLI.html +1 -1
  76. data/docs/api/Brut/FactoryBot.html +1 -1
  77. data/docs/api/Brut/Framework/App.html +1 -1
  78. data/docs/api/Brut/Framework/Config.html +1 -1
  79. data/docs/api/Brut/Framework/Container.html +1 -1
  80. data/docs/api/Brut/Framework/Error.html +1 -1
  81. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  82. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  83. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  84. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  85. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  86. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  87. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  88. data/docs/api/Brut/Framework/Errors.html +1 -1
  89. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  90. data/docs/api/Brut/Framework/MCP.html +1 -1
  91. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  92. data/docs/api/Brut/Framework.html +1 -1
  93. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  94. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  95. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  96. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  97. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +37 -18
  109. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  112. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  130. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  139. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  140. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  141. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  142. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Layout.html +171 -3
  144. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  153. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  154. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  155. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  156. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  158. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  160. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  161. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  172. data/docs/api/Brut/FrontEnd.html +1 -1
  173. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  174. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  175. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  176. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  177. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  178. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  179. data/docs/api/Brut/I18n.html +1 -1
  180. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  181. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
  182. data/docs/api/Brut/Instrumentation/Methods.html +1 -1
  183. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  184. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  185. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  186. data/docs/api/Brut/Instrumentation.html +1 -1
  187. data/docs/api/Brut/RubocopConfig.html +1 -1
  188. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  189. data/docs/api/Brut/SinatraHelpers.html +1 -1
  190. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  191. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  192. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  193. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  194. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  195. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  197. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  199. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  200. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  201. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  210. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  211. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  212. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  213. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  214. data/docs/api/Brut/SpecSupport.html +1 -1
  215. data/docs/api/Brut.html +1 -1
  216. data/docs/api/Clock.html +1 -1
  217. data/docs/api/ModuleName.html +1 -1
  218. data/docs/api/RichString.html +1 -1
  219. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  220. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  221. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  222. data/docs/api/Sequel/Extensions.html +1 -1
  223. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  224. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  225. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  226. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  227. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  228. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  229. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  230. data/docs/api/Sequel/Plugins.html +1 -1
  231. data/docs/api/Sequel.html +1 -1
  232. data/docs/api/_index.html +1 -1
  233. data/docs/api/file.README.html +1 -1
  234. data/docs/api/index.html +1 -1
  235. data/docs/api/method_list.html +157 -141
  236. data/docs/api/top-level-namespace.html +1 -1
  237. data/docs/assets/adrs.md.YglbWtQe.js +1 -0
  238. data/docs/assets/adrs.md.YglbWtQe.lean.js +1 -0
  239. data/docs/assets/ai.md.ChLnvDAX.js +1 -0
  240. data/docs/assets/ai.md.ChLnvDAX.lean.js +1 -0
  241. data/docs/assets/{app.BDtsVxyd.js → app.B0X8upRm.js} +1 -1
  242. data/docs/assets/{assets.md.7C3HWkga.js → assets.md.BEF6Oz6K.js} +2 -2
  243. data/docs/assets/assets.md.BEF6Oz6K.lean.js +1 -0
  244. data/docs/assets/{brut-js.md.B4GYxQVw.js → brut-js.md.CbJAe2Ky.js} +2 -2
  245. data/docs/assets/brut-js.md.CbJAe2Ky.lean.js +1 -0
  246. data/docs/assets/business-logic.md.DbuaOYGU.js +1 -0
  247. data/docs/assets/business-logic.md.DbuaOYGU.lean.js +1 -0
  248. data/docs/assets/chunks/@localSearchIndexroot.C0s1k0UQ.js +1 -0
  249. data/docs/assets/chunks/VPLocalSearchBox.jLmhant1.js +8 -0
  250. data/docs/assets/chunks/framework.C4nOkCZI.js +18 -0
  251. data/docs/assets/chunks/{theme.DZKmijwi.js → theme.CtVUdCdt.js} +2 -2
  252. data/docs/assets/{cli.md.CjsktgFz.js → cli.md.DDMar_51.js} +2 -2
  253. data/docs/assets/cli.md.DDMar_51.lean.js +1 -0
  254. data/docs/assets/{components.md.rMhQ0WdZ.js → components.md.C6nWgDP0.js} +5 -5
  255. data/docs/assets/components.md.C6nWgDP0.lean.js +1 -0
  256. data/docs/assets/{configuration.md.BK42Yjp_.js → configuration.md.CpbYHWPb.js} +2 -2
  257. data/docs/assets/configuration.md.CpbYHWPb.lean.js +1 -0
  258. data/docs/assets/{css.md.CltvJqAa.js → css.md.K5rOCOQY.js} +2 -2
  259. data/docs/assets/css.md.K5rOCOQY.lean.js +1 -0
  260. data/docs/assets/{custom-element-tests.md.B_rbta32.js → custom-element-tests.md.DiLe-eFw.js} +2 -2
  261. data/docs/assets/custom-element-tests.md.DiLe-eFw.lean.js +1 -0
  262. data/docs/assets/{database-access.md.gnluu54N.js → database-access.md.Dc8l2Plf.js} +2 -2
  263. data/docs/assets/database-access.md.Dc8l2Plf.lean.js +1 -0
  264. data/docs/assets/{database-schema.md.LpmBPVEU.js → database-schema.md.BJ_JhXmO.js} +2 -2
  265. data/docs/assets/database-schema.md.BJ_JhXmO.lean.js +1 -0
  266. data/docs/assets/{deployment.md.BLseERGV.js → deployment.md.C1u5ep0g.js} +2 -2
  267. data/docs/assets/deployment.md.C1u5ep0g.lean.js +1 -0
  268. data/docs/assets/{dev-environment.md.DRH2D2-O.js → dev-environment.md.B1S9p5ZK.js} +2 -2
  269. data/docs/assets/{dev-environment.md.DRH2D2-O.lean.js → dev-environment.md.B1S9p5ZK.lean.js} +1 -1
  270. data/docs/assets/{dir-structure.md.CWir1pic.js → dir-structure.md.D1T2kGwj.js} +2 -2
  271. data/docs/assets/dir-structure.md.D1T2kGwj.lean.js +1 -0
  272. data/docs/assets/doc-conventions.md.CDnWaEFg.js +1 -0
  273. data/docs/assets/doc-conventions.md.CDnWaEFg.lean.js +1 -0
  274. data/docs/assets/{end-to-end-tests.md.DzqRpZ43.js → end-to-end-tests.md.BJJdNDYL.js} +2 -2
  275. data/docs/assets/end-to-end-tests.md.BJJdNDYL.lean.js +1 -0
  276. data/docs/assets/{features.md.DPFXsy0z.js → features.md.BDWxnyNO.js} +2 -2
  277. data/docs/assets/features.md.BDWxnyNO.lean.js +1 -0
  278. data/docs/assets/{flash-and-session.md.nPvUpnUx.js → flash-and-session.md.CUsMxoNl.js} +2 -2
  279. data/docs/assets/flash-and-session.md.CUsMxoNl.lean.js +1 -0
  280. data/docs/assets/{form-constraints.md.KTv5cdR4.js → form-constraints.md.KlfXSKm2.js} +2 -2
  281. data/docs/assets/form-constraints.md.KlfXSKm2.lean.js +1 -0
  282. data/docs/assets/{forms.md.v9qIbmUM.js → forms.md.Bii91k3E.js} +3 -3
  283. data/docs/assets/forms.md.Bii91k3E.lean.js +1 -0
  284. data/docs/assets/{getting-started.md.DTOl4c2g.js → getting-started.md.ChAvueK7.js} +4 -4
  285. data/docs/assets/getting-started.md.ChAvueK7.lean.js +1 -0
  286. data/docs/assets/{handlers.md.h84MMB1R.js → handlers.md.C5tUwmmo.js} +2 -2
  287. data/docs/assets/handlers.md.C5tUwmmo.lean.js +1 -0
  288. data/docs/assets/{hooks.md.Jmb5VOLA.js → hooks.md.CoiYCKRc.js} +2 -2
  289. data/docs/assets/hooks.md.CoiYCKRc.lean.js +1 -0
  290. data/docs/assets/{i18n.md.BAm9t9JJ.js → i18n.md.DxkCKhUw.js} +2 -2
  291. data/docs/assets/i18n.md.DxkCKhUw.lean.js +1 -0
  292. data/docs/assets/{index.md.Bn9e0sRJ.js → index.md.DnphWyQd.js} +1 -1
  293. data/docs/assets/{index.md.Bn9e0sRJ.lean.js → index.md.DnphWyQd.lean.js} +1 -1
  294. data/docs/assets/{instrumentation.md._lNSriEZ.js → instrumentation.md.BcxjC4jd.js} +2 -2
  295. data/docs/assets/instrumentation.md.BcxjC4jd.lean.js +1 -0
  296. data/docs/assets/{javascript.md.DzrMxUmI.js → javascript.md.D6fxhaQb.js} +2 -2
  297. data/docs/assets/javascript.md.D6fxhaQb.lean.js +1 -0
  298. data/docs/assets/jobs.md.Bc7Y1YpK.js +1 -0
  299. data/docs/assets/jobs.md.Bc7Y1YpK.lean.js +1 -0
  300. data/docs/assets/{keyword-injection.md.95Zgh2eN.js → keyword-injection.md.CqLnnzIz.js} +2 -2
  301. data/docs/assets/keyword-injection.md.CqLnnzIz.lean.js +1 -0
  302. data/docs/assets/layouts.md.HEbeK7Jr.js +68 -0
  303. data/docs/assets/layouts.md.HEbeK7Jr.lean.js +1 -0
  304. data/docs/assets/lsp.md.bE9dW8n9.js +1 -0
  305. data/docs/assets/lsp.md.bE9dW8n9.lean.js +1 -0
  306. data/docs/assets/{markdown-examples.md.CCFEQO44.js → markdown-examples.md.BPmtHlc-.js} +2 -2
  307. data/docs/assets/markdown-examples.md.BPmtHlc-.lean.js +1 -0
  308. data/docs/assets/{middleware.md.Czz_UlJN.js → middleware.md.BhOIsg59.js} +2 -2
  309. data/docs/assets/middleware.md.BhOIsg59.lean.js +1 -0
  310. data/docs/assets/overview.md.BpWAgPFH.js +1 -0
  311. data/docs/assets/overview.md.BpWAgPFH.lean.js +1 -0
  312. data/docs/assets/{pages.md.B7Hc-i6H.js → pages.md.B3sQXpEd.js} +2 -2
  313. data/docs/assets/pages.md.B3sQXpEd.lean.js +1 -0
  314. data/docs/assets/{recipes_alternate-layouts.md.BwEytl59.js → recipes_alternate-layouts.md.C1QzVkA7.js} +2 -2
  315. data/docs/assets/recipes_alternate-layouts.md.C1QzVkA7.lean.js +1 -0
  316. data/docs/assets/{recipes_authentication.md.nwO6F7Ou.js → recipes_authentication.md.CyvoIW82.js} +2 -2
  317. data/docs/assets/recipes_authentication.md.CyvoIW82.lean.js +1 -0
  318. data/docs/assets/{recipes_custom-flash.md.CrQbI5eH.js → recipes_custom-flash.md.6gFqf2uL.js} +2 -2
  319. data/docs/assets/recipes_custom-flash.md.6gFqf2uL.lean.js +1 -0
  320. data/docs/assets/{recipes_form-errors.md.Bv5RCKqH.js → recipes_form-errors.md.B5ptSzMO.js} +2 -2
  321. data/docs/assets/recipes_form-errors.md.B5ptSzMO.lean.js +1 -0
  322. data/docs/assets/{recipes_indexed-forms.md.CstYyOSo.js → recipes_indexed-forms.md.BYYQGW2C.js} +2 -2
  323. data/docs/assets/recipes_indexed-forms.md.BYYQGW2C.lean.js +1 -0
  324. data/docs/assets/{recipes_migrations.md.CTcnWDJF.js → recipes_migrations.md.Cid7-3cu.js} +2 -2
  325. data/docs/assets/recipes_migrations.md.Cid7-3cu.lean.js +1 -0
  326. data/docs/assets/{recipes_text-field-component.md.H4wLAK0Z.js → recipes_text-field-component.md.VhOsCtKI.js} +2 -2
  327. data/docs/assets/recipes_text-field-component.md.VhOsCtKI.lean.js +1 -0
  328. data/docs/assets/roadmap.md.CJsbUmK_.js +1 -0
  329. data/docs/assets/roadmap.md.CJsbUmK_.lean.js +1 -0
  330. data/docs/assets/{routes.md.BD6y2i-f.js → routes.md.C1dgIBtD.js} +2 -2
  331. data/docs/assets/routes.md.C1dgIBtD.lean.js +1 -0
  332. data/docs/assets/security.md.Jn4SY1uK.js +1 -0
  333. data/docs/assets/security.md.Jn4SY1uK.lean.js +1 -0
  334. data/docs/assets/{seed-data.md.BvFZlqIk.js → seed-data.md.UZW0WxYN.js} +2 -2
  335. data/docs/assets/seed-data.md.UZW0WxYN.lean.js +1 -0
  336. data/docs/assets/space-time-continuum.md.D9rYGDFH.js +1 -0
  337. data/docs/assets/space-time-continuum.md.D9rYGDFH.lean.js +1 -0
  338. data/docs/assets/{tutorial.md.BM40jnoq.js → tutorial.md.BX6f6l00.js} +2 -2
  339. data/docs/assets/tutorial.md.BX6f6l00.lean.js +1 -0
  340. data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.js → tutorials_01-intro.md.CzZ3kpF_.js} +2 -2
  341. data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.lean.js → tutorials_01-intro.md.CzZ3kpF_.lean.js} +1 -1
  342. data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.js → tutorials_02-dialog.md.De6iTsWX.js} +2 -2
  343. data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.lean.js → tutorials_02-dialog.md.De6iTsWX.lean.js} +1 -1
  344. data/docs/assets/{unit-tests.md.DUGrnLj5.js → unit-tests.md.vDsdBbO_.js} +2 -2
  345. data/docs/assets/unit-tests.md.vDsdBbO_.lean.js +1 -0
  346. data/docs/assets/why.md.4WpxdrQ2.js +1 -0
  347. data/docs/assets/why.md.4WpxdrQ2.lean.js +1 -0
  348. data/docs/assets.html +7 -7
  349. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  350. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  351. data/docs/brut-js/api/Autosubmit.html +1 -1
  352. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  353. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  354. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  355. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  356. data/docs/brut-js/api/BufferedLogger.html +1 -1
  357. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  358. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  359. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  360. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  361. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  362. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  363. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  364. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  365. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  366. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  367. data/docs/brut-js/api/Form.html +1 -1
  368. data/docs/brut-js/api/Form.js.html +1 -1
  369. data/docs/brut-js/api/I18nTranslation.html +1 -1
  370. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  371. data/docs/brut-js/api/LocaleDetection.html +1 -1
  372. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  373. data/docs/brut-js/api/Logger.html +1 -1
  374. data/docs/brut-js/api/Logger.js.html +1 -1
  375. data/docs/brut-js/api/Message.html +1 -1
  376. data/docs/brut-js/api/Message.js.html +1 -1
  377. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  378. data/docs/brut-js/api/RichString.html +1 -1
  379. data/docs/brut-js/api/RichString.js.html +1 -1
  380. data/docs/brut-js/api/Tabs.html +1 -1
  381. data/docs/brut-js/api/Tabs.js.html +1 -1
  382. data/docs/brut-js/api/Tracing.html +1 -1
  383. data/docs/brut-js/api/Tracing.js.html +1 -1
  384. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  385. data/docs/brut-js/api/external-Performance.html +1 -1
  386. data/docs/brut-js/api/external-Promise.html +1 -1
  387. data/docs/brut-js/api/external-ValidityState.html +1 -1
  388. data/docs/brut-js/api/external-Window.html +1 -1
  389. data/docs/brut-js/api/external-fetch.html +1 -1
  390. data/docs/brut-js/api/global.html +1 -1
  391. data/docs/brut-js/api/index.html +1 -1
  392. data/docs/brut-js/api/index.js.html +1 -1
  393. data/docs/brut-js/api/module-testing.html +1 -1
  394. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  395. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  396. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  397. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  398. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  399. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  400. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  401. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  402. data/docs/brut-js/api/testing_index.js.html +1 -1
  403. data/docs/brut-js.html +7 -7
  404. data/docs/business-logic.html +7 -7
  405. data/docs/cli.html +7 -7
  406. data/docs/components.html +10 -10
  407. data/docs/configuration.html +7 -7
  408. data/docs/css.html +7 -7
  409. data/docs/custom-element-tests.html +7 -7
  410. data/docs/database-access.html +7 -7
  411. data/docs/database-schema.html +7 -7
  412. data/docs/deployment.html +7 -7
  413. data/docs/dev-environment.html +7 -7
  414. data/docs/dir-structure.html +7 -7
  415. data/docs/doc-conventions.html +7 -7
  416. data/docs/end-to-end-tests.html +7 -7
  417. data/docs/features.html +7 -7
  418. data/docs/flash-and-session.html +7 -7
  419. data/docs/form-constraints.html +7 -7
  420. data/docs/forms.html +8 -8
  421. data/docs/getting-started.html +9 -9
  422. data/docs/handlers.html +7 -7
  423. data/docs/hashmap.json +1 -1
  424. data/docs/hooks.html +7 -7
  425. data/docs/i18n.html +7 -7
  426. data/docs/index.html +6 -6
  427. data/docs/instrumentation.html +7 -7
  428. data/docs/javascript.html +7 -7
  429. data/docs/jobs.html +7 -7
  430. data/docs/keyword-injection.html +7 -7
  431. data/docs/layouts.html +42 -12
  432. data/docs/lsp.html +7 -7
  433. data/docs/markdown-examples.html +7 -7
  434. data/docs/middleware.html +7 -7
  435. data/docs/overview.html +7 -7
  436. data/docs/pages.html +7 -7
  437. data/docs/recipes/alternate-layouts.html +8 -8
  438. data/docs/recipes/authentication.html +7 -7
  439. data/docs/recipes/custom-flash.html +8 -8
  440. data/docs/recipes/form-errors.html +7 -7
  441. data/docs/recipes/indexed-forms.html +7 -7
  442. data/docs/recipes/migrations.html +7 -7
  443. data/docs/recipes/text-field-component.html +7 -7
  444. data/docs/roadmap.html +7 -7
  445. data/docs/routes.html +7 -7
  446. data/docs/security.html +7 -7
  447. data/docs/seed-data.html +7 -7
  448. data/docs/space-time-continuum.html +7 -7
  449. data/docs/tutorial.html +7 -7
  450. data/docs/tutorials/01-intro.html +7 -7
  451. data/docs/tutorials/02-dialog.html +7 -7
  452. data/docs/unit-tests.html +7 -7
  453. data/docs/why.html +7 -7
  454. data/lib/brut/front_end/components/page_identifier.rb +7 -4
  455. data/lib/brut/front_end/layout.rb +11 -0
  456. data/lib/brut/front_end/page.rb +1 -1
  457. data/lib/brut/version.rb +1 -1
  458. data/mkbrut/Gemfile.lock +1 -1
  459. data/mkbrut/lib/mkbrut/version.rb +1 -1
  460. data/mkbrut/templates/Base/app/src/front_end/layouts/blank_layout.rb +11 -0
  461. data/mkbrut/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +3 -6
  462. metadata +115 -116
  463. data/brutrb.com/recipes/blank-layouts.md +0 -22
  464. data/docs/assets/adrs.md.BxjHi9-8.js +0 -1
  465. data/docs/assets/adrs.md.BxjHi9-8.lean.js +0 -1
  466. data/docs/assets/ai.md.Cy9GWnER.js +0 -1
  467. data/docs/assets/ai.md.Cy9GWnER.lean.js +0 -1
  468. data/docs/assets/assets.md.7C3HWkga.lean.js +0 -1
  469. data/docs/assets/brut-js.md.B4GYxQVw.lean.js +0 -1
  470. data/docs/assets/business-logic.md.BY4hGy0m.js +0 -1
  471. data/docs/assets/business-logic.md.BY4hGy0m.lean.js +0 -1
  472. data/docs/assets/chunks/@localSearchIndexroot.BWVzhs5N.js +0 -1
  473. data/docs/assets/chunks/VPLocalSearchBox.DCJk5nAW.js +0 -8
  474. data/docs/assets/chunks/framework.1L-BeKqY.js +0 -18
  475. data/docs/assets/cli.md.CjsktgFz.lean.js +0 -1
  476. data/docs/assets/components.md.rMhQ0WdZ.lean.js +0 -1
  477. data/docs/assets/configuration.md.BK42Yjp_.lean.js +0 -1
  478. data/docs/assets/css.md.CltvJqAa.lean.js +0 -1
  479. data/docs/assets/custom-element-tests.md.B_rbta32.lean.js +0 -1
  480. data/docs/assets/database-access.md.gnluu54N.lean.js +0 -1
  481. data/docs/assets/database-schema.md.LpmBPVEU.lean.js +0 -1
  482. data/docs/assets/deployment.md.BLseERGV.lean.js +0 -1
  483. data/docs/assets/dir-structure.md.CWir1pic.lean.js +0 -1
  484. data/docs/assets/doc-conventions.md.DOkAuXlt.js +0 -1
  485. data/docs/assets/doc-conventions.md.DOkAuXlt.lean.js +0 -1
  486. data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +0 -1
  487. data/docs/assets/features.md.DPFXsy0z.lean.js +0 -1
  488. data/docs/assets/flash-and-session.md.nPvUpnUx.lean.js +0 -1
  489. data/docs/assets/form-constraints.md.KTv5cdR4.lean.js +0 -1
  490. data/docs/assets/forms.md.v9qIbmUM.lean.js +0 -1
  491. data/docs/assets/getting-started.md.DTOl4c2g.lean.js +0 -1
  492. data/docs/assets/handlers.md.h84MMB1R.lean.js +0 -1
  493. data/docs/assets/hooks.md.Jmb5VOLA.lean.js +0 -1
  494. data/docs/assets/i18n.md.BAm9t9JJ.lean.js +0 -1
  495. data/docs/assets/instrumentation.md._lNSriEZ.lean.js +0 -1
  496. data/docs/assets/javascript.md.DzrMxUmI.lean.js +0 -1
  497. data/docs/assets/jobs.md.S-2amAYp.js +0 -1
  498. data/docs/assets/jobs.md.S-2amAYp.lean.js +0 -1
  499. data/docs/assets/keyword-injection.md.95Zgh2eN.lean.js +0 -1
  500. data/docs/assets/layouts.md.CVGl9xIO.js +0 -38
  501. data/docs/assets/layouts.md.CVGl9xIO.lean.js +0 -1
  502. data/docs/assets/lsp.md.Dn1rIiW0.js +0 -1
  503. data/docs/assets/lsp.md.Dn1rIiW0.lean.js +0 -1
  504. data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +0 -1
  505. data/docs/assets/middleware.md.Czz_UlJN.lean.js +0 -1
  506. data/docs/assets/overview.md.DlKiRRG_.js +0 -1
  507. data/docs/assets/overview.md.DlKiRRG_.lean.js +0 -1
  508. data/docs/assets/pages.md.B7Hc-i6H.lean.js +0 -1
  509. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +0 -1
  510. data/docs/assets/recipes_authentication.md.nwO6F7Ou.lean.js +0 -1
  511. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +0 -15
  512. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +0 -1
  513. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +0 -1
  514. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +0 -1
  515. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +0 -1
  516. data/docs/assets/recipes_migrations.md.CTcnWDJF.lean.js +0 -1
  517. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +0 -1
  518. data/docs/assets/roadmap.md.C6PRi0DX.js +0 -1
  519. data/docs/assets/roadmap.md.C6PRi0DX.lean.js +0 -1
  520. data/docs/assets/routes.md.BD6y2i-f.lean.js +0 -1
  521. data/docs/assets/security.md.C0G_AZR-.js +0 -1
  522. data/docs/assets/security.md.C0G_AZR-.lean.js +0 -1
  523. data/docs/assets/seed-data.md.BvFZlqIk.lean.js +0 -1
  524. data/docs/assets/space-time-continuum.md.xl44xDos.js +0 -1
  525. data/docs/assets/space-time-continuum.md.xl44xDos.lean.js +0 -1
  526. data/docs/assets/tutorial.md.BM40jnoq.lean.js +0 -1
  527. data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +0 -1
  528. data/docs/assets/why.md.C-hk5xgJ.js +0 -1
  529. data/docs/assets/why.md.C-hk5xgJ.lean.js +0 -1
  530. data/docs/recipes/blank-layouts.html +0 -43
@@ -23,10 +23,16 @@ class Message extends BaseCustomElement {
23
23
  "key",
24
24
  ]
25
25
 
26
+ /*
27
+ * Creates a new `<brut-message>` element with the given attributes.
28
+ */
26
29
  static createElement(document,attributes) {
27
30
  const element = document.createElement(Message.tagName)
28
- element.setAttribute("key",attributes.key)
29
- element.setAttribute("show-warnings",attributes["show-warnings"])
31
+ Object.entries(attributes).forEach(([name,value]) => {
32
+ if (value !== null && value !== undefined) {
33
+ element.setAttribute(name,value)
34
+ }
35
+ })
30
36
  return element
31
37
  }
32
38
 
@@ -49,7 +55,7 @@ class Message extends BaseCustomElement {
49
55
  return
50
56
  }
51
57
 
52
- this.textContent = RichString.fromString(translation.translation()).capitalize().toString()
58
+ this.textContent = RichString.fromString(translation.translation(), {allowBlank: true }).capitalize().toString()
53
59
  }
54
60
  }
55
61
 
@@ -13,10 +13,13 @@ class RichString {
13
13
  *
14
14
  * @param {null|undefined|String|RichString} possiblyDefinedStringOrRichString - if `null`, `undefined`, or otherwise falsey, this method returns `null`. If a String, returns a new `RichString` wrapping it. If a `RichString`, returns the `RichString` unchanged.
15
15
  */
16
- static fromString(possiblyDefinedStringOrRichString) {
16
+ static fromString(possiblyDefinedStringOrRichString, {allowBlank=false} = {}) {
17
17
  if (possiblyDefinedStringOrRichString instanceof RichString) {
18
18
  return possiblyDefinedStringOrRichString
19
19
  }
20
+ if (allowBlank && possiblyDefinedStringOrRichString === "") {
21
+ return new RichString("")
22
+ }
20
23
  if (!possiblyDefinedStringOrRichString) {
21
24
  return null
22
25
  }
@@ -0,0 +1,102 @@
1
+ import { BaseCustomElement, RichString, Message } from "brut-js"
2
+
3
+ /**
4
+ * Support for a toast component, which is a momentary message shown to the user, for example
5
+ * if an aynchronous update has occured.
6
+ *
7
+ * To use this element, you should set up your CSS so that the element is hidden if there is no `key`
8
+ * attribute set. When the `key` attribute *is* set, the element should be shown. You can use CSS animations
9
+ * for this as needed, but the main thing to remember is that, without a `key` attribute, this element
10
+ * should not be visible.
11
+ *
12
+ * The `key` attribute is expected to be an i18n key that references a `<brut-i18n-message>` on
13
+ * the page, which contains the actual message to show the visitor. When the `key` attribute is
14
+ * set, this component will find an `<output>` inside itself, and replace the entire contents
15
+ * with a `<brut-message>` component, using the same `key`. This will cause the `<brut-message>`
16
+ * to look up the key and put that text into the element.
17
+ *
18
+ * In addition to this lookup, this element will set appropriate ARIA attributes on the
19
+ * created `<brut-message>` element.
20
+ *
21
+ * Further, if there is a `<button>` inside this element, it will be used to close the toast by removing the
22
+ * `key` attribute (which, assuming your CSS is correct, will hide the element).
23
+ *
24
+ * @property {string} key - an I18n key of the message to show in the toast. When you generate
25
+ * the toast's HTML on the server, do not set key. Then, when you need
26
+ * to display the toast, use JavaScript to set the key. This will
27
+ * trigger its behavior as described above.
28
+ *
29
+ * @example
30
+ * <style>
31
+ * brut-toast {
32
+ * display: none;
33
+ * }
34
+ * brut-toast[key] {
35
+ * display: block;
36
+ * }
37
+ * </style>
38
+ * <brut-i18n-translation key="toast.saved">Save successful</brut-i18n-translation>
39
+ * <brut-toast>
40
+ * <div>
41
+ * <output></output>
42
+ * <button>Close</button>
43
+ * </div>
44
+ * </brut-toast>
45
+ * <!-- now, if you set the key to "toast.saved", the HTML will be changed as follows: -->
46
+ * <brut-toast key="toast.saved">
47
+ * <div>
48
+ * <output>
49
+ * <brut-message key="toast.saved" role="status" aria-live="polite" aria-atomic="true">
50
+ * Save successful
51
+ * </brut-message>
52
+ * </output>
53
+ * <button>Close</button>
54
+ * </div>
55
+ * </brut-toast>
56
+ */
57
+ class Toast extends BaseCustomElement {
58
+ static tagName = "brut-toast"
59
+
60
+ static observedAttributes = [
61
+ "show-warnings",
62
+ "key",
63
+ ]
64
+
65
+ #key = null
66
+ #closeListener = (event) => {
67
+ event.preventDefault()
68
+ this.removeAttribute("key")
69
+ }
70
+
71
+ keyChangedCallback({newValue}) {
72
+ this.#key = RichString.fromString(newValue)
73
+ }
74
+
75
+ update() {
76
+ const closeButton = this.querySelector("button")
77
+
78
+ if (closeButton) {
79
+ closeButton.addEventListener("click", this.#closeListener)
80
+ }
81
+
82
+ if (!this.#key) {
83
+ return
84
+ }
85
+ const output = this.querySelector("output")
86
+ if (!output) {
87
+ this.logger.warn("No <output> element found, so toast will not be displayed")
88
+ return
89
+ }
90
+ const messageNode = Message.createElement(document,{
91
+ "key": this.#key,
92
+ "role": "status",
93
+ "aria-live": "polite",
94
+ "aria-atomic": "true"
95
+ })
96
+ output.replaceChildren(messageNode)
97
+ this.style.animation = "none"
98
+ this.offsetWidth // Trigger reflow to restart the animation
99
+ this.style.animation = ""
100
+ }
101
+ }
102
+ export default Toast
data/brut-js/src/index.js CHANGED
@@ -12,6 +12,7 @@ import LocaleDetection from "./LocaleDetection"
12
12
  import Message from "./Message"
13
13
  import RichString from "./RichString"
14
14
  import Tabs from "./Tabs"
15
+ import Toast from "./Toast"
15
16
  import Tracing from "./Tracing"
16
17
 
17
18
  /**
@@ -105,6 +106,7 @@ BrutCustomElements.addElementClasses(
105
106
  AjaxSubmit,
106
107
  ConstraintViolationMessage,
107
108
  Tabs,
109
+ Toast,
108
110
  LocaleDetection,
109
111
  Autosubmit,
110
112
  Tracing,
@@ -125,6 +127,7 @@ export {
125
127
  Message,
126
128
  RichString,
127
129
  Tabs,
130
+ Toast,
128
131
  Tracing
129
132
  }
130
133
 
@@ -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" },
@@ -52,6 +52,7 @@ of what each one does.
52
52
  | `<brut-locale-detection>` | Sends an Ajax request to the server with the browser's reported locale and timezone. See [space-time continuum](/space-time-continuum#getting-timezone-from-the-browser) for more details. |
53
53
  | `<brut-message>` | Shows a message using an [i18n](/i18n) key to dynamically pull a localized message for client-side constraint violations. |
54
54
  | `<brut-tabs>` | Uses ARIA roles related to a tab control and implements it client-side. |
55
+ | `<brut-toast>` | Support for managing i18-capable [toast notifications](https://en.wikipedia.org/wiki/Pop-up_notification) |
55
56
  | `<brut-tracing>` | Sends observability data back to the server to unify a server-side request with client-side tracing.|
56
57
 
57
58
  > [!NOTE]
@@ -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
-