brut 0.14.0 → 0.16.0.pre

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 (569) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +10 -0
  4. data/Dockerfile.dx +3 -6
  5. data/Gemfile.lock +1 -1
  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/brut-js/specs/Toast.spec.js +34 -0
  11. data/brut-js/src/I18nTranslation.js +3 -0
  12. data/brut-js/src/Message.js +9 -3
  13. data/brut-js/src/RichString.js +4 -1
  14. data/brut-js/src/Toast.js +102 -0
  15. data/brut-js/src/index.js +3 -0
  16. data/brutrb.com/.vitepress/config.mjs +4 -3
  17. data/brutrb.com/brut-js.md +1 -0
  18. data/brutrb.com/deployment.md +23 -9
  19. data/brutrb.com/jobs.md +107 -7
  20. data/brutrb.com/recipes/dev-env-secrets.md +87 -0
  21. data/brutrb.com/roadmap.md +2 -7
  22. data/docs/404.html +3 -3
  23. data/docs/adrs.html +7 -7
  24. data/docs/ai.html +7 -7
  25. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  26. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  27. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  28. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  29. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  30. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  31. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  32. data/docs/api/Brut/BackEnd.html +1 -1
  33. data/docs/api/Brut/CLI/App.html +1 -1
  34. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  67. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  69. data/docs/api/Brut/CLI/Apps.html +1 -1
  70. data/docs/api/Brut/CLI/Command.html +1 -1
  71. data/docs/api/Brut/CLI/Error.html +1 -1
  72. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  73. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  74. data/docs/api/Brut/CLI/Executor.html +1 -1
  75. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  76. data/docs/api/Brut/CLI/Options.html +1 -1
  77. data/docs/api/Brut/CLI/Output.html +1 -1
  78. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  79. data/docs/api/Brut/CLI.html +1 -1
  80. data/docs/api/Brut/FactoryBot.html +1 -1
  81. data/docs/api/Brut/Framework/App.html +1 -1
  82. data/docs/api/Brut/Framework/Config.html +1 -1
  83. data/docs/api/Brut/Framework/Container.html +1 -1
  84. data/docs/api/Brut/Framework/Error.html +1 -1
  85. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  86. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  87. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  88. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  89. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  90. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  91. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  92. data/docs/api/Brut/Framework/Errors.html +1 -1
  93. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  94. data/docs/api/Brut/Framework/MCP.html +1 -1
  95. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  96. data/docs/api/Brut/Framework.html +1 -1
  97. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +37 -18
  113. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  116. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  134. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  143. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  144. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  145. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  146. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Layout.html +171 -3
  148. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  158. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  160. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  161. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  162. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  163. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  164. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  165. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  172. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  175. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  176. data/docs/api/Brut/FrontEnd.html +1 -1
  177. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  178. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  179. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  180. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  181. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  182. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  183. data/docs/api/Brut/I18n.html +1 -1
  184. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  185. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
  186. data/docs/api/Brut/Instrumentation/Methods.html +1 -1
  187. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  188. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  189. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  190. data/docs/api/Brut/Instrumentation.html +1 -1
  191. data/docs/api/Brut/RubocopConfig.html +1 -1
  192. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  193. data/docs/api/Brut/SinatraHelpers.html +1 -1
  194. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  195. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  197. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  199. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  200. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  201. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  202. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  210. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  211. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  212. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  213. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  214. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  215. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  216. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  217. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  218. data/docs/api/Brut/SpecSupport.html +1 -1
  219. data/docs/api/Brut.html +1 -1
  220. data/docs/api/Clock.html +1 -1
  221. data/docs/api/ModuleName.html +1 -1
  222. data/docs/api/RichString.html +1 -1
  223. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  224. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  225. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  226. data/docs/api/Sequel/Extensions.html +1 -1
  227. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  228. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  229. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  230. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  231. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  232. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  233. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  234. data/docs/api/Sequel/Plugins.html +1 -1
  235. data/docs/api/Sequel.html +1 -1
  236. data/docs/api/_index.html +1 -1
  237. data/docs/api/file.README.html +1 -1
  238. data/docs/api/index.html +1 -1
  239. data/docs/api/method_list.html +157 -141
  240. data/docs/api/top-level-namespace.html +1 -1
  241. data/docs/assets/adrs.md.YglbWtQe.js +1 -0
  242. data/docs/assets/adrs.md.YglbWtQe.lean.js +1 -0
  243. data/docs/assets/ai.md.ChLnvDAX.js +1 -0
  244. data/docs/assets/ai.md.ChLnvDAX.lean.js +1 -0
  245. data/docs/assets/{app.BDtsVxyd.js → app.Dm7v_ouO.js} +1 -1
  246. data/docs/assets/{assets.md.7C3HWkga.js → assets.md.BEF6Oz6K.js} +2 -2
  247. data/docs/assets/assets.md.BEF6Oz6K.lean.js +1 -0
  248. data/docs/assets/{brut-js.md.B4GYxQVw.js → brut-js.md.BMz0X1Rz.js} +2 -2
  249. data/docs/assets/brut-js.md.BMz0X1Rz.lean.js +1 -0
  250. data/docs/assets/business-logic.md.DbuaOYGU.js +1 -0
  251. data/docs/assets/business-logic.md.DbuaOYGU.lean.js +1 -0
  252. data/docs/assets/chunks/@localSearchIndexroot.BiNFswvo.js +1 -0
  253. data/docs/assets/chunks/VPLocalSearchBox.DQK6jQou.js +8 -0
  254. data/docs/assets/chunks/framework.C4nOkCZI.js +18 -0
  255. data/docs/assets/chunks/{theme.DZKmijwi.js → theme.BuExsdM9.js} +2 -2
  256. data/docs/assets/{cli.md.CjsktgFz.js → cli.md.DDMar_51.js} +2 -2
  257. data/docs/assets/cli.md.DDMar_51.lean.js +1 -0
  258. data/docs/assets/{components.md.rMhQ0WdZ.js → components.md.Dfd3w6UW.js} +5 -5
  259. data/docs/assets/components.md.Dfd3w6UW.lean.js +1 -0
  260. data/docs/assets/{configuration.md.BK42Yjp_.js → configuration.md.DTYoV2Ea.js} +2 -2
  261. data/docs/assets/configuration.md.DTYoV2Ea.lean.js +1 -0
  262. data/docs/assets/{css.md.CltvJqAa.js → css.md.K5rOCOQY.js} +2 -2
  263. data/docs/assets/css.md.K5rOCOQY.lean.js +1 -0
  264. data/docs/assets/{custom-element-tests.md.B_rbta32.js → custom-element-tests.md.DiLe-eFw.js} +2 -2
  265. data/docs/assets/custom-element-tests.md.DiLe-eFw.lean.js +1 -0
  266. data/docs/assets/{database-access.md.gnluu54N.js → database-access.md.Dc8l2Plf.js} +2 -2
  267. data/docs/assets/database-access.md.Dc8l2Plf.lean.js +1 -0
  268. data/docs/assets/{database-schema.md.LpmBPVEU.js → database-schema.md.BJ_JhXmO.js} +2 -2
  269. data/docs/assets/database-schema.md.BJ_JhXmO.lean.js +1 -0
  270. data/docs/assets/{deployment.md.BLseERGV.js → deployment.md.C1u5ep0g.js} +2 -2
  271. data/docs/assets/deployment.md.C1u5ep0g.lean.js +1 -0
  272. data/docs/assets/{dev-environment.md.DRH2D2-O.js → dev-environment.md.B1S9p5ZK.js} +2 -2
  273. data/docs/assets/{dev-environment.md.DRH2D2-O.lean.js → dev-environment.md.B1S9p5ZK.lean.js} +1 -1
  274. data/docs/assets/{dir-structure.md.CWir1pic.js → dir-structure.md.D1T2kGwj.js} +2 -2
  275. data/docs/assets/dir-structure.md.D1T2kGwj.lean.js +1 -0
  276. data/docs/assets/doc-conventions.md.CDnWaEFg.js +1 -0
  277. data/docs/assets/doc-conventions.md.CDnWaEFg.lean.js +1 -0
  278. data/docs/assets/{end-to-end-tests.md.DzqRpZ43.js → end-to-end-tests.md.BJJdNDYL.js} +2 -2
  279. data/docs/assets/end-to-end-tests.md.BJJdNDYL.lean.js +1 -0
  280. data/docs/assets/{features.md.DPFXsy0z.js → features.md.BDWxnyNO.js} +2 -2
  281. data/docs/assets/features.md.BDWxnyNO.lean.js +1 -0
  282. data/docs/assets/{flash-and-session.md.nPvUpnUx.js → flash-and-session.md.CUsMxoNl.js} +2 -2
  283. data/docs/assets/flash-and-session.md.CUsMxoNl.lean.js +1 -0
  284. data/docs/assets/{form-constraints.md.KTv5cdR4.js → form-constraints.md.KlfXSKm2.js} +2 -2
  285. data/docs/assets/form-constraints.md.KlfXSKm2.lean.js +1 -0
  286. data/docs/assets/{forms.md.v9qIbmUM.js → forms.md.DEkmJUvb.js} +3 -3
  287. data/docs/assets/forms.md.DEkmJUvb.lean.js +1 -0
  288. data/docs/assets/{getting-started.md.DTOl4c2g.js → getting-started.md.DO-4eoGW.js} +4 -4
  289. data/docs/assets/getting-started.md.DO-4eoGW.lean.js +1 -0
  290. data/docs/assets/{handlers.md.h84MMB1R.js → handlers.md.C5tUwmmo.js} +2 -2
  291. data/docs/assets/handlers.md.C5tUwmmo.lean.js +1 -0
  292. data/docs/assets/{hooks.md.Jmb5VOLA.js → hooks.md.CoiYCKRc.js} +2 -2
  293. data/docs/assets/hooks.md.CoiYCKRc.lean.js +1 -0
  294. data/docs/assets/{i18n.md.BAm9t9JJ.js → i18n.md.DxkCKhUw.js} +2 -2
  295. data/docs/assets/i18n.md.DxkCKhUw.lean.js +1 -0
  296. data/docs/assets/{index.md.Bn9e0sRJ.js → index.md.DnphWyQd.js} +1 -1
  297. data/docs/assets/{index.md.Bn9e0sRJ.lean.js → index.md.DnphWyQd.lean.js} +1 -1
  298. data/docs/assets/{instrumentation.md._lNSriEZ.js → instrumentation.md.BcxjC4jd.js} +2 -2
  299. data/docs/assets/instrumentation.md.BcxjC4jd.lean.js +1 -0
  300. data/docs/assets/{javascript.md.DzrMxUmI.js → javascript.md.D6fxhaQb.js} +2 -2
  301. data/docs/assets/javascript.md.D6fxhaQb.lean.js +1 -0
  302. data/docs/assets/jobs.md.Bc7Y1YpK.js +1 -0
  303. data/docs/assets/jobs.md.Bc7Y1YpK.lean.js +1 -0
  304. data/docs/assets/{keyword-injection.md.95Zgh2eN.js → keyword-injection.md.CqLnnzIz.js} +2 -2
  305. data/docs/assets/keyword-injection.md.CqLnnzIz.lean.js +1 -0
  306. data/docs/assets/layouts.md.HEbeK7Jr.js +68 -0
  307. data/docs/assets/layouts.md.HEbeK7Jr.lean.js +1 -0
  308. data/docs/assets/lsp.md.bE9dW8n9.js +1 -0
  309. data/docs/assets/lsp.md.bE9dW8n9.lean.js +1 -0
  310. data/docs/assets/{markdown-examples.md.CCFEQO44.js → markdown-examples.md.BPmtHlc-.js} +2 -2
  311. data/docs/assets/markdown-examples.md.BPmtHlc-.lean.js +1 -0
  312. data/docs/assets/{middleware.md.Czz_UlJN.js → middleware.md.BhOIsg59.js} +2 -2
  313. data/docs/assets/middleware.md.BhOIsg59.lean.js +1 -0
  314. data/docs/assets/overview.md.BpWAgPFH.js +1 -0
  315. data/docs/assets/overview.md.BpWAgPFH.lean.js +1 -0
  316. data/docs/assets/{pages.md.B7Hc-i6H.js → pages.md.B3sQXpEd.js} +2 -2
  317. data/docs/assets/pages.md.B3sQXpEd.lean.js +1 -0
  318. data/docs/assets/{recipes_alternate-layouts.md.BwEytl59.js → recipes_alternate-layouts.md.C1QzVkA7.js} +2 -2
  319. data/docs/assets/recipes_alternate-layouts.md.C1QzVkA7.lean.js +1 -0
  320. data/docs/assets/{recipes_authentication.md.nwO6F7Ou.js → recipes_authentication.md.CyvoIW82.js} +2 -2
  321. data/docs/assets/recipes_authentication.md.CyvoIW82.lean.js +1 -0
  322. data/docs/assets/{recipes_custom-flash.md.CrQbI5eH.js → recipes_custom-flash.md.6gFqf2uL.js} +2 -2
  323. data/docs/assets/recipes_custom-flash.md.6gFqf2uL.lean.js +1 -0
  324. data/docs/assets/{recipes_form-errors.md.Bv5RCKqH.js → recipes_form-errors.md.B5ptSzMO.js} +2 -2
  325. data/docs/assets/recipes_form-errors.md.B5ptSzMO.lean.js +1 -0
  326. data/docs/assets/{recipes_indexed-forms.md.CstYyOSo.js → recipes_indexed-forms.md.BYYQGW2C.js} +2 -2
  327. data/docs/assets/recipes_indexed-forms.md.BYYQGW2C.lean.js +1 -0
  328. data/docs/assets/{recipes_migrations.md.CTcnWDJF.js → recipes_migrations.md.Cid7-3cu.js} +2 -2
  329. data/docs/assets/recipes_migrations.md.Cid7-3cu.lean.js +1 -0
  330. data/docs/assets/{recipes_text-field-component.md.H4wLAK0Z.js → recipes_text-field-component.md.VhOsCtKI.js} +2 -2
  331. data/docs/assets/recipes_text-field-component.md.VhOsCtKI.lean.js +1 -0
  332. data/docs/assets/roadmap.md.CJsbUmK_.js +1 -0
  333. data/docs/assets/roadmap.md.CJsbUmK_.lean.js +1 -0
  334. data/docs/assets/{routes.md.BD6y2i-f.js → routes.md.C1dgIBtD.js} +2 -2
  335. data/docs/assets/routes.md.C1dgIBtD.lean.js +1 -0
  336. data/docs/assets/security.md.Jn4SY1uK.js +1 -0
  337. data/docs/assets/security.md.Jn4SY1uK.lean.js +1 -0
  338. data/docs/assets/{seed-data.md.BvFZlqIk.js → seed-data.md.UZW0WxYN.js} +2 -2
  339. data/docs/assets/seed-data.md.UZW0WxYN.lean.js +1 -0
  340. data/docs/assets/space-time-continuum.md.D9rYGDFH.js +1 -0
  341. data/docs/assets/space-time-continuum.md.D9rYGDFH.lean.js +1 -0
  342. data/docs/assets/{tutorial.md.BM40jnoq.js → tutorial.md.BX6f6l00.js} +2 -2
  343. data/docs/assets/tutorial.md.BX6f6l00.lean.js +1 -0
  344. data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.js → tutorials_01-intro.md.CzZ3kpF_.js} +2 -2
  345. data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.lean.js → tutorials_01-intro.md.CzZ3kpF_.lean.js} +1 -1
  346. data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.js → tutorials_02-dialog.md.D2vSjDVf.js} +2 -2
  347. data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.lean.js → tutorials_02-dialog.md.D2vSjDVf.lean.js} +1 -1
  348. data/docs/assets/{unit-tests.md.DUGrnLj5.js → unit-tests.md.vDsdBbO_.js} +2 -2
  349. data/docs/assets/unit-tests.md.vDsdBbO_.lean.js +1 -0
  350. data/docs/assets/why.md.4WpxdrQ2.js +1 -0
  351. data/docs/assets/why.md.4WpxdrQ2.lean.js +1 -0
  352. data/docs/assets.html +7 -7
  353. data/docs/brut-js/api/AjaxSubmit.html +2 -2
  354. data/docs/brut-js/api/AjaxSubmit.js.html +2 -2
  355. data/docs/brut-js/api/Autosubmit.html +2 -2
  356. data/docs/brut-js/api/Autosubmit.js.html +2 -2
  357. data/docs/brut-js/api/BaseCustomElement.html +2 -2
  358. data/docs/brut-js/api/BaseCustomElement.js.html +2 -2
  359. data/docs/brut-js/api/BrutCustomElements.html +3 -3
  360. data/docs/brut-js/api/BufferedLogger.html +2 -2
  361. data/docs/brut-js/api/ConfirmSubmit.html +2 -2
  362. data/docs/brut-js/api/ConfirmSubmit.js.html +2 -2
  363. data/docs/brut-js/api/ConfirmationDialog.html +2 -2
  364. data/docs/brut-js/api/ConfirmationDialog.js.html +2 -2
  365. data/docs/brut-js/api/ConstraintViolationMessage.html +2 -2
  366. data/docs/brut-js/api/ConstraintViolationMessage.js.html +2 -2
  367. data/docs/brut-js/api/ConstraintViolationMessages.html +2 -2
  368. data/docs/brut-js/api/ConstraintViolationMessages.js.html +2 -2
  369. data/docs/brut-js/api/CopyToClipboard.html +2 -2
  370. data/docs/brut-js/api/CopyToClipboard.js.html +2 -2
  371. data/docs/brut-js/api/Form.html +2 -2
  372. data/docs/brut-js/api/Form.js.html +2 -2
  373. data/docs/brut-js/api/I18nTranslation.html +2 -2
  374. data/docs/brut-js/api/I18nTranslation.js.html +5 -2
  375. data/docs/brut-js/api/LocaleDetection.html +2 -2
  376. data/docs/brut-js/api/LocaleDetection.js.html +2 -2
  377. data/docs/brut-js/api/Logger.html +2 -2
  378. data/docs/brut-js/api/Logger.js.html +2 -2
  379. data/docs/brut-js/api/Message.html +2 -2
  380. data/docs/brut-js/api/Message.js.html +11 -5
  381. data/docs/brut-js/api/PrefixedLogger.html +2 -2
  382. data/docs/brut-js/api/RichString.html +9 -9
  383. data/docs/brut-js/api/RichString.js.html +6 -3
  384. data/docs/brut-js/api/Tabs.html +2 -2
  385. data/docs/brut-js/api/Tabs.js.html +2 -2
  386. data/docs/brut-js/api/Toast.html +270 -0
  387. data/docs/brut-js/api/Toast.js.html +153 -0
  388. data/docs/brut-js/api/Tracing.html +2 -2
  389. data/docs/brut-js/api/Tracing.js.html +2 -2
  390. data/docs/brut-js/api/external-CustomElementRegistry.html +3 -3
  391. data/docs/brut-js/api/external-Performance.html +3 -3
  392. data/docs/brut-js/api/external-Promise.html +3 -3
  393. data/docs/brut-js/api/external-ValidityState.html +3 -3
  394. data/docs/brut-js/api/external-Window.html +4 -4
  395. data/docs/brut-js/api/external-fetch.html +3 -3
  396. data/docs/brut-js/api/global.html +3 -3
  397. data/docs/brut-js/api/index.html +2 -2
  398. data/docs/brut-js/api/index.js.html +5 -2
  399. data/docs/brut-js/api/module-testing.html +2 -2
  400. data/docs/brut-js/api/testing.AssetMetadata.html +2 -2
  401. data/docs/brut-js/api/testing.AssetMetadataLoader.html +2 -2
  402. data/docs/brut-js/api/testing.CustomElementTest.html +2 -2
  403. data/docs/brut-js/api/testing.DOMCreator.html +2 -2
  404. data/docs/brut-js/api/testing_AssetMetadata.js.html +2 -2
  405. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +2 -2
  406. data/docs/brut-js/api/testing_CustomElementTest.js.html +2 -2
  407. data/docs/brut-js/api/testing_DOMCreator.js.html +2 -2
  408. data/docs/brut-js/api/testing_index.js.html +2 -2
  409. data/docs/brut-js.html +8 -8
  410. data/docs/business-logic.html +7 -7
  411. data/docs/cli.html +7 -7
  412. data/docs/components.html +10 -10
  413. data/docs/configuration.html +7 -7
  414. data/docs/css.html +7 -7
  415. data/docs/custom-element-tests.html +7 -7
  416. data/docs/database-access.html +7 -7
  417. data/docs/database-schema.html +7 -7
  418. data/docs/deployment.html +7 -7
  419. data/docs/dev-environment.html +7 -7
  420. data/docs/dir-structure.html +7 -7
  421. data/docs/doc-conventions.html +7 -7
  422. data/docs/end-to-end-tests.html +7 -7
  423. data/docs/features.html +7 -7
  424. data/docs/flash-and-session.html +7 -7
  425. data/docs/form-constraints.html +7 -7
  426. data/docs/forms.html +8 -8
  427. data/docs/getting-started.html +9 -9
  428. data/docs/handlers.html +7 -7
  429. data/docs/hashmap.json +1 -1
  430. data/docs/hooks.html +7 -7
  431. data/docs/i18n.html +7 -7
  432. data/docs/index.html +6 -6
  433. data/docs/instrumentation.html +7 -7
  434. data/docs/javascript.html +7 -7
  435. data/docs/jobs.html +7 -7
  436. data/docs/keyword-injection.html +7 -7
  437. data/docs/layouts.html +42 -12
  438. data/docs/lsp.html +7 -7
  439. data/docs/markdown-examples.html +7 -7
  440. data/docs/middleware.html +7 -7
  441. data/docs/overview.html +7 -7
  442. data/docs/pages.html +7 -7
  443. data/docs/recipes/alternate-layouts.html +8 -8
  444. data/docs/recipes/authentication.html +7 -7
  445. data/docs/recipes/custom-flash.html +8 -8
  446. data/docs/recipes/form-errors.html +7 -7
  447. data/docs/recipes/indexed-forms.html +7 -7
  448. data/docs/recipes/migrations.html +7 -7
  449. data/docs/recipes/text-field-component.html +7 -7
  450. data/docs/roadmap.html +7 -7
  451. data/docs/routes.html +7 -7
  452. data/docs/security.html +7 -7
  453. data/docs/seed-data.html +7 -7
  454. data/docs/space-time-continuum.html +7 -7
  455. data/docs/tutorial.html +7 -7
  456. data/docs/tutorials/01-intro.html +7 -7
  457. data/docs/tutorials/02-dialog.html +7 -7
  458. data/docs/unit-tests.html +7 -7
  459. data/docs/why.html +7 -7
  460. data/dx/bash_customizations +3 -4
  461. data/dx/build.pre +15 -0
  462. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +41 -15
  463. data/lib/brut/cli/apps/test.rb +2 -0
  464. data/lib/brut/framework/container.rb +6 -4
  465. data/lib/brut/framework/mcp.rb +1 -1
  466. data/lib/brut/version.rb +1 -1
  467. data/mkbrut/Gemfile.lock +2 -2
  468. data/mkbrut/lib/mkbrut/add_segment.rb +38 -0
  469. data/mkbrut/lib/mkbrut/add_segment_options.rb +22 -0
  470. data/mkbrut/lib/mkbrut/app.rb +7 -2
  471. data/mkbrut/lib/mkbrut/base.rb +1 -0
  472. data/mkbrut/lib/mkbrut/cli.rb +90 -8
  473. data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +19 -7
  474. data/mkbrut/lib/mkbrut/ops/insert_into_file.rb +36 -0
  475. data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +14 -2
  476. data/mkbrut/lib/mkbrut/ops.rb +1 -0
  477. data/mkbrut/lib/mkbrut/segments/bare_bones.rb +8 -0
  478. data/mkbrut/lib/mkbrut/segments/demo.rb +8 -0
  479. data/mkbrut/lib/mkbrut/segments/heroku.rb +23 -3
  480. data/mkbrut/lib/mkbrut/segments/sidekiq.rb +136 -1
  481. data/mkbrut/lib/mkbrut/segments.rb +1 -1
  482. data/mkbrut/lib/mkbrut/version.rb +1 -1
  483. data/mkbrut/lib/mkbrut.rb +2 -0
  484. data/mkbrut/templates/Base/.gitignore +3 -0
  485. data/mkbrut/templates/Base/Dockerfile.dx +18 -21
  486. data/mkbrut/templates/Base/Gemfile.erb +1 -1
  487. data/mkbrut/templates/Base/bin/run +107 -67
  488. data/mkbrut/templates/Base/bin/run.run +4 -0
  489. data/mkbrut/templates/Base/bin/setup +32 -1
  490. data/mkbrut/templates/Base/dx/bash_customizations +0 -4
  491. data/mkbrut/templates/Base/package.json.erb +1 -1
  492. data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +15 -15
  493. data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +5 -4
  494. data/mkbrut/templates/segments/Sidekiq/app/boot_sidekiq.rb +2 -0
  495. data/mkbrut/templates/segments/Sidekiq/app/config/sidekiq.yml +4 -0
  496. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/app_job.rb +3 -0
  497. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/example_job.rb +12 -0
  498. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/segments/sidekiq_segment.rb +56 -0
  499. data/mkbrut/templates/segments/Sidekiq/bin/run.sidekiq +4 -0
  500. data/mkbrut/templates/segments/Sidekiq/specs/back_end/jobs/example_job.spec.rb +5 -0
  501. data/mkbrut/templates/segments/Sidekiq/specs/integration/sidekiq_works.spec.rb +38 -0
  502. metadata +131 -116
  503. data/docs/assets/adrs.md.BxjHi9-8.js +0 -1
  504. data/docs/assets/adrs.md.BxjHi9-8.lean.js +0 -1
  505. data/docs/assets/ai.md.Cy9GWnER.js +0 -1
  506. data/docs/assets/ai.md.Cy9GWnER.lean.js +0 -1
  507. data/docs/assets/assets.md.7C3HWkga.lean.js +0 -1
  508. data/docs/assets/brut-js.md.B4GYxQVw.lean.js +0 -1
  509. data/docs/assets/business-logic.md.BY4hGy0m.js +0 -1
  510. data/docs/assets/business-logic.md.BY4hGy0m.lean.js +0 -1
  511. data/docs/assets/chunks/@localSearchIndexroot.BWVzhs5N.js +0 -1
  512. data/docs/assets/chunks/VPLocalSearchBox.DCJk5nAW.js +0 -8
  513. data/docs/assets/chunks/framework.1L-BeKqY.js +0 -18
  514. data/docs/assets/cli.md.CjsktgFz.lean.js +0 -1
  515. data/docs/assets/components.md.rMhQ0WdZ.lean.js +0 -1
  516. data/docs/assets/configuration.md.BK42Yjp_.lean.js +0 -1
  517. data/docs/assets/css.md.CltvJqAa.lean.js +0 -1
  518. data/docs/assets/custom-element-tests.md.B_rbta32.lean.js +0 -1
  519. data/docs/assets/database-access.md.gnluu54N.lean.js +0 -1
  520. data/docs/assets/database-schema.md.LpmBPVEU.lean.js +0 -1
  521. data/docs/assets/deployment.md.BLseERGV.lean.js +0 -1
  522. data/docs/assets/dir-structure.md.CWir1pic.lean.js +0 -1
  523. data/docs/assets/doc-conventions.md.DOkAuXlt.js +0 -1
  524. data/docs/assets/doc-conventions.md.DOkAuXlt.lean.js +0 -1
  525. data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +0 -1
  526. data/docs/assets/features.md.DPFXsy0z.lean.js +0 -1
  527. data/docs/assets/flash-and-session.md.nPvUpnUx.lean.js +0 -1
  528. data/docs/assets/form-constraints.md.KTv5cdR4.lean.js +0 -1
  529. data/docs/assets/forms.md.v9qIbmUM.lean.js +0 -1
  530. data/docs/assets/getting-started.md.DTOl4c2g.lean.js +0 -1
  531. data/docs/assets/handlers.md.h84MMB1R.lean.js +0 -1
  532. data/docs/assets/hooks.md.Jmb5VOLA.lean.js +0 -1
  533. data/docs/assets/i18n.md.BAm9t9JJ.lean.js +0 -1
  534. data/docs/assets/instrumentation.md._lNSriEZ.lean.js +0 -1
  535. data/docs/assets/javascript.md.DzrMxUmI.lean.js +0 -1
  536. data/docs/assets/jobs.md.S-2amAYp.js +0 -1
  537. data/docs/assets/jobs.md.S-2amAYp.lean.js +0 -1
  538. data/docs/assets/keyword-injection.md.95Zgh2eN.lean.js +0 -1
  539. data/docs/assets/layouts.md.CVGl9xIO.js +0 -38
  540. data/docs/assets/layouts.md.CVGl9xIO.lean.js +0 -1
  541. data/docs/assets/lsp.md.Dn1rIiW0.js +0 -1
  542. data/docs/assets/lsp.md.Dn1rIiW0.lean.js +0 -1
  543. data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +0 -1
  544. data/docs/assets/middleware.md.Czz_UlJN.lean.js +0 -1
  545. data/docs/assets/overview.md.DlKiRRG_.js +0 -1
  546. data/docs/assets/overview.md.DlKiRRG_.lean.js +0 -1
  547. data/docs/assets/pages.md.B7Hc-i6H.lean.js +0 -1
  548. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +0 -1
  549. data/docs/assets/recipes_authentication.md.nwO6F7Ou.lean.js +0 -1
  550. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +0 -15
  551. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +0 -1
  552. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +0 -1
  553. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +0 -1
  554. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +0 -1
  555. data/docs/assets/recipes_migrations.md.CTcnWDJF.lean.js +0 -1
  556. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +0 -1
  557. data/docs/assets/roadmap.md.C6PRi0DX.js +0 -1
  558. data/docs/assets/roadmap.md.C6PRi0DX.lean.js +0 -1
  559. data/docs/assets/routes.md.BD6y2i-f.lean.js +0 -1
  560. data/docs/assets/security.md.C0G_AZR-.js +0 -1
  561. data/docs/assets/security.md.C0G_AZR-.lean.js +0 -1
  562. data/docs/assets/seed-data.md.BvFZlqIk.lean.js +0 -1
  563. data/docs/assets/space-time-continuum.md.xl44xDos.js +0 -1
  564. data/docs/assets/space-time-continuum.md.xl44xDos.lean.js +0 -1
  565. data/docs/assets/tutorial.md.BM40jnoq.lean.js +0 -1
  566. data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +0 -1
  567. data/docs/assets/why.md.C-hk5xgJ.js +0 -1
  568. data/docs/assets/why.md.C-hk5xgJ.lean.js +0 -1
  569. data/docs/recipes/blank-layouts.html +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6954b7b2b7dd919d891eb6ee7a734e3c4279f7781eeb899c88ac80982662ba6
4
- data.tar.gz: b02e176ae104ab3214d52877f13a81e17fa32c4de53d57c5bbe193cd9209f9ad
3
+ metadata.gz: 98447eff97921a8aeb30a9ffa801452a97c8f46a6169e300f6792582c6af191c
4
+ data.tar.gz: 3040a27ce2283e967f0f0bac694cb0a4fcaa7c7c0bd1bb2c52246dea9dcc2ba5
5
5
  SHA512:
6
- metadata.gz: a7f6684baab276fcaab1db9eee5427c9e79d5e50a09032bcaf4e417dbee109fa0c4b77322884304a13310ae6b013fbd23d880b1928c83f1a5db627bb09f403b9
7
- data.tar.gz: db5de2626cdeabe12ce571fd046c0472a5295d8831684c9a95b4e2b251f59217c7ec7e5de9ae5c473a2ead812d675d76de1d8e38730dbe4cca7e960bf8cf3839
6
+ metadata.gz: 834e282f6c08942742733de34e04b9b4ada8cf03bbff02b6604fe54a49af28d1fdebe9da173b852e49f29ab87562335495fc9b6846a0b3a6184aa833677908d1
7
+ data.tar.gz: 9d41d53d28c12e4d5a99ab81490f10c5e0ccb4f6158c1c1dee51ba5321e3d1e92c04e5e1d566815319dea20bc0071662da174c4d930e84d7a30510da1a382cca
data/.gitignore CHANGED
@@ -31,6 +31,8 @@
31
31
  /brut-js/node_modules
32
32
  /brut-js/docs/node_modules
33
33
  /brut-css/node_modules
34
+ # Configured location for npm install -g
35
+ /.npm-global
34
36
 
35
37
  # This is where the bundle is built during testing for BuildJS
36
38
  /brut-js/specs/public/js
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Brut CHANGELOG
2
2
 
3
+ ## v0.16.0.pre - Sep 29, 2025
4
+
5
+ * Sidekiq support - this may have some churn in various config files that only reveal themselves on deploy so this
6
+ is a pre-release. Do not use this version.
7
+
8
+ ## v0.15.0 - Sep 16, 2025
9
+
10
+ * Added `<brut-toast>` to help with toast notifications. They rely on CSS for showing, hiding, and animations, all your
11
+ client-side JS needs to do is set the `key` attribute to show the Toast with an i18n-provided message
12
+
3
13
  ## v0.14.0 - Sep 9, 2025
4
14
 
5
15
  * **BREAKING CHANGE** Layouts now have access to the page object they are
data/Dockerfile.dx CHANGED
@@ -37,6 +37,9 @@ ENV EDITOR=vim
37
37
  RUN apt-get install -y vim && \
38
38
  echo "set -o vi" >> /root/.bashrc
39
39
 
40
+ # Install NodeJS
41
+ COPY --from=node:22-slim /usr/local /usr/local
42
+
40
43
 
41
44
  # Setup a non-root user
42
45
 
@@ -70,12 +73,6 @@ COPY --chown=appuser:${user_gid} dx/bash_customizations.local /home/appuser/.bas
70
73
  # ONLY in that group and not in all the groups in which they are a part.
71
74
  USER appuser
72
75
 
73
- # Install NodeJS, per https://nodejs.org/en/download
74
- RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash && \
75
- \. "$HOME/.nvm/nvm.sh" && \
76
- nvm install 22 && \
77
- node -v && nvm current && npm -v
78
-
79
76
  # Node's colors are hand-crafted to always look bad and render at least some text unreadable
80
77
  # no matter what your setup. Cool.
81
78
  ENV NODE_DISABLE_COLORS=1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brut (0.14.0)
4
+ brut (0.16.0.pre)
5
5
  concurrent-ruby
6
6
  i18n
7
7
  irb
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "brut-css",
3
- "version": "0.14.0",
3
+ "version": "0.16.0.pre",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "brut-css",
9
- "version": "0.14.0",
9
+ "version": "0.16.0.pre",
10
10
  "license": "Hippocratic-2.1",
11
11
  "devDependencies": {
12
12
  "comment-parser": "^1.4.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brut-css",
3
- "version": "0.14.0",
3
+ "version": "0.16.0.pre",
4
4
  "description": "Utility CSS Library for Full Stack Developers",
5
5
  "keywords": [
6
6
  "css"
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "brut-js",
3
- "version": "0.14.0",
3
+ "version": "0.16.0.pre",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "brut-js",
9
- "version": "0.14.0",
9
+ "version": "0.16.0.pre",
10
10
  "license": "Hippocratic-2.1",
11
11
  "devDependencies": {
12
12
  "esbuild": "^0.24.2",
data/brut-js/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brut-js",
3
- "version": "0.14.0",
3
+ "version": "0.16.0.pre",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "WebComponents",
@@ -0,0 +1,34 @@
1
+ import { withHTML } from "./SpecHelper.js"
2
+
3
+ describe("<brut-toast>", () => {
4
+ withHTML(`
5
+ <div>
6
+ <brut-i18n-translation key="toast.saved" value="Save successful"></brut-i18n-translation>
7
+ <brut-toast show-warnings>
8
+ <div>
9
+ <output><span>Message here</span></output>
10
+ <button>Close</button>
11
+ </div>
12
+ <button>Close</button>
13
+ </brut-toast>
14
+ </div>
15
+ `).test("setting the key shows the message, then it's removed when closed", ({window,document,assert}) => {
16
+
17
+ const toast = document.querySelector("brut-toast")
18
+ assert(toast, "brut-toast element should be present")
19
+
20
+ toast.setAttribute("key", "toast.saved")
21
+ const output = toast.querySelector("output")
22
+ assert.equal(output.textContent.trim(), "Save successful")
23
+ const message = output.querySelector("brut-message")
24
+ assert(message, "brut-message should be present")
25
+ console.log(message.outerHTML)
26
+ assert.equal(message.getAttribute("role"), "status")
27
+ assert.equal(message.getAttribute("aria-live"), "polite")
28
+ assert.equal(message.getAttribute("aria-atomic"), "true")
29
+
30
+ const button = toast.querySelector("button")
31
+ button.click()
32
+ assert.equal(toast.getAttribute("key"), null)
33
+ })
34
+ })
@@ -49,6 +49,9 @@ class I18nTranslation extends BaseCustomElement {
49
49
  * }
50
50
  */
51
51
  translation(interpolatedValues) {
52
+ if (!this.#value) {
53
+ this.logger.warn("No value attribute for key '%s', so translation will be blank", this.#key)
54
+ }
52
55
  return this.#value.replaceAll(/%\{([^}%]+)\}/g, (match,key) => {
53
56
  if (interpolatedValues[key]) {
54
57
  return interpolatedValues[key]
@@ -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
 
@@ -129,12 +129,13 @@ export default defineConfig({
129
129
  text: "Recipes",
130
130
  collapsed: true,
131
131
  items: [
132
- { text: "Migration Basics", link: "/recipes/migrations" },
133
- { text: "Styling Form Errors", link: "/recipes/form-errors" },
134
- { text: "Authentication", link: "/recipes/authentication" },
135
132
  { text: "Alternate Layouts", link: "/recipes/alternate-layouts" },
133
+ { text: "Authentication", link: "/recipes/authentication" },
136
134
  { text: "Custom Flash Class", link: "/recipes/custom-flash" },
137
135
  { text: "Indexed Form Elements", link: "/recipes/indexed-forms" },
136
+ { text: "Managing Secrets in the Dev Environment", link: "/recipes/dev-env-secrets" },
137
+ { text: "Migration Basics", link: "/recipes/migrations" },
138
+ { text: "Styling Form Errors", link: "/recipes/form-errors" },
138
139
  { text: "Text Field Component", link: "/recipes/text-field-component" },
139
140
  ],
140
141
  },
@@ -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]
@@ -22,30 +22,45 @@ When creating your Brut app with `mkbrut`, the Heroku segment can be used to cre
22
22
 
23
23
  How to deploy:
24
24
 
25
- 1. Auth to Heroku from inside your dev container:
25
+ 1. Get an auth token from Heroku, which you can do from inside the container, and save it to
26
+ `bash_customizations.local`:
26
27
 
27
28
  ```
28
29
  your-computer> dx/exec bash
29
30
  devcontainer> heroku auth:login
30
31
  # You will need to copy/paste the URL to log in
31
- devcontainer> heroku container:login
32
+ devcontainer> heroku authorizations:create -d "container pushes" --expires-in 31536000
33
+ # Copy the token output by this command
34
+ devcontainer> echo "HEROKU_API_KEY=«TOKEN YOU COPIED»" >> dx/bash_customizations.local
32
35
  ```
36
+ 2. Exit the devcontainer and stop `dx/start` (e.g. hit `Ctrl-C` wherever you ran it)
37
+ 3. Rebuild and restart the devcontainer (this will set `HEROKU_API_KEY` for you)
33
38
 
34
- 2. Create your app using the container stack:
39
+ ```
40
+ your-computer> dx/build
41
+ your-computer> dx/start
42
+ # In another terminal window
43
+ your-computer> dx/exec bash
44
+ devcontainer> echo $HEROKU_API_KEY
45
+ # You should see the token
46
+ ```
47
+
48
+ Setting this environment variable avoids having to constantly re-authenticate to Heroku.
49
+
50
+ 4. Create your app using the container stack:
35
51
 
36
52
  ```
37
53
  > heroku create --stack container -a «your heroku app name»
38
54
  ```
39
- 3. Ensure your app's source code is all checked in, there are no uncommitted or unadded files, and you have pushed to main.
40
- 4. `bin/deploy`
55
+ 5. Ensure your app's source code is all checked in, there are no uncommitted or unadded files, and you have pushed to the `main` branch of your remote Git repository.
56
+ 6. `bin/deploy`
41
57
 
42
58
  This will generate a `Dockerfile` for each process (by default, `Dockerfile.web` and `Dockerfile.release`), build images, push those images to Heroku, and ask Heroku to release them.
43
59
 
44
60
  Debugging Tips:
45
61
 
46
- * Keep in mind it's hard to make general deployment tools. You are expected to understand your deployment and be capable of deploying an arbitrary Rack app manually. Brut's tooling automates what you need to know.
47
- * `bin/deploy` runs the `deploy` subcommand, so `bin/deploy help deploy` can provide
48
- some options for debugging issues:
62
+ * Keep in mind it's hard to make general deployment tools. You are expected to understand your deployment and be capable of deploying an arbitrary Rack app manually. Brut's tooling automates what you need to do based on what you already need to know.
63
+ * `bin/deploy` runs the `deploy` subcommand, so `bin/deploy help deploy` can provide some options for debugging issues:
49
64
 
50
65
  ```
51
66
  devcontainer> bin/deploy help deploy
@@ -106,7 +121,6 @@ some options for debugging issues:
106
121
  You'll need to have a better understanding of Docker to do this, however if you
107
122
  are deploying with Docker, this is an understanding you hopefully already have.
108
123
 
109
-
110
124
  ### Other Mechanisms for Deployment
111
125
 
112
126
  As a Rack app, other deployments should be possible. To make the app work, you'll need to make sure a few things are dealt with:
data/brutrb.com/jobs.md CHANGED
@@ -1,14 +1,114 @@
1
1
  # Background Jobs
2
2
 
3
- Brut provides little direct support for background jobs. Currently, Brut recommends Sidekiq, since it is
4
- battle-tested, well-supported, and open source.
3
+ Brut ships without any background job system, however it should work with any system you'd like to use. Brut
4
+ can install/configure Sidekiq for you, however you are expected to understand Sidekiq in order to use it.
5
5
 
6
- When you set up your Brut app, it should ask if you want Sidekiq support and add the necessary configuraiton.
6
+ ## Setting up Sidekiq
7
7
 
8
- It will expect jobs in `app/src/back_end/jobs`.
8
+ Brut's code-generation system used for installing capabilities are called *segments*, and Brut provides a
9
+ Sidekiq segment you can use to get an initial working setup of Sidekiq in your Brut app.
9
10
 
10
- > [!WARNING]
11
- > The way Sidekiq is configured with Brut is effective and reliable, but it is complex. It currently
12
- > involves several moving parts to make it work properly. This will be an area for improvement.
11
+ ### Adding the Segment
13
12
 
13
+ 1. Ensure your project files are all committed. This is so you can easily see (and, if needed, undo) the
14
+ changes `mkbrut` will make.
15
+ 2. Use `mkbrut` to add the segment:
14
16
 
17
+ ```
18
+ docker run \
19
+ --pull always \
20
+ -v "$PWD":"$PWD" \
21
+ -w "$PWD" \
22
+ -u $(id -u):$(id -g) \
23
+ -it \
24
+ thirdtank/mkbrut \
25
+ mkbrut add-segment -r /path/to/your/project sidekiq
26
+ ```
27
+ 3. This will modify and create various files in your project. Check them out if you like:
28
+
29
+ ```
30
+ > git status
31
+ ```
32
+ 4. Exit your dev environment (i.e. hit `Ctrl-C` wherever you ran `dx/start`).
33
+ 5. Rebuild and restart your dev environment. This may take a moment, since Valkey will be downloaded.
34
+
35
+ ```
36
+ your-computer> dx/build
37
+ your-computer> dx/start
38
+ ```
39
+ 6. In another Terminal, connect to your dev container and run `bin/setup`
40
+
41
+ ```
42
+ your-computer> dx/exec bash
43
+ devcontainer> bin/setup
44
+ ```
45
+ 7. The segment provides an integration test that will use the actual Sidekiq server and client, running
46
+ against the actual Valkey database that was installed:
47
+
48
+ ```
49
+ devcontainer> bin/test e2e specs/integration/sidekiq_works.spec.rb
50
+ ```
51
+
52
+ If this test passes, you are ready to go.
53
+
54
+ ### Using Sidekiq in Brut
55
+
56
+ Jobs live in `app/src/back_end/jobs`, however this is just a convention and is not enforced - you can place a
57
+ job anywhere that Zeitwerk will find the class. Brut also provides basic configuration and a base job.
58
+
59
+ | File | Purpose|
60
+ |------|--------|
61
+ | `app/config/sidekiq.yml` | Standard configuration for Sidekiq |
62
+ | `app/src/back-end/jobs/app_job.r` | Base class for your jobs that includes `Sidekiq::Job` |
63
+ | `app/src/back-end/segments/sidekiq_segment.rb` | Initial client and server configuration for Sidekiq (that you can't do with `sidekiq.yml`. This sets up basic observability for your jobs |
64
+
65
+ ### Accessing the Web UI
66
+
67
+ The Sidekiq segment mounts the Sidekiq Web UI to your app inside `config.ru`:
68
+
69
+ ```ruby
70
+ # ...
71
+ map "/sidekiq" do
72
+ use Rack::Auth::Basic, "Sidekiq" do |username, password|
73
+ [username, password] == [ENV.fetch("SIDEKIQ_BASIC_AUTH_USER"), ENV.fetch("SIDEKIQ_BASIC_AUTH_PASSWORD")]
74
+ end
75
+ run Sidekiq::Web.new
76
+ end
77
+ # ...
78
+ ```
79
+
80
+ Values for `SIDEKIQ_BASIC_AUTH_USER` and `SIDEKIQ_BASIC_AUTH_PASSWORD` for dev and test are placed into
81
+ `.env.development` and `.env.test`, respectively. You must provide these values for production, based on
82
+ however you are managing environment variables.
83
+
84
+ Once you start the app, navigat to `http://localhost:6502/sidekiq` and enter the username/password from
85
+ `.env.development`. You should see the web UI.
86
+
87
+ ### Deploying with The Heroku Segment
88
+
89
+ If you have set up [Heroku Container-based Deployment](/deployment.md#heroku-container-based-deployment), you
90
+ may need to modify `deploy/heroku_config.rb`. The Sidekiq segement should have edited this, however if you
91
+ installed the Heroku segment after setting up Sidekiq, you'll need to add to the file:
92
+
93
+ ```ruby [2-6]
94
+ class HerokuConfig
95
+ def self.additional_images
96
+ {
97
+ "sidekiq" => {
98
+ cmd: "bin/run-sidekiq",
99
+ }
100
+ }
101
+ end
102
+ end
103
+ ```
104
+
105
+ ## Setting Up Other Job Systems
106
+
107
+ To use another job system, you'll likely want to start with `app/src/app.rb`. You can place all your
108
+ initialize code in `#boot!` to get things working, then factor it out from there. `App`, the class in that
109
+ file, is a normal class, so you can extract your setup to other normal classes and bring them in as you would
110
+ in any other Ruby app.
111
+
112
+ Just note that `App`'s `initialize` method should avoid making network connections, so while you are safe to
113
+ create objects and configuration here, do not connect to databases or anything like that. You *can* do that
114
+ inside `boot!`.
@@ -0,0 +1,87 @@
1
+ # Managing Secrets in the Dev Environment
2
+
3
+ Often, you need API keys like GitHub or Heroku tokens in order to perform development tasks. These should not be checked into version
4
+ control, however you can still manage them.
5
+
6
+ ## Feature - API Keys
7
+
8
+ * Developers need do use the Heroku command-line app inside the dev container.
9
+ * Develoeprs do not want to have to perform a daily, browser-based authentication via `heroku auth:login`
10
+
11
+ ### Recipe
12
+
13
+ The file `dx/bash_customizations.local` is set up for exactly this. It is not checked into version control (see your `.gitignore`), and it
14
+ is included when the development environment is built.
15
+
16
+ ```bash
17
+ # dx/bash_customizations.local
18
+ HEROKU_API_KEY=xxxxxx
19
+ ```
20
+
21
+ When you change this file, you must rebuild your dev environment:
22
+
23
+ 1. `Ctrl-C` wherever you ran `dx/start`
24
+ 2. `dx/build`
25
+ 3. `dx/start`
26
+ 4. `dx/exec bash`, then `bin/setup`, then continue where you left off
27
+
28
+ #### How This Works
29
+
30
+ Here is a snippet of how this works. In the first `RUN` directlive, the non-root user is created. When that is completed, `~/.profile` and
31
+ `~/.bashrc` are modified to source both `bash_customizations` (per-project customizations that should **not** contain secrets) and
32
+ `bash_customizations.local`, which is the file we are discussing.
33
+
34
+ After that, the files are copied into the image via the `COPY` directives.
35
+
36
+ ```dockerfile
37
+ # Snippet from Dockerfile.dx
38
+ RUN useradd --uid ${user_uid} --gid ${user_gid} --groups ${sadly_user_must_be_added_to_root}${docker_gid} --create-home --home-dir /home/appuser appuser && \
39
+ echo ". ~/.bash_customizations" >> /home/appuser/.profile && \
40
+ echo ". ~/.bash_customizations.local" >> /home/appuser/.profile && \
41
+ echo ". ~/.bash_customizations" >> /home/appuser/.bashrc && \
42
+ echo ". ~/.bash_customizations.local" >> /home/appuser/.bashrc
43
+
44
+ COPY --chown=appuser:${user_gid} dx/show-help-in-app-container-then-wait.sh /home/appuser
45
+ COPY --chown=appuser:${user_gid} dx/bash_customizations /home/appuser/.bash_customizations
46
+ COPY --chown=appuser:${user_gid} dx/bash_customizations.local /home/appuser/.bash_customizations.local
47
+ ```
48
+
49
+ > [!WARNING]
50
+ > The resulting image **will** contain the secrets from `bash_customizations.local`, so it's
51
+ > **very important** you never push that image to a regsitry.
52
+
53
+ ## Feature - SSH Keys
54
+
55
+ * You need an SSH key in order to push to GitHub from the dev container
56
+ * You do not want to creata new key every time
57
+
58
+ ### Recipe
59
+
60
+ Ultimately, you want the SSH key to be copied into the container and set up as if you'd created the key there. The recipe below is an
61
+ example of how you could do this, and should demonstrate the various seams in Brut's dev environment to allow you to craft it how you like.
62
+
63
+ 1. Choose a directory in the project where each developer will store their keys. **This directory should be excluded from version control**
64
+
65
+ ```
66
+ mkdir dx/credentials
67
+ echo "/dx/credetials" >> .gitignore
68
+ ```
69
+
70
+ 2. Assuming you create an SSH key already, place `id_ed25519` (private key) and `id_ed25519.pub` (public key) into `dx/credentials`.
71
+ 3. Create `dx/credentials/known_hosts` using `id_ed25519.pub`:
72
+
73
+ ```
74
+ github.com ssh-ed25519 «key from id_ed25519.pub here»
75
+ ```
76
+ 4. Your dev container will have access to `dx/credentials` already, so you can use `bin/setup` to copy them to the right place. How
77
+ you do this depends on how complicated you want to get. You can examine Brut's `bin/setup` to see how it manages it. You will
78
+ see that ti uses `ssh-agent` to avoid requiring the passcode every time, and that it uses `chmod` to make sure the SSH
79
+ directories are the right permissions.
80
+
81
+ > [!WARNING]
82
+ > The resulting image **will** contain your SSH key, so it's
83
+ > **very important** you never push that image to a regsitry.
84
+
85
+
86
+ This recipe is scant on details, since each credential is highly specific. The key points to know are that you can store information in the
87
+ project, but not checked in, then rely on that information being available to `bin/setup` inside the container.