brut 0.0.26 → 0.0.28

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 (386) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/brutrb.com/.vitepress/config.mjs +2 -0
  4. data/brutrb.com/ai.md +6 -1
  5. data/brutrb.com/components.md +1 -1
  6. data/brutrb.com/dev-environment.md +6 -5
  7. data/brutrb.com/doc-conventions.md +1 -1
  8. data/brutrb.com/forms.md +22 -26
  9. data/brutrb.com/getting-started.md +94 -23
  10. data/brutrb.com/instrumentation.md +12 -0
  11. data/brutrb.com/layouts.md +130 -0
  12. data/brutrb.com/lsp.md +23 -0
  13. data/docs/404.html +2 -2
  14. data/docs/ai.html +5 -5
  15. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  16. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  17. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  18. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  19. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  20. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  21. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  22. data/docs/api/Brut/BackEnd.html +1 -1
  23. data/docs/api/Brut/CLI/App.html +38 -13
  24. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  25. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +2 -2
  26. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +2 -2
  27. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +2 -2
  28. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +2 -2
  29. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  30. data/docs/api/Brut/CLI/Apps/DB/Create.html +2 -2
  31. data/docs/api/Brut/CLI/Apps/DB/Drop.html +2 -2
  32. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +9 -3
  33. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +11 -11
  34. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +2 -2
  35. data/docs/api/Brut/CLI/Apps/DB/Seed.html +2 -2
  36. data/docs/api/Brut/CLI/Apps/DB/Status.html +12 -12
  37. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +270 -0
  39. data/docs/api/Brut/CLI/Apps/DeployBase.html +257 -0
  40. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +585 -0
  41. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +196 -0
  42. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +2 -2
  44. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +2 -2
  45. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +2 -2
  46. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +2 -2
  47. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +2 -2
  48. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +2 -2
  50. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +2 -2
  52. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Test/Audit.html +12 -10
  54. data/docs/api/Brut/CLI/Apps/Test/E2e.html +8 -8
  55. data/docs/api/Brut/CLI/Apps/Test/JS.html +9 -9
  56. data/docs/api/Brut/CLI/Apps/Test/Run.html +18 -18
  57. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  58. data/docs/api/Brut/CLI/Apps.html +2 -2
  59. data/docs/api/Brut/CLI/Command.html +113 -28
  60. data/docs/api/Brut/CLI/Error.html +1 -1
  61. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  62. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  63. data/docs/api/Brut/CLI/Executor.html +169 -38
  64. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  65. data/docs/api/Brut/CLI/Options.html +68 -19
  66. data/docs/api/Brut/CLI/Output.html +1 -1
  67. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  68. data/docs/api/Brut/CLI.html +1 -1
  69. data/docs/api/Brut/FactoryBot.html +1 -1
  70. data/docs/api/Brut/Framework/App.html +1 -1
  71. data/docs/api/Brut/Framework/Config.html +1 -1
  72. data/docs/api/Brut/Framework/Container.html +110 -29
  73. data/docs/api/Brut/Framework/Error.html +1 -1
  74. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +89 -1
  75. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  76. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  77. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  78. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  79. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  80. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  81. data/docs/api/Brut/Framework/Errors.html +31 -8
  82. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  83. data/docs/api/Brut/Framework/MCP.html +1 -1
  84. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  85. data/docs/api/Brut/Framework.html +1 -1
  86. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  87. data/docs/api/Brut/FrontEnd/Component/Helpers.html +36 -26
  88. data/docs/api/Brut/FrontEnd/Component.html +7 -7
  89. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  90. data/docs/api/Brut/FrontEnd/Components/FormTag.html +37 -29
  91. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  92. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  93. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  94. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +20 -117
  95. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +25 -23
  96. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +73 -380
  97. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +22 -138
  98. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components.html +23 -2
  104. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +24 -68
  108. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +4 -4
  110. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +72 -22
  116. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  117. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  126. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  127. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  128. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  129. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +6 -2
  136. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  140. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  141. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  142. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  143. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  144. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  145. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  146. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  147. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  148. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  157. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  159. data/docs/api/Brut/FrontEnd.html +1 -1
  160. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  161. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  162. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  163. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  164. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  165. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  166. data/docs/api/Brut/I18n.html +1 -1
  167. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  168. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  169. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  170. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  171. data/docs/api/Brut/Instrumentation.html +1 -1
  172. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  173. data/docs/api/Brut/SinatraHelpers.html +1 -1
  174. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  175. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  176. data/docs/api/Brut/SpecSupport/E2ETestServer.html +20 -20
  177. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  178. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  179. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  180. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  181. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  182. data/docs/api/Brut/SpecSupport/HandlerSupport.html +2 -2
  183. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +142 -0
  184. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +142 -0
  185. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +155 -0
  186. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +55 -25
  187. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +149 -0
  188. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +46 -19
  189. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +149 -0
  190. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +149 -0
  191. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +165 -0
  192. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +158 -0
  193. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +156 -0
  194. data/docs/api/Brut/SpecSupport/Matchers.html +2 -2
  195. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +24 -24
  196. data/docs/api/Brut/SpecSupport/RSpecSetup.html +55 -20
  197. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport.html +1 -1
  199. data/docs/api/Brut.html +1 -1
  200. data/docs/api/Clock.html +1 -1
  201. data/docs/api/RichString.html +1 -1
  202. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  203. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +5 -1
  204. data/docs/api/Sequel/Extensions/BrutMigrations.html +36 -28
  205. data/docs/api/Sequel/Extensions.html +1 -1
  206. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  207. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  208. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  209. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  210. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  211. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  212. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  213. data/docs/api/Sequel/Plugins.html +1 -1
  214. data/docs/api/Sequel.html +1 -1
  215. data/docs/api/SpecSupport/Matchers/BeABug.html +143 -0
  216. data/docs/api/_index.html +92 -1
  217. data/docs/api/class_list.html +1 -1
  218. data/docs/api/file.README.html +1 -1
  219. data/docs/api/index.html +1 -1
  220. data/docs/api/method_list.html +392 -336
  221. data/docs/api/top-level-namespace.html +1 -1
  222. data/docs/assets/{ai.md.tZrjP9im.js → ai.md._6HCDL6d.js} +1 -1
  223. data/docs/assets/ai.md._6HCDL6d.lean.js +1 -0
  224. data/docs/assets/{app.D_yaTITQ.js → app.BX81XO4N.js} +1 -1
  225. data/docs/assets/chunks/@localSearchIndexroot.CoYzciVi.js +1 -0
  226. data/docs/assets/chunks/{VPLocalSearchBox.B2-ZzyTY.js → VPLocalSearchBox.gABXcTWp.js} +1 -1
  227. data/docs/assets/chunks/{theme.CfGFVRvE.js → theme.DwUXXAL3.js} +2 -2
  228. data/docs/assets/{components.md.eCttGlN-.js → components.md.CRUMdRoN.js} +1 -1
  229. data/docs/assets/{configuration.md.BRriU0cL.js → configuration.md.BGHl8oRC.js} +1 -1
  230. data/docs/assets/{dev-environment.md.BNc8AYiK.js → dev-environment.md.GZv6xvi9.js} +1 -1
  231. data/docs/assets/doc-conventions.md.-kN3Xo5C.js +1 -0
  232. data/docs/assets/{doc-conventions.md.DCfRXXi-.lean.js → doc-conventions.md.-kN3Xo5C.lean.js} +1 -1
  233. data/docs/assets/{forms.md.CBTYQ_Cz.js → forms.md.B-koVgyw.js} +23 -23
  234. data/docs/assets/{forms.md.CBTYQ_Cz.lean.js → forms.md.B-koVgyw.lean.js} +1 -1
  235. data/docs/assets/getting-started.md.Ciz82L0m.js +25 -0
  236. data/docs/assets/getting-started.md.Ciz82L0m.lean.js +1 -0
  237. data/docs/assets/{instrumentation.md.CL6ax7nT.js → instrumentation.md.a9Pjps4P.js} +2 -2
  238. data/docs/assets/{instrumentation.md.CL6ax7nT.lean.js → instrumentation.md.a9Pjps4P.lean.js} +1 -1
  239. data/docs/assets/layouts.md.cPnh3NId.js +51 -0
  240. data/docs/assets/layouts.md.cPnh3NId.lean.js +1 -0
  241. data/docs/assets/lsp.md.Bsu-f6VU.js +1 -0
  242. data/docs/assets/lsp.md.Bsu-f6VU.lean.js +1 -0
  243. data/docs/assets/{overview.md.CDalkuxV.js → overview.md.C5wlBcR5.js} +3 -3
  244. data/docs/assets/recipes_authentication.md.CAsXf7hk.js +1 -0
  245. data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +1 -0
  246. data/docs/assets.html +4 -4
  247. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  248. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  249. data/docs/brut-js/api/Autosubmit.html +1 -1
  250. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  251. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  252. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  253. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  254. data/docs/brut-js/api/BufferedLogger.html +1 -1
  255. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  256. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  257. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  258. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  259. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  260. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  261. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  262. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  263. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  264. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  265. data/docs/brut-js/api/Form.html +1 -1
  266. data/docs/brut-js/api/Form.js.html +1 -1
  267. data/docs/brut-js/api/I18nTranslation.html +1 -1
  268. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  269. data/docs/brut-js/api/LocaleDetection.html +1 -1
  270. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  271. data/docs/brut-js/api/Logger.html +1 -1
  272. data/docs/brut-js/api/Logger.js.html +1 -1
  273. data/docs/brut-js/api/Message.html +1 -1
  274. data/docs/brut-js/api/Message.js.html +1 -1
  275. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  276. data/docs/brut-js/api/RichString.html +1 -1
  277. data/docs/brut-js/api/RichString.js.html +1 -1
  278. data/docs/brut-js/api/Tabs.html +1 -1
  279. data/docs/brut-js/api/Tabs.js.html +1 -1
  280. data/docs/brut-js/api/Tracing.html +1 -1
  281. data/docs/brut-js/api/Tracing.js.html +1 -1
  282. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  283. data/docs/brut-js/api/external-Performance.html +1 -1
  284. data/docs/brut-js/api/external-Promise.html +1 -1
  285. data/docs/brut-js/api/external-ValidityState.html +1 -1
  286. data/docs/brut-js/api/external-Window.html +1 -1
  287. data/docs/brut-js/api/external-fetch.html +1 -1
  288. data/docs/brut-js/api/global.html +1 -1
  289. data/docs/brut-js/api/index.html +1 -1
  290. data/docs/brut-js/api/index.js.html +1 -1
  291. data/docs/brut-js/api/module-testing.html +1 -1
  292. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  293. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  294. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  295. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  296. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  297. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  298. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  299. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  300. data/docs/brut-js/api/testing_index.js.html +1 -1
  301. data/docs/brut-js.html +4 -4
  302. data/docs/business-logic.html +4 -4
  303. data/docs/cli.html +4 -4
  304. data/docs/components.html +6 -6
  305. data/docs/configuration.html +5 -5
  306. data/docs/css.html +4 -4
  307. data/docs/custom-element-tests.html +4 -4
  308. data/docs/database-access.html +4 -4
  309. data/docs/database-schema.html +4 -4
  310. data/docs/deployment.html +4 -4
  311. data/docs/dev-environment.html +5 -5
  312. data/docs/doc-conventions.html +5 -5
  313. data/docs/end-to-end-tests.html +4 -4
  314. data/docs/flash-and-session.html +4 -4
  315. data/docs/forms.html +27 -27
  316. data/docs/getting-started.html +29 -6
  317. data/docs/handlers.html +4 -4
  318. data/docs/hashmap.json +1 -1
  319. data/docs/hooks.html +4 -4
  320. data/docs/i18n.html +4 -4
  321. data/docs/index.html +3 -3
  322. data/docs/instrumentation.html +5 -5
  323. data/docs/javascript.html +4 -4
  324. data/docs/jobs.html +4 -4
  325. data/docs/keyword-injection.html +4 -4
  326. data/docs/layouts.html +74 -0
  327. data/docs/lsp.html +24 -0
  328. data/docs/markdown-examples.html +4 -4
  329. data/docs/middleware.html +4 -4
  330. data/docs/not-released.html +4 -4
  331. data/docs/overview.html +8 -8
  332. data/docs/pages.html +5 -5
  333. data/docs/recipes/authentication.html +24 -0
  334. data/docs/routes.html +4 -4
  335. data/docs/security.html +4 -4
  336. data/docs/seed-data.html +4 -4
  337. data/docs/space-time-continuum.html +4 -4
  338. data/docs/tutorial.html +4 -4
  339. data/docs/unit-tests.html +4 -4
  340. data/lib/brut/cli/app.rb +7 -2
  341. data/lib/brut/cli/apps/deploy_base.rb +86 -0
  342. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +194 -0
  343. data/lib/brut/cli/apps/test.rb +2 -1
  344. data/lib/brut/cli/command.rb +7 -13
  345. data/lib/brut/cli/executor.rb +31 -5
  346. data/lib/brut/cli/options.rb +4 -0
  347. data/lib/brut/cli.rb +4 -3
  348. data/lib/brut/framework/container.rb +25 -7
  349. data/lib/brut/framework/errors/abstract_method.rb +7 -0
  350. data/lib/brut/framework/errors.rb +4 -2
  351. data/lib/brut/front_end/component.rb +33 -9
  352. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +16 -74
  353. data/lib/brut/front_end/forms/constraint_violation.rb +2 -18
  354. data/lib/brut/front_end/forms/input.rb +8 -8
  355. data/lib/brut/front_end/forms/input_declarations.rb +3 -3
  356. data/lib/brut/front_end/forms/radio_button_group_input.rb +1 -1
  357. data/lib/brut/front_end/forms/select_input.rb +1 -1
  358. data/lib/brut/front_end/forms/validity_state.rb +23 -0
  359. data/lib/brut/front_end/middlewares/reload_app.rb +2 -0
  360. data/lib/brut/spec_support/handler_support.rb +1 -1
  361. data/lib/brut/spec_support/matcher.rb +1 -1
  362. data/lib/brut/spec_support/matchers/be_a_bug.rb +11 -0
  363. data/lib/brut/spec_support/matchers/be_page_for.rb +10 -0
  364. data/lib/brut/spec_support/matchers/be_routing_for.rb +18 -0
  365. data/lib/brut/spec_support/matchers/have_constraint_violation.rb +10 -0
  366. data/lib/brut/spec_support/matchers/have_generated.rb +28 -0
  367. data/lib/brut/spec_support/matchers/have_html_attribute.rb +10 -0
  368. data/lib/brut/spec_support/matchers/have_i18n_string.rb +13 -0
  369. data/lib/brut/spec_support/matchers/have_link_to.rb +15 -0
  370. data/lib/brut/spec_support/matchers/have_redirected_to.rb +23 -0
  371. data/lib/brut/spec_support/matchers/have_returned_http_status.rb +20 -0
  372. data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +22 -0
  373. data/lib/brut/spec_support/rspec_setup.rb +42 -2
  374. data/lib/brut/version.rb +1 -1
  375. data/lib/sequel/extensions/brut_instrumentation.rb +4 -0
  376. metadata +51 -24
  377. data/docs/assets/ai.md.tZrjP9im.lean.js +0 -1
  378. data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +0 -1
  379. data/docs/assets/doc-conventions.md.DCfRXXi-.js +0 -1
  380. data/docs/assets/getting-started.md.Bz2s1Vjb.js +0 -2
  381. data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +0 -1
  382. data/lib/brut/spec_support/matchers/have_rendered.rb +0 -14
  383. /data/docs/assets/{components.md.eCttGlN-.lean.js → components.md.CRUMdRoN.lean.js} +0 -0
  384. /data/docs/assets/{configuration.md.BRriU0cL.lean.js → configuration.md.BGHl8oRC.lean.js} +0 -0
  385. /data/docs/assets/{dev-environment.md.BNc8AYiK.lean.js → dev-environment.md.GZv6xvi9.lean.js} +0 -0
  386. /data/docs/assets/{overview.md.CDalkuxV.lean.js → overview.md.C5wlBcR5.lean.js} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 327b83333a37823010e1d2763646d56bd503d505d6c965ddc5d0d8098ecd9299
4
- data.tar.gz: d12d8d46f6e459d97f3489218302da647e073ac4d8e48cf57cd1d1fee998c6a1
3
+ metadata.gz: '0212844b1dd1d69682b3b4ec4ab2a9917087b3d931916e39d9ccf0239d6a2f9c'
4
+ data.tar.gz: 871a018ce32f9d1f556eef7950570c8a4dd072736be733e6a7a925e14a8c968c
5
5
  SHA512:
6
- metadata.gz: a55e3607d5d127e74f93864f6dc19fad6dc2a0631e56559cf92df4b5762771d82d5bc0c5a2d289d6948a0899568e0138e503fea98f91d42007cefb9b3e83482b
7
- data.tar.gz: e901897e8d8eb2a2ef08bc4e5efacc0c3a13f6981f8523a2e0db3ade740f648923ecb29e9634ea133dd16efccedeecb0b9ec06f820c0b4d77b7f34068f96d540
6
+ metadata.gz: eb49d9e87e53893962c86e2a26ca54f8c82cc9a8fbbeac5e12e1d7aaa4b47cbb13de6ab9429779422a6bc075dd3f4499bf6b18accb7c7ff87943eb32afda0342
7
+ data.tar.gz: c8f56cb073d99b1b3f304afe2daf3839b6b2b39ebe492bde05f00037ebf448459049560d3b32707450b65e32916366ea766d57598752026d0c025d1db63d7fe7
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brut (0.0.26)
4
+ brut (0.0.28)
5
5
  concurrent-ruby
6
6
  i18n
7
7
  irb
@@ -39,6 +39,7 @@ export default defineConfig({
39
39
  items: [
40
40
  { text: "Routes", link: "/routes" },
41
41
  { text: "Pages", link: "/pages" },
42
+ { text: "Layouts", link: "/layouts" },
42
43
  { text: "Forms", link: "/forms" },
43
44
  { text: "Handlers and Actions", link: "/handlers" },
44
45
  { text: "Components", link: "/components" },
@@ -89,6 +90,7 @@ export default defineConfig({
89
90
  { text: "Middleware", link: "/middleware" },
90
91
  { text: "Instrumentation", link: "/instrumentation" },
91
92
  { text: "Security", link: "/security" },
93
+ { text: "LSP Support", link: "/lsp" },
92
94
  ],
93
95
  },
94
96
  {
data/brutrb.com/ai.md CHANGED
@@ -58,7 +58,7 @@ The logo is level 4 - ChatGPT created it. I'd love to have a real person make a
58
58
  something to get this launched. Please reach out if you want to make a better one + other assets. I'm
59
59
  willing to pay a real person.
60
60
 
61
- ## AI That Teachs You About Brut Does Not Currently Exist
61
+ ## AI Completions Should Be Viewed with Skepticism
62
62
 
63
63
  Due to how LLMs work, there is naturally nothing in any model about Brut. Anything an
64
64
  existing model tells you about Brut is **100% untrustworthy**. We hope to allow LLMs to consume Brut's
@@ -66,3 +66,8 @@ code, documentation, and examples, so it can be an additional source of help, bu
66
66
  the case.
67
67
 
68
68
  **Do not ask an LLM about Brut** until this part of the documentation changes.
69
+
70
+ For completion-based AI suggestions, **view them with skepticism**. In my
71
+ experience, completions from e.g. GitHub CoPilot work OK when replicating a pattern
72
+ in the file you are editing, but suggestions in a freshly-opened file tend to be
73
+ entirely imaginary or Rails-based. Beware.
@@ -136,7 +136,7 @@ To use it without having to instantiate it, call `global_component` with the com
136
136
  class HomePage < AppPage
137
137
  def page_template
138
138
  header do
139
- render global_component(FlashMessage)
139
+ global_component(FlashMessage) # note: render not required
140
140
  end
141
141
  end
142
142
  end
@@ -137,16 +137,17 @@ that the versions of these command line apps provided when you set up your app a
137
137
  While you are free to set up mise or rbenv or whatever to run all this on your computer, this way of
138
138
  working is currently not supported nor encouraged. For now, Brut will focus on the Docker-based approach.
139
139
 
140
- You are encouraged to understand how it works, especially because the Docker skills you learn in doing so
141
- will help with production deployment and operations.
140
+ The primary reason is that it's a tightly controlled environment that is almost
141
+ entirely scriptable, but does not require devs to abandon their preffered editor.
142
+ Environment manager-based approaches tend to be more fussy and require documentation
143
+ to ensure they are set up.
142
144
 
143
- Beyond this, we recommend that you keep a few things in mind with respect to automation:
145
+ Keep in mind a few things when adding your own automation:
144
146
 
145
147
  * The *Foundational Core* is bootstrapped in a degenerate environment without reliable tools beyond Bash.
146
148
  This is why it's almost entirely written in Bash, since it's available everywhere and relatively stable.
147
149
  * The *Workspace* **can and should** rely on the languages and third party modules that are part of your
148
- app. Just keep in mind that `bin/setup` can only rely on programming language runtimes and not any
149
- particular Ruby gem having been installed.
150
+ app. The only exception is `bin/setup`, since it installs third party modules. As such, it should work entirely based on Ruby and its standard library.
150
151
 
151
152
  ## Technical Notes
152
153
 
@@ -6,7 +6,7 @@ Brut attempts to use existing terminology where possible, particularly where tha
6
6
 
7
7
  When speaking about Ruby, we prefer the term *initializer* over constructor, *parameters* over arguments, and *methods* over messages. We also prefer *tests* over specs, however test files *are* located in `specs/` and named `*.spec.rb` to be consistent with RSpec's nomenclature. We prefer *end-to-end* or *e2e* tests instead of browser tests or request specs.
8
8
 
9
- Further, Brut doesn't render HTML, it *generates* it. The browser renders the HTML for the website's visitor.
9
+ Further, Brut doesn't render HTML, it *generates* it. The browser renders the HTML for the website's visitor. One exception is Phlex, which uses the term *render* to mean "generate HTML to an internal buffer that will be delivered to the client later". Thus, you will need to call Phlex's `render` method from time to time, even though it is not rendering HTML but helping to generate it.
10
10
 
11
11
  Lastly, the documentation tries to talk about the person accessing a website as a "vistor" not a "user". Though the "user" nomenclature is near-ossified in software development, we feel "visitor" is more apt.
12
12
 
data/brutrb.com/forms.md CHANGED
@@ -87,7 +87,7 @@ class LoginPage < AppPage
87
87
  end
88
88
  ```
89
89
 
90
- Brut can generate the HTML for the needed inputs via `Brut::FrontEnd::Components::Inputs::TextField.for_form_input`, which is a very long name. Hold that thought for now. This method will generate an `<input>` element for you, based on how you've set up the field in your form class. The HTML element will have a value set based on the form, if there is a value.
90
+ Brut can generate the HTML for the needed inputs via `Brut::FrontEnd::Components::Inputs::TextField`, which is a very long class name. Hold that thought for now. This method will generate an `<input>` element for you, based on how you've set up the field in your form class. The HTML element will have a value set based on the form, if there is a value.
91
91
 
92
92
  ```ruby {11,12}
93
93
  # app/src/front_end/pages/login_page.rb
@@ -100,8 +100,8 @@ class LoginPage < AppPage
100
100
  form_tag(method: :post,
101
101
  action: LoginHandler.routing) do
102
102
  # We promise you don't have to type this every time!
103
- Brut::FrontEnd::Components::Inputs::TextField.for_form_input(form: @form, input_name: :email)
104
- Brut::FrontEnd::Components::Inputs::TextField.for_form_input(form: @form, input_name: :password)
103
+ Brut::FrontEnd::Components::Inputs::TextField.new(form: @form, input_name: :email)
104
+ Brut::FrontEnd::Components::Inputs::TextField.new(form: @form, input_name: :password)
105
105
  button { "Login" }
106
106
  end
107
107
  end
@@ -134,7 +134,7 @@ class LoginPage < AppPage
134
134
  end
135
135
  ```
136
136
 
137
- This allows you to call `Inputs::TextField.for_form_input`:
137
+ This allows you to call `Inputs::TextField` like a method:
138
138
 
139
139
  ```ruby {12,13}
140
140
  # app/src/front_end/pages/login_page.rb
@@ -148,8 +148,8 @@ class LoginPage < AppPage
148
148
  def page_template
149
149
  form_tag(method: :post,
150
150
  action: LoginHandler.routing) do
151
- Inputs::TextField.for_form_input(form: @form, input_name: :email)
152
- Inputs::TextField.for_form_input(form: @form, input_name: :password)
151
+ Inputs::TextField(form: @form, input_name: :email)
152
+ Inputs::TextField(form: @form, input_name: :password)
153
153
  button { "Login" }
154
154
  end
155
155
  end
@@ -217,7 +217,7 @@ with that email and password. Let's assume the existence of the class `Authorize
217
217
  If that returns `nil`, we want to re-render the `LoginPage`, exposing some sort of constraint violation message
218
218
  so it can be rendered. We also want the form fields to be pre-filled with the values the visitor provided.
219
219
 
220
- `for_form_input` can handle this, so we need to pass our form object into `LoginPage` instead of allowing `LoginPage` to create an empty one. We can do that by adding a `form:` keyword argument that defaults to `nil`:
220
+ `Brut::FrontEnd::Components` can handle this, so we need to pass our form object into `LoginPage` instead of allowing `LoginPage` to create an empty one. We can do that by adding a `form:` keyword argument that defaults to `nil`:
221
221
 
222
222
  ```ruby {3,4}
223
223
  # app/src/front_end/pages/login_page.rb
@@ -229,8 +229,8 @@ class LoginPage < AppPage
229
229
  def page_template
230
230
  form_tag(method: :post,
231
231
  action: LoginHandler.routing) do
232
- Inputs::TextField.for_form_input(form: @form, input_name: :email)
233
- Inputs::TextField.for_form_input(form: @form, input_name: :password)
232
+ Inputs::TextField(form: @form, input_name: :email)
233
+ Inputs::TextField(form: @form, input_name: :password)
234
234
  button { "Login" }
235
235
  end
236
236
  end
@@ -267,12 +267,11 @@ class LoginHandler < AppHandler
267
267
  end
268
268
  ```
269
269
 
270
- When `LoginPage` generates HTML, different HTML is generated, since the form being passed to
271
- `for_form_input` contains constraint violations.
270
+ When `LoginPage` generates HTML, different HTML is generated, since the form being passed to the components contains constraint violations.
272
271
 
273
272
  #### Showing Constraint Violations in HTML
274
273
 
275
- When `Inputs::TextField.for_form_input` is called with an existing form that has constraint violations, different HTML is generated. This is what would be produced by our existing `LoginPage` (again, formatted her for clarity):
274
+ When `Brut::FrontEnd::Components::Inputs::TextField` is created with an existing form that has constraint violations, different HTML is generated. This is what would be produced by our existing `LoginPage` (again, formatted her for clarity):
276
275
 
277
276
  ```html {3}
278
277
  <form method="post" action="/login">
@@ -335,10 +334,10 @@ def page_template
335
334
  form_tag(method: :post,
336
335
  action: LoginHandler.routing) do
337
336
 
338
- Inputs::TextField.for_form_input(form: @form, input_name: :email)
337
+ Inputs::TextField(form: @form, input_name: :email)
339
338
  ConstraintViolations(form: @form, input_name: :email)
340
339
 
341
- Inputs::TextField.for_form_input(form: @form, input_name: :password)
340
+ Inputs::TextField(form: @form, input_name: :password)
342
341
  ConstraintViolations(form: @form, input_name: :password)
343
342
 
344
343
  button { "Login" }
@@ -419,10 +418,10 @@ def page_template
419
418
  form_tag(method: :post,
420
419
  action: LoginHandler.routing) do
421
420
 
422
- Inputs::TextField.for_form_input(form: @form, input_name: :email)
421
+ Inputs::TextField(form: @form, input_name: :email)
423
422
  ConstraintViolations(form: @form, input_name: :email)
424
423
 
425
- Inputs::TextField.for_form_input(form: @form, input_name: :password)
424
+ Inputs::TextField(form: @form, input_name: :password)
426
425
  ConstraintViolations(form: @form, input_name: :password)
427
426
 
428
427
  button { "Login" }
@@ -564,7 +563,7 @@ class LoginForm < AppForm
564
563
  end
565
564
  ```
566
565
 
567
- Checkboxes can be rendered by `Inputs::TextField.for_form_input`, and their `value` attribute would always be the string `"true"`. If the form's value for the input is the string `"true"`, the checkbox would have the `checked` attribute:
566
+ Checkboxes can be rendered by a `Brut::FrontEnd::Components::Inputs::TextField`, and their `value` attribute would always be the string `"true"`. If the form's value for the input is the string `"true"`, the checkbox would have the `checked` attribute:
568
567
 
569
568
  ```html
570
569
  <!-- Form.new(params: { remember: "true" }) -->
@@ -578,8 +577,7 @@ Radio buttons are implemented in HTML by `<input type="radio">`, with an expecta
578
577
  having the same value for the `name` attribute, but different values for the `value` attributes, one of which may
579
578
  be `checked`.
580
579
 
581
- Brut implements this via `Brut::FrontEnd::Components::Inputs::RadioButton`, which has the class method
582
- `for_form_input`. To create radio buttons in a form, use `radio_button_group`:
580
+ Brut implements this via `Brut::FrontEnd::Components::Inputs::RadioButton`, whose initializer behaves like the other form input components. To create radio buttons in a form, use `radio_button_group`:
583
581
 
584
582
  ```ruby {5}
585
583
  # app/src/front_end/forms/login_form.rb
@@ -598,7 +596,7 @@ def view_template
598
596
  [ :never, :one_week, :one_month ].each do |remember|
599
597
  label do
600
598
  render(
601
- Inputs::RadioButton.for_form_input(
599
+ Inputs::RadioButton(
602
600
  form:,
603
601
  input_name: :remember,
604
602
  value: remember
@@ -643,8 +641,7 @@ class LoginForm < AppForm
643
641
  end
644
642
  ```
645
643
 
646
- Creating the HTML can be done with `Brut::FrontEnd::Components::Inputs::Select`. It's `for_form_input` is more complex, since it provides a way to show visitor-friendly values instead of the innate `value` for each option,
647
- as well as to allow for a "blank" entry.
644
+ Creating the HTML can be done with `Brut::FrontEnd::Components::Inputs::Select`. It's initializer is more complex, since it provides a way to show visitor-friendly values instead of the innate `value` for each option, as well as to allow for a "blank" entry.
648
645
 
649
646
  Let's suppose we have a class named `LoginRememberOption`. It's a simple wrapper around a value we might store in the database and use to lookup an I18n key.
650
647
 
@@ -677,7 +674,7 @@ To show these options in a `<select>`, we might do this:
677
674
  def view_template
678
675
  form do
679
676
  render(
680
- Inputs::Select.for_form_input(
677
+ Inputs::Select(
681
678
  form:,
682
679
  input_name: :remember,
683
680
  options: LoginRememberOption.all,
@@ -718,8 +715,7 @@ end
718
715
 
719
716
  In this case, we need `required: false` or every single field we generate will be required.
720
717
 
721
- To generate the HTML, use the optional `index:` parameter to `for_form_input` as well as for
722
- `ConstraintViolations`:
718
+ To generate the HTML, use the optional `index:` parameter to the initializer as well as for `ConstraintViolations`:
723
719
 
724
720
  ```ruby {11,16}
725
721
  # Inside e.g. app/src/front_end/pages/create_bulk_widget_page.rb
@@ -729,7 +725,7 @@ def page_template
729
725
  action: BulkWidgetForm.routing) do
730
726
 
731
727
  10.times do |i|
732
- Inputs::TextField.for_form_input(
728
+ Inputs::TextField(
733
729
  form: @form,
734
730
  input_name: :name,
735
731
  index: i
@@ -1,66 +1,137 @@
1
1
  # Getting Started
2
2
 
3
- To make a new app with Brut, you'll need to clone a template app, initialize it, then set up your dev environment.
3
+ Brut is developed alongside a separate gem called `mkbrut`, which allows you to
4
+ create a new Brut app. It will set up your dev environment as well.
4
5
 
5
- ## Clone the App Template
6
+ ## Get `mkbrut`
6
7
 
7
- To get started, clone the Brut app template:
8
+ The simplest way to use `mkbrut` is to use an existing [Docker image](https://hub.docker.com/repository/docker/thirdtank/mkbrut/general). You don't have to install or configure Ruby:
8
9
 
9
10
  ```
10
- > git clone https://github.com/thirdtank/brut-app-template your-app-name
11
+ docker run \
12
+ -v "$PWD":"$PWD" \
13
+ -w "$PWD" \
14
+ -it \
15
+ thirdtank/mkbrut \
16
+ mkbrut my-new-app
17
+ ```
18
+
19
+ If you already have Ruby 3.4 installed, you can install `mkbrut` directly:
20
+
21
+ ```
22
+ > gem install mkbrut
23
+ > mkbrut my-new-app
11
24
  ```
12
25
 
13
26
  ## Init Your App
14
27
 
15
- The template includes `init`, which will ask you a few questions to get everything set up.
28
+ A Brut app just needs a name, which will be used to derive a few more useful values.
29
+ For now:
16
30
 
31
+ ::: code-group
32
+
33
+ ``` [Docker-based]
34
+ docker run \
35
+ -v "$PWD":"$PWD" \
36
+ -w "$PWD" \
37
+ -it \
38
+ thirdtank/mkbrut \
39
+ mkbrut my-new-app
17
40
  ```
18
- > cd your-app-name
19
- > ./init
41
+
42
+ ``` [RubyGems-based]
43
+ mkbrut my-new-app
20
44
  ```
21
45
 
22
- You'll need to provide four pieces of info:
46
+ :::
47
+
48
+ This will create your new app, along with some demo routes, components, handlers, and tests. If this is your first time using Brut, we recommend you examine these demo components.
49
+
50
+ To create your app without the demo components:
23
51
 
24
- * Your app's name, suitable has a hostname or identifier
25
- * A prefix for your app's externalizable ids
26
- * A prefix for your app's custom elements
27
- * An organization name, needed for deployment
52
+ ::: code-group
53
+
54
+ ``` [Docker-based]
55
+ docker run \
56
+ -v "$PWD":"$PWD" \
57
+ -w "$PWD" \
58
+ -it \
59
+ thirdtank/mkbrut \
60
+ mkbrut my-new-app --no-demo
61
+ ```
62
+
63
+ ``` [RubyGems-based]
64
+ mkbrut my-new-app --no-demo
65
+ ```
28
66
 
29
- ::: tip
30
- Choose your app's name wisely, however everything else can be easily changed later, so don't stress!
31
67
  :::
32
68
 
33
- ## Set Up Your Dev Environment
69
+ ## Start Your Dev Environment
34
70
 
35
- Brut includes a dev environment based on Docker.
71
+ Brut includes a dev environment based on Docker. It uses Docker compose to run a
72
+ Docker container where your app will run, a Docker container for Postgres, and a
73
+ Docker container for local observability via OpenTelemetry.
36
74
 
37
75
  1. [Install Docker](https://docs.docker.com/get-started/get-docker/)
38
- 2. Build Your images
76
+ 2. Build the image used to create you app's container:
39
77
 
40
78
  ```
41
79
  > dx/build
42
80
  ```
43
- 3. Start up the environment
81
+ 3. Start up all the containers:
44
82
 
45
83
  ```
46
84
  > dx/start
47
85
  ```
48
- 4. Install gems and modules for your app. In another terminal:
86
+ 4. Now, "log in" to the container where your app and its tests will run:
87
+
88
+ ```
89
+ > dx/exec login
90
+ ```
91
+
92
+ 5. Set everything up:
49
93
 
50
94
  ```
51
- > dx/exec bin/setup
95
+ inside-container> bin/setup
52
96
  ```
53
97
 
54
- Now, you're ready to go
98
+ Now, you're ready to go. See [Dev Environemnt](/dev-environment) for details on how
99
+ this all works.
55
100
 
56
101
  ## Run the App
57
102
 
58
103
  ```
59
- > dx/exec bin/dev
104
+ inside-container> bin/dev
60
105
  ```
61
106
 
62
107
  You can now visit your app at `localhost:6502`
63
108
 
109
+ You can make changes and see them when you reload. Open up `app/src/front_end/pages/home_page.rb` *in your editor running on your computer* and change the `h1` to look like so:
110
+
111
+ ```ruby {6}
112
+ class HomePage < AppPage
113
+ def page_template
114
+ div(class: "flex flex-column items-center justify-center h-80vh") do
115
+ img(src: "/static/images/icon.png", class: "h-50")
116
+ h1(class: "ff-sans ma-0 lh-title f-5") do
117
+ "Welcome to My New App!"
118
+ end
119
+
120
+ # ...
121
+ ```
122
+
123
+ When you reload your browser, you'll see your change
124
+
125
+ ## Run the Tests
126
+
127
+ There are a few tests you can run, as well as some checks that you aren't using
128
+ RubyGems with security vulnerabilities. Run it all now with `bin/ci`:
129
+
130
+ ```
131
+ inside-container> bin/dev
132
+ ```
133
+
64
134
  ## Now Build The Rest of Your App 🦉
65
135
 
66
- You can [follow the tutorial](/tutorial), check out the [conceptual overview](/overview), or dive straight into the API docs.
136
+ You can [follow the tutorial](/tutorial), check out the [conceptual overview](/overview), or dive straight into the API docs. You might also want to check out the docs for [LSP Support](/lsp).
137
+
@@ -66,6 +66,17 @@ Here is a non-exhaustive list of what Brut automatically instruments:
66
66
  * Ignored parameters on all form submissions
67
67
  * How long reloading takes in development
68
68
  * CSP reporting results
69
+ * SQL Statements
70
+
71
+ > [!WARNING]
72
+ > `Sequel::Extensions::BrutInstrumentation` sets up telemetry for
73
+ > Sequel, and it does it in a relatively simplistic way. The result
74
+ > is that *all* SQL statements are part of the telemetry, including
75
+ > the actual values inserted or used in `WHERE` clauses.
76
+ > While you should not be putting sensitive data into your database,
77
+ > be warned that this is happening. There are plans to improve this
78
+ > to be more flexible and reduce the Schance of sensitive data
79
+ > being sent in traces.
69
80
 
70
81
  ### Adding Your Own Instrumentation
71
82
 
@@ -175,6 +186,7 @@ specific issues.
175
186
 
176
187
  _Last Updated June 12, 2025_
177
188
 
189
+
178
190
  Brut does not have plans to support non-OTel instrumentation, nor does it have plans to provide hooks to
179
191
  use proprietary formats. This could change of course.
180
192
 
@@ -0,0 +1,130 @@
1
+ # Layouts
2
+
3
+ Brut supports *layouts*, which are a way to centralizing common HTML amongst many
4
+ different pages. Conceptually, they are the same as a Rails layout. Technically, they are a Phlex component designed to render a page from a `yield` block.
5
+
6
+ ## Overview
7
+
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.
10
+
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.
14
+
15
+ ```ruby {33}
16
+ class DefaultLayout < Brut::FrontEnd::Layout
17
+ include Brut::FrontEnd::Components
18
+
19
+ def initialize(page_name:)
20
+ @page_name = page_name
21
+ end
22
+
23
+ def view_template
24
+ doctype
25
+ html(lang: "en") do
26
+ head do
27
+ meta(charset: "utf-8")
28
+ meta(content: "width=device-width,initial-scale=1", name:"viewport")
29
+ meta(content: "website", property:"og:type")
30
+ link(rel: "manifest", href: "/static/manifest.json")
31
+ link(rel: "preload", as: "style", href: asset_path("/css/styles.css"))
32
+ link(rel: "stylesheet", href: asset_path("/css/styles.css"))
33
+ script(defer: true, src: asset_path("/js/app.js"))
34
+ title { app_name }
35
+ PageIdentifier(@page_name)
36
+ I18nTranslations("cv.fe")
37
+ I18nTranslations("cv.this_field")
38
+ Traceparent()
39
+ render(
40
+ Brut::FrontEnd::RequestContext.inject(
41
+ Brut::FrontEnd::Components::LocaleDetection
42
+ )
43
+ )
44
+ end
45
+ body do
46
+ brut_tracing url: "/__brut/instrumentation", show_warnings: true
47
+ main class: @page_name do
48
+ yield
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ ```
55
+
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
+ ```
83
+
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`.
87
+
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
+ ```
103
+
104
+ ## Testing
105
+
106
+ You generally don't test a layout, aside from end-to-end tests. If your layout
107
+ needs complex logic, you are encouraged to extract that to a
108
+ [component](/components) and test that instead.
109
+
110
+ ## Recommended Practices
111
+
112
+ Keep your layouts as simple as you can.
113
+
114
+
115
+
116
+ ## Technical Notes
117
+
118
+ > [!IMPORTANT]
119
+ > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
120
+ > internals, the source code is always more correct.
121
+
122
+ _Last Updated July 1, 2025_
123
+
124
+ Layouts work due to the implementation of the method `view_template` in `Brut::FrontEnd::Page`. This is why a page class must provide `page_template` instead.
125
+
126
+ While you could override `view_template` in your page to provide a "blank layout",
127
+ this is discouraged, as the use of `view_template` should be considered a
128
+ private implementation detail.
129
+
130
+
data/brutrb.com/lsp.md ADDED
@@ -0,0 +1,23 @@
1
+ # Language Server Protocol (LSP) Support
2
+
3
+ Because Brut development happens inside Docker, but your editor likely runs on your
4
+ computer, getting LSP servers running takes a few more steps.
5
+
6
+ ## Overview
7
+
8
+ When you created your app with `mkbrut`, the following LSP-related modules are set
9
+ up and/or installed:
10
+
11
+ * Shopify's Ruby LSP server (installed from `bin/setup`)
12
+ * Microsoft's TypeScript/JavaScript and CSS LSP serfvers (specified in `package.json`, installed when `npm install` runs from `bin/setup`)
13
+
14
+ In order to use them from your computer a few configurations are needed, some of
15
+ which Brut has done, and some you will need to do.
16
+
17
+ | Configuration | Description | Brut Handled? |
18
+ |---|---|---|
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
+ | 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. | ❌ |
22
+ | Other languages or plugins to existing LSP servers | I haven't used these, so no idea how well they work. | ❌ |
23
+