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
@@ -0,0 +1,120 @@
1
+ # Directory Structure
2
+
3
+ ```
4
+ .
5
+ ├── app
6
+ │   ├── config
7
+ │   │   └── i18n
8
+ │   │   └── en
9
+ │   ├── public
10
+ │   │   ├── css
11
+ │   │   ├── js
12
+ │   │   └── static
13
+ │   │   └── images
14
+ │   └── src
15
+ │   ├── back_end
16
+ │   │   ├── data_models
17
+ │   │      ├── db
18
+ │   │      ├── migrations
19
+ │   │      └── seed
20
+ │   ├── cli
21
+ │   └── front_end
22
+ │   ├── components
23
+ │   ├── css
24
+ │   ├── fonts
25
+ │   ├── forms
26
+ │   ├── handlers
27
+ │   ├── images
28
+ │   ├── js
29
+ │   ├── layouts
30
+ │   ├── pages
31
+ │   ├── route_hooks
32
+ │   ├── support
33
+ │   └── svgs
34
+ ├── bin
35
+ ├── deploy
36
+ ├── dx
37
+ └── specs
38
+ ├── back_end
39
+ │   ├── data_models
40
+ │      └── db
41
+ ├── e2e
42
+ ├── factories
43
+ │   └── db
44
+ └── front_end
45
+ ├── components
46
+ ├── handlers
47
+ ├── js
48
+ ├── pages
49
+ └── support
50
+ ```
51
+
52
+ ## Top Level
53
+
54
+ | Directory | Purpose |
55
+ |-----------|---------|
56
+ | `app/` | Contains all configuration and source code specific to your app |
57
+ | `bin/` | Contains tasks and other CLIs to do development of your app, such as `bin/test` |
58
+ | `dx/` | Contains scripts to manage your development environment |
59
+ | `specs/` | Contains all tests |
60
+
61
+ ## Inside `app`/
62
+
63
+ | Directory | Purpose |
64
+ |-----------|---------|
65
+ | `bootstrap.rb` | A ruby file that sets up your app and ensures everything is `require`d in the right way. |
66
+ | `config/` | Configuration for your app, such as localizations and translations. Brut tries very hard to make sure there is no YAML in here at all. YAML is not good for you. |
67
+ | `public/` | Root of public assets served by the app. |
68
+ | `src/` | All source code for your app |
69
+
70
+ Inside `app/src`
71
+
72
+ | Directory | Purpose |
73
+ |-----------|---------|
74
+ | `app.rb` | The core of your app, mostly configuration, such as routes, hooks, middleware, etc. |
75
+ | `back_end/` | Back end classes for your app including database schema, DB models, seed data, and your domain logic |
76
+ | `cli/` | Any CLIs or tasks for your app |
77
+ | `front_end/` | The front-end for your app, including pages, components, forms, handlers, JavaScript, and assets |
78
+
79
+ Inside `app/src/back_end`
80
+
81
+ | Directory | Purpose |
82
+ |-----------|---------|
83
+ | `data_models/app_data_model.rb` | Base class for all DB model classes |
84
+ | `data_models/db` | DB model classes |
85
+ | `data_models/db.rb` | Namespace module for DB model classes |
86
+ | `data_models/migrations` | Database schema migrations |
87
+ | `data_models/seed` | Seed data used for local development |
88
+
89
+ Inside `app/src/front_end`
90
+
91
+ |Directory | Purpose |
92
+ |----------------|---------|
93
+ | `components/` | Component classes |
94
+ | `css/` | CSS, managed by esbuild and `bin/build-assets` |
95
+ | `fonts/` | Custom fonts, managed by esbuild and `bin/build-assets` |
96
+ | `forms/` | Form classes |
97
+ | `handlers/` | Handler classes |
98
+ | `images/` | Images, copied to `app/public` by `bin/build-assets` |
99
+ | `js/` | JavaScript, managed by esbuild and `bin/build-assets` |
100
+ | `layouts/` | Layout classes |
101
+ | `middlewares/` | Rack Middleware, if any |
102
+ | `pages/` | Page classes |
103
+ | `route_hooks/` | Route hooks, if any |
104
+ | `support/` | General support classes/junk drawer. |
105
+ | `svgs/` | SVGs you want to render inline |
106
+
107
+ ## Inside `specs/`
108
+
109
+ `specs/` is intended to mirror `app/src`, but has a few extra directories:
110
+
111
+
112
+ |Directory | Purpose |
113
+ |----------------|---------|
114
+ | `specs/back_end` | tests for all back-end code, organized the same as `app/src/back_end` |
115
+ | `specs/back_end/data_models/db` | tests for all DB classes, if needed |
116
+ | `specs/e2e` | End-to-end tests, organized however you like |
117
+ | `specs/factories` | Root of all factories for FactoryBot. You can create subdirectories here for non-DB classes you may want to be able to create |
118
+ | `specs/factories/db` | Factories to create DB records |
119
+ | `specs/front_end` | tests for all front-end code, organized the same as `app/src/front_end` |
120
+ | `specs/js`| *JavaScript* code to test any autonomous custom elements you have created |
@@ -2,32 +2,34 @@
2
2
 
3
3
  ## Terminology
4
4
 
5
- Brut attempts to use existing terminology where possible, particularly where that technology applies to the web platform. For example, there is not a thing called "CSS variables", rather the term is "custom properties". HTML entities are *elements* or *tags* that have *attributes*. As another example, HTML doesn't have *validations*, rather it as *constraints*, which can be *violated*.
5
+ Brut attempts to use existing terminology where possible, particularly where that technology applies to the web platform. For example, there is not a thing called "CSS variables", rather the term is "custom properties".
6
6
 
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.
7
+ Here are some common exampels:
8
8
 
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
9
 
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.
10
+ - HTML entities are **elements** or **tags**
11
+ - HTML elements have **attributes**.
12
+ - Forms don't have validations, they have **constraints** which are **violated** by invalid data.
13
+ - Ruby classes don't have constructors, they have **initializers**.
14
+ - Invoking behavior on a Ruby object is **calling a method**, not sending a message.
15
+ - Despite being in `specs/`, the files in there are **tests**, not specifications or "specs".
16
+ - Tests that use a browser are **end to end** or **e2e** tests.
17
+ - HTML is not rendered, but **generated**. The browser renders the HTML sent to it by the server, along with the CSS.
18
+ - Your app or site doesn't have users, it has **visitors**.
12
19
 
13
20
  ## Structure of These Documents
14
21
 
15
22
  Each page here documents on aspect of Brut, called a *module*, and these pages are organized along four sections:
16
23
 
17
- * **Overview** - provides detailed information on how this part of Brut works, with minimal examples to orient you to
18
- the terms and design of that module. Links to reference documentation are provided inline as needed.
19
- * **Testing** - information about how to write tests for the code in this module. For example, in [Pages](/pages), we detail how you are intended to test page classes.
20
- * **Recommended Practices** - this section outlines what we believe is the best way to use the module, along with
21
- justifications for the recommended approach. While you are free to ignore this advice, it's often useful to
22
- understand the intention of the authors.
23
- * **Technical Notes** - where appropriate, technical details about how or why the module works the way it does
24
- are provided. This section should be marked with a date to allow you to understand the recency of the
25
- information. It may not always be up to date, but this can help further clarify what is happening under the
26
- covers and why.
24
+ * **Overview** - What the module does, how it works, and a brief example.
25
+ * **Testing** - How to test the code you write in this module.
26
+ * **Recommended Practices** - Opinions from the creators about how best to think about the code in this module.
27
+ * **Technical Notes** - details about the technical implementations that may be
28
+ useful as context.
27
29
 
28
30
  ## Names of the Library and Associated Modules
29
31
 
30
- This framework is called "Brut" though may be called "BrutRB". It lives at `brutrb.com`.
32
+ This framework is called "Brut" though may be called "BrutRB" or "brut-rp". It lives at `brutrb.com`. Never use "brutRB", "brut_rb", etc.
31
33
 
32
34
  The JavaScript library is called "BrutJS", but is `brut-js` in code or the filesystem. "Brut-JS" is wrong, as is `brut_js`.
33
35
 
data/brutrb.com/dx ADDED
@@ -0,0 +1 @@
1
+ ../dx
@@ -55,7 +55,7 @@ RSpec.describe "logging into the website" do
55
55
  end
56
56
  ```
57
57
 
58
- `playwright-ruby-client` provides excellent documentation on how it has adapter Playwright's API for use
58
+ `playwright-ruby-client` provides excellent documentation on how it has adapted Playwright's API for use
59
59
  in Ruby.
60
60
 
61
61
  ### Test Setup
@@ -70,9 +70,15 @@ jobs, but rather assert the effects those jobs will have. Redis is flushed betwe
70
70
 
71
71
  Inside your test, `t` is available to produce translations. You can also access all your page and handler classes, so you can (and should) use `.routing`, e.g. `DashboardPage.routing`, to generate or access routes for your app.
72
72
 
73
- You can set `e2e_timeout` on any test to override the default amount of time Playwright will wait for a
73
+ You can set the `e2e_timeout` metadata on any test to override the default amount of time Playwright will wait for a
74
74
  locator to locate an element. The default is 5 seconds.
75
- `, to generate or access routes for your app.
75
+
76
+ ```ruby
77
+ RSpec.describe "Test for login", e2e_timeout: 10 do
78
+ # ...
79
+ end
80
+ ```
81
+
76
82
  You can also configure behavior with environment variables:
77
83
 
78
84
  | Variable | Default | Purpose |
@@ -122,12 +128,9 @@ The main Playwright documentation encourages you to locate elements by "accessib
122
128
  indirect ways of finding elements. In practice, this is error prone and tedious. Determining the
123
129
  accessible name of an element is not always easy.
124
130
 
125
- We recommend you assess your app's accssibility in another way than trying to do it while performing
126
- end-to-end tests. Instead, locate elements with CSS selectors—this is what you'd use to debug your app so
127
- it makes sense as a testing technique.
131
+ We recommend you assess your app's accessibility in another way than trying to do it while performing end-to-end tests. Instead, locate elements with CSS selectors—this is what you'd use to debug your app so it makes sense as a testing technique.
128
132
 
129
- Insulating your end-to-end tests from markup changes does not produce significant savinsg and can make
130
- tests more difficult to write.
133
+ Insulating your end-to-end tests from markup changes does not produce significant savings and can make tests more difficult to write.
131
134
 
132
135
  ### Testing Must Inform your HTML
133
136
 
@@ -170,5 +173,4 @@ make sure it's still needed.
170
173
 
171
174
  _Last Updated June 13, 2025_
172
175
 
173
- The test server is run bin `bin/test-server`, which is why Sidekiq will be running when your app is
174
- running for an e2e test.
176
+ The test server is run via `bin/test-server`.
@@ -0,0 +1,373 @@
1
+ # Quick Tour of Brut's Features
2
+
3
+ ## Pages
4
+
5
+ A [*Page*](/pages) models, well, a web page. It's a class that holds all the data
6
+ necessary to generate its HTML as well as a method called `page_template`, which
7
+ generates the HTML via Phlex.
8
+
9
+ A page's routing is convention-based and starts with a URL:
10
+
11
+ ```ruby{6}
12
+ class App < Brut::Framework::App
13
+ def id = "my-app"
14
+ def organization = "my-org"
15
+
16
+ routes do
17
+ page "/dashboard"
18
+ end
19
+ end
20
+ ```
21
+
22
+ This URL means our page class is expected in `DashboardPage`.
23
+
24
+ ```ruby
25
+ class DashboardPage < AppPage
26
+ def initialize
27
+ @now = Time.now
28
+ end
29
+
30
+ def page_template
31
+ main do
32
+ h1 { "Hello!" }
33
+ h2 do
34
+ plain("It's ")
35
+ time(datetime: l(@now, format: iso_8601)) do
36
+ l(@now, format: date)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ ```
43
+
44
+ This would all produce HTML like so, depending on the value of
45
+
46
+ ```html [/dashboard]
47
+ <main>
48
+ <h1>Hello!</h1>
49
+ <h2>It's
50
+ <time datetime="2025-02-17">
51
+ Monday, Feb 17
52
+ </time>
53
+ </h2>
54
+ </main>
55
+ ```
56
+
57
+ Note that the actual HTML delivered would include the code for a layout.
58
+
59
+ ## Layouts
60
+
61
+ Brut includes the concept of [layouts](/layouts), and they work similar to Rails.
62
+ Layouts are classes, however, and implement the Phlex-standard `view_template`
63
+ method:
64
+
65
+ ```ruby
66
+ class DefaultLayout < Brut::FrontEnd::Layout
67
+ def initialize(page_name:)
68
+ @page_name = page_name
69
+ end
70
+
71
+ def view_template
72
+ doctype
73
+ html(lang: "en") do
74
+ head do
75
+ meta(charset: "utf-8")
76
+ link(rel: "preload", as: "style", href: asset_path("/css/styles.css"))
77
+ link(rel: "stylesheet", href: asset_path("/css/styles.css"))
78
+ script(defer: true, src: asset_path("/js/app.js"))
79
+ title { app_name }
80
+ end
81
+ body do
82
+ yield
83
+ end
84
+ end
85
+ end
86
+ end
87
+ ```
88
+
89
+ This produces this HTML:
90
+
91
+ ```html
92
+ <!DOCTYPE html>
93
+ <html>
94
+ <head>
95
+ <meta charset="utf-8">
96
+ <link rel="preload" as="style" href="/css/styles-«HASH».css">
97
+ <link rel="stylesheet" href="/css/styles-«HASH».css">
98
+ <script defer src="/js/app-«HASH».js">
99
+ <title>My Awesome App</title>
100
+ </head>
101
+ <body>
102
+ <-- page HTML here -->
103
+ </body>
104
+ </html>
105
+ ```
106
+
107
+
108
+ ## Components
109
+
110
+ *Components* are a way to manage the complexity of HTML generation. The are Phlex components, meaning they are a class that implements `view_template`.
111
+
112
+ Here's an example of a flash message component:
113
+
114
+ ```ruby
115
+ # components/flash_component.rb
116
+ class FlashComponent < AppComponent
117
+ def initialize(flash:)
118
+ if flash.notice?
119
+ @message_key = flash.notice
120
+ @role = :info
121
+ elsif flash.alert?
122
+ @message_key = flash.alert
123
+ @role = :alert
124
+ end
125
+ end
126
+
127
+ def any_message? = !@message_key.nil?
128
+
129
+ def view_template
130
+ if any_message?
131
+ div(role: @role) do
132
+ t([ :flash, @message_key ])
133
+ end
134
+ end
135
+ end
136
+ end
137
+ ```
138
+
139
+ You can then use this in any other view using `render`, provided by Phlex.
140
+
141
+ ```ruby
142
+ def page_template
143
+ header do
144
+ render FlashComponent.new(flash:)
145
+ end
146
+ end
147
+ ```
148
+
149
+ ## Forms
150
+
151
+ [*Forms*](/forms) are a major concept like pages, since they are the way a browser
152
+ submits data to the server.
153
+
154
+ In Brut, a form does three things:
155
+
156
+ * Describes the data in the `<form>` tag
157
+ * Implies a route where its data is submitted via HTTP POST
158
+ * Provides access to the submitted data (via an object with methods, not a Hash of Whatever)
159
+
160
+ Like `page`, `form` declares a form's route:
161
+
162
+ ```ruby{2}
163
+ routes do
164
+ form "/login"
165
+ end
166
+ ```
167
+
168
+ Brut is convention-based, so it will expect a class named `LoginForm` to exist. It
169
+ will also expect `LoginHandler` to exist, which is a class that will receive the
170
+ form submission and process it. More on handlers below.
171
+
172
+ `LoginForm` uses class methods to declare its inputs. These class methods mirror
173
+ the various form element tags in HTML (`input`, `select`, etc.), and the methods
174
+ attributes allow you to declare names and client-side constraints:
175
+
176
+ ```ruby [forms/login_form.rb]
177
+ class LoginForm < AppForm
178
+ input :email
179
+ input :password, minlength: 8
180
+ end
181
+ ```
182
+
183
+ An instance of this class can be used to create HTML:
184
+
185
+ ```ruby
186
+ def view_template
187
+ FormTag(for: @form) do
188
+ Inputs::InputTag(form: @form, input_name: :email)
189
+ Inputs::InputTag(form: @form, input_name: :password)
190
+ button { "Login" }
191
+ end
192
+ end
193
+ ```
194
+
195
+ > [!NOTE]
196
+ > We'll explain what `FormTag` and `Inputs::InputTag` are in the [forms
197
+ > section](/forms)
198
+
199
+ This generates this HTML:
200
+
201
+ ```html
202
+ <form action="/login" method="POST">
203
+ <input type="email" name="email" required>
204
+ <input type="password" name="password" required minlength="8">
205
+ <button>Login</button>
206
+ </form>
207
+ ```
208
+
209
+ When the form is submitted, an instance of `LoginForm` is created and made available
210
+ to `LoginHandler`
211
+
212
+ ## Handlers
213
+
214
+ A handler is like a controller in Rails, except it only has one method: `handle`.
215
+ Unlike a Rails controller, a handler class is given its arguments explicitly, and
216
+ `handle`'s return value dictates what will happen next.
217
+
218
+ ```ruby
219
+ class LoginHandler < AppHandler
220
+ def initialize(form:)
221
+ @form = form
222
+ end
223
+ def handle
224
+ if @form.email == "secret@example.com" &&
225
+ @form.password = "sup3rs3cret!"
226
+ redirect_to(DashboardPage)
227
+ else
228
+ form.server_side_constraint_violation(
229
+ input_name: :email,
230
+ key: :no_such_user
231
+ )
232
+ LoginPage.new(form:)
233
+ end
234
+ end
235
+ end
236
+ ```
237
+
238
+ Note that we access the form's values as methods, not by digging into a Hash of
239
+ Whatever. Also note that returning an instance of a page will generate that page's
240
+ HTML, much like Rails' `render :edit` might. Lastly, `redirect_to` is a
241
+ convenience to generate a URL to `DashboardPage`, and ultimately causes `handle` to
242
+ return a `URI`, which Brut interprets as a redirect.
243
+
244
+
245
+ ## JavaScript
246
+
247
+ Brut doesn't include a front-end framework, however you can certainly use one. All
248
+ JavaScript is bundled into a single bundle by [esbuild](https://esbuild.github.io/).
249
+
250
+ Brut includes [BrutJS](/brut-js), which is a collection of autonomous custom
251
+ elements AKA Web Components that provide convenient features like autosubmit, form
252
+ submission confirmation and more:
253
+
254
+ ```ruby{5,7}
255
+ def view_template
256
+ FormTag(for: @form) do
257
+ Inputs::InputTag(form: @form, input_name: :email)
258
+ Inputs::InputTag(form: @form, input_name: :password)
259
+ brut_confirm_submit(message: "Really login? In this economy?!") do
260
+ button { "Login" }
261
+ end
262
+ end
263
+ end
264
+ ```
265
+
266
+ When "Login" is pressed, `window.confirm` will ask if the visitor wants to proceed.
267
+ This custom element can also use a `<dialog>` that you style, and works even better
268
+ if that `<dialog>` makes use of `<brut-confirmation-dialog>`.
269
+
270
+ ## CSS
271
+
272
+ Brut includes [BrutCSS](/css#using-brut-css), which is a lightweight utility-based
273
+ CSS library to let you get started quickly. It's *not* TailwindCSS, nor will it ever
274
+ be.
275
+
276
+ You can replace it with whatever you like easily enough.
277
+
278
+
279
+ ## Database Schema
280
+
281
+ Brut provides access to an SQL database via Sequel. Brut uses Sequel's database schema management, however it is enhanced to
282
+ encourage good practices by default.
283
+
284
+ > [!NOTE]
285
+ > Brut currently *only supports* PostgreSQL. It may support all RDBMSes that Sequel supports, but as of now,
286
+ > it's just Postgres.
287
+
288
+ Consider a `households` table that relates to an `accounts` table.
289
+
290
+ ```ruby
291
+ create_table :households,
292
+ comment: "Family unit managing the data" do
293
+ column :timezone, :text
294
+ column :dinner_time_of_day, :text
295
+ constraint(
296
+ :time_must_be_time,
297
+ %{
298
+ (dinner_time_of_day ~ '^[01][0-9]:[0-5][0-9]$') OR
299
+ (dinner_time_of_day ~ '^2[0-3]:[0-5][0-9]$')
300
+ }
301
+ )
302
+ end
303
+
304
+ create_table :accounts,
305
+ comment: "People or systems who can access this system",
306
+ external_id: true do
307
+
308
+ column :email, :email_address, unique: true
309
+ column :deactivated_at, :timestamptz, null: true
310
+ foreign_key :household_id, :households
311
+ end
312
+ ```
313
+
314
+ This is mostly using [Sequel's built-in migrations API](https://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html). But, a few additional behaviors are happening:
315
+
316
+ * Columns default to `NOT NULL`
317
+ * Tables require comments
318
+ * Foreign keys default to having constraints and indexes
319
+
320
+ There are other quality-of-life features of Brut's migration system, all designed to default to a good practice, with a way to do it
321
+ however you want when needed.
322
+
323
+ ## Database Access
324
+
325
+ Brut uses `Sequel::Model` to access data in your database. To discourage the conflation of "models of database tables" with "models
326
+ of your application's domain", these classes are in the `DB` namespace. Thus, the class `DB::Household` would be able to access the
327
+ `households` table defined above. This frees you up to create a `Household` class to model your domain's logic without being coupled
328
+ to how you store some data in a database.
329
+
330
+ ```ruby
331
+ class DB::Account < AppDataModel
332
+ has_external_id :ac
333
+ many_to_one :household
334
+ end
335
+
336
+ class DB::Household < AppDataModel
337
+ one_to_many :accounts
338
+ end
339
+ ```
340
+
341
+ ## Domain and Business Logic
342
+
343
+ Brut uses Zeitwerk for code loading, so any directories you create will be auto-loaded and refreshed during development. This means that you can create a class named `Household` in `app/src/back_end/domain/household.rb` and it would be loaded. Or, you could create `HouseholdService` in `app/src/back_end/services/household_service.rb` if you like.
344
+
345
+ > [!TIP]
346
+ > Providing a generally-useful abstraction for business or domain logic is not usually feasible.
347
+ > Thus, Brut doesn't provide much beyond Zeitwerk's auto-loading feature. It may provide more
348
+ > assistance in the future, but for now, Brut's approach is to free you from any prescription
349
+ > or moral imperative. Manage your domain and business logic how you see fit. You know your domain
350
+ > and team better than we do.
351
+
352
+ ## Testing
353
+
354
+ Brut provides support for three types of tests:
355
+
356
+ * Unit Tests, using RSpec
357
+ * End-to-end tests, using RSpec and Playwright
358
+ * Custom Element tests, written in JavaScript, using Mocha
359
+
360
+ Since Brut is based on classes, objects, and methods, your unit tests will usually
361
+ be straightforward, however Brut provides helpers to test your Page and Component
362
+ HTML using Nokogiri. FactoryBot is included and configured to manage test data.
363
+
364
+ ## Tasks
365
+
366
+ Brut doesn't use Rake tasks. It uses CLI apps powered by Ruby's `OptionParser`. Brut provides bootstrapping classes to make your own CLIs, as well as some light abstractions to make `OptionParser` a little more ergonomic. Brut's dev and production management CLIs are built using this support.
367
+
368
+ ## Observability
369
+
370
+ Brut has built-in support for [OpenTelemetry](https://opentelemetry.io/). Brut includes configuration for the [otel-desktop-viewer](https://github.com/CtrlSpice/otel-desktop-viewer) or a text-based viewer suitable for development. For production, most observability vendors provide OpenTelemetry ingestion any many have free tiers.
371
+
372
+ Brut does support logging, however you are encouraged to use OpenTelemetry instead.
373
+