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,187 @@
1
+ # Renders an HTML `<select>`.
2
+ class Brut::FrontEnd::Components::Inputs::SelectTagWithOptions < Brut::FrontEnd::Components::Input
3
+ # Creates the appropriate select input for the given {Brut::FrontEnd::Form} and input name.
4
+ # Generally, you want to use this method over the initializer.
5
+ #
6
+ # @param [Brut::FrontEnd::Form} form The form that is being rendered.
7
+ # This method will consult this class to understand the requirements
8
+ # on this select so its HTML is generated correctly.
9
+ # @param [String] input_name the name of the input, which should be a member of `form`
10
+ # @param [Array<Object>] options An array of objects represented what is being selected.
11
+ # These can be any object and are ideally whatever domain object or
12
+ # data type you want on the backend to represent this selection.
13
+ # @param [Symbol|String] value_attribute the name of an attribute to determine an option's value.
14
+ # This will be called on each element of `options` to get the value used for the `<option>`'s
15
+ # `value` attribute. The value returned by `value_attribute` should be unique amongst the
16
+ # `options` provided *and* be distinct from whatever `value` is used for `include_blank`.
17
+ # @param [Symbol|String] option_text_attribute the name of an attribute to determine the text for an option.
18
+ # This will be called on each element of `options` to get the value used for the `<option>`'s
19
+ # text content. The value returned by `option_text_attribute` need not be unique, though if it
20
+ # is not unique, it will certainly be confusing.
21
+ # @param [Integer] index if this input is part of an array, this is the index into that array.
22
+ # This is used to get the input's value.
23
+ # @param [Hash] html_attributes any additional HTML attributes to include on the `<select>` element.
24
+ # @param [false|true|Hash] include_blank configure how and if to include a blank element in the select.
25
+ # If this is false, there will be no blank element. If it's `true`, there will be one with
26
+ # no value or text. If this is a `Hash` it must contain a `value:` key and a `text_content:` key
27
+ # to be used as the `value` attribute and option text content, respectively.
28
+ #
29
+ # @return [Brut::FrontEnd::Components::Inputs::SelectTagWithOptions] the select input ready to be placed into a view.
30
+ def self.for_form_input(form:,
31
+ input_name:,
32
+ options:,
33
+ include_blank: false,
34
+ value_attribute:,
35
+ option_text_attribute:,
36
+ index: nil,
37
+ html_attributes: {})
38
+ html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
39
+ default_html_attributes = {}
40
+ index ||= 0
41
+ input = form.input(input_name, index:)
42
+ default_html_attributes[:required] = input.required
43
+ if !form.new? && !input.valid?
44
+ default_html_attributes["data-invalid"] = true
45
+ input.validity_state.each do |constraint,violated|
46
+ if violated
47
+ default_html_attributes["data-#{constraint}"] = true
48
+ end
49
+ end
50
+ end
51
+ name = if input.array?
52
+ "#{input.name}[]"
53
+ else
54
+ input.name
55
+ end
56
+
57
+ Brut::FrontEnd::Components::Inputs::SelectTagWithOptions.new(
58
+ name: name,
59
+ options:,
60
+ selected_value: input.value,
61
+ value_attribute:,
62
+ option_text_attribute:,
63
+ include_blank:,
64
+ html_attributes: default_html_attributes.merge(html_attributes)
65
+ )
66
+ end
67
+
68
+ # Create the element.
69
+ #
70
+ # @param [String] name the name of the input
71
+ # @param [Array<Object>] options An array of objects represented what is being selected.
72
+ # These can be any object and are ideally whatever domain object or
73
+ # data type you want on the backend to represent this selection.
74
+ # @param [Symbol|String] value_attribute the name of an attribute to determine an option's value.
75
+ # This will be called on each element of `options` to get the value used for the `<option>`'s
76
+ # `value` attribute. The value returned by `value_attribute` should be unique amongst the
77
+ # `options` provided *and* be distinct from whatever `value` is used for `include_blank`.
78
+ # @param [String] selected_value the value of the selected option. Note that this is the *value*
79
+ # of the selected option, not the selected option itself. To set the selected value
80
+ # based on a selected option, omit this and use `selected_option`
81
+ # @param [String] selected_option the selected option. Note that `value_attribute` will be called
82
+ # on this to determine the selected value to use when generating HTML. Also note that
83
+ # this object must be in `options` or an exeception is raised.
84
+ # @param [Symbol|String] option_text_attribute the name of an attribute to determine the text for an option.
85
+ # This will be called on each element of `options` to get the value used for the `<option>`'s
86
+ # text content. The value returned by `option_text_attribute` need not be unique, though if it
87
+ # is not unique, it will certainly be confusing.
88
+ # @param [Hash] html_attributes any additional HTML attributes to include on the `<select>` element.
89
+ # @param [false|true|Hash] include_blank configure how and if to include a blank element in the select.
90
+ # If this is false, there will be no blank element. If it's `true`, there will be one with
91
+ # no value or text. If this is a `Hash` it must contain a `value:` key and a `text_content:` key
92
+ # to be used as the `value` attribute and option text content, respectively.
93
+ #
94
+ # @raise ArgumentError if `selected_option` is present, but not in `options` or if `selected_value` is
95
+ # present, but no option's value for `value_attribute` is that value.
96
+ #
97
+ # XXX: Why does this not ask the form for the selected_value?
98
+ # XXX: This doesn't do well when values are strings
99
+ def initialize(name:,
100
+ options:,
101
+ value_attribute:,
102
+ selected_value: nil,
103
+ selected_option: nil,
104
+ option_text_attribute:,
105
+ include_blank: false,
106
+ html_attributes:)
107
+ @options = options
108
+ @include_blank = IncludeBlank.from_param(include_blank)
109
+ @value_attribute = value_attribute
110
+ @option_text_attribute = option_text_attribute
111
+ @html_attributes = html_attributes
112
+ @html_attributes[:name] = name
113
+
114
+ if selected_value.nil?
115
+ if selected_option.nil?
116
+ @selected_value = nil # explicitly nothing is selected
117
+ else
118
+ option = options.detect { |option|
119
+ option.send(@value_attribute) == selected_option.send(@value_attribute)
120
+ }
121
+ if option.nil?
122
+ raise ArgumentError, "selected_option #{selected_option} (with #{value_attribute} '#{selected_option.send(value_attribute)}') was not found in options"
123
+ end
124
+ @selected_value = option.send(@value_attribute)
125
+ end
126
+ else
127
+ if selected_value.kind_of?(Array)
128
+ raise "WTF: #{name}"
129
+ end
130
+ option = options.detect { |option|
131
+ selected_value == option.send(@value_attribute)
132
+ }
133
+ if option.nil?
134
+ raise ArgumentError, "selected_value #{selected_value} was not the value for #{value_attribute} on any of the options: #{options.map { |option| option.send(value_attribute) }.join(', ')}"
135
+ end
136
+ @selected_value = option.send(@value_attribute)
137
+ end
138
+ end
139
+
140
+ def view_template
141
+ select(**@html_attributes) {
142
+ if @include_blank
143
+ option(**@include_blank.option_attributes) {
144
+ @include_blank.text_content
145
+ }
146
+ end
147
+ options = @options.each do |option|
148
+ value = option.send(@value_attribute)
149
+ option_attributes = { value: value }
150
+ if value == @selected_value
151
+ option_attributes[:selected] = true
152
+ end
153
+ option(**option_attributes) {
154
+ option.send(@option_text_attribute)
155
+ }
156
+ end
157
+ }
158
+ end
159
+ private
160
+
161
+ # @!visibility private
162
+ class IncludeBlank
163
+ attr_reader :text_content, :option_attributes
164
+ def self.from_param(include_blank)
165
+ if !include_blank
166
+ return nil
167
+ else
168
+ self.new(include_blank)
169
+ end
170
+ end
171
+ def initialize(include_blank)
172
+ if include_blank == true
173
+ @text_content = ""
174
+ @option_attributes = {}
175
+ elsif include_blank.kind_of?(Hash)
176
+ if include_blank.key?(:value) && include_blank.key?(:text_content)
177
+ @text_content = include_blank[:text_content]
178
+ @option_attributes = { value: include_blank[:value] }
179
+ else
180
+ raise ArgumentError, "when include_blank: is a Hash, it must include both :value and :text_content as keys. Got: #{include_blank.keys.join(", ")}"
181
+ end
182
+ else
183
+ raise ArgumentError,"include_blank: was a #{include_blank.class}. It should be true, false, nil, or a Hash"
184
+ end
185
+ end
186
+ end
187
+ end
@@ -1,5 +1,5 @@
1
1
  # Generates an HTML `<textarea>` field.
2
- class Brut::FrontEnd::Components::Inputs::Textarea < Brut::FrontEnd::Components::Input
2
+ class Brut::FrontEnd::Components::Inputs::TextareaTag < Brut::FrontEnd::Components::Input
3
3
  # Creates the appropriate textarea for the given {Brut::FrontEnd::Form} and input name.
4
4
  # Generally, you want to use this method over the initializer.
5
5
  #
@@ -35,7 +35,7 @@ class Brut::FrontEnd::Components::Inputs::Textarea < Brut::FrontEnd::Components:
35
35
  end
36
36
  end
37
37
  value = input.value
38
- Brut::FrontEnd::Components::Inputs::Textarea.new(default_html_attributes.merge(html_attributes), value)
38
+ Brut::FrontEnd::Components::Inputs::TextareaTag.new(default_html_attributes.merge(html_attributes), value)
39
39
  end
40
40
  # Create an instance
41
41
  #
@@ -1,4 +1,5 @@
1
- # Renders a date or timestamp accessibly, using the `<time>` element. Likely you will use this via the {Brut::FrontEnd::Component::Helpers#time_tag} method. This will account for the current request's time zone. See {Clock}.
1
+ # Renders a date or timestamp accessibly, using the `<time>` element.
2
+ # This will account for the current request's time zone. See {Clock}.
2
3
  class Brut::FrontEnd::Components::TimeTag < Brut::FrontEnd::Component
3
4
  include Brut::I18n::ForHTML
4
5
  # Creates the component
@@ -68,12 +68,12 @@ class Brut::FrontEnd::Form
68
68
  @inputs = self.class.input_definitions.map { |name,input_definition|
69
69
  value = @params[name] || @params[name.to_sym]
70
70
  inputs = if value.kind_of?(Array)
71
- value.map { |one_value|
72
- input_definition.make_input(value: one_value)
71
+ value.map.with_index { |one_value, index|
72
+ input_definition.make_input(value: one_value, index:)
73
73
  }
74
74
  else
75
75
  [
76
- input_definition.make_input(value:)
76
+ input_definition.make_input(value:, index: nil)
77
77
  ]
78
78
  end
79
79
 
@@ -98,7 +98,7 @@ class Brut::FrontEnd::Form
98
98
  input = inputs[index]
99
99
  if input.nil?
100
100
  input_definition = self.class.input_definitions.fetch(input_name.to_s)
101
- input = input_definition.make_input(value:"")
101
+ input = input_definition.make_input(value:"", index:)
102
102
  inputs[index] = input
103
103
  end
104
104
  input
@@ -13,9 +13,10 @@ class Brut::FrontEnd::Forms::Input
13
13
  # Create the input with the given definition and value
14
14
  # @param [Brut::FrontEnd::Forms::InputDefinition] input_definition
15
15
  # @param [String] value
16
- def initialize(input_definition:, value:)
16
+ def initialize(input_definition:, value:, index:)
17
17
  @input_definition = input_definition
18
18
  @validity_state = Brut::FrontEnd::Forms::ValidityState.new
19
+ @index = index
19
20
  self.value=(value)
20
21
  end
21
22
 
@@ -109,7 +109,10 @@ class Brut::FrontEnd::Forms::InputDefinition
109
109
 
110
110
 
111
111
  # Create an Input based on this definition, initializing it with the given value.
112
- def make_input(value:)
113
- Brut::FrontEnd::Forms::Input.new(input_definition: self, value: value)
112
+ # @param [String] value the value to give this input initially.
113
+ # @param [Integer] index the index of this input, if it is part of an array of
114
+ # inputs. `nil` is allowed only if the input definition is not for an array.
115
+ def make_input(value:, index:)
116
+ Brut::FrontEnd::Forms::Input.new(input_definition: self, value:, index:)
114
117
  end
115
118
  end
@@ -8,9 +8,10 @@ class Brut::FrontEnd::Forms::RadioButtonGroupInput
8
8
  attr_reader :validity_state
9
9
 
10
10
  # (see Brut::FrontEnd::Forms::Input#initialize)
11
- def initialize(input_definition:, value:)
11
+ def initialize(input_definition:, value:, index:)
12
12
  @input_definition = input_definition
13
13
  @validity_state = Brut::FrontEnd::Forms::ValidityState.new
14
+ @index = index
14
15
  if input_definition.array?
15
16
  value ||= []
16
17
  end
@@ -23,7 +23,7 @@ class Brut::FrontEnd::Forms::RadioButtonGroupInputDefinition
23
23
  def array? = @array
24
24
 
25
25
  # Create an Input based on this defitition, initializing it with the given value.
26
- def make_input(value:)
27
- Brut::FrontEnd::Forms::RadioButtonGroupInput.new(input_definition: self, value: value)
26
+ def make_input(value:, index:)
27
+ Brut::FrontEnd::Forms::RadioButtonGroupInput.new(input_definition: self, value:, index:)
28
28
  end
29
29
  end
@@ -9,12 +9,10 @@ class Brut::FrontEnd::Forms::SelectInput
9
9
  attr_reader :validity_state
10
10
 
11
11
  # (see Brut::FrontEnd::Forms::Input#initialize)
12
- def initialize(input_definition:, value:)
12
+ def initialize(input_definition:, value:, index:)
13
13
  @input_definition = input_definition
14
14
  @validity_state = Brut::FrontEnd::Forms::ValidityState.new
15
- if input_definition.array?
16
- value ||= []
17
- end
15
+ @index = index
18
16
  self.value=(value)
19
17
  end
20
18
 
@@ -21,7 +21,7 @@ class Brut::FrontEnd::Forms::SelectInputDefinition
21
21
  def array? = @array
22
22
 
23
23
  # Create an Input based on this defitition, initializing it with the given value.
24
- def make_input(value:)
25
- Brut::FrontEnd::Forms::SelectInput.new(input_definition: self, value: value)
24
+ def make_input(value:, index:)
25
+ Brut::FrontEnd::Forms::SelectInput.new(input_definition: self, value:, index:)
26
26
  end
27
27
  end
@@ -1,12 +1,24 @@
1
1
  module Brut::FrontEnd
2
- # A handler responds to all HTTP requests other than those that render a page. It will be given any data it needs
3
- # to handle the request to its {#handle} method, which you must implement.
4
- # You define this method to accept the parameters you expect. See {Brut::FrontEnd::RequestContext} for how that works.
2
+ # A handler responds to all HTTP requests other than those that render a page.
3
+ # Like a page, the handler is initialized with any of the data it needs. The {#handle} method will
4
+ # be called to perform whatever action is needed, and its return value will determine what
5
+ # ther esponse will be.
5
6
  #
6
- # You may also define `before_handle` which will be given any subset of those parameters and can perform logic before
7
- # handle is called. This is most useful in a base class to check for permissions or other cross-cutting concerns.
7
+ # To create a handler, after defining a route or form,
8
+ # subclass this class (or, more likely, your app's `AppHandler`) and
9
+ # provide an initializer that accepts keyword arguments. The names of these arguments will be used
10
+ # to locate the values that Brut will pass in when creating your page object. If your handler
11
+ # is for a form, be sure to include the `form:` keyword argument.
8
12
  #
9
- # The primary method of this class is {#handle!} which you should not override, but *should* call in a test.
13
+ # Consult Brut's documentation on keyword injection to know what values you may use and how values are located.
14
+ #
15
+ # Then, implement {#handle} to perform whatever logic is needed to handle the request.
16
+ #
17
+ # You may also define `before_handle` which will be called before {#handle} to potentially abort
18
+ # the request. This is mostly useful if you have a base class for some of your handlers and want to
19
+ # share cross-cutting logic.
20
+ #
21
+ # Note that the public API for handlers is {#handle!}, which is what you should call in a test.
10
22
  class Handler
11
23
  include Brut::FrontEnd::HandlingResults
12
24
  include Brut::Framework::Errors
@@ -27,32 +39,22 @@ module Brut::FrontEnd
27
39
  abstract_method!
28
40
  end
29
41
 
42
+ # Override this to performa any checks before {#handle} is called. This should
43
+ # return `nil` if {#handle} should proceed to be called. Generally, you don't need to override
44
+ # this as {#handle} can include the logic. Where this is useful is to share cross-cutting logic
45
+ # across other handlers.
46
+ #
47
+ # @return [URI|Brut::FrontEnd::Component,Array,Brut::FrontEnd::HttpStatus,Brut::FrontEnd::Download] See
48
+ # {#handle} for what each return value means.
49
+ def before_handle = nil
50
+
30
51
  # Called by Brut to handle the request. Do not override this. If your handler responds to `before_handle` that is called with the
31
52
  # same args as you have defined for {#handle}. If `before_handle` returns anything other than `nil`, that value is returned and
32
53
  # should be one of the values documented in {#handle}. If `before_handle` returns `nil`, {#handle} is called and whatever it
33
54
  # returns is returned here.
34
55
  def handle!(**args)
35
56
  result = nil
36
- if self.respond_to?(:before_handle)
37
- before_handle_args = self.method(:before_handle).parameters.map { |(type,name)|
38
- if type == :keyreq
39
- if args.key?(name)
40
- [ name, args[name] ]
41
- else
42
- raise ArgumentError,"before_handle requires keyword arg '#{name}' but `handle` did not receive it. It must"
43
- end
44
- elsif type == :key
45
- if args.key?(name)
46
- [ name, args[name] ]
47
- else
48
- nil
49
- end
50
- else
51
- raise ArgumentError,"before_handle must only have keyword args. Got '#{name}' of type '#{type}'"
52
- end
53
- }.compact.to_h
54
- result = self.before_handle(**before_handle_args)
55
- end
57
+ result = self.before_handle
56
58
  if result.nil?
57
59
  result = self.handle(**args)
58
60
  end
@@ -1,9 +1,12 @@
1
1
  # Receives content security policy violations and logs them. This is set up in {Brut::Framework::MCP}, however CSP reporting is
2
2
  # configured in {Brut::FrontEnd::RouteHooks::CSPNoInlineStylesOrScripts::ReportOnly}.
3
3
  class Brut::FrontEnd::Handlers::CspReportingHandler < Brut::FrontEnd::Handler
4
- def handle(body:)
4
+ def initialize(body:)
5
+ @body = body
6
+ end
7
+ def handle
5
8
  begin
6
- parsed = JSON.parse(body.read)
9
+ parsed = JSON.parse(@body.read)
7
10
  Brut.container.instrumentation.add_attributes(parsed.merge(prefix: "brut.csp-reporting"))
8
11
  rescue => ex
9
12
  Brut.container.instrumentation.record_exception(ex)
@@ -65,12 +65,16 @@ class Brut::FrontEnd::Handlers::InstrumentationHandler < Brut::FrontEnd::Handler
65
65
  def as_carrier = { "traceparent" => @value }
66
66
  end
67
67
 
68
- def handle(http_tracestate:, http_traceparent:)
69
- traceparent = TraceParent.from_header(http_traceparent)
70
- span = Span.from_header(http_tracestate)
68
+ def initialize(http_tracestate:, http_traceparent:)
69
+ @http_tracestate = http_tracestate
70
+ @http_traceparent = http_traceparent
71
+ end
72
+ def handle
73
+ traceparent = TraceParent.from_header(@http_traceparent)
74
+ span = Span.from_header(@http_tracestate)
71
75
 
72
76
  if span.nil? || traceparent.nil?
73
- SemanticLogger[self.class].info "Missing traceparent or span: #{http_tracestate}, #{http_traceparent}"
77
+ SemanticLogger[self.class].info "Missing traceparent or span: #{@http_tracestate}, #{@http_traceparent}"
74
78
  return http_status(400)
75
79
  end
76
80
 
@@ -5,9 +5,13 @@
5
5
  # {Brut::FrontEnd::Session#http_accept_language=} *only* if the `Accept-Language` header did not provide a value that is supported by
6
6
  # the app.
7
7
  class Brut::FrontEnd::Handlers::LocaleDetectionHandler < Brut::FrontEnd::Handler
8
- def handle(body:,session:)
8
+ def initialize(body:,session:)
9
+ @body = body
10
+ @session = session
11
+ end
12
+ def handle
9
13
  begin
10
- parsed = JSON.parse(body.read)
14
+ parsed = JSON.parse(@body.read)
11
15
 
12
16
  Brut.container.instrumentation.add_attributes(
13
17
  prefix: "brut.locale-detection",
@@ -19,9 +23,9 @@ class Brut::FrontEnd::Handlers::LocaleDetectionHandler < Brut::FrontEnd::Handler
19
23
  locale = parsed["locale"]
20
24
  timezone = parsed["timeZone"]
21
25
 
22
- session.timezone_from_browser = timezone
23
- if !session.http_accept_language.known?
24
- session.http_accept_language = Brut::I18n::HTTPAcceptLanguage.from_browser(locale)
26
+ @session.timezone_from_browser = timezone
27
+ if !@session.http_accept_language.known?
28
+ @session.http_accept_language = Brut::I18n::HTTPAcceptLanguage.from_browser(locale)
25
29
  end
26
30
  end
27
31
  rescue => ex
@@ -1,7 +1,10 @@
1
1
  # Used in development to handle a defined route but a missing page. This arranges to render a nicer error page than the default.
2
2
  class Brut::FrontEnd::Handlers::MissingHandler < Brut::FrontEnd::Handler
3
- def handle(route:)
4
- Brut::FrontEnd::Pages::MissingPage.new(route:)
3
+ def initialize(route:)
4
+ @route = route
5
+ end
6
+ def handle
7
+ Brut::FrontEnd::Pages::MissingPage.new(route: @route)
5
8
  end
6
9
 
7
10
  class Form < Brut::FrontEnd::Form
@@ -1,3 +1,19 @@
1
+ # A layout is common HTML that surrounds different pages. For example, it would hold your
2
+ # DOCTYPE, `<head>`, and possibly any common `<body>` elements that every page needs.
3
+ #
4
+ # A layout is a Phlex component but it must contain a call to `yield` somewhere in the
5
+ # implementation of `view_template`.
6
+ #
7
+ # This base class contains helper methods needed for implementing a layout.
1
8
  class Brut::FrontEnd::Layout < Brut::FrontEnd::Component
9
+ # Get the actual path of an asset managed by Brut. This handles
10
+ # locating the asset's URL as well as ensuring the hash is properly
11
+ # inserted into the filename.
12
+ #
13
+ # @param [String] path the path to an asset, such as `/css/styles.css`.
14
+ #
15
+ # @return [String] the actual path to the current version of that asset.
16
+ #
17
+ # @see Brut::FrontEnd::AssetPathResolver
2
18
  def asset_path(path) = Brut.container.asset_path_resolver.resolve(path)
3
19
  end
@@ -1,48 +1,46 @@
1
- # A page backs a web page. A page renders everything in the browser window. Technically, it is exactly like a component except that
2
- # it can have a layout.
1
+ # A Page backs a web page, which handles rendering everything in a browser window when a URL is requested.
2
+ # Technically, a page is identical to a {Brut::FrontEnd::Component}, except that a page has a layout.
3
+ # A {Brut::FrontEnd::Layout} is common HTML that surrounds your page's HTML.
4
+ # Your page is a Phlex component, but instead of implementing `view_template`, you
5
+ # implement {#page_template} to ensure the layout is used.
3
6
  #
4
- # When subclassing this to create a page, your initializer's signature will determine what data
5
- # is required for your page to work. It can be anything, just keep in mind that any component your page uses may
6
- # require additional data.
7
+ # To create a page, after defining a route, subclass this class (or, more likely, your app's `AppPage`) and
8
+ # provide an initializer that accepts keyword arguments. The names of these arguments will be used to locate the
9
+ # values that Brut will pass in when creating your page object.
7
10
  #
8
- # If your page does not override {#render} (which, generally, it won't), an ERB file is expected to exist alongside it in the
9
- # app. For example, if you have a page named `Auth::LoginPage`, it would expected to be in
10
- # `app/src/front_end/pages/auth/login_page.rb`. Thus, Brut will also expect
11
- # `app/src/front_end/pages/auth/login_page.html.erb` to exist as well. That ERB file is used with an instance of your
12
- # pages's class to render the page's HTML.
11
+ # Consult Brut's documentation on keyword injection to know what values you may use and how values are located.
13
12
  #
14
13
  # @see Brut::FrontEnd::Component
14
+ # @see Brut::FrontEnd::Layout
15
15
  class Brut::FrontEnd::Page < Brut::FrontEnd::Component
16
16
  include Brut::FrontEnd::HandlingResults
17
17
 
18
- # Returns the name of the layout for this page. This string is used to find an ERB file in `app/src/front_end/layouts`. Every page
19
- # must have a layout. If you wish to render a page with no layout, create an empty layout in your app and use that.
18
+ # Returns the name of the layout for this page. This string is used to find a class named
19
+ # `«camelized-layout»Layout` in your app. The default value is "default", meaning that the class
20
+ # `DefaultLayout` will be used.
20
21
  #
21
- # Note that the layout can be dynamic. It is requested when {#render} is called, so you can override this
22
+ # Note that the layout can be dynamic. It is requested when {#page_template} is called, so you can override this
22
23
  # method and use any ivar set in your constructor to change what layout is used.
23
24
  #
25
+ # If your page does not need a layout, you have two options:
26
+ #
27
+ # * Create your own blank layout named, e.g. `BlankLayout` and have this method return `"blank"`.
28
+ # * Implement `view_template` instead of `page_template`, thus overriding this class' implementation that uses
29
+ # layouts.
30
+ #
24
31
  # @return [String] The name of the layout. May not be `nil`.
25
32
  def layout = "default"
26
33
 
27
- # Called after the page is created, but before {#render} is called. This allows you to do any pre-flight checks and potentially
34
+ # Called after the page is created, but before {#page_template} is called. This allows you to do any pre-flight checks and potentially
28
35
  # redirect the user or produce an error.
29
36
  #
30
- # @return [URI|Brut::FrontEnd::HttpStatus|Object] If you return a `URI` (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#redirect_to}), the user is redirected and {#render} is never called. If you return a {Brut::FrontEnd::HttpStatus} (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#http_status}), {#render} is skipped and that status is returned with no content. If anything else is returned, {#render} is called as normal.
31
- def before_render = nil
32
-
33
- def with_layout(&block)
34
- layout_class = Module.const_get(
35
- layout_class = RichString.new([
36
- self.layout,
37
- "layout"
38
- ].join("_")).camelize
39
- )
40
- render layout_class.new(page_name:,&block)
41
- end
42
-
37
+ # @return [URI|Brut::FrontEnd::HttpStatus|Object] If you return a `URI` (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#redirect_to}), the user is redirected and no HTML is generated. If you return a {Brut::FrontEnd::HttpStatus} (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#http_status}), HTML generation is skipped and that status is returned with no content. If anything else is returned, HTML is generated normal.
38
+ def before_generate = nil
43
39
 
40
+ # Core method of this class. Do not override. This handles the use of {#before_generate} and is what Brut
41
+ # calls to possibly render the page.
44
42
  def handle!
45
- case before_render
43
+ case before_generate
46
44
  in URI => uri
47
45
  uri
48
46
  in Brut::FrontEnd::HttpStatus => http_status
@@ -52,6 +50,15 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
52
50
  end
53
51
  end
54
52
 
53
+ # Override this method to produce your page's HTML. You are intended to call Phlex
54
+ # methods here. Anything you can do inside the Phlex-standard `view_template` method, you can
55
+ # do here. The only difference is that this will all be rendered in the context of your configured
56
+ # {#layout}.
57
+ def page_template = abstract_method!
58
+
59
+ # Phlex's API to produce markup. Do not override this or you will lose your layout.
60
+ # This implementation locates the configured layout, renders it, and renders {#page_template}
61
+ # inside.
55
62
  def view_template
56
63
  with_layout do
57
64
  page_template
@@ -65,8 +72,24 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
65
72
  # Convienience method for {.page_name}.
66
73
  def page_name = self.class.page_name
67
74
 
75
+ private
76
+
77
+ # Locates the layout class and uses it to render itself, along
78
+ # with the block given.
79
+ #
68
80
  # @!visibility private
69
- def component_name = raise Brut::Framework::Errors::Bug,"#{self.class} is not a component"
81
+ def with_layout(&block)
82
+ layout_class = Module.const_get(
83
+ layout_class = RichString.new([
84
+ self.layout,
85
+ "layout"
86
+ ].join("_")).camelize
87
+ )
88
+ Brut.container.instrumentation.add_prefixed_attributes("brut", layout_class: layout_class)
89
+ render layout_class.new(page_name:,&block)
90
+ end
91
+
92
+
70
93
 
71
94
  end
72
95
 
@@ -112,12 +112,13 @@ class Brut::FrontEnd::RequestContext
112
112
  # @param [Class] klass a class that is to be instantiated entirely by the contents of this `RequestContext`.
113
113
  # @param [Hash] request_params Query string parameters provided by Rack.
114
114
  # @param [Brut::FrontEnd::Routing::Route] route the route that triggered the request.
115
+ # @param [Brut::FrontEnd::Form] form the form, if available
115
116
  # @return [Hash] can be splatted to keyword arguments and passed to the constructor of `klass`
116
117
  #
117
118
  # @raise [ArgumentError] if the constructor has any non-keyword arguments, or if any required keyword argument is
118
119
  # not present in this `RequestContext`.
119
- def as_constructor_args(klass, request_params:, route:nil)
120
- args_for_method(method: klass.instance_method(:initialize), request_params:, form: nil, route:)
120
+ def as_constructor_args(klass, request_params:, route:nil, form: nil)
121
+ args_for_method(method: klass.instance_method(:initialize), request_params:, form: , route:)
121
122
  end
122
123
 
123
124
  # Based on `object`' method, returns a Hash that maps all keywords it requires to the values stored in this
@@ -218,7 +218,11 @@ private
218
218
  joined_path = joined_path + "#" + URI.encode_uri_component(anchor)
219
219
  end
220
220
  uri = URI(joined_path)
221
- uri.query = URI.encode_www_form(query_string_params)
221
+ query_string = URI.encode_www_form(query_string_params)
222
+ if query_string.to_s.strip != ""
223
+ uri.query = query_string
224
+ end
225
+
222
226
  uri.extend(Phlex::SGML::SafeObject)
223
227
  end
224
228