brut 0.0.29 → 0.2.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 (517) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +23 -2
  4. data/assets/LogoStop.pxd +0 -0
  5. data/assets/MetroLogo.graffle +0 -0
  6. data/assets/SocialImage.png +0 -0
  7. data/assets/SocialImage.pxd +0 -0
  8. data/brutrb.com/.vitepress/config.mjs +48 -9
  9. data/brutrb.com/.vitepress/theme/style.css +14 -35
  10. data/brutrb.com/adrs.md +15 -0
  11. data/brutrb.com/ai.md +10 -15
  12. data/brutrb.com/assets.md +2 -9
  13. data/brutrb.com/brut-js.md +12 -2
  14. data/brutrb.com/cli.md +9 -13
  15. data/brutrb.com/components.md +118 -96
  16. data/brutrb.com/configuration.md +3 -4
  17. data/brutrb.com/css.md +2 -2
  18. data/brutrb.com/custom-element-tests.md +3 -4
  19. data/brutrb.com/database-access.md +1 -1
  20. data/brutrb.com/database-schema.md +29 -41
  21. data/brutrb.com/dev-environment.md +13 -8
  22. data/brutrb.com/dir-structure.md +120 -0
  23. data/brutrb.com/doc-conventions.md +17 -15
  24. data/brutrb.com/dx +1 -0
  25. data/brutrb.com/end-to-end-tests.md +12 -10
  26. data/brutrb.com/features.md +373 -0
  27. data/brutrb.com/flash-and-session.md +115 -131
  28. data/brutrb.com/form-constraints.md +266 -0
  29. data/brutrb.com/forms.md +140 -765
  30. data/brutrb.com/getting-started.md +10 -11
  31. data/brutrb.com/handlers.md +119 -95
  32. data/brutrb.com/hooks.md +18 -20
  33. data/brutrb.com/i18n.md +6 -4
  34. data/brutrb.com/images/DevEnvironment.graffle +0 -0
  35. data/brutrb.com/images/DevEnvironment.png +0 -0
  36. data/brutrb.com/images/LogoStop.png +0 -0
  37. data/brutrb.com/index.md +0 -3
  38. data/brutrb.com/instrumentation.md +7 -10
  39. data/brutrb.com/javascript.md +14 -14
  40. data/brutrb.com/keyword-injection.md +72 -114
  41. data/brutrb.com/layouts.md +20 -52
  42. data/brutrb.com/lsp.md +1 -1
  43. data/brutrb.com/overview.md +30 -372
  44. data/brutrb.com/pages.md +119 -207
  45. data/brutrb.com/public/SocialImage.png +0 -0
  46. data/brutrb.com/public/favicon.ico +0 -0
  47. data/brutrb.com/recipes/alternate-layouts.md +32 -0
  48. data/brutrb.com/recipes/authentication.md +315 -6
  49. data/brutrb.com/recipes/blank-layouts.md +22 -0
  50. data/brutrb.com/recipes/custom-flash.md +51 -0
  51. data/brutrb.com/recipes/indexed-forms.md +149 -0
  52. data/brutrb.com/recipes/text-field-component.md +182 -0
  53. data/brutrb.com/roadmap.md +57 -0
  54. data/brutrb.com/routes.md +56 -82
  55. data/brutrb.com/security.md +0 -3
  56. data/brutrb.com/space-time-continuum.md +8 -12
  57. data/brutrb.com/tutorial.md +5 -1
  58. data/brutrb.com/why.md +19 -0
  59. data/docs/404.html +8 -3
  60. data/docs/SocialImage.png +0 -0
  61. data/docs/adrs.html +29 -0
  62. data/docs/ai.html +11 -6
  63. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  64. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  65. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  66. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  67. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  68. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  69. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  70. data/docs/api/Brut/BackEnd.html +1 -1
  71. data/docs/api/Brut/CLI/App.html +1 -1
  72. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  73. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  74. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  75. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  76. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  77. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  78. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  79. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  80. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  81. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  82. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  83. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  84. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  85. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  86. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  87. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  88. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  89. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  90. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  91. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  92. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +3 -3
  93. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  94. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  95. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  96. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  97. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +2 -2
  98. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  99. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  100. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  101. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  102. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  103. data/docs/api/Brut/CLI/Apps/Test/JS.html +6 -6
  104. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  105. data/docs/api/Brut/CLI/Apps/Test.html +2 -2
  106. data/docs/api/Brut/CLI/Apps.html +1 -1
  107. data/docs/api/Brut/CLI/Command.html +3 -3
  108. data/docs/api/Brut/CLI/Error.html +1 -1
  109. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  110. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  111. data/docs/api/Brut/CLI/Executor.html +1 -1
  112. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  113. data/docs/api/Brut/CLI/Options.html +1 -1
  114. data/docs/api/Brut/CLI/Output.html +1 -1
  115. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  116. data/docs/api/Brut/CLI.html +1 -1
  117. data/docs/api/Brut/FactoryBot.html +1 -1
  118. data/docs/api/Brut/Framework/App.html +1 -1
  119. data/docs/api/Brut/Framework/Config.html +1 -1
  120. data/docs/api/Brut/Framework/Container.html +1 -1
  121. data/docs/api/Brut/Framework/Error.html +1 -1
  122. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  123. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  124. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  125. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  126. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  127. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  128. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  129. data/docs/api/Brut/Framework/Errors.html +1 -1
  130. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  131. data/docs/api/Brut/Framework/MCP.html +1 -1
  132. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  133. data/docs/api/Brut/Framework.html +1 -1
  134. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +3 -3
  145. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Form.html +9 -11
  155. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  157. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +135 -20
  162. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +135 -20
  164. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  167. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  172. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  175. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  176. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  177. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  178. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  179. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  180. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  181. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  182. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  183. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  184. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  185. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  186. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  187. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  188. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +2 -2
  189. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  190. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  191. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  192. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  193. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  194. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  195. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  196. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  197. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  198. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  199. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  200. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  201. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  202. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  203. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  204. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  205. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  206. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  207. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  208. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  209. data/docs/api/Brut/FrontEnd.html +1 -1
  210. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  211. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  212. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  213. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  214. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  215. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  216. data/docs/api/Brut/I18n.html +1 -1
  217. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  218. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  219. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  220. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  221. data/docs/api/Brut/Instrumentation.html +1 -1
  222. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  223. data/docs/api/Brut/SinatraHelpers.html +1 -1
  224. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  225. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  226. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  227. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  228. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  229. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  230. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  231. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  232. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  233. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  234. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  235. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  236. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  237. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  238. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  239. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  240. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  241. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  242. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  243. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  244. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  245. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  246. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  247. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  248. data/docs/api/Brut/SpecSupport.html +1 -1
  249. data/docs/api/Brut.html +1 -1
  250. data/docs/api/Clock.html +1 -1
  251. data/docs/api/RichString.html +150 -343
  252. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  253. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  254. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  255. data/docs/api/Sequel/Extensions.html +1 -1
  256. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  257. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  258. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  259. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  260. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  261. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  262. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  263. data/docs/api/Sequel/Plugins.html +1 -1
  264. data/docs/api/Sequel.html +1 -1
  265. data/docs/api/_index.html +5 -5
  266. data/docs/api/class_list.html +1 -1
  267. data/docs/api/file.README.html +22 -3
  268. data/docs/api/index.html +22 -3
  269. data/docs/api/method_list.html +290 -306
  270. data/docs/api/top-level-namespace.html +1 -1
  271. data/docs/assets/DevEnvironment.DaFcVfwP.png +0 -0
  272. data/docs/assets/LogoStop.Gb3tDhL1.png +0 -0
  273. data/docs/assets/adrs.md.JRxZ5uYE.js +1 -0
  274. data/docs/assets/adrs.md.JRxZ5uYE.lean.js +1 -0
  275. data/docs/assets/{ai.md._6HCDL6d.js → ai.md.Cy9GWnER.js} +1 -1
  276. data/docs/assets/ai.md.Cy9GWnER.lean.js +1 -0
  277. data/docs/assets/{app.BhrfSt68.js → app.Dm3x-DQc.js} +1 -1
  278. data/docs/assets/{assets.md.D3wunzLx.js → assets.md.7C3HWkga.js} +3 -3
  279. data/docs/assets/{assets.md.D3wunzLx.lean.js → assets.md.7C3HWkga.lean.js} +1 -1
  280. data/docs/assets/{brut-js.md.o2DAO2s2.js → brut-js.md.B4GYxQVw.js} +1 -1
  281. data/docs/assets/{brut-js.md.o2DAO2s2.lean.js → brut-js.md.B4GYxQVw.lean.js} +1 -1
  282. data/docs/assets/chunks/@localSearchIndexroot.BqRrkR00.js +1 -0
  283. data/docs/assets/chunks/{VPLocalSearchBox.Dpot_2H4.js → VPLocalSearchBox.DL6bnqee.js} +1 -1
  284. data/docs/assets/chunks/{theme.N2SNVLgU.js → theme.BXdlf6e8.js} +2 -2
  285. data/docs/assets/{cli.md.RmeA2b0i.js → cli.md.CjsktgFz.js} +15 -20
  286. data/docs/assets/components.md.Pg_Lo35G.js +96 -0
  287. data/docs/assets/{components.md.CRUMdRoN.lean.js → components.md.Pg_Lo35G.lean.js} +1 -1
  288. data/docs/assets/{configuration.md.LG-zIBww.js → configuration.md.BfeGnEci.js} +3 -3
  289. data/docs/assets/{css.md.DJgj2clw.js → css.md.CltvJqAa.js} +3 -3
  290. data/docs/assets/{custom-element-tests.md.BrYJQEl3.js → custom-element-tests.md.B_rbta32.js} +3 -3
  291. data/docs/assets/{database-access.md.C7l-Vuvb.js → database-access.md.gnluu54N.js} +1 -1
  292. data/docs/assets/{database-schema.md.BUjR0VS1.js → database-schema.md.CSYk6E6v.js} +6 -6
  293. data/docs/assets/{database-schema.md.BUjR0VS1.lean.js → database-schema.md.CSYk6E6v.lean.js} +1 -1
  294. data/docs/assets/dev-environment.md.Dy6EldaM.js +16 -0
  295. data/docs/assets/dev-environment.md.Dy6EldaM.lean.js +1 -0
  296. data/docs/assets/dir-structure.md.CWir1pic.js +46 -0
  297. data/docs/assets/dir-structure.md.CWir1pic.lean.js +1 -0
  298. data/docs/assets/doc-conventions.md.DOkAuXlt.js +1 -0
  299. data/docs/assets/doc-conventions.md.DOkAuXlt.lean.js +1 -0
  300. data/docs/assets/{end-to-end-tests.md.yfQHC0b5.js → end-to-end-tests.md.DzqRpZ43.js} +5 -3
  301. data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +1 -0
  302. data/docs/assets/features.md.DPFXsy0z.js +154 -0
  303. data/docs/assets/features.md.DPFXsy0z.lean.js +1 -0
  304. data/docs/assets/flash-and-session.md.nPvUpnUx.js +79 -0
  305. data/docs/assets/{flash-and-session.md.BXY8RvT0.lean.js → flash-and-session.md.nPvUpnUx.lean.js} +1 -1
  306. data/docs/assets/form-constraints.md.x5tNpTTI.js +90 -0
  307. data/docs/assets/form-constraints.md.x5tNpTTI.lean.js +1 -0
  308. data/docs/assets/forms.md.BQZlCwvi.js +64 -0
  309. data/docs/assets/forms.md.BQZlCwvi.lean.js +1 -0
  310. data/docs/assets/{getting-started.md.Dj0qtZI2.js → getting-started.md.BcXnNuD6.js} +5 -5
  311. data/docs/assets/{getting-started.md.Dj0qtZI2.lean.js → getting-started.md.BcXnNuD6.lean.js} +1 -1
  312. data/docs/assets/handlers.md.Chyri6KA.js +54 -0
  313. data/docs/assets/handlers.md.Chyri6KA.lean.js +1 -0
  314. data/docs/assets/{hooks.md.C4-moMny.js → hooks.md.Jmb5VOLA.js} +4 -4
  315. data/docs/assets/{hooks.md.C4-moMny.lean.js → hooks.md.Jmb5VOLA.lean.js} +1 -1
  316. data/docs/assets/{i18n.md.Do9i1qWl.js → i18n.md.xQhiGo1G.js} +2 -2
  317. data/docs/assets/{i18n.md.Do9i1qWl.lean.js → i18n.md.xQhiGo1G.lean.js} +1 -1
  318. data/docs/assets/index.md.Bn9e0sRJ.js +1 -0
  319. data/docs/assets/index.md.Bn9e0sRJ.lean.js +1 -0
  320. data/docs/assets/{instrumentation.md.a9Pjps4P.js → instrumentation.md.BgcaGVYH.js} +2 -2
  321. data/docs/assets/{instrumentation.md.a9Pjps4P.lean.js → instrumentation.md.BgcaGVYH.lean.js} +1 -1
  322. data/docs/assets/{javascript.md.GWbhRS51.js → javascript.md.DzrMxUmI.js} +7 -7
  323. data/docs/assets/{javascript.md.GWbhRS51.lean.js → javascript.md.DzrMxUmI.lean.js} +1 -1
  324. data/docs/assets/keyword-injection.md.95Zgh2eN.js +21 -0
  325. data/docs/assets/{keyword-injection.md.Dt2tKREs.lean.js → keyword-injection.md.95Zgh2eN.lean.js} +1 -1
  326. data/docs/assets/{layouts.md.cPnh3NId.js → layouts.md.CJGDFY-m.js} +2 -15
  327. data/docs/assets/layouts.md.CJGDFY-m.lean.js +1 -0
  328. data/docs/assets/{lsp.md.Bsu-f6VU.js → lsp.md.Dn1rIiW0.js} +1 -1
  329. data/docs/assets/{lsp.md.Bsu-f6VU.lean.js → lsp.md.Dn1rIiW0.lean.js} +1 -1
  330. data/docs/assets/overview.md.iMnwLO4x.js +1 -0
  331. data/docs/assets/overview.md.iMnwLO4x.lean.js +1 -0
  332. data/docs/assets/pages.md.B7Hc-i6H.js +45 -0
  333. data/docs/assets/pages.md.B7Hc-i6H.lean.js +1 -0
  334. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.js +22 -0
  335. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +1 -0
  336. data/docs/assets/recipes_authentication.md.Dzvi_g69.js +156 -0
  337. data/docs/assets/recipes_authentication.md.Dzvi_g69.lean.js +1 -0
  338. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +15 -0
  339. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +1 -0
  340. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.js +26 -0
  341. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +1 -0
  342. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.js +74 -0
  343. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +1 -0
  344. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.js +101 -0
  345. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +1 -0
  346. data/docs/assets/roadmap.md.C6PRi0DX.js +1 -0
  347. data/docs/assets/roadmap.md.C6PRi0DX.lean.js +1 -0
  348. data/docs/assets/routes.md.B8kfUPHU.js +21 -0
  349. data/docs/assets/{routes.md.BMM7peut.lean.js → routes.md.B8kfUPHU.lean.js} +1 -1
  350. data/docs/assets/{security.md.C668yXCi.js → security.md.C0G_AZR-.js} +1 -1
  351. data/docs/assets/{security.md.C668yXCi.lean.js → security.md.C0G_AZR-.lean.js} +1 -1
  352. data/docs/assets/space-time-continuum.md.xl44xDos.js +1 -0
  353. data/docs/assets/{space-time-continuum.md.KPUIKysQ.lean.js → space-time-continuum.md.xl44xDos.lean.js} +1 -1
  354. data/docs/assets/{style.B2o1L9eN.css → style.B1z60PPQ.css} +1 -1
  355. data/docs/assets/tutorial.md.BYXj4cOu.js +1 -0
  356. data/docs/assets/tutorial.md.BYXj4cOu.lean.js +1 -0
  357. data/docs/assets/why.md.C-hk5xgJ.js +1 -0
  358. data/docs/assets/why.md.C-hk5xgJ.lean.js +1 -0
  359. data/docs/assets.html +12 -7
  360. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  361. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  362. data/docs/brut-js/api/Autosubmit.html +1 -1
  363. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  364. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  365. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  366. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  367. data/docs/brut-js/api/BufferedLogger.html +1 -1
  368. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  369. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  370. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  371. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  372. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  373. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  374. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  375. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  376. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  377. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  378. data/docs/brut-js/api/Form.html +1 -1
  379. data/docs/brut-js/api/Form.js.html +1 -1
  380. data/docs/brut-js/api/I18nTranslation.html +1 -1
  381. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  382. data/docs/brut-js/api/LocaleDetection.html +1 -1
  383. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  384. data/docs/brut-js/api/Logger.html +1 -1
  385. data/docs/brut-js/api/Logger.js.html +1 -1
  386. data/docs/brut-js/api/Message.html +1 -1
  387. data/docs/brut-js/api/Message.js.html +1 -1
  388. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  389. data/docs/brut-js/api/RichString.html +1 -1
  390. data/docs/brut-js/api/RichString.js.html +1 -1
  391. data/docs/brut-js/api/Tabs.html +1 -1
  392. data/docs/brut-js/api/Tabs.js.html +1 -1
  393. data/docs/brut-js/api/Tracing.html +1 -1
  394. data/docs/brut-js/api/Tracing.js.html +1 -1
  395. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  396. data/docs/brut-js/api/external-Performance.html +1 -1
  397. data/docs/brut-js/api/external-Promise.html +1 -1
  398. data/docs/brut-js/api/external-ValidityState.html +1 -1
  399. data/docs/brut-js/api/external-Window.html +1 -1
  400. data/docs/brut-js/api/external-fetch.html +1 -1
  401. data/docs/brut-js/api/global.html +1 -1
  402. data/docs/brut-js/api/index.html +1 -1
  403. data/docs/brut-js/api/index.js.html +1 -1
  404. data/docs/brut-js/api/module-testing.html +1 -1
  405. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  406. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  407. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  408. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  409. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  410. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  411. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  412. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  413. data/docs/brut-js/api/testing_index.js.html +1 -1
  414. data/docs/brut-js.html +12 -7
  415. data/docs/business-logic.html +10 -5
  416. data/docs/cli.html +26 -26
  417. data/docs/components.html +61 -64
  418. data/docs/configuration.html +13 -8
  419. data/docs/css.html +14 -9
  420. data/docs/custom-element-tests.html +14 -9
  421. data/docs/database-access.html +12 -7
  422. data/docs/database-schema.html +15 -10
  423. data/docs/deployment.html +10 -5
  424. data/docs/dev-environment.html +17 -7
  425. data/docs/dir-structure.html +74 -0
  426. data/docs/doc-conventions.html +11 -6
  427. data/docs/end-to-end-tests.html +15 -8
  428. data/docs/favicon.ico +0 -0
  429. data/docs/features.html +182 -0
  430. data/docs/flash-and-session.html +73 -82
  431. data/docs/form-constraints.html +118 -0
  432. data/docs/forms.html +57 -367
  433. data/docs/getting-started.html +15 -10
  434. data/docs/handlers.html +51 -61
  435. data/docs/hashmap.json +1 -1
  436. data/docs/hooks.html +14 -9
  437. data/docs/i18n.html +12 -7
  438. data/docs/index.html +11 -6
  439. data/docs/instrumentation.html +12 -7
  440. data/docs/javascript.html +17 -12
  441. data/docs/jobs.html +10 -5
  442. data/docs/keyword-injection.html +22 -21
  443. data/docs/layouts.html +12 -20
  444. data/docs/lsp.html +11 -6
  445. data/docs/markdown-examples.html +10 -5
  446. data/docs/middleware.html +10 -5
  447. data/docs/overview.html +11 -138
  448. data/docs/pages.html +49 -121
  449. data/docs/recipes/alternate-layouts.html +50 -0
  450. data/docs/recipes/authentication.html +166 -6
  451. data/docs/recipes/blank-layouts.html +43 -0
  452. data/docs/recipes/custom-flash.html +54 -0
  453. data/docs/recipes/indexed-forms.html +102 -0
  454. data/docs/recipes/text-field-component.html +129 -0
  455. data/docs/roadmap.html +29 -0
  456. data/docs/routes.html +16 -19
  457. data/docs/security.html +11 -6
  458. data/docs/seed-data.html +10 -5
  459. data/docs/space-time-continuum.html +11 -6
  460. data/docs/tutorial.html +11 -6
  461. data/docs/unit-tests.html +10 -5
  462. data/docs/why.html +29 -0
  463. data/lib/brut/cli/apps/test.rb +1 -1
  464. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +2 -2
  465. data/lib/brut/front_end/form.rb +8 -8
  466. data/lib/brut/front_end/forms/radio_button_group_input.rb +8 -1
  467. data/lib/brut/front_end/forms/select_input.rb +8 -1
  468. data/lib/brut/junk_drawer.rb +48 -9
  469. data/lib/brut/version.rb +1 -1
  470. data/specs/brut/front_end/forms/radio_button_group_input.spec.rb +54 -0
  471. data/specs/brut/front_end/forms/select_input.spec.rb +54 -0
  472. data/specs/brut/junk_drawer.spec.rb +75 -0
  473. metadata +129 -82
  474. data/brutrb.com/images/logo-300.png +0 -0
  475. data/brutrb.com/images/logo.png +0 -0
  476. data/brutrb.com/not-released.md +0 -5
  477. data/brutrb.com/public/images/logo-300.png +0 -0
  478. data/brutrb.com/public/images/logo.png +0 -0
  479. data/docs/assets/LogoStop.X8x-4riz.png +0 -0
  480. data/docs/assets/ai.md._6HCDL6d.lean.js +0 -1
  481. data/docs/assets/chunks/@localSearchIndexroot.CeRAdP1K.js +0 -1
  482. data/docs/assets/components.md.CRUMdRoN.js +0 -104
  483. data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
  484. data/docs/assets/dev-environment.md.GZv6xvi9.js +0 -11
  485. data/docs/assets/dev-environment.md.GZv6xvi9.lean.js +0 -1
  486. data/docs/assets/doc-conventions.md.-kN3Xo5C.js +0 -1
  487. data/docs/assets/doc-conventions.md.-kN3Xo5C.lean.js +0 -1
  488. data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +0 -1
  489. data/docs/assets/flash-and-session.md.BXY8RvT0.js +0 -93
  490. data/docs/assets/forms.md.B-koVgyw.js +0 -379
  491. data/docs/assets/forms.md.B-koVgyw.lean.js +0 -1
  492. data/docs/assets/handlers.md.089DVD3v.js +0 -69
  493. data/docs/assets/handlers.md.089DVD3v.lean.js +0 -1
  494. data/docs/assets/index.md.CuBB-BdM.js +0 -1
  495. data/docs/assets/index.md.CuBB-BdM.lean.js +0 -1
  496. data/docs/assets/keyword-injection.md.Dt2tKREs.js +0 -25
  497. data/docs/assets/layouts.md.cPnh3NId.lean.js +0 -1
  498. data/docs/assets/not-released.md.BBy28McC.js +0 -1
  499. data/docs/assets/not-released.md.BBy28McC.lean.js +0 -1
  500. data/docs/assets/overview.md.DVKRM8zl.js +0 -133
  501. data/docs/assets/overview.md.DVKRM8zl.lean.js +0 -1
  502. data/docs/assets/pages.md.BE3kfOc5.js +0 -122
  503. data/docs/assets/pages.md.BE3kfOc5.lean.js +0 -1
  504. data/docs/assets/recipes_authentication.md.CAsXf7hk.js +0 -1
  505. data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +0 -1
  506. data/docs/assets/routes.md.BMM7peut.js +0 -29
  507. data/docs/assets/space-time-continuum.md.KPUIKysQ.js +0 -1
  508. data/docs/assets/tutorial.md.BnoGjrdK.js +0 -1
  509. data/docs/assets/tutorial.md.BnoGjrdK.lean.js +0 -1
  510. data/docs/images/logo-300.png +0 -0
  511. data/docs/images/logo.png +0 -0
  512. data/docs/not-released.html +0 -24
  513. /data/docs/assets/{cli.md.RmeA2b0i.lean.js → cli.md.CjsktgFz.lean.js} +0 -0
  514. /data/docs/assets/{configuration.md.LG-zIBww.lean.js → configuration.md.BfeGnEci.lean.js} +0 -0
  515. /data/docs/assets/{css.md.DJgj2clw.lean.js → css.md.CltvJqAa.lean.js} +0 -0
  516. /data/docs/assets/{custom-element-tests.md.BrYJQEl3.lean.js → custom-element-tests.md.B_rbta32.lean.js} +0 -0
  517. /data/docs/assets/{database-access.md.C7l-Vuvb.lean.js → database-access.md.gnluu54N.lean.js} +0 -0
@@ -1,77 +1,67 @@
1
1
  # Keyword Injection
2
2
 
3
- Brut is desiged around classes and objects, as compared to modules and DSLs. Almost everything you do when building your app is to create a class that has an initializer and implements one or more methods. But, these initalizers and methods often need information from the request that Brut is managing.
3
+ Brut is desiged around classes and objects, as compared to modules and DSLs. Almost everything you do when building your app is to create a class that has an initializer and implements one or more methods. But, these classes often need information from the request that Brut is managing.
4
4
 
5
- In a basic Rack or Sinatra app, you would access this information via Rack's API, which is essentially a `Hash` of whatever. It's error-prone and requires consulting documentation, source code, or runtime information to figure out what's stored where.
5
+ In a basic Rack or Sinatra app, you would access this information via Rack's API, which is essentially a Hash of Whatever. It's error-prone and requires consulting documentation, source code, or runtime information to figure out what's stored where.
6
6
 
7
7
  Brut can instead inject these values explicitly into the classes of yours it creates. It does this based on the
8
- names of keyword arguments declared by your class' intializer or a template method Brut will call.
8
+ names of keyword arguments declared by your class' intializer.
9
9
 
10
10
  ## Overview
11
11
 
12
- For example, a [Page](/pages) requires you to implement an initializer. That initializer's keyword arguments define what
13
- information is needed. Brut provides that information when it creates the object. This is a form of dependency injection and it can simplify your code if used effectively.
14
-
15
- Consider this route:
16
-
17
- ```ruby
18
- page "/widgets/:id"
19
- ```
20
-
21
- Brut will expect to find `WidgetsByIdPage`. Your initializer can declare `id:` as a keyword arg and this will be passed when the
22
- class is created:
12
+ A [Page](/pages) may need the session, flash, HTTP headers, query string parameters, or placeholder values from the URI. These can all be provided by declaring them as keyword arguments to the page's initializer:
23
13
 
24
14
  ```ruby
25
- class WidgetsByIdPage < AppPage
26
- def initialize(id:)
27
- @widget = DB::Widget.find(id)
15
+ clas WidgetsByIdPage < AppPage
16
+ def initialize(id:, # ":id" from /widgets/:id
17
+ session:, # AppSession instance for this request
18
+ flash:, # Flash for this request
19
+ http_user_agent:, # Value of User-Agent header
20
+ compact:) # query string param "compact"
21
+
22
+ # ...
28
23
  end
29
24
  end
30
25
  ```
31
26
 
32
- If the page requires access to the session, it can declare that:
33
-
34
- ```ruby {2,4}
35
- class WidgetsByIdPage < AppPage
36
- def initialize(id:, session:)
37
- @widget = DB::Widget.find(id)
38
- @current_user = session.current_user
39
- end
40
- end
41
- ```
27
+ Brut uses this technique in multiple places. It allows you to design classes whose
28
+ dependencies are clear and explicit, but without having to dig around into hashes or
29
+ manually construct higher-level objects.
42
30
 
43
- Because `session:` is a required argument, Brut cannot instantiate the page without it, so it will always be
44
- passed in and availbale.
45
31
 
46
32
  ### Standard Injectible Information
47
33
 
48
34
  In any request, the following information is available to be injected:
49
35
 
50
- * `session:` - An instance of your app's `Brut::FrontEnd::Session` subclass for the current visitor's session.
51
- * `flash:` - An instance of your app's `Brut::FrontEnd::Flash` subclass.
52
- * `xhr:` - true if this was an Ajax request.
53
- * `body:` - the body submitted, if any.
54
- * `csrf_token:` - The current CSRF token.
55
- * `clock:` - A `Clock` to be used to access the current time in the visitor's time zone.
56
- * `http_*` - any parameter that starts with `http_` is assumed to be for an HTTP header. For example, `http_accept_language` would be
57
- given the value for the "Accept-Language" header. See the section on HTTP headers below.
58
- * `env:` - The Rack env. This is discouraged, but available if you can't get what you want directly
59
-
60
- Depending on the context, other information is available:
61
-
62
- * `form:` - If a form was submitted, this is the `Brut::FrontEnd::Form` subclass containing the data. See [Forms](/forms).
63
- * Any query string paramter - Note that if these conflict with existing Brut values, the behavior is undefined. Name your query string parameters carefully. These should have default values or your page won't work if they are omitted.
64
- * Any route parameter - These should not have default values, since they are required for Brut to match the route.
65
-
66
- A `Brut::FrontEnd::RouteHook` is slightly different. Only the following data is available to be injected:
67
-
68
- * `request_context:` - The current request context, thought it may be `nil` depending on when the hook runs
69
- * `session:` - An instance of your app's `Brut::FrontEnd::Session` subclass for the current visitor's session.
70
- * `request:` - The Rack request
71
- * `response:` - The Rack response
72
- * `env:` - The Rack env.
73
-
74
- ### HTTP Headers
36
+ | Value | Always Present? | Description |
37
+ | --- | --- | ---- |
38
+ | `session:` | Yes | An instance of your app's `Brut::FrontEnd::Session` subclass for the current visitor's session. |
39
+ | `flash:` | Yes | An instance of your app's `Brut::FrontEnd::Flash` subclass. |
40
+ | `xhr:` | Yes | true if this was an Ajax request. |
41
+ | `body:` | Yes | the body submitted, if any. |
42
+ | `csrf_token:` | Yes | The current CSRF token. |
43
+ | `clock:` | Yes | A `Clock` to be used to access the current time in the visitor's time zone. |
44
+ | `http_*` | No | any parameter that starts with `http_` is assumed to be for an HTTP header. For example, `http_accept_language` would be given the value for the "Accept-Language" header. See the section on HTTP headers below. |
45
+ | `rack_request_*:` | ❌ No | Any value from the [`Rack::Request`](https://rubydoc.info/gems/rack/3.1.16/Rack/Request) or, more likely, from the [`Helpers`](https://rubydoc.info/gems/rack/3.1.16/Rack/Request/Helpers) module. |
46
+ | `env:` | Yes | The Rack env. This is discouraged, but available if you can't get what you want directly |
47
+ | `form:` | ❌ No | The [form](/forms) that was submitted, for [handlers](/handlers) only |
48
+ | Any query string parameter | No | For [pages](/pages) only |
49
+ | Any route placeholder | Yes | For [pages](/pages) and [handlers](/handlers) |
50
+
51
+ #### Route Hooks
52
+
53
+ [Route hooks](/hooks) are slightly different. They have access to only these
54
+ values:
55
+
56
+ | Name | Always Present? | Description |
57
+ | --- | --- | --- |
58
+ | `request_context:` | ❌ No | The current `Brut::FrontEnd::RequestContext`, thought it may be `nil` if the hook runs before `Brut::FrontEnd::InlineSvgLocator` |
59
+ | `session:` | ✅ Yes | An instance of your app's `Brut::FrontEnd::Session` subclass for the current visitor's session. |
60
+ | `request:` | ✅ Yes | The Rack request |
61
+ | `response:` | ✅ Yes | The Rack response |
62
+ | `env:` | ✅ Yes | The Rack env. |
63
+
64
+ #### HTTP Headers
75
65
 
76
66
  Since any header can be sent with a request, Brut allows you to access them, including non-standard ones. Rack (which is based on CGI), provides access to all HTTP headers in the `env` by taking the header name, replacing dashes ("-") with underscores ("\_"), and prepending `http_` to the name, then uppercasing it. Thus, "User-Agent" becomes `HTTP_USER_AGENT`.
77
67
 
@@ -88,22 +78,14 @@ things:
88
78
  - If the header was not provided, no value is injected, and your code will receive the default value.
89
79
  - If the header *was* provided, it's value is injected, even if it's the empty string.
90
80
 
91
- ### Ordering and Disambiguation
92
-
93
- You are discouraged from using builtin keys for your own data or request parameters. For example, you should not have a query string
94
- parameter named `env` as this conflicts with the builtin `env` that Brut will inject.
95
-
96
- Since you can inject your own data (see below), you are free to corrupt the request context. Please don't do this. Brut may actively
97
- prevent this in the future.
98
-
99
- You can also use the request context to put your own data that can be injected.
100
-
101
81
  ### Injecting Custom Data
102
82
 
103
- The correct place to inject your own data into the request is in a [before hook](/hooks). When you
104
- configure a before hook, it will run after Brut's internal
105
- `Brut::FrontEnd::RouteHooks::SetupRequestContext`, which ensures the request context exists and is ready
106
- for use.
83
+ The true power of keyword injection is that you can store your own data into the
84
+ request context and have it injected into classes when Brut instantiates them.
85
+
86
+ The place to do this is in a [before hook](/hooks), since that happens before any
87
+ page or handler is created, but *after* the `Brut::FrontEnd::RequestContext` is
88
+ created (which is where all of this information is stored).
107
89
 
108
90
  For example, here is how you might inject the currently logged-in account based on the session:
109
91
 
@@ -118,71 +100,44 @@ class AuthBeforeHook < Brut::FrontEnd::RouteHook
118
100
  end
119
101
  ```
120
102
 
121
- Note that the value is only injected if it exists. It's important not to inject `nil` for values that
122
- don't exist.
103
+ Note that the value is only injected if it exists. It's important not to inject `nil` for values that don't exist.
123
104
 
124
- You may be thinking that this particular example is unnecessary. You could simply inject `session:` and
125
- call `session.authenticated_account`:
105
+ With this in place, any page that requires an authenticated account can declare it:
126
106
 
127
107
  ```ruby
128
- class DashboardPage < AppPage
129
- def initialize(session:)
130
- @widgets = session.authenticated_account.widgets # e.g.
131
- end
132
- end
133
- ```
134
-
135
- If `DashboardPage` requires an authenticated account, by only injecting the session, you'll need to handle the case where `session.authenticated_account` is `nil`. Instead, if you configure the `AuthBeforeHook` as above, then inject `authenticated_account`, you avoid the need for this logic:
136
-
137
- ```ruby
138
- class DashboardPage < AppPage
108
+ class PreferencesPage < AppPage
139
109
  def initialize(authenticated_account:)
140
- @widgets = authenticated_account.widgets # e.g.
110
+ # ...
141
111
  end
142
112
  end
143
113
  ```
144
114
 
145
- Because `AuthBeforeHook` never injects `nil`, `DashboardPage` can rely on `authenticated_account` always being present. Further, if a visitor tried to access `/dashboard_page` without having been authenticated, Brut would be unable to create an instance of `DashboardPage` and generate an error.
146
-
147
- ### `nil` and Empty Strings
148
-
149
- When a keyword argument has no default value, Brut will require that value to exist and be available for injection. If the keyword is
150
- not one of the canned always-available values, it will look in the request context, then in the query string.
115
+ If the request context has no value for `authenticated_account`, the page cannot be
116
+ instantiated. Thus, the page's code can always rely on a non-`nil` value for
117
+ `authenticated_account` (provided you don't inject `nil`).
151
118
 
152
- If the request has the keyword as a key, *it will inject whatever value it finds, including `nil`*. In general, you should avoid
153
- injecting `nil` when you actually intend to not have a value.
154
-
155
- For example, the `AuthBeforeHook` above, you could implement it like so:
156
-
157
- ```ruby
158
- request_context[:authenticated_account] = session.authenticated_account
159
- ```
160
-
161
- The problem is that if the visitor is not logged in, the `:authenticated_account` *will* have a value, and that value will be `nil`. This is almost certainly not what you want.
162
-
163
- For query string parameters, the HTTP spec says that they are strings. Thus, if a query string parameter is present in ther request
164
- URL, it will *always* have a value and *never* be `nil`. If the paramter doesn't have a value after the `=` (e.g. for `foo` in `?foo=&bar=quux`), the value will be the empty string.
165
-
166
- This means you must write code to explicitly handle the cases you care about.
119
+ > [!WARNING]
120
+ > Do not inject `nil` into the request context. Brut currently allows
121
+ > it, but may prevent it in a future update. `nil` is no good for nobody.
167
122
 
168
123
  ### When Values Aren't Available
169
124
 
170
125
  When a value is not available for injection, and the keyword doesn't provide a default, Brut will raise an error. This is because
171
126
  such a situation represents a design error.
172
127
 
173
- For example, the `DashboardPage` above requires an `authenticated_account`. Your app should never route a logged-out visitor to that
174
- page. This allows the `DashboardPage` to avoid having to check for `nil` and figure out what to do.
175
-
176
- This is most relevant for query string parameters, since they can be easily manipulated by the visitor in their browser. Query string
177
- parameters should always have a default value, even if it's `nil`.
128
+ The tables above document which values should always be available. You should never
129
+ provide a default value for these, e.g. `session:` or `env:`. For values that are
130
+ not always available, you should provide a default value unless you are sure there
131
+ will be no routing to the page or handler without the value set.
178
132
 
179
- *Path* parameters (like `:id` in `WidgetsByIdPage`) should *never* have a default value as their absence means a different URL was
180
- requested. For example, `/widgets` would trigger a `WidgetsPage`. *Only* if the `:id` path parameter is present would the
181
- `WidgetsByIdPage` be triggered, so it's safe to omit the default value for `id:` (and pointless to include one).
133
+ This is most important for query string parameters. Since a user can easily
134
+ manipulate these, if your page accepts, say, the parameter `use_detailed_view`, but
135
+ that parameter isn't present, Brut will not be able to instantiate your page unless
136
+ `use_detailed_view:` has a default value in the initializer's keyword arguments.
182
137
 
183
138
  See [route hooks](/hooks).
184
139
 
185
- ### Testing
140
+ ## Testing
186
141
 
187
142
  Brut will not create your classes in a test. Instead, you must pass in the values you want. There are various
188
143
  helpers in `Brut::SpecSupport` to create blank or empty versions of the special classes.
@@ -213,6 +168,9 @@ Outside of Brut, the way to interpret this arguments is as follows:
213
168
 
214
169
  Any method or intializer that will be keyword-injected should be designed with this in mind. Thus, the following guidelines will be helpful in managing your app:
215
170
 
171
+ * **Do not provide default values when Brut documents the value is always
172
+ available**
173
+ - If your page needs the session, it will always be there. Don't default `session:` to some other value (especially `nil`!)
216
174
  * **Choose arguments based on the needs of the class:**
217
175
  - If a value is optional, default it to either `nil` or a symbol that indicates what happens when the value is omitted
218
176
  - If an optional value has a default, use that (this should be rare for pages, handlers, components, and hooks)
@@ -6,11 +6,13 @@ different pages. Conceptually, they are the same as a Rails layout. Technicall
6
6
  ## Overview
7
7
 
8
8
  Your app should include `app/src/front_end/layouts/default_layout.rb`. The name
9
- "default" is special, in that all pages will use this layout by default.
9
+ "default" isn't special, it's just what the `layout` method from
10
+ `Brut::FrontEnd::Page` returns.
10
11
 
11
- Since a layout is a Phlex component, its HTML is generated from `view_template`, and
12
- it is expected to have exactly one `yield`, where the page's content will be
13
- inserted.
12
+ A layout is a Phlex component that's expected to have a single call to `yield` in
13
+ its `view_template` method.
14
+
15
+ Here is the `DefaultLayout` provided to new Brut apps:
14
16
 
15
17
  ```ruby {33}
16
18
  class DefaultLayout < Brut::FrontEnd::Layout
@@ -53,53 +55,18 @@ class DefaultLayout < Brut::FrontEnd::Layout
53
55
  end
54
56
  ```
55
57
 
56
- ### Maintaining Layouts
57
-
58
- You are free to manage this how you like, however a few components inside the
59
- `<head>` and `<body>` that are important to keep:
60
-
61
- * `Brut::FrontEnd::Components::PageIdentifier` includes a `<meta>` tag with the page's name in it, which is handy for managing your end-to-end tests.
62
- * `Brut::FrontEnd::Components::I18nTranslations` includes translatsion for common client-side constraint violations. See [Forms](/forms), [I18n](/i18n), and [JavaScript](/javascript) for more details on how this is used.
63
- * `Brut::FrontEnd::Components::Traceparent` ensures that the OpenTelemetry *traceparent* is available so when client-side telemetry is reported back to the server, it can be connected to the request that initiated it.
64
- * The `<brut-tracing>` element collects the client-side telemetry and sends it back
65
- to the server.
66
-
67
- ### Creating Alternate Layouts
68
-
69
- The way each page knows to use `DefaultLayout` is due to the `layout` method of
70
- `Brut::FrontEnd::Page`, which returns `"default"`. The return value of `layout` is
71
- used to figure out the name of the layout class.
72
-
73
- You can set up your own by overriding `layout`:
74
-
75
- ```ruby
76
- class MyOtherPage < AppPage
77
- def layout = "other_design"
78
-
79
- # ...
80
-
81
- end
82
- ```
58
+ You will likely want to customize what's in your layout, but a few components
59
+ included by default are important for other features of Brut:
83
60
 
84
- Brut will expect the class `OtherDesignLayout` to exist and provide HTML. Based on
85
- Zeitwerk's conventions, that class should be in
86
- `app/src/front_end/layouts/other_design_layout.rb`.
61
+ | Component | Purpose
62
+ |---|---|
63
+ | `Brut::FrontEnd::Components::PageIdentifier` | Creates a `<meta>` tag with the page's name in it, which is handy for managing your end-to-end tests. |
64
+ | `Brut::FrontEnd::Components::I18nTranslations` | Includes translatsion for common client-side constraint violations. These are used by `<brut-cv-messages>` and `,brut-cv>`. See [Forms](/forms), [I18n](/i18n), and [JavaScript](/javascript) for more details |
65
+ | `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
+ | `<brut-tracing>` / `brut_tracing` | Custom element that collects the client-side telemetry and sends it back to the server. See [observability](/instrumentation) |
87
67
 
88
- ### No Layout
89
-
90
- If you don't want a layout, you are encouraged to creat a blank layout, for example:
91
-
92
- ```ruby
93
- class BlankLayout < Brut::FrontEnd::Layout
94
- def view_template
95
- yield
96
- end
97
- end
98
-
99
- # use like so:
100
-
101
- def layout = "blank"
102
- ```
68
+ See [creating alternate layouts](/recipes/alternate-layouts) and [blank
69
+ layouts](/recipes/blank-layouts) for customization options.
103
70
 
104
71
  ## Testing
105
72
 
@@ -109,9 +76,10 @@ needs complex logic, you are encouraged to extract that to a
109
76
 
110
77
  ## Recommended Practices
111
78
 
112
- Keep your layouts as simple as you can.
113
-
114
-
79
+ Layouts can use components, just keep in mind that any data a component needs must
80
+ be passed to its initializer. Since the layout doesn't have access to the page, this
81
+ implies that components used in your layout must either not require dynamic data or
82
+ be [global components](/components#global-components)
115
83
 
116
84
  ## Technical Notes
117
85
 
data/brutrb.com/lsp.md CHANGED
@@ -18,6 +18,6 @@ which Brut has done, and some you will need to do.
18
18
  |---|---|---|
19
19
  | Paths inside Docker Must Match Your Computer | When an LSP server communicates about a file, it does so with a path. That means that paths inside the Docker container must be the same as those on your computer. Brut achievecs this by using `${CWD}` inside `docker-compose.dx.yml` | ✅ |
20
20
  | Third party libraries must *also* be installed in a path that is the same in both places | When jumping to a definition, the LSP server will again use paths, which must match. Because Node modules are installed local to your app, they already work. Ruby Gems, however, are configured to be installed in `local-gems` in your app. Brut should've added this to `.gitignore` and setup everything inside Docker to use it. | ✅ |
21
- | Your editor must use `dx/exec` to execute LSP commands | Your editor will need to know that the LSP servers are running inside Docker. If your editor allows configuring the commands used to do this, you must prefix them with `dx/exec bashc -lc`. See [my blog post](https://naildrivin5.com/blog/2025/06/12/neovim-and-lsp-servers-working-with-docker-based-development.html) for details. | ❌ |
21
+ | Your editor must use `dx/exec` to execute LSP commands | Your editor will need to know that the LSP servers are running inside Docker. If your editor allows configuring the commands used to do this, you must prefix them with `dx/exec`. See [my blog post](https://naildrivin5.com/blog/2025/06/12/neovim-and-lsp-servers-working-with-docker-based-development.html) for details. | ❌ |
22
22
  | Other languages or plugins to existing LSP servers | I haven't used these, so no idea how well they work. | ❌ |
23
23