brut 0.0.20 → 0.0.22

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 (728) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +24 -3
  3. data/.nvim.lua +1 -0
  4. data/Dockerfile.dx +12 -3
  5. data/Gemfile.lock +9 -7
  6. data/README.md +0 -7
  7. data/Rakefile +6 -4
  8. data/bin/dev +20 -0
  9. data/bin/docs +27 -0
  10. data/bin/setup +47 -1
  11. data/brut-css/.nvim.lua +1 -0
  12. data/brut-css/README.md +28 -0
  13. data/brut-css/bin/build +31 -0
  14. data/brut-css/bin/dev +1 -0
  15. data/brut-css/bin/docs +15 -0
  16. data/brut-css/bin/setup +5 -0
  17. data/brut-css/config/media-queries-all.css +15 -0
  18. data/brut-css/config/media-queries-minimal.css +5 -0
  19. data/brut-css/config/postcss.config.cjs +7 -0
  20. data/brut-css/config/pseudo-classes-all.css +9 -0
  21. data/brut-css/dx +1 -0
  22. data/brut-css/package-lock.json +3217 -0
  23. data/brut-css/package.json +36 -0
  24. data/brut-css/src/css/appearance.css +145 -0
  25. data/brut-css/src/css/border.css +522 -0
  26. data/brut-css/src/css/colors.css +3502 -0
  27. data/brut-css/src/css/dimensions.css +548 -0
  28. data/brut-css/src/css/flex.css +179 -0
  29. data/brut-css/src/css/index.css +13 -0
  30. data/brut-css/src/css/layout.css +120 -0
  31. data/brut-css/src/css/list.css +41 -0
  32. data/brut-css/src/css/positioning.css +354 -0
  33. data/brut-css/src/css/properties/colors.css +455 -0
  34. data/brut-css/src/css/properties/index.css +3 -0
  35. data/brut-css/src/css/properties/spacing.css +140 -0
  36. data/brut-css/src/css/properties/typography.css +224 -0
  37. data/brut-css/src/css/reset.css +107 -0
  38. data/brut-css/src/css/spacing.css +585 -0
  39. data/brut-css/src/css/typography.css +519 -0
  40. data/brut-css/src/css/utils.css +104 -0
  41. data/brut-css/src/docs/1_getting-started/1_overview.md +46 -0
  42. data/brut-css/src/docs/1_getting-started/2_installation.md +25 -0
  43. data/brut-css/src/docs/1_getting-started/3_core-concepts.md +75 -0
  44. data/brut-css/src/docs/1_getting-started/4_simple-example.md +132 -0
  45. data/brut-css/src/docs/1_getting-started/page.html.ejs +10 -0
  46. data/brut-css/src/docs/2_properties/page.html.ejs +71 -0
  47. data/brut-css/src/docs/3_classes/color-demo.html.ejs +31 -0
  48. data/brut-css/src/docs/3_classes/page.html.ejs +87 -0
  49. data/brut-css/src/docs/4_customization/1_design-system.md +36 -0
  50. data/brut-css/src/docs/4_customization/2_breakpoints.md +75 -0
  51. data/brut-css/src/docs/4_customization/3_pseudo-classes.md +74 -0
  52. data/brut-css/src/docs/4_customization/4_advanced-configuration.md +40 -0
  53. data/brut-css/src/docs/4_customization/page.html.ejs +10 -0
  54. data/brut-css/src/docs/docs.css +98 -0
  55. data/brut-css/src/docs/includes/body-and-header.html.ejs +30 -0
  56. data/brut-css/src/docs/includes/footer-and-rest.html.ejs +9 -0
  57. data/brut-css/src/docs/includes/head.html.ejs +5 -0
  58. data/brut-css/src/docs/includes/nav.html.ejs +10 -0
  59. data/brut-css/src/docs/index.html.ejs +32 -0
  60. data/brut-css/src/docs/prism-twilight.min.css +1 -0
  61. data/brut-css/src/js/Logger.js +71 -0
  62. data/brut-css/src/js/build.js +111 -0
  63. data/brut-css/src/js/cli/CLIArgError.js +7 -0
  64. data/brut-css/src/js/cli/Debug.js +27 -0
  65. data/brut-css/src/js/cli/DocsDir.js +16 -0
  66. data/brut-css/src/js/cli/DocsTemplateSourceDir.js +16 -0
  67. data/brut-css/src/js/cli/InputFile.js +31 -0
  68. data/brut-css/src/js/cli/MediaQueryConfigFile.js +10 -0
  69. data/brut-css/src/js/cli/OutputFile.js +22 -0
  70. data/brut-css/src/js/cli/ParsedArg.js +17 -0
  71. data/brut-css/src/js/cli/PathToBrutCSSRoot.js +19 -0
  72. data/brut-css/src/js/cli/PseudoClassConfigFile.js +11 -0
  73. data/brut-css/src/js/cli.js +108 -0
  74. data/brut-css/src/js/docGenerator.js +467 -0
  75. data/brut-css/src/js/mediaQueryConfigParser.js +98 -0
  76. data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +49 -0
  77. data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +42 -0
  78. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +9 -0
  79. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +185 -0
  80. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +8 -0
  81. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +7 -0
  82. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +73 -0
  83. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +9 -0
  84. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +4 -0
  85. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +8 -0
  86. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +12 -0
  87. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +4 -0
  88. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +8 -0
  89. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +5 -0
  90. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +9 -0
  91. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +49 -0
  92. data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +45 -0
  93. data/brut-css/src/js/pseudoClassConfigParser.js +145 -0
  94. data/brut-js/.projections.json +10 -0
  95. data/brut-js/README.md +118 -0
  96. data/brut-js/bin/build +10 -0
  97. data/brut-js/bin/ci +5 -0
  98. data/brut-js/bin/setup +5 -0
  99. data/brut-js/docs/README.md +8 -0
  100. data/brut-js/docs/jsdoc-plugins/customElementTag.js +8 -0
  101. data/brut-js/docs/jsdoc-theme/publish.js +692 -0
  102. data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +25 -0
  103. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +202 -0
  104. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +2 -0
  105. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +28 -0
  106. data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +327 -0
  107. data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +111 -0
  108. data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +132 -0
  109. data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +10 -0
  110. data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +199 -0
  111. data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +143 -0
  112. data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +2 -0
  113. data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +13 -0
  114. data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +32 -0
  115. data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +38 -0
  116. data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +14 -0
  117. data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +38 -0
  118. data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +131 -0
  119. data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +14 -0
  120. data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +131 -0
  121. data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +108 -0
  122. data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +19 -0
  123. data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +8 -0
  124. data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +19 -0
  125. data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +7 -0
  126. data/brut-js/docs/jsdoc.config.json +23 -0
  127. data/brut-js/docs/package-lock.json +343 -0
  128. data/brut-js/docs/package.json +7 -0
  129. data/brut-js/package-lock.json +2171 -0
  130. data/brut-js/package.json +32 -0
  131. data/brut-js/specs/AjaxSubmit.spec.js +256 -0
  132. data/brut-js/specs/Autosubmit.spec.js +127 -0
  133. data/brut-js/specs/ConfirmSubmit.spec.js +193 -0
  134. data/brut-js/specs/ConstraintViolationMessage.spec.js +33 -0
  135. data/brut-js/specs/ConstraintViolationMessages.spec.js +29 -0
  136. data/brut-js/specs/CopyToClipboard.spec.js +35 -0
  137. data/brut-js/specs/Form.spec.js +181 -0
  138. data/brut-js/specs/I18nTranslation.spec.js +19 -0
  139. data/brut-js/specs/LocaleDetection.spec.js +22 -0
  140. data/brut-js/specs/Message.spec.js +15 -0
  141. data/brut-js/specs/SpecHelper.js +23 -0
  142. data/brut-js/specs/Tabs.spec.js +41 -0
  143. data/brut-js/specs/config/asset_metadata.json +7 -0
  144. data/brut-js/src/AjaxSubmit.js +384 -0
  145. data/brut-js/src/Autosubmit.js +63 -0
  146. data/brut-js/src/BaseCustomElement.js +261 -0
  147. data/brut-js/src/ConfirmSubmit.js +116 -0
  148. data/brut-js/src/ConfirmationDialog.js +143 -0
  149. data/brut-js/src/ConstraintViolationMessage.js +125 -0
  150. data/brut-js/src/ConstraintViolationMessages.js +98 -0
  151. data/brut-js/src/CopyToClipboard.js +96 -0
  152. data/brut-js/src/Form.js +151 -0
  153. data/brut-js/src/I18nTranslation.js +61 -0
  154. data/brut-js/src/LocaleDetection.js +117 -0
  155. data/brut-js/src/Logger.js +90 -0
  156. data/brut-js/src/Message.js +56 -0
  157. data/brut-js/src/RichString.js +113 -0
  158. data/brut-js/src/Tabs.js +168 -0
  159. data/brut-js/src/Tracing.js +247 -0
  160. data/brut-js/src/appForTestingOnly.js +15 -0
  161. data/brut-js/src/index.js +130 -0
  162. data/brut-js/src/testing/AssetMetadata.js +35 -0
  163. data/brut-js/src/testing/AssetMetadataLoader.js +25 -0
  164. data/brut-js/src/testing/CustomElementTest.js +235 -0
  165. data/brut-js/src/testing/DOMCreator.js +45 -0
  166. data/brut-js/src/testing/index.js +48 -0
  167. data/brutrb.com/.vitepress/config.mjs +106 -0
  168. data/brutrb.com/.vitepress/plugins/jsdocLinker.js +34 -0
  169. data/brutrb.com/.vitepress/plugins/rdocLinker.js +18 -0
  170. data/brutrb.com/.vitepress/theme/custom.css +7 -0
  171. data/brutrb.com/.vitepress/theme/index.js +18 -0
  172. data/brutrb.com/.vitepress/theme/style.css +149 -0
  173. data/brutrb.com/ai.md +68 -0
  174. data/brutrb.com/assets.md +138 -0
  175. data/brutrb.com/bin/build +5 -0
  176. data/brutrb.com/bin/deploy +7 -0
  177. data/brutrb.com/bin/dev +5 -0
  178. data/brutrb.com/bin/setup +5 -0
  179. data/brutrb.com/brut-js.md +117 -0
  180. data/brutrb.com/business-logic.md +55 -0
  181. data/brutrb.com/cli.md +278 -0
  182. data/brutrb.com/components.md +243 -0
  183. data/brutrb.com/configuration.md +257 -0
  184. data/brutrb.com/css.md +103 -0
  185. data/brutrb.com/custom-element-tests.md +149 -0
  186. data/brutrb.com/database-access.md +201 -0
  187. data/brutrb.com/database-schema.md +312 -0
  188. data/brutrb.com/deployment.md +66 -0
  189. data/brutrb.com/dev-environment.md +179 -0
  190. data/brutrb.com/doc-conventions.md +39 -0
  191. data/brutrb.com/end-to-end-tests.md +174 -0
  192. data/brutrb.com/flash-and-session.md +224 -0
  193. data/brutrb.com/forms.md +866 -0
  194. data/brutrb.com/getting-started.md +66 -0
  195. data/brutrb.com/handlers.md +153 -0
  196. data/brutrb.com/hooks.md +178 -0
  197. data/brutrb.com/i18n.md +188 -0
  198. data/brutrb.com/images/Makefile +10 -0
  199. data/brutrb.com/images/dev-env-overview.dot +54 -0
  200. data/brutrb.com/images/dev-env-overview.png +0 -0
  201. data/brutrb.com/images/dev-env-protocol.dot +37 -0
  202. data/brutrb.com/images/dev-env-protocol.png +0 -0
  203. data/brutrb.com/images/logo-300.png +0 -0
  204. data/brutrb.com/images/logo.png +0 -0
  205. data/brutrb.com/images/overview.graffle +0 -0
  206. data/brutrb.com/images/overview.png +0 -0
  207. data/brutrb.com/images/spa.dot +19 -0
  208. data/brutrb.com/images/spa.png +0 -0
  209. data/brutrb.com/images/workspace-protocol.dot +44 -0
  210. data/brutrb.com/images/workspace-protocol.png +0 -0
  211. data/brutrb.com/index.md +36 -0
  212. data/brutrb.com/instrumentation.md +183 -0
  213. data/brutrb.com/javascript.md +122 -0
  214. data/brutrb.com/jobs.md +14 -0
  215. data/{doc-src → brutrb.com}/keyword-injection.md +122 -68
  216. data/brutrb.com/markdown-examples.md +85 -0
  217. data/brutrb.com/middleware.md +80 -0
  218. data/brutrb.com/not-released.md +5 -0
  219. data/brutrb.com/overview.md +404 -0
  220. data/brutrb.com/package-lock.json +2404 -0
  221. data/brutrb.com/package.json +11 -0
  222. data/brutrb.com/pages.md +378 -0
  223. data/brutrb.com/public/images/logo-300.png +0 -0
  224. data/brutrb.com/public/images/logo.png +0 -0
  225. data/brutrb.com/routes.md +215 -0
  226. data/brutrb.com/security.md +105 -0
  227. data/brutrb.com/seed-data.md +63 -0
  228. data/brutrb.com/space-time-continuum.md +85 -0
  229. data/brutrb.com/tutorial.md +3 -0
  230. data/brutrb.com/unit-tests.md +148 -0
  231. data/docker-compose.dx.yml +6 -3
  232. data/docs/404.html +21 -0
  233. data/docs/CNAME +1 -0
  234. data/docs/ai.html +24 -0
  235. data/docs/api/Brut/BackEnd/SeedData.html +493 -0
  236. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +214 -0
  237. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +125 -0
  238. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +125 -0
  239. data/docs/api/Brut/BackEnd/Sidekiq.html +125 -0
  240. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +414 -0
  241. data/docs/api/Brut/BackEnd/Validators.html +128 -0
  242. data/docs/api/Brut/BackEnd.html +132 -0
  243. data/docs/api/Brut/CLI/App.html +1576 -0
  244. data/docs/api/Brut/CLI/AppRunner.html +491 -0
  245. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +264 -0
  246. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +306 -0
  247. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +262 -0
  248. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +314 -0
  249. data/docs/api/Brut/CLI/Apps/BuildAssets.html +183 -0
  250. data/docs/api/Brut/CLI/Apps/DB/Create.html +365 -0
  251. data/docs/api/Brut/CLI/Apps/DB/Drop.html +357 -0
  252. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +383 -0
  253. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +335 -0
  254. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +329 -0
  255. data/docs/api/Brut/CLI/Apps/DB/Seed.html +347 -0
  256. data/docs/api/Brut/CLI/Apps/DB/Status.html +383 -0
  257. data/docs/api/Brut/CLI/Apps/DB.html +183 -0
  258. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +303 -0
  259. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +512 -0
  260. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +398 -0
  261. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +374 -0
  262. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +410 -0
  263. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +262 -0
  264. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +303 -0
  265. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +480 -0
  266. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +450 -0
  267. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +380 -0
  268. data/docs/api/Brut/CLI/Apps/Scaffold.html +253 -0
  269. data/docs/api/Brut/CLI/Apps/Test/Audit.html +464 -0
  270. data/docs/api/Brut/CLI/Apps/Test/E2e.html +407 -0
  271. data/docs/api/Brut/CLI/Apps/Test/JS.html +262 -0
  272. data/docs/api/Brut/CLI/Apps/Test/Run.html +578 -0
  273. data/docs/api/Brut/CLI/Apps/Test.html +253 -0
  274. data/docs/api/Brut/CLI/Apps.html +125 -0
  275. data/docs/api/Brut/CLI/Command.html +2342 -0
  276. data/docs/api/Brut/CLI/Error.html +139 -0
  277. data/docs/api/Brut/CLI/ExecutionResults/Result.html +664 -0
  278. data/docs/api/Brut/CLI/ExecutionResults.html +675 -0
  279. data/docs/api/Brut/CLI/Executor.html +430 -0
  280. data/docs/api/Brut/CLI/InvalidOption.html +245 -0
  281. data/docs/api/Brut/CLI/Options.html +753 -0
  282. data/docs/api/Brut/CLI/Output.html +699 -0
  283. data/docs/api/Brut/CLI/SystemExecError.html +451 -0
  284. data/docs/api/Brut/CLI.html +263 -0
  285. data/docs/api/Brut/FactoryBot.html +225 -0
  286. data/docs/api/Brut/Framework/App.html +1097 -0
  287. data/docs/api/Brut/Framework/Config.html +1045 -0
  288. data/docs/api/Brut/Framework/Container.html +1379 -0
  289. data/docs/api/Brut/Framework/Error.html +140 -0
  290. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +144 -0
  291. data/docs/api/Brut/Framework/Errors/Bug.html +234 -0
  292. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +257 -0
  293. data/docs/api/Brut/Framework/Errors/MissingParameter.html +273 -0
  294. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +471 -0
  295. data/docs/api/Brut/Framework/Errors/NotFound.html +308 -0
  296. data/docs/api/Brut/Framework/Errors/NotImplemented.html +234 -0
  297. data/docs/api/Brut/Framework/Errors.html +328 -0
  298. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +392 -0
  299. data/docs/api/Brut/Framework/MCP.html +861 -0
  300. data/docs/api/Brut/Framework/ProjectEnvironment.html +648 -0
  301. data/docs/api/Brut/Framework.html +129 -0
  302. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +317 -0
  303. data/docs/api/Brut/FrontEnd/Component/Helpers.html +326 -0
  304. data/docs/api/Brut/FrontEnd/Component.html +365 -0
  305. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +470 -0
  306. data/docs/api/Brut/FrontEnd/Components/FormTag.html +518 -0
  307. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +317 -0
  308. data/docs/api/Brut/FrontEnd/Components/Input.html +195 -0
  309. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +339 -0
  310. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +660 -0
  311. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +417 -0
  312. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +918 -0
  313. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +651 -0
  314. data/docs/api/Brut/FrontEnd/Components/Inputs.html +125 -0
  315. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +367 -0
  316. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +336 -0
  317. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +655 -0
  318. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +352 -0
  319. data/docs/api/Brut/FrontEnd/Components.html +135 -0
  320. data/docs/api/Brut/FrontEnd/Download.html +467 -0
  321. data/docs/api/Brut/FrontEnd/Flash.html +1150 -0
  322. data/docs/api/Brut/FrontEnd/Form.html +1157 -0
  323. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +634 -0
  324. data/docs/api/Brut/FrontEnd/Forms/Input.html +615 -0
  325. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +547 -0
  326. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1318 -0
  327. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +609 -0
  328. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +587 -0
  329. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +613 -0
  330. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +582 -0
  331. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +609 -0
  332. data/docs/api/Brut/FrontEnd/Forms.html +127 -0
  333. data/docs/api/Brut/FrontEnd/GenericResponse.html +377 -0
  334. data/docs/api/Brut/FrontEnd/Handler.html +442 -0
  335. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +318 -0
  336. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +336 -0
  337. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +399 -0
  338. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +354 -0
  339. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +151 -0
  340. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +315 -0
  341. data/docs/api/Brut/FrontEnd/Handlers.html +125 -0
  342. data/docs/api/Brut/FrontEnd/HandlingResults.html +339 -0
  343. data/docs/api/Brut/FrontEnd/HttpMethod.html +661 -0
  344. data/docs/api/Brut/FrontEnd/HttpStatus.html +496 -0
  345. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +284 -0
  346. data/docs/api/Brut/FrontEnd/Layout.html +318 -0
  347. data/docs/api/Brut/FrontEnd/Middleware.html +135 -0
  348. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +288 -0
  349. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +292 -0
  350. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +324 -0
  351. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +372 -0
  352. data/docs/api/Brut/FrontEnd/Middlewares.html +125 -0
  353. data/docs/api/Brut/FrontEnd/Page.html +773 -0
  354. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +797 -0
  355. data/docs/api/Brut/FrontEnd/Pages.html +125 -0
  356. data/docs/api/Brut/FrontEnd/RequestContext.html +1312 -0
  357. data/docs/api/Brut/FrontEnd/RouteHook.html +424 -0
  358. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +242 -0
  359. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +249 -0
  360. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +264 -0
  361. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +261 -0
  362. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +284 -0
  363. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +252 -0
  364. data/docs/api/Brut/FrontEnd/RouteHooks.html +115 -0
  365. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +227 -0
  366. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +305 -0
  367. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +324 -0
  368. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +319 -0
  369. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +315 -0
  370. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +315 -0
  371. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +327 -0
  372. data/docs/api/Brut/FrontEnd/Routing/Route.html +761 -0
  373. data/docs/api/Brut/FrontEnd/Routing.html +927 -0
  374. data/docs/api/Brut/FrontEnd/Session.html +1195 -0
  375. data/docs/api/Brut/FrontEnd.html +134 -0
  376. data/docs/api/Brut/I18n/BaseMethods.html +931 -0
  377. data/docs/api/Brut/I18n/ForBackEnd.html +302 -0
  378. data/docs/api/Brut/I18n/ForCLI.html +302 -0
  379. data/docs/api/Brut/I18n/ForHTML.html +296 -0
  380. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +316 -0
  381. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +930 -0
  382. data/docs/api/Brut/I18n.html +127 -0
  383. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +435 -0
  384. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +286 -0
  385. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +302 -0
  386. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +864 -0
  387. data/docs/api/Brut/Instrumentation.html +126 -0
  388. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +532 -0
  389. data/docs/api/Brut/SinatraHelpers.html +281 -0
  390. data/docs/api/Brut/SpecSupport/ClockSupport.html +383 -0
  391. data/docs/api/Brut/SpecSupport/ComponentSupport.html +502 -0
  392. data/docs/api/Brut/SpecSupport/E2ETestServer.html +503 -0
  393. data/docs/api/Brut/SpecSupport/E2eSupport.html +142 -0
  394. data/docs/api/Brut/SpecSupport/EnhancedNode.html +403 -0
  395. data/docs/api/Brut/SpecSupport/FlashSupport.html +278 -0
  396. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +401 -0
  397. data/docs/api/Brut/SpecSupport/GeneralSupport.html +195 -0
  398. data/docs/api/Brut/SpecSupport/HandlerSupport.html +160 -0
  399. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +553 -0
  400. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +439 -0
  401. data/docs/api/Brut/SpecSupport/Matchers.html +125 -0
  402. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +335 -0
  403. data/docs/api/Brut/SpecSupport/RSpecSetup.html +602 -0
  404. data/docs/api/Brut/SpecSupport/SessionSupport.html +196 -0
  405. data/docs/api/Brut/SpecSupport.html +129 -0
  406. data/docs/api/Brut.html +225 -0
  407. data/docs/api/Clock.html +603 -0
  408. data/docs/api/RichString.html +968 -0
  409. data/docs/api/SemanticLogger/Appender/Async.html +219 -0
  410. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +115 -0
  411. data/docs/api/Sequel/Extensions/BrutMigrations.html +533 -0
  412. data/docs/api/Sequel/Extensions.html +117 -0
  413. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +105 -0
  414. data/docs/api/Sequel/Plugins/CreatedAt.html +125 -0
  415. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +207 -0
  416. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +186 -0
  417. data/docs/api/Sequel/Plugins/ExternalId.html +218 -0
  418. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +202 -0
  419. data/docs/api/Sequel/Plugins/FindBang.html +125 -0
  420. data/docs/api/Sequel/Plugins.html +117 -0
  421. data/docs/api/Sequel.html +117 -0
  422. data/docs/api/_index.html +1553 -0
  423. data/docs/api/class_list.html +54 -0
  424. data/docs/api/css/common.css +1 -0
  425. data/docs/api/css/full_list.css +58 -0
  426. data/docs/api/css/style.css +503 -0
  427. data/docs/api/file.README.html +127 -0
  428. data/docs/api/file_list.html +59 -0
  429. data/docs/api/frames.html +22 -0
  430. data/docs/api/index.html +127 -0
  431. data/docs/api/js/app.js +344 -0
  432. data/docs/api/js/full_list.js +242 -0
  433. data/docs/api/js/jquery.js +4 -0
  434. data/docs/api/method_list.html +3998 -0
  435. data/docs/api/top-level-namespace.html +112 -0
  436. data/docs/assets/ai.md.tZrjP9im.js +1 -0
  437. data/docs/assets/ai.md.tZrjP9im.lean.js +1 -0
  438. data/docs/assets/app.D_yaTITQ.js +1 -0
  439. data/docs/assets/assets.md.D3wunzLx.js +19 -0
  440. data/docs/assets/assets.md.D3wunzLx.lean.js +1 -0
  441. data/docs/assets/brut-js.md.o2DAO2s2.js +12 -0
  442. data/docs/assets/brut-js.md.o2DAO2s2.lean.js +1 -0
  443. data/docs/assets/business-logic.md.BY4hGy0m.js +1 -0
  444. data/docs/assets/business-logic.md.BY4hGy0m.lean.js +1 -0
  445. data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +1 -0
  446. data/docs/assets/chunks/VPLocalSearchBox.B2-ZzyTY.js +8 -0
  447. data/docs/assets/chunks/framework.1L-BeKqY.js +18 -0
  448. data/docs/assets/chunks/theme.CfGFVRvE.js +2 -0
  449. data/docs/assets/cli.md.RmeA2b0i.js +127 -0
  450. data/docs/assets/cli.md.RmeA2b0i.lean.js +1 -0
  451. data/docs/assets/components.md.eCttGlN-.js +104 -0
  452. data/docs/assets/components.md.eCttGlN-.lean.js +1 -0
  453. data/docs/assets/configuration.md.BRriU0cL.js +78 -0
  454. data/docs/assets/configuration.md.BRriU0cL.lean.js +1 -0
  455. data/docs/assets/css.md.DJgj2clw.js +21 -0
  456. data/docs/assets/css.md.DJgj2clw.lean.js +1 -0
  457. data/docs/assets/custom-element-tests.md.BrYJQEl3.js +69 -0
  458. data/docs/assets/custom-element-tests.md.BrYJQEl3.lean.js +1 -0
  459. data/docs/assets/database-access.md.C7l-Vuvb.js +63 -0
  460. data/docs/assets/database-access.md.C7l-Vuvb.lean.js +1 -0
  461. data/docs/assets/database-schema.md.BUjR0VS1.js +63 -0
  462. data/docs/assets/database-schema.md.BUjR0VS1.lean.js +1 -0
  463. data/docs/assets/deployment.md.Dbka4OTr.js +1 -0
  464. data/docs/assets/deployment.md.Dbka4OTr.lean.js +1 -0
  465. data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
  466. data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
  467. data/docs/assets/dev-environment.md.BNc8AYiK.js +11 -0
  468. data/docs/assets/dev-environment.md.BNc8AYiK.lean.js +1 -0
  469. data/docs/assets/doc-conventions.md.DCfRXXi-.js +1 -0
  470. data/docs/assets/doc-conventions.md.DCfRXXi-.lean.js +1 -0
  471. data/docs/assets/end-to-end-tests.md.yfQHC0b5.js +26 -0
  472. data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +1 -0
  473. data/docs/assets/flash-and-session.md.BXY8RvT0.js +93 -0
  474. data/docs/assets/flash-and-session.md.BXY8RvT0.lean.js +1 -0
  475. data/docs/assets/forms.md.CBTYQ_Cz.js +379 -0
  476. data/docs/assets/forms.md.CBTYQ_Cz.lean.js +1 -0
  477. data/docs/assets/getting-started.md.Bz2s1Vjb.js +2 -0
  478. data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +1 -0
  479. data/docs/assets/handlers.md.089DVD3v.js +69 -0
  480. data/docs/assets/handlers.md.089DVD3v.lean.js +1 -0
  481. data/docs/assets/hooks.md.C4-moMny.js +80 -0
  482. data/docs/assets/hooks.md.C4-moMny.lean.js +1 -0
  483. data/docs/assets/i18n.md.Do9i1qWl.js +23 -0
  484. data/docs/assets/i18n.md.Do9i1qWl.lean.js +1 -0
  485. data/docs/assets/index.md.B28EwVpq.js +1 -0
  486. data/docs/assets/index.md.B28EwVpq.lean.js +1 -0
  487. data/docs/assets/instrumentation.md.CL6ax7nT.js +35 -0
  488. data/docs/assets/instrumentation.md.CL6ax7nT.lean.js +1 -0
  489. data/docs/assets/javascript.md.GWbhRS51.js +31 -0
  490. data/docs/assets/javascript.md.GWbhRS51.lean.js +1 -0
  491. data/docs/assets/jobs.md.S-2amAYp.js +1 -0
  492. data/docs/assets/jobs.md.S-2amAYp.lean.js +1 -0
  493. data/docs/assets/keyword-injection.md.Dt2tKREs.js +25 -0
  494. data/docs/assets/keyword-injection.md.Dt2tKREs.lean.js +1 -0
  495. data/docs/assets/markdown-examples.md.CCFEQO44.js +33 -0
  496. data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +1 -0
  497. data/docs/assets/middleware.md.Czz_UlJN.js +20 -0
  498. data/docs/assets/middleware.md.Czz_UlJN.lean.js +1 -0
  499. data/docs/assets/not-released.md.BBy28McC.js +1 -0
  500. data/docs/assets/not-released.md.BBy28McC.lean.js +1 -0
  501. data/docs/assets/overview.Da81cB9R.png +0 -0
  502. data/docs/assets/overview.md.CDalkuxV.js +133 -0
  503. data/docs/assets/overview.md.CDalkuxV.lean.js +1 -0
  504. data/docs/assets/pages.md.BE3kfOc5.js +122 -0
  505. data/docs/assets/pages.md.BE3kfOc5.lean.js +1 -0
  506. data/docs/assets/routes.md.BMM7peut.js +29 -0
  507. data/docs/assets/routes.md.BMM7peut.lean.js +1 -0
  508. data/docs/assets/security.md.C668yXCi.js +1 -0
  509. data/docs/assets/security.md.C668yXCi.lean.js +1 -0
  510. data/docs/assets/seed-data.md.BvFZlqIk.js +14 -0
  511. data/docs/assets/seed-data.md.BvFZlqIk.lean.js +1 -0
  512. data/docs/assets/spa.qejUdp-5.png +0 -0
  513. data/docs/assets/space-time-continuum.md.KPUIKysQ.js +1 -0
  514. data/docs/assets/space-time-continuum.md.KPUIKysQ.lean.js +1 -0
  515. data/docs/assets/style.D73IYGCX.css +1 -0
  516. data/docs/assets/tutorial.md.BnoGjrdK.js +1 -0
  517. data/docs/assets/tutorial.md.BnoGjrdK.lean.js +1 -0
  518. data/docs/assets/unit-tests.md.DUGrnLj5.js +13 -0
  519. data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +1 -0
  520. data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
  521. data/docs/assets.html +42 -0
  522. data/docs/brut-css/brut.css +1 -0
  523. data/docs/brut-css/brut.max.css +22372 -0
  524. data/docs/brut-css/classes/appearances.html +783 -0
  525. data/docs/brut-css/classes/background-colors.html +3529 -0
  526. data/docs/brut-css/classes/border-colors.html +3529 -0
  527. data/docs/brut-css/classes/borders.html +2293 -0
  528. data/docs/brut-css/classes/dimensions.html +2581 -0
  529. data/docs/brut-css/classes/flex.html +917 -0
  530. data/docs/brut-css/classes/foreground-colors.html +3261 -0
  531. data/docs/brut-css/classes/junk-drawer.html +431 -0
  532. data/docs/brut-css/classes/layout.html +668 -0
  533. data/docs/brut-css/classes/lists.html +331 -0
  534. data/docs/brut-css/classes/positioning.html +1751 -0
  535. data/docs/brut-css/classes/spacings.html +2633 -0
  536. data/docs/brut-css/classes/typography.html +2206 -0
  537. data/docs/brut-css/customization/advanced-configuration.html +204 -0
  538. data/docs/brut-css/customization/breakpoints.html +227 -0
  539. data/docs/brut-css/customization/design-system.html +197 -0
  540. data/docs/brut-css/customization/pseudo-classes.html +228 -0
  541. data/docs/brut-css/docs.css +98 -0
  542. data/docs/brut-css/getting-started/core-concepts.html +234 -0
  543. data/docs/brut-css/getting-started/installation.html +190 -0
  544. data/docs/brut-css/getting-started/overview.html +210 -0
  545. data/docs/brut-css/getting-started/simple-example.html +285 -0
  546. data/docs/brut-css/index.html +193 -0
  547. data/docs/brut-css/prism-twilight.min.css +1 -0
  548. data/docs/brut-css/properties/colors.html +1548 -0
  549. data/docs/brut-css/properties/spacings.html +614 -0
  550. data/docs/brut-css/properties/typography.html +777 -0
  551. data/docs/brut-js/api/AjaxSubmit.html +374 -0
  552. data/docs/brut-js/api/AjaxSubmit.js.html +435 -0
  553. data/docs/brut-js/api/Autosubmit.html +192 -0
  554. data/docs/brut-js/api/Autosubmit.js.html +114 -0
  555. data/docs/brut-js/api/BaseCustomElement.html +1091 -0
  556. data/docs/brut-js/api/BaseCustomElement.js.html +312 -0
  557. data/docs/brut-js/api/BrutCustomElements.html +172 -0
  558. data/docs/brut-js/api/BufferedLogger.html +173 -0
  559. data/docs/brut-js/api/ConfirmSubmit.html +278 -0
  560. data/docs/brut-js/api/ConfirmSubmit.js.html +167 -0
  561. data/docs/brut-js/api/ConfirmationDialog.html +425 -0
  562. data/docs/brut-js/api/ConfirmationDialog.js.html +194 -0
  563. data/docs/brut-js/api/ConstraintViolationMessage.html +448 -0
  564. data/docs/brut-js/api/ConstraintViolationMessage.js.html +176 -0
  565. data/docs/brut-js/api/ConstraintViolationMessages.html +590 -0
  566. data/docs/brut-js/api/ConstraintViolationMessages.js.html +149 -0
  567. data/docs/brut-js/api/CopyToClipboard.html +345 -0
  568. data/docs/brut-js/api/CopyToClipboard.js.html +147 -0
  569. data/docs/brut-js/api/Form.html +294 -0
  570. data/docs/brut-js/api/Form.js.html +202 -0
  571. data/docs/brut-js/api/I18nTranslation.html +409 -0
  572. data/docs/brut-js/api/I18nTranslation.js.html +112 -0
  573. data/docs/brut-js/api/LocaleDetection.html +312 -0
  574. data/docs/brut-js/api/LocaleDetection.js.html +168 -0
  575. data/docs/brut-js/api/Logger.html +702 -0
  576. data/docs/brut-js/api/Logger.js.html +141 -0
  577. data/docs/brut-js/api/Message.html +238 -0
  578. data/docs/brut-js/api/Message.js.html +107 -0
  579. data/docs/brut-js/api/PrefixedLogger.html +369 -0
  580. data/docs/brut-js/api/RichString.html +1049 -0
  581. data/docs/brut-js/api/RichString.js.html +164 -0
  582. data/docs/brut-js/api/Tabs.html +295 -0
  583. data/docs/brut-js/api/Tabs.js.html +219 -0
  584. data/docs/brut-js/api/Tracing.html +277 -0
  585. data/docs/brut-js/api/Tracing.js.html +298 -0
  586. data/docs/brut-js/api/external-CustomElementRegistry.html +140 -0
  587. data/docs/brut-js/api/external-Performance.html +138 -0
  588. data/docs/brut-js/api/external-Promise.html +138 -0
  589. data/docs/brut-js/api/external-ValidityState.html +138 -0
  590. data/docs/brut-js/api/external-Window.html +233 -0
  591. data/docs/brut-js/api/external-fetch.html +138 -0
  592. data/docs/brut-js/api/global.html +400 -0
  593. data/docs/brut-js/api/index.html +168 -0
  594. data/docs/brut-js/api/index.js.html +181 -0
  595. data/docs/brut-js/api/module-testing.html +383 -0
  596. data/docs/brut-js/api/scripts/linenumber.js +25 -0
  597. data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +202 -0
  598. data/docs/brut-js/api/scripts/prettify/lang-css.js +2 -0
  599. data/docs/brut-js/api/scripts/prettify/prettify.js +28 -0
  600. data/docs/brut-js/api/styles/jsdoc-default.css +327 -0
  601. data/docs/brut-js/api/styles/prettify-jsdoc.css +111 -0
  602. data/docs/brut-js/api/styles/prettify-tomorrow.css +132 -0
  603. data/docs/brut-js/api/testing.AssetMetadata.html +172 -0
  604. data/docs/brut-js/api/testing.AssetMetadataLoader.html +171 -0
  605. data/docs/brut-js/api/testing.CustomElementTest.html +679 -0
  606. data/docs/brut-js/api/testing.DOMCreator.html +171 -0
  607. data/docs/brut-js/api/testing_AssetMetadata.js.html +86 -0
  608. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +76 -0
  609. data/docs/brut-js/api/testing_CustomElementTest.js.html +286 -0
  610. data/docs/brut-js/api/testing_DOMCreator.js.html +96 -0
  611. data/docs/brut-js/api/testing_index.js.html +99 -0
  612. data/docs/brut-js.html +35 -0
  613. data/docs/business-logic.html +24 -0
  614. data/docs/cli.html +150 -0
  615. data/docs/components.html +127 -0
  616. data/docs/configuration.html +101 -0
  617. data/docs/css.html +44 -0
  618. data/docs/custom-element-tests.html +92 -0
  619. data/docs/database-access.html +86 -0
  620. data/docs/database-schema.html +86 -0
  621. data/docs/deployment.html +24 -0
  622. data/docs/dev-environment.html +34 -0
  623. data/docs/doc-conventions.html +24 -0
  624. data/docs/end-to-end-tests.html +49 -0
  625. data/docs/flash-and-session.html +116 -0
  626. data/docs/forms.html +402 -0
  627. data/docs/getting-started.html +25 -0
  628. data/docs/handlers.html +92 -0
  629. data/docs/hashmap.json +1 -0
  630. data/docs/hooks.html +103 -0
  631. data/docs/i18n.html +46 -0
  632. data/docs/images/logo-300.png +0 -0
  633. data/docs/images/logo.png +0 -0
  634. data/docs/index.html +24 -0
  635. data/docs/instrumentation.html +58 -0
  636. data/docs/javascript.html +54 -0
  637. data/docs/jobs.html +24 -0
  638. data/docs/keyword-injection.html +48 -0
  639. data/docs/markdown-examples.html +56 -0
  640. data/docs/middleware.html +43 -0
  641. data/docs/not-released.html +24 -0
  642. data/docs/overview.html +156 -0
  643. data/docs/pages.html +145 -0
  644. data/docs/routes.html +52 -0
  645. data/docs/security.html +24 -0
  646. data/docs/seed-data.html +37 -0
  647. data/docs/space-time-continuum.html +24 -0
  648. data/docs/tutorial.html +24 -0
  649. data/docs/unit-tests.html +36 -0
  650. data/docs/vp-icons.css +1 -0
  651. data/lib/brut/back_end/seed_data.rb +19 -2
  652. data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
  653. data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
  654. data/lib/brut/back_end/sidekiq.rb +2 -1
  655. data/lib/brut/back_end/validator.rb +5 -1
  656. data/lib/brut/back_end.rb +4 -2
  657. data/lib/brut/cli/app_runner.rb +1 -1
  658. data/lib/brut/cli/apps/test.rb +5 -0
  659. data/lib/brut/cli.rb +4 -3
  660. data/lib/brut/factory_bot.rb +0 -5
  661. data/lib/brut/framework/app.rb +70 -5
  662. data/lib/brut/framework/config.rb +5 -3
  663. data/lib/brut/framework/container.rb +3 -2
  664. data/lib/brut/framework/errors.rb +12 -4
  665. data/lib/brut/framework/mcp.rb +58 -1
  666. data/lib/brut/framework/project_environment.rb +6 -2
  667. data/lib/brut/framework.rb +1 -1
  668. data/lib/brut/front_end/component.rb +69 -71
  669. data/lib/brut/front_end/components/constraint_violations.rb +1 -4
  670. data/lib/brut/front_end/components/form_tag.rb +1 -1
  671. data/lib/brut/front_end/components/input.rb +3 -3
  672. data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
  673. data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +7 -9
  674. data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
  675. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
  676. data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
  677. data/lib/brut/front_end/components/time_tag.rb +2 -1
  678. data/lib/brut/front_end/form.rb +4 -4
  679. data/lib/brut/front_end/forms/input.rb +2 -1
  680. data/lib/brut/front_end/forms/input_definition.rb +5 -2
  681. data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
  682. data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
  683. data/lib/brut/front_end/forms/select_input.rb +2 -4
  684. data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
  685. data/lib/brut/front_end/handler.rb +28 -26
  686. data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
  687. data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
  688. data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
  689. data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
  690. data/lib/brut/front_end/layout.rb +16 -0
  691. data/lib/brut/front_end/page.rb +52 -29
  692. data/lib/brut/front_end/request_context.rb +3 -2
  693. data/lib/brut/front_end/routing.rb +5 -1
  694. data/lib/brut/front_end.rb +4 -13
  695. data/lib/brut/i18n/base_methods.rb +167 -79
  696. data/lib/brut/i18n/for_back_end.rb +4 -0
  697. data/lib/brut/i18n/for_cli.rb +4 -0
  698. data/lib/brut/i18n/for_html.rb +32 -4
  699. data/lib/brut/i18n/http_accept_language.rb +47 -0
  700. data/lib/brut/instrumentation/open_telemetry.rb +36 -1
  701. data/lib/brut/instrumentation.rb +3 -5
  702. data/lib/brut/sinatra_helpers.rb +11 -3
  703. data/lib/brut/spec_support/component_support.rb +30 -16
  704. data/lib/brut/spec_support/e2e_support.rb +1 -1
  705. data/lib/brut/spec_support/e2e_test_server.rb +3 -0
  706. data/lib/brut/spec_support/general_support.rb +3 -0
  707. data/lib/brut/spec_support/handler_support.rb +6 -1
  708. data/lib/brut/spec_support/matcher.rb +1 -0
  709. data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
  710. data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
  711. data/lib/brut/spec_support/matchers/have_i18n_string.rb +2 -5
  712. data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
  713. data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
  714. data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
  715. data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
  716. data/lib/brut/spec_support.rb +1 -1
  717. data/lib/brut/version.rb +1 -1
  718. data/lib/brut.rb +5 -4
  719. data/lib/sequel/extensions/brut_migrations.rb +1 -1
  720. metadata +648 -13
  721. data/doc-src/architecture.md +0 -102
  722. data/doc-src/assets.md +0 -98
  723. data/doc-src/forms.md +0 -214
  724. data/doc-src/handlers.md +0 -83
  725. data/doc-src/javascript.md +0 -265
  726. data/doc-src/pages.md +0 -210
  727. data/doc-src/route-hooks.md +0 -59
  728. data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -0,0 +1,179 @@
1
+ # Dev Environment
2
+
3
+ Brut provides sophisticatd tooling to manage your dev environment
4
+
5
+ ## Overview
6
+
7
+ A development environments or *dev environment* is made up of two parts:
8
+
9
+ * *Foundational Core* - the operating system and tools needed to run the app and *its* tools. This
10
+ includes language runtimes, system libraries (like ImageMagick), and system tools like web browsers.
11
+ * *Workspace* - the tools and code bundled with the app that you use day-to-day to work on the app itself.
12
+ This would include scripts to run the app in development, run tests, perform scaffolding, or manage the
13
+ database.
14
+
15
+ On many teams, the Foundational Core is different per developer, since some run Linux, some run MacOs.
16
+ Some might use mise to manage their version of Ruby while others use rbenv. Some will set up Postgres via
17
+ homebrew, while others might use Popstgres.app.
18
+
19
+ Brut takes a different approach. Everyone shares the same Foundational Core, and this is defined by a
20
+ `Dockerfile`, a `docker-compose.yml` file, and some lightweight Bash scripts.
21
+
22
+ This means that everyone uses the same version of everything, and they are all managed the same way.
23
+
24
+ Brut also provides sophisticated tooling for the Workspace. Like Rails, Brut provides a command-line
25
+ based flow that can be scripted into any editor. Unlike Rails, Brut's Workspace is comprised of separate
26
+ command-line apps and not Rake tasks.
27
+
28
+ ### Conceptual Overview
29
+
30
+ Your dev environment consists of a Docker container that has languages, an operating system, and other
31
+ system components installed in it. It will have access to the files on your computer so that it can run
32
+ your app. The app will be exposed so that a browser on your computer can access it. Postgres will be run
33
+ as a separate Docker container available to the dev Docker container.
34
+
35
+ Your editor and version control system run on your computer.
36
+
37
+ ![Conceptual Overview](/images/dev-env-overview.png)
38
+
39
+ ### Foundational Core Command Line Apps
40
+
41
+ These are the commands you will use to manage the *foundational core*, which is the Docker containers and
42
+ their contents.
43
+
44
+ A few brief terminoloy notes if you aren't familiar with Docker:
45
+
46
+ * A Docker *container* is akin to a virtual machine. On Linux this isn't strictly true, but conceptually, you can think of this like a virtual computer.
47
+ * A Docker *image* is what you use to start a container. This is akin to a disk image you might use to
48
+ create a new computer or virtual machine.
49
+ * A Dockerfile (often named `Dockerfile`) is a set of instructions to create an image.
50
+
51
+ A few verbs to provide additional help:
52
+
53
+ * One *builds* a Docker image from a Dockerfile.
54
+ * One *starts* a Docker container from an image.
55
+ * One *stops* a Docker conatiner when it's no longer needed.
56
+
57
+
58
+ | App | Purpose |
59
+ |------------|------------------------------------------------------------------------|
60
+ | `dx/build` | Builds a Docker *image* from a `Dockerfile.dx` |
61
+ | `dx/start` | Starts all Docker containers, including those for databases and caches |
62
+ | `dx/stop` | Stops all Docker containers |
63
+ | `dx/exec` | Execute a command inside a running Docker container |
64
+
65
+ The workflow for the foundational core is shown in this diagram.
66
+
67
+ ![Foundational Core Workflow](/images/dev-env-protocol.png)
68
+
69
+ In words:
70
+
71
+ 1. You build the images based on the latest instrutions via `dx/build`.
72
+ 2. You start up the environment with `dx/start`.
73
+ 3. You then use `dx/exec` to execute commands from the Workspace (see below).
74
+ 4. When you are done working for the day, `dx/stop` shuts everything down.
75
+
76
+ ### Workspace Command Line Apps
77
+
78
+ The workspace is where you'll run your day-to-day commands, such as running tests, starting the dev
79
+ server, managing the database schema, etc.
80
+
81
+ Several of the commands accept or require subcommands. Each CLI app responds to `--help` and will show
82
+ you full documentation about what the command and subcommands do.
83
+
84
+ | App | Subcommand | Descriptions |
85
+ |-----------------|-----------------------|-------------------------------------------------------------------------------------------|
86
+ | <code style="white-space: nowrap">bin/ci</code> | None | Runs all tests and security checks |
87
+ | <code style="white-space: nowrap">bin/console</code> | None | Starts up a local IRB session with your app loaded |
88
+ | <code style="white-space: nowrap">bin/db</code> | | Tools for managing the database | |
89
+ | | `create` | Create the database if it does not exist |
90
+ | | `drop` | Drop the database if it exists |
91
+ | | `migrate` | Apply any outstanding migrations to the database |
92
+ | | `new_migration` | Create a new migration file |
93
+ | | `rebuild` | Drop, re-create, and run migrations, effecitvely rebuilding the entire database |
94
+ | | `seed` | Load seed data into the database |
95
+ | | `status` | Check the status of the database and migrations |
96
+ | <code style="white-space: nowrap">bin/dbconsole</code> | None | Starts up a `psql` session to your database |
97
+ | <code style="white-space: nowrap">bin/dev</code> | None | Starts the app in dev mode, rebuilding assets and reload as needed |
98
+ | <code style="white-space: nowrap">bin/setup</code> | None | Install and setup all third party libraries and other configuration needed to use the app |
99
+ | <code style="white-space: nowrap">bin/scaffold</code> | | Generate Brut classes or files like database migrations or page classes |
100
+ | | `action` | Create a handler for an action |
101
+ | | `component` | Create a new component and associated test |
102
+ | | `custom_element_test` | Create a test for a custom element in your app |
103
+ | | `form` | Create a form and handler |
104
+ | | `page` | Create a new page and associated test |
105
+ | | `test` | Create the shell of a unit test based on an existing source file |
106
+ | | `test:e2e` | Create the shell of an end-to-end test |
107
+ | <code style="white-space: nowrap">bin/test</code> | | Run tests |
108
+ | | `audit` | Audits all of the app's classes to see if test files exist |
109
+ | | `e2e` | Run e2e tests |
110
+ | | `js` | Run JavaScript unit tests |
111
+ | | `run` | Run non-e2e tests (default) |
112
+
113
+
114
+ The workflow for your Workspace is shown in this diagram
115
+
116
+ ![Workspace Workflow](/images/workspace-protocol.png)
117
+
118
+ In words:
119
+
120
+ 1. You'll run `bin/setup` to get everything set up for working.
121
+ 2. You'll start your dev server with `bin/dev`.
122
+ 3. You'll write code, using tools like `bin/db` and `bin/scaffold` to assist.
123
+ 4. Using `bin/test`, you can test any code you've written a test for.
124
+ 5. When you are at a stopping point, use `bin/ci` to test the entire app.
125
+
126
+ ### Extending and Enhancing
127
+
128
+ TBD
129
+
130
+ ## Testing
131
+
132
+ There aren't tests for this code, because you are using all day every day. Brut's test suite will ensure
133
+ that the versions of these command line apps provided when you set up your app are working.
134
+
135
+ ## Recommended Practices
136
+
137
+ While you are free to set up mise or rbenv or whatever to run all this on your computer, this way of
138
+ working is currently not supported nor encouraged. For now, Brut will focus on the Docker-based approach.
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.
142
+
143
+ Beyond this, we recommend that you keep a few things in mind with respect to automation:
144
+
145
+ * The *Foundational Core* is bootstrapped in a degenerate environment without reliable tools beyond Bash.
146
+ This is why it's almost entirely written in Bash, since it's available everywhere and relatively stable.
147
+ * 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
+
151
+ ## Technical Notes
152
+
153
+ > [!IMPORTANT]
154
+ > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
155
+ > internals, the source code is always more correct.
156
+
157
+ _Last Updated June 12, 2025_
158
+
159
+ Everything in `bin/` is intended to be a short shim that calls into classes managed either by Brut or by
160
+ your app. For example, here is `bin/db`:
161
+
162
+ ```ruby
163
+ #!/usr/bin/env ruby
164
+
165
+ require "bundler"
166
+ Bundler.require
167
+ require "pathname"
168
+ require "brut/cli/apps/db"
169
+
170
+ exit Brut::CLI.app(
171
+ Brut::CLI::Apps::DB,
172
+ project_root: Pathname($0).dirname / ".."
173
+ )
174
+ ```
175
+
176
+ These files have a lot of duplication, but should be relatively stable.
177
+
178
+ This means that Brut-provided CLIs *will* be updated when you update Brut. Compare this to the files in
179
+ `dx/` which are entire Bash scripts that will not be updated when Brut is updated.
@@ -0,0 +1,39 @@
1
+ # Documentation Conventions
2
+
3
+ ## Terminology
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*.
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.
8
+
9
+ Further, Brut doesn't render HTML, it *generates* it. The browser renders the HTML for the website's visitor.
10
+
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
+
13
+ ## Structure of These Documents
14
+
15
+ Each page here documents on aspect of Brut, called a *module*, and these pages are organized along four sections:
16
+
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.
27
+
28
+ ## Names of the Library and Associated Modules
29
+
30
+ This framework is called "Brut" though may be called "BrutRB". It lives at `brutrb.com`.
31
+
32
+ The JavaScript library is called "BrutJS", but is `brut-js` in code or the filesystem. "Brut-JS" is wrong, as is `brut_js`.
33
+
34
+ The CSS library is called "BrutCSS", but is `brut-css` in code or the filesystem. "Brut-CSS" is wrong, as is "brut-css".
35
+
36
+
37
+ ## On Using VitePress
38
+
39
+ This site is built using [VitePress](https://vitepress.dev), which is a client-side heavy framework. It kinda goes against the ethos of Brut, but it is allowing me to write documentation that looks decent and is mostly navigable. I would like to use a more accessible, customized system for documenting Brut, but for now, it's more important to get the documentation out. A better documentation experience is planned.
@@ -0,0 +1,174 @@
1
+ # End to End Tests
2
+
3
+ Is there a greater pain the world than an end-to-end test? Is there a more punishing API than trying to
4
+ convince a browser to browse and use a website? Is there an answer for why the way we interact with
5
+ browsers when writing code is 100% different than the we do when writing a test?
6
+
7
+ Brut cannot answer these things, but it does provide a way to write end-to-end tests with a browser, with
8
+ a somewhat slightly reduced amount of pain.
9
+
10
+ ## Overview
11
+
12
+ Brut uses [Playwright](https://playwright.dev/) and the
13
+ [playwright-ruby-client](https://playwright-ruby-client.vercel.app/) to allow you to write end-to-end
14
+ tests that use a web browser. Brut sets up headless Chromium to do this.
15
+
16
+ You can run End-to-End (e2e) tests with `bin/test e2e`. You must use this to run individual tests as
17
+ well, since this will ensure proper set up for the tests, which is more than is needed for a normal unit
18
+ test.
19
+
20
+ ### Using Playwright
21
+
22
+ At a high level, e2e tests look like normal RSpec tests:
23
+
24
+ ```ruby
25
+ require "spec_helper"
26
+
27
+ RSpec.describe "logging into the website" do
28
+
29
+ # ...
30
+
31
+ end
32
+ ```
33
+
34
+ The contents of your `it` blocks will use `playwright-ruby-client` to interact with the browser and make
35
+ assertions. The value `page` is available to use this API.
36
+
37
+ ```ruby
38
+ require "spec_helper"
39
+
40
+ RSpec.describe "logging into the website" do
41
+ it "shows an error when login is invalid" do
42
+ page.goto("/")
43
+
44
+ email = page.locator("form input[type='email']")
45
+ password = page.locator("form input[type='password']")
46
+ button = page.locator("form button")
47
+
48
+ email.fill("pat@example.com")
49
+ password.fill("12345678")
50
+ button.click
51
+
52
+ flash = page.locator("[role='alert']")
53
+ expect(flash).to have_text("No email/password in our system")
54
+ end
55
+ end
56
+ ```
57
+
58
+ `playwright-ruby-client` provides excellent documentation on how it has adapter Playwright's API for use
59
+ in Ruby.
60
+
61
+ ### Test Setup
62
+
63
+ Brut will run your app via `Brut::SpecSupport::E2ETestServer`. It will run it before the first e2e test, leave it running during the remainder of the test suite, then stop it. This means that database changes your test makes will persist across tests.
64
+
65
+ If you are using Sidekiq, Sidekiq will be set up like normal. Jobs queued will go to Redis, and those jobs
66
+ will be processed by Sidekiq, just like in production. Thus, you should not assert things about specific
67
+ jobs, but rather assert the effects those jobs will have. Redis is flushed between each test.
68
+
69
+ ### Test Helpers and Configuration
70
+
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
+
73
+ You can set `e2e_timeout` on any test to override the default amount of time Playwright will wait for a
74
+ locator to locate an element. The default is 5 seconds.
75
+ `, to generate or access routes for your app.
76
+ You can also configure behavior with environment variables:
77
+
78
+ | Variable | Default | Purpose |
79
+ |---------------------|---------|---------------------------------------------------------------------------------------------------|
80
+ | `E2E_TIMEOUT_MS` | 5000 | Number of milliseconds Playwright will wait for a locator to appear |
81
+ | `E2E_SLOW_MO` | 0 | Number of milliseconds Playwright will pause between operations. Useful to detect race conditions |
82
+ | `E2E_RECORD_VIDEOS` | unset | If set, videos of each test run are saved in `tmp/e2e-videos` |
83
+
84
+
85
+ ### Quirks of Playwright
86
+
87
+ The Playwright JavaScript API is heavily asycnhronous, requiring liberal use of `await`. The
88
+ `playwright-ruby-client` wrapper abstracts that so you can write more straightfoward code.
89
+
90
+ The main thing to be aware of is that locators that fail only fail when you attempt to assert something.
91
+
92
+ For example
93
+
94
+ ```ruby
95
+ button = page.locator("form button") # suppose this button doesn't exist
96
+
97
+ button.click # This is where you'll see a failure
98
+ ```
99
+
100
+ This hidden asynchronous behavior also means that certain calls will wait a period of time for the element
101
+ you are locating to appear. This is why the example test above works without having to explicitly wait
102
+ for a page refresh. After `button.click`, presumably the back-end is contacted and the page is
103
+ re-rendered with an error. As long as that happens within a second or so, the code will wait for an
104
+ element matching `[role='alert']` to show up.
105
+
106
+ ## Recommended Practices
107
+
108
+ E2e tests are slow. They can also be flaky if you aren't careful in how you write them and how you
109
+ author your HTML.
110
+
111
+ ### Test Major Flows, Not Exhaustive Branches
112
+
113
+ E2e tests give the most value when they assert that a sequence of actions the visitor takes result in what
114
+ you expect—a "major flow". Testing some error cases can be useful, but you should not use e2e tests to
115
+ assert every single possible thing that could happen on a page.
116
+
117
+ In fact, your app might be better off leaving some behaviors better untested instead of tested by an e2e test. Use your judgement and be aware of the carrying cost of each e2e test.
118
+
119
+ ### Use CSS Selectors
120
+
121
+ The main Playwright documentation encourages you to locate elements by "accessible names" and other
122
+ indirect ways of finding elements. In practice, this is error prone and tedious. Determining the
123
+ accessible name of an element is not always easy.
124
+
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.
128
+
129
+ Insulating your end-to-end tests from markup changes does not produce significant savinsg and can make
130
+ tests more difficult to write.
131
+
132
+ ### Testing Must Inform your HTML
133
+
134
+ To allow CSS selectors to survive minor changes to a page, your HTML should be authored with testing in
135
+ mind. In the example above, we locate the flash by looking for `[role='alert']`, since this is the most
136
+ semantically correct way to mark up a flash message that contains an error.
137
+
138
+ ARIA roles that should be applied for accessibility purposes can be leveraged as locators, as can custom
139
+ elements. Remember that any custom element is valid, even if it has no associated JavaScript. Custom
140
+ elements are an excellent way to "tag" markup for use in tests or progressively-enhanced behavior.
141
+
142
+ CSS classes, on the other hand, are not a good candidate for identifying markup in a test. CSS classes
143
+ exist to afford visual styling of elements and are the most likely to change as the app evolves. A better
144
+ fallback if there is no other way to locate an element is to use `data-testid`. It makes itself painfully
145
+ clear why it's there. Use this sparingly, but it's there if you need it.
146
+
147
+ ### Asserting the Lack of Content Basically Doesn't Work
148
+
149
+ To assert that some content or an element **is not** on the page requires locating it and waiting the
150
+ timeout for that locate to fail. This sucks. Don't do it.
151
+
152
+ If you need to assert that something did not happen, you may want to design your page or app such that
153
+ markup appears that indicates whatever it is didn't happen. This is not ideal, but a web page is a living
154
+ thing that never stops changing, so your test can't just assume it's all synchronous.
155
+
156
+ ### Try to Use the Defaults for Timeouts
157
+
158
+ Your app should not take 5 seconds to do anythning, especially not inside a test. You may need to bump up
159
+ the timeout to figure out what's going wrong, or set `E2E_SLOW_MO` to watch a video test, but once you've
160
+ sorted out the issue, restore these to their defaults.
161
+
162
+ If you *must* set `e2e_timeout` as metadata on test, **explain why** and try removing it every so often to
163
+ make sure it's still needed.
164
+
165
+ ## Technical Notes
166
+
167
+ > [!IMPORTANT]
168
+ > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
169
+ > internals, the source code is always more correct.
170
+
171
+ _Last Updated June 13, 2025_
172
+
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.
@@ -0,0 +1,224 @@
1
+ # Flash and Session
2
+
3
+ Brut sessions are stored in cookies, encrypted to prevent tampering. The *flash*, which is a way to temporarily
4
+ store small bits of information between page loads, is encoded in the session.
5
+
6
+ ## Overview
7
+
8
+ Unlike Rails, the session and flash are presented to you as objects, not Hashes. By declaring the `session:`
9
+ parameter on an initializer, you'll be given the current session for the request as an `AppSession`, which
10
+ inherits from `Brut::FrontEnd::Session`. Similarly, declaring `flash:`, you'll get a `Brut::FrontEnd::Flash`.
11
+
12
+ The idea is to use Ruby's type system to describe what data is in the session and flash.
13
+
14
+ ### Session
15
+
16
+ Brut's session is somewhat richer than you might get from other frameworks. In particular, the session can
17
+ provide you:
18
+
19
+ * The current `Brut::I18n::HTTPAcceptLanguage`, which is the visitor's locale. See [I18n](/i18n) for how this
20
+ works and how to use this value.
21
+ * The timezone as provided by the browser.
22
+ * An explicitly-set timezone that may or may not be what the browser provided. See [Space-Time Continuum](/space-time-continuum) for more details.
23
+
24
+ The session also handles serializing the flash to and from the browser's cookies and can store any arbitrary data
25
+ you like via `[]`. You are encouraged to add methods to your app's `AppSession` to make it explicit what you are
26
+ storing.
27
+
28
+ Let's see the [route hook](/hooks) from the [pages](/pages) section again.
29
+
30
+ > [!CAUTION]
31
+ > This hook is not production-ready. It lacks certain error-handling situations and
32
+ > makes an assumption about how the session is managed. It's for demonstration only.
33
+ > The [route hooks](/hooks) section has a more
34
+ > appropriate example.
35
+
36
+ ```ruby
37
+ class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
38
+ def before(request_context:,session:)
39
+ if session.current_user_id
40
+ user = DB::User.find(id: session.current_user_id)
41
+ if user
42
+ request_context[:current_user] = user
43
+ end
44
+ end
45
+ end
46
+ end
47
+ ```
48
+
49
+ When this hook executes, `session` will be an `AppSession`, serialized from the browser's cookies. Here's what
50
+ that class might look like:
51
+
52
+ ```ruby
53
+ # app/src/front_end/support/app_session.rb
54
+ class AppSession < Brut::FrontEnd::Session
55
+ def login!(current_user:)
56
+ self[:current_user_id] = current_user.id
57
+ end
58
+
59
+ def logout!
60
+ self[:current_user_id] = nil
61
+ end
62
+
63
+ def logged_in?
64
+ !!self.current_user_id
65
+ end
66
+
67
+ def current_user_id = self[:current_user_id]
68
+ end
69
+ ```
70
+
71
+ The session is a rich object and not just a thin wrapper over a Hash. You could even have the session perform
72
+ the lookup in the database:
73
+
74
+ ```ruby {11,14-16}
75
+ # app/src/front_end/support/app_session.rb
76
+ class AppSession < Brut::FrontEnd::Session
77
+ def login!(current_user:)
78
+ self[:current_user_id] = current_user.id
79
+ end
80
+ def logout!
81
+ self[:current_user_id] = nil
82
+ end
83
+
84
+ def logged_in?
85
+ !!self.current_user
86
+ end
87
+
88
+ def current_user
89
+ DB::User.find(id: self[:current_user_id])
90
+ end
91
+ end
92
+ ```
93
+
94
+ Now, the hook could call `current_user`:
95
+
96
+ ```ruby {3-5}
97
+ class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
98
+ def before(request_context:,session:)
99
+ if session.logged_in?
100
+ request_context[:current_user] = session.current_user
101
+ end
102
+ end
103
+ end
104
+ ```
105
+
106
+ Let's see `LoginHandler` from the [handlers](/handlers) section, to see how to save the current user. Given what
107
+ we've learned, the declaration of the `session:` parameter to the initializer means the relevant instance of
108
+ `AppSession` will be passed in.
109
+
110
+ ```ruby {20}
111
+ # app/src/front_end/handlers/login_handler.rb
112
+ class LoginHandler < AppHandler
113
+ def initialize(form:, session:)
114
+ @form = form
115
+ @session = session
116
+ end
117
+
118
+ def handle
119
+ if !@form.constraint_violations?
120
+ authorized_user = AuthorizedUser.login(
121
+ email: form.email,
122
+ password: form.password
123
+ )
124
+ if authorized_user.nil?
125
+ @form.server_side_constraint_violation(
126
+ input_name: :email,
127
+ key: :login_not_found
128
+ )
129
+ else
130
+ session.login!(current_user: authorized_user.user)
131
+ end
132
+ end
133
+ if @form.constraint_violations?
134
+ LoginPage.new(form: @form)
135
+ else
136
+ redirect_to(DashboardPage.routing)
137
+ end
138
+ end
139
+ end
140
+ ```
141
+
142
+ Brut will handle saving the updated values in the response so when, in this case, the `DashboardPage` is
143
+ rendered, it can see which user is logged in.
144
+
145
+ ### Flash
146
+
147
+ By default, your app will use Brut's flash class, `Brut::FrontEnd::Flash`. This is because you typically don't
148
+ need to enhance the flash. Brut's flash has an "alert" and "notice", and you can use them however you see fit. You can also set arbitrary messages in the flash via `[]`.
149
+
150
+ The contents of the flash only survive one request, so anything you set will be available in that session's next
151
+ request, but not after that.
152
+
153
+ Note that the flash's alert and notice are intended to be I18n keys. You don't have to use them this way, but it
154
+ is encouraged. If you pass an array into `alert=` or `notice=`, the elements will be joined to form an I18n key.
155
+
156
+ You can create your own subclass if you need a richer flash class than the one Brut provides.
157
+
158
+ First, create your class. It can be anywhere, but we recommend `app/src/front_end/support/app_flash.rb`:
159
+
160
+ ```ruby
161
+ # app/src/front_end/support/app_flash.rb
162
+ class AppFlash < Brut::FrontEnd::Flash
163
+ # For example
164
+ def debug=(debug)
165
+ self[:debug] = debug
166
+ end
167
+ def debug = self[:debug]
168
+ def debug? = !!self.debug
169
+ end
170
+ ```
171
+
172
+ Then, in your `App`, located in `app/src/app.rb`, use `Brut.container.override` to change the class used for the
173
+ flash:
174
+
175
+ ```ruby
176
+ class App < Brut::Framework::App
177
+ # ...
178
+ def initialize
179
+ Brut.container.override(
180
+ "flash_class",
181
+ AppFlash
182
+ )
183
+ end
184
+
185
+ # ...
186
+ end
187
+ ```
188
+
189
+ Brut's configuration system is discussed in more detail in [Configuration](/configuration).
190
+
191
+ ## Testing
192
+
193
+ Testing your session or flash classes may not be super valuable, however they are normal Ruby objects so you can
194
+ test them in a conventional way. Both classes treat their internals as a Hash, so you can implement and assert
195
+ via the `[]` and `[]=` methods.
196
+
197
+ ## Recommended Practices
198
+
199
+ While you can use both the flash and the session has a hash of whatever, your are encouraged to avoid this in
200
+ your production code. Create well-defined attributes or methods to manipulate these objects using the language
201
+ of your domain.
202
+
203
+
204
+ ## Technical Notes
205
+
206
+ > [!IMPORTANT]
207
+ > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
208
+ > internals, the source code is always more correct.
209
+
210
+ _Last Updated May 7, 2025_
211
+
212
+ The session is based on [`Rack::Session`](https://github.com/rack/rack-session), which is configured explicitly
213
+ in your app's `config.ru`. (TBD: WHY?)
214
+
215
+ The session object itself is created on demand for any route hook that needs it.
216
+ Since `Brut::FrontEnd::RouteHooks::SetupRequestContext` requires the session, the
217
+ `Brut::FrontEnd::RequestContext` is created here and given the session (and flash) that is used for subsequent
218
+ hooks and HTML generation.
219
+
220
+ The flash is created largely on-demand and is a special hash serialized into the session. The hash contains the
221
+ current age of the flash and then all the messages. This format could use improvement and may change.
222
+ `Brut::FrontEnd::RouteHooks::AgeFlash` is a route hook that handles increasing the age of the flash, however the
223
+ flash itself controls when to "age out" messages. None of this is currently configurable.
224
+