brut 0.0.21 → 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 (689) 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/brutrb.com/keyword-injection.md +237 -0
  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/cli/app_runner.rb +1 -1
  652. data/lib/brut/cli/apps/test.rb +5 -0
  653. data/lib/brut/framework/mcp.rb +0 -4
  654. data/lib/brut/front_end/component.rb +59 -84
  655. data/lib/brut/front_end/components/constraint_violations.rb +1 -4
  656. data/lib/brut/front_end/components/form_tag.rb +1 -1
  657. data/lib/brut/front_end/components/input.rb +3 -3
  658. data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
  659. data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +6 -8
  660. data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
  661. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
  662. data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
  663. data/lib/brut/front_end/components/time_tag.rb +2 -1
  664. data/lib/brut/front_end/form.rb +4 -4
  665. data/lib/brut/front_end/forms/input.rb +2 -1
  666. data/lib/brut/front_end/forms/input_definition.rb +5 -2
  667. data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
  668. data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
  669. data/lib/brut/front_end/forms/select_input.rb +2 -4
  670. data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
  671. data/lib/brut/front_end/handler.rb +28 -26
  672. data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
  673. data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
  674. data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
  675. data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
  676. data/lib/brut/front_end/page.rb +4 -6
  677. data/lib/brut/front_end/request_context.rb +3 -2
  678. data/lib/brut/i18n/base_methods.rb +136 -82
  679. data/lib/brut/i18n/for_back_end.rb +1 -0
  680. data/lib/brut/i18n/for_cli.rb +1 -0
  681. data/lib/brut/i18n/for_html.rb +32 -4
  682. data/lib/brut/instrumentation/open_telemetry.rb +12 -2
  683. data/lib/brut/sinatra_helpers.rb +10 -3
  684. data/lib/brut/spec_support/component_support.rb +18 -18
  685. data/lib/brut/spec_support/e2e_test_server.rb +3 -0
  686. data/lib/brut/version.rb +1 -1
  687. data/lib/sequel/extensions/brut_migrations.rb +1 -1
  688. metadata +647 -5
  689. data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -0,0 +1,66 @@
1
+ # Getting Started
2
+
3
+ To make a new app with Brut, you'll need to clone a template app, initialize it, then set up your dev environment.
4
+
5
+ ## Clone the App Template
6
+
7
+ To get started, clone the Brut app template:
8
+
9
+ ```
10
+ > git clone https://github.com/thirdtank/brut-app-template your-app-name
11
+ ```
12
+
13
+ ## Init Your App
14
+
15
+ The template includes `init`, which will ask you a few questions to get everything set up.
16
+
17
+ ```
18
+ > cd your-app-name
19
+ > ./init
20
+ ```
21
+
22
+ You'll need to provide four pieces of info:
23
+
24
+ * Your app's name, suitable has a hostname or identifier
25
+ * A prefix for your app's externalizable ids
26
+ * A prefix for your app's custom elements
27
+ * An organization name, needed for deployment
28
+
29
+ ::: tip
30
+ Choose your app's name wisely, however everything else can be easily changed later, so don't stress!
31
+ :::
32
+
33
+ ## Set Up Your Dev Environment
34
+
35
+ Brut includes a dev environment based on Docker.
36
+
37
+ 1. [Install Docker](https://docs.docker.com/get-started/get-docker/)
38
+ 2. Build Your images
39
+
40
+ ```
41
+ > dx/build
42
+ ```
43
+ 3. Start up the environment
44
+
45
+ ```
46
+ > dx/start
47
+ ```
48
+ 4. Install gems and modules for your app. In another terminal:
49
+
50
+ ```
51
+ > dx/exec bin/setup
52
+ ```
53
+
54
+ Now, you're ready to go
55
+
56
+ ## Run the App
57
+
58
+ ```
59
+ > dx/exec bin/dev
60
+ ```
61
+
62
+ You can now visit your app at `localhost:6502`
63
+
64
+ ## Now Build The Rest of Your App 🦉
65
+
66
+ You can [follow the tutorial](/tutorial), check out the [conceptual overview](/overview), or dive straight into the API docs.
@@ -0,0 +1,153 @@
1
+ # Handlers & Actions
2
+
3
+ Handlers process form submissions, and *actions* work similarly to process any arbitrary HTTP request.
4
+
5
+ ## Overview
6
+
7
+ Where a [page](/pages) renders a web page in HTML, a *handler* responds to all other HTTP requests. To respond to such HTTP requests, you'd first create a [route](/routes), using `form`, `action`, or `path`.
8
+
9
+ ### Declaring Routes
10
+
11
+ `form` and `action` are intended to be used when an HTTP form is being submitted. The latter—`action`—is for when your form has no user-editable elements. This is akin to Rails' `button_to` helper, where the contents of the URL contains everything needed to service the request.
12
+
13
+ `path` is for arbitrary HTTP requests and methods, so to create a webhook that responds to a PUT, you'd use:
14
+
15
+ ```ruby
16
+ # inside app/src/app.rb
17
+ path "/webhooks/stripe", method: :put
18
+ ```
19
+
20
+ In all cases, the class to receive and process these requests is a handler, whose name is conventional based on the route. For example, the webhook above would be handled by `Webhooks::StripeHandler`.
21
+
22
+ ### Implementing Handlers
23
+
24
+ A handler works like a page, in that its initializer can receive any injectible arguments. These would include a form object, dynamic elements of the route, or anything else available from the request. See [Keyword Injection](/keyword-injection) for the details, noting that your handler can be inejcted with custom objects you've configured in a route hook.
25
+
26
+ After the handler is created, it's `before_handle` method is called. If it returns `nil`, `handle` is called to trigger whatever logic the handler needs to trigger.
27
+
28
+ Both `handle` and `before_handle` can return a variety of objects that determine what will happen:
29
+
30
+ * An instance of a page or component means that page or component is rendered. This is not a redirect to the page, so it is more like Rails' `render :new` and **not** a `redirect_to(new_widgets_path)`.
31
+ * A `Brut::FrontEnd::HttpStatus` which sends that status and an empty body back. This object can be created from
32
+ an integer using the `http_status` helper, available to all handlers.
33
+ * A `URI`, which will cause a redirect to that URI. You can create the `URI` yourself, or use the helper
34
+ `redirect_to`, which accepts a string.
35
+ * A two-element array with a page or component as the first element and an `Brut::FrontEnd::HttpStatus` as the
36
+ second. This will render that page or component's HTML, but use the given status instead of 200. This can be useful for Ajax requests where you want to use HTML your respond format, but also, say, a 422 status to indicate a constraint violation has occured.
37
+ * A `Brut::FrontEnd::Download`, which encapsulates a file to be downloaded.
38
+ * A `Brut::FrontEnd::GenericResponse`, which wraps any Rack response with a defined type.
39
+
40
+ `before_handle` may also return `nil` to indicate that `handle` should be called. `handle` may not return `nil`.
41
+
42
+ Supposing our `LoginForm` and `LoginHandler` wanted to use a common pattern of re-rendering `LoginPage` on constraint violations, and forwarding on to, say, a `DashboardPage`. Your handler might look like so:
43
+
44
+ ```ruby {23-27}
45
+ # app/src/front_end/handlers/login_handler.rb
46
+ class LoginHandler < AppHandler
47
+ def initialize(form:, session:) # We'll discuss the session later
48
+ @form = form
49
+ @session = session
50
+ end
51
+
52
+ def handle
53
+ if !@form.constraint_violations?
54
+ authorized_user = AuthorizedUser.login(
55
+ email: form.email,
56
+ password: form.password
57
+ )
58
+ if authorized_user.nil?
59
+ @form.server_side_constraint_violation(
60
+ input_name: :email,
61
+ key: :login_not_found
62
+ )
63
+ else
64
+ session.authorized_user = authorized_user
65
+ end
66
+ end
67
+ if @form.constraint_violations?
68
+ LoginPage.new(form: @form)
69
+ else
70
+ redirect_to(DashboardPage.routing)
71
+ end
72
+ end
73
+ end
74
+ ```
75
+
76
+ > [!IMPORTANT]
77
+ > The only way to render something other than HTML is to do so as a
78
+ > `GenericResponse`, which is basically the low-level Rack API. Brut
79
+ > encourages Ajax responses to be HTML and for you to use the browser's
80
+ > APIs to interact with that HTML. Brut may make it easier to work
81
+ > with other types of content in the future.
82
+
83
+ ## Testing
84
+
85
+ Testing handlers requires calling their *public API*, which is `handle!`. This is not the method you implement (which is the non-bang `handle`). The reason is that `handle!` manages the logic around calling `before_handle`, which allows your tests to always call `handle!` and know they are testing how the handler would be used in produciton.
86
+
87
+ Each handler spec includes `Brut::SpecSupport::HandlerSupport`, which allows you to create production-like flash, clock, and session objects. To assert the results of calling `handle!`, there are several RSpec matchers you can use to make your tests easier to write.
88
+
89
+ * `have_redirected_to` will check that the handler redirected to a give URI.
90
+ * `have_rendered` will check that the handler rendered a specific page
91
+ * `have_returned_http_status` will check that the handler returned an HTTP status
92
+ * `have_constraint_violation` will check if a form had a particular constraint violation set on it
93
+
94
+ ```ruby
95
+ require "spec_helper"
96
+
97
+ RSpec.describe LoginPage do
98
+ describe "#handle!" do
99
+ context "when login is not valid" do
100
+ it "re-renders LoginPage" do
101
+ form = LoginForm.new(params: {
102
+ email: "nonexistent@example.com",
103
+ password: "not a password",
104
+ })
105
+ result = described_class.new(
106
+ form:,
107
+ session: empty_session # empty_session provided by HandlerSupport
108
+ )
109
+ expect(result).to have_rendered(LoginPage)
110
+ expect(form).to have_constraint_violation(:email, key: :login_not_found)
111
+ end
112
+ end
113
+ context "when login is valid" do
114
+ it "forward to the DashboardPage" do
115
+ user = create(:user, # Assume this is set up via FactoryBot
116
+ email: "pat@example.com",
117
+ password: "1q2w3e4r5t6y7u8i9o")
118
+
119
+ form = LoginForm.new(params: {
120
+ email: "pat@example.com",
121
+ password: "1q2w3e4r5t6y7u8i9o",
122
+ })
123
+ session = empty_session
124
+ result = described_class.new(
125
+ form:,
126
+ session:,
127
+ )
128
+ expect(result).to have_redirected_to(DashboardPage.routing)
129
+ expect(session.authorized_user).not_to eq(nil)
130
+ # Session will be explained later
131
+ end
132
+ end
133
+ end
134
+ end
135
+ ```
136
+
137
+ ## Recommended Practices
138
+
139
+ You should avoid having business logic in your handlers. Since handlers bridge the gap between HTTP and your app, their API is naturally simplistic and String-based. The handler should defer to business logic (which can be done by either passing the form object directly, or extracting its data and passing that). Based on the response, the handler will then decide what HTTP response is approriate.
140
+
141
+ This means that your handlers will be relatively simple and their tests will as well. It does mean that their tests may require the use of mocks or stubs, but that's fine. Mocks and stubs exist for a reason.
142
+
143
+
144
+ ## Technical Notes
145
+
146
+ > [!IMPORTANT]
147
+ > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
148
+ > internals, the source code is always more correct.
149
+
150
+ _Last Updated May 5, 2025_
151
+
152
+ None at this time.
153
+
@@ -0,0 +1,178 @@
1
+ # Route Hooks
2
+
3
+ Route hooks are similar to [Middleware](/middleware), but have a richer API and aren't as low-level. Route
4
+ hooks can happen before a page or handler is called, or after.
5
+
6
+ ## Overview
7
+
8
+ We've seen examples thusfar of using a route hook to place the authenticated user or account into the
9
+ request context for later injection into pages or handlers. Brut uses route hooks to locale detection and
10
+ for content security policies.
11
+
12
+ At its core, a before hook is a class that extends `Brut::FrontEnd::RouteHook` and implements `before`
13
+ and an after hook implements `after`. Both `before` and `after` can be [injected](/keyword-injection) with request-time information.
14
+
15
+ To register a hook, you'd call `before` or `after` in your `App`:
16
+
17
+ ```ruby
18
+ class App < Brut::Framework::App
19
+
20
+ # ...
21
+
22
+ before :RequireAuthBeforeHook
23
+
24
+ # ...
25
+
26
+ end
27
+ ```
28
+
29
+ The value can be a string or symbol, but should not be the class itself, as this can mess with load order.
30
+
31
+ The code for `RequireAuthBeforeHook` we saw before was marked with a red warning that it wasn't production ready. Let's
32
+ see what a more realistic hook would look like.
33
+
34
+ The purpose of `RequireAuthBeforeHook` is to detect if a user is logged in. If they are, all is well. If they are not,
35
+ we want to redirect them to a login page unless the page they've requested is allowed for logged-out users.
36
+
37
+ Let's suppose that the home page (`/`) and any page starting with `/auth/` are allowed for logged-out users (`/auth/` being pages related to logging in).
38
+
39
+ Brut also reserves some routes for its own, and we want those to be allowed to logged-out users as well. Brut sets
40
+ `"brut.owned_path"` in the Rack environment if the requested URL is one it is managing.
41
+
42
+ `before` will need access to the request context, session, Rack request, and Rack environment:
43
+
44
+
45
+ ```ruby
46
+ # app/src/front_end/route_hooks/require_auth_before_hook.rb
47
+ class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
48
+ def before(request_context:,session:,request:,env:)
49
+ # ...
50
+ end
51
+ end
52
+ ```
53
+
54
+ We'll use the Rack request's `path_info` to check for allowed routes, and the aforementioned `env["brut.owned_path"]` to
55
+ check for a Brut-owned path:
56
+
57
+ ```ruby {4-6}
58
+ # app/src/front_end/route_hooks/require_auth_before_hook.rb
59
+ class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
60
+ def before(request_context:,session:,request:,env:)
61
+ is_home_page = request.path_info.match(/^\/?$/)
62
+ is_auth_route = request.path_info.match?(/^\/auth\//)
63
+ is_brut_owned_path = env["brut.owned_path"]
64
+
65
+ # ...
66
+
67
+ end
68
+ end
69
+ ```
70
+
71
+ Now, we can use this local variables to figure out if the route requires a user to be logged-in:
72
+
73
+ ```ruby {8-10}
74
+ # app/src/front_end/route_hooks/require_auth_before_hook.rb
75
+ class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
76
+ def before(request_context:,session:,request:,env:)
77
+ is_home_page = request.path_info.match(/^\/?$/)
78
+ is_auth_route = request.path_info.match?(/^\/auth\//)
79
+ is_brut_owned_path = env["brut.owned_path"]
80
+
81
+ requires_login = !is_home_page &&
82
+ !is_auth_route &&
83
+ !is_brut_owned_path
84
+
85
+ # ...
86
+
87
+ end
88
+ end
89
+ ```
90
+
91
+ Now, we'll check if someone *is* logged in. If they are, we'll set the `authenticated_account` in the request context.
92
+
93
+ ```ruby {12-15}
94
+ # app/src/front_end/route_hooks/require_auth_before_hook.rb
95
+ class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
96
+ def before(request_context:,session:,request:,env:)
97
+ is_home_page = request.path_info.match(/^\/?$/)
98
+ is_auth_route = request.path_info.match?(/^\/auth\//)
99
+ is_brut_owned_path = env["brut.owned_path"]
100
+
101
+ requires_login = !is_home_page &&
102
+ !is_auth_route &&
103
+ !is_brut_owned_path
104
+
105
+ if session.logged_in?
106
+ request_context[:authenticated_account] = session.authenticated_account
107
+ requires_login = false
108
+ end
109
+
110
+ # ...
111
+
112
+ end
113
+ end
114
+ ```
115
+
116
+ Now, we can test if the visitor needs to log in before proceeding. The return value of `before` controls
117
+ what will happen, similar to how handlers work.
118
+
119
+
120
+ * `URI` - the browser will be redirected to this URI. This can be done by using the `redirect_to` helper.
121
+ * `Brut::FrontEnd::HttpStatus` - the request will be terminated with this status. This can be done using the `http_status` helper.
122
+ * `false` - the request is terminated with a 500
123
+ * `true` or `nil` - the request will continue to the next hook or to the route handler. You are encouraged to use the `continue` helper to more clearly indicate that the request will proceed.
124
+
125
+ In our case, if the visitor requires a login, we'll `redirect_to` the `LoginPage`. Otherwise, we'll
126
+ `continue`.
127
+
128
+ ```ruby {17-21}
129
+ # app/src/front_end/route_hooks/require_auth_before_hook.rb
130
+ class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
131
+ def before(request_context:,session:,request:,env:)
132
+ is_home_page = request.path_info.match(/^\/?$/)
133
+ is_auth_route = request.path_info.match?(/^\/auth\//)
134
+ is_brut_owned_path = env["brut.owned_path"]
135
+
136
+ requires_login = !is_home_page &&
137
+ !is_auth_route &&
138
+ !is_brut_owned_path
139
+
140
+ if session.logged_in?
141
+ request_context[:authenticated_account] = session.authenticated_account
142
+ requires_login = false
143
+ end
144
+
145
+ if requires_login
146
+ redirect_to(Auth::LoginPage)
147
+ else
148
+ continue
149
+ end
150
+
151
+ end
152
+ end
153
+ ```
154
+
155
+ ## Testing
156
+
157
+ Route hooks are normal classes, you could test them as you would a handler or other class. This may be
158
+ advisable for complex hooks, however it may be more realistic to test their behavior through end-to-end
159
+ tests as this will ensure they are configured correctly in the context of the app.
160
+
161
+ ## Recommended Practices
162
+
163
+ Route hooks and [page hooks](/pages#hooks) serve similar purposes, so logic in one can be placed in other
164
+ other at your discretion. We recommend you use route hooks for cross-cutting issues across the entire
165
+ app, such as login checks or for adding context to a request.
166
+
167
+ For page- or use-case-specific behavior, it may be better to put the logic in a page hook.
168
+
169
+ ## Technical Notes
170
+
171
+ > [!IMPORTANT]
172
+ > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
173
+ > internals, the source code is always more correct.
174
+
175
+ _Last Updated June 12, 2025_
176
+
177
+ Route hooks and Middlewares do not share implementations, however they are similar in concept. These
178
+ concepts may be unified in the future.
@@ -0,0 +1,188 @@
1
+ # Internationaliztion and Localization
2
+
3
+ Brut uses Ruby's i18n gem to provide support for localization and internationalization.
4
+
5
+ ## Overview
6
+
7
+ `Brut::I18n::BaseMethods` provides the core implementation of Brut's i18n support, and it largely wraps the `t`
8
+ and `l` methods of the i18n gem.
9
+
10
+
11
+ Consider this:
12
+
13
+ ```ruby
14
+ t("my.key", foo: "Bar")
15
+ ```
16
+
17
+ This will locate the string with the key `my.key` and return it, replacing `%{foo}` with `"Bar"`, if `%{foo}` is
18
+ present in the string.
19
+
20
+ The keys are located in files in `app/config/i18n`. The directories there correspond to the locales your app
21
+ supports, e.g .`app/config/i18n/en` would hold translations for English.
22
+
23
+ The translation files themselves are 🎉**NOT YAML**🎊. They are Ruby files. By default, there are two files: `app/config/i18n/en/1_defaults.rb` and `app/config/i18n/en/2_app.rb` (noting that `/en/` is for English and other langauges are obviously supported).
24
+
25
+ `1_defaults.rb` provides values for keys Brut may require or use, such as for front-end constraint violations.
26
+ `2_app.rb` provides your app's keys. If this file contains the same keys as `1_defaults.rb`, your file's values
27
+ will be used.
28
+
29
+ The file is a giant Hash, so the key above might look like so:
30
+
31
+ ```ruby
32
+ {
33
+ my: {
34
+ key: "Hello there %{foo}",
35
+ },
36
+ }
37
+ ```
38
+
39
+ ### Enhancements
40
+
41
+ Brut's `t` tries to balance predictability with flexibility. It will always try to tell you what keys it was
42
+ checking when it cannot find a translation or what interpolated values were missing.
43
+
44
+ #### Basic Usage
45
+
46
+ Often, keys contain dynamic elements. Rather that creating a key like `widget.status.#{widget.status}`, you can
47
+ pass an array in and it'll be joined for you:
48
+
49
+ ```ruby
50
+ t([ :widget, :status, widget.status ]) #=> widget.status.active e.g.
51
+ ```
52
+
53
+ `t` can also take a block that will be evaluated and substituted into the `block` interpolation value:
54
+
55
+ ```ruby
56
+ {
57
+ supportlink: "To contact support %{block}',
58
+ }
59
+
60
+ t(:supportlink) do
61
+ "<a href='https://support.example.com'>Contact Support</a>"
62
+ end
63
+ ```
64
+
65
+ See below for how this affects HTML generation.
66
+
67
+ #### Page- and Component-specific Values
68
+
69
+ If you call `t` inside `page_template`, or inside `view_template` of a [page private
70
+ component](/components#page-private-components), you can simplify the key specification.
71
+
72
+ ```ruby
73
+ t(:nevermind)
74
+ ```
75
+
76
+ If this is used on, say, `NewWidgetPage`, Brut will try to locate the key `pages.NewWidgetPage.nevermind`. This works on page private components as well.
77
+
78
+ Inside a normal component, it works simliarly. Suppose `FlashComponent` had this:
79
+
80
+ ```ruby
81
+ if !flash
82
+ t(:default_message)
83
+ end
84
+ ```
85
+
86
+ This would locate `components.FlashComponent.default_message`.
87
+
88
+ This saves some typing, but it can also assist refactoring. If you rename a page or component, calls to `t` will blow up, reminding you to move the translations inside `2_app.rb`.
89
+
90
+ ### HTML Escaping
91
+
92
+ `Brut::I18n::BaseMethods` cannot be used directly. One of three submodules must be used: `ForHTML`, `ForCLI`, or
93
+ `ForBackend`. This is to allow the `ForHTML` module to properly escape HTML.
94
+
95
+ When a translation accepts a block, that block could be HTML and you would want that HTML be included in the
96
+ page, un-escaped. Brut achieves this by first using Phlex's `capture` method, then marking it as HTML safe using
97
+ `safe`.
98
+
99
+ Thus, as long as you aren't introducing injections in your translations file or source code, it is safe to do the following:
100
+
101
+ ```ruby
102
+ def view_template
103
+ div do
104
+ raw(
105
+ t(page: :contact_support) do
106
+ a(href: "https://support.example.com") do
107
+ t(page: :support_link_name)
108
+ end
109
+ end
110
+ )
111
+ end
112
+ end
113
+ ```
114
+
115
+ The result of `t` is safe HTML, so you must use `raw` to avoid escaping it.
116
+
117
+ In a CLI or back-end context, HTML escaping is not relevant and can actually create problems, so `ForCLI` and
118
+ `ForBackend` no-op `safe` and `capture`.
119
+
120
+ When using `ForHTML`, all interpolated values are HTML-escaped. `ForCLI` and `ForBackend` are not.
121
+
122
+ ### Localizing Dates and Times
123
+
124
+ `l` can be called and this defers to the Ruby I18n library.
125
+
126
+ Date and time formats can be configured in the translation files. `l` does not accept a full key for the format.
127
+ It is created dynamically by the library, so you must take care in which one you use. If you pass a `Date` into
128
+ `l`, `date.formats.«format»` is used. If you pass a `Time` in, `time.formats.«format»` is used.
129
+
130
+ The values of the formats are strings suitable for
131
+ [`strftime`](https://www.man7.org/linux/man-pages/man3/strftime.3.html). The site [strif.me](https://www.strfti.me/) can be helpful in conjuring the right value.
132
+
133
+ Brut includes translations for various formats that you can inspect in `app/config/i18n/«lang»/1_defaults.rb`.
134
+
135
+ ### Displaying Dates and Times in HTML
136
+
137
+ While `l` will return a string you can use anywhere, you are most likely going to show dates and times in HTML.
138
+ For that, you should use a `<time>` element. Brut provides `Brut::FrontEnd::Components::TimeTag` (remember that if you `include Brut::FrontEnd::Components`, it's a Phlex *kit* and thus you can use `TimeTag(...)` directly) to do this. It contains additional behavior to make friendly dates and times.
139
+
140
+ * You can give it a `timestamp:` or `date:` to control which formatting style is used.
141
+ * `skip_year_if_same`, if true, will omit the year from any format if the current year is the same as the year being displayed. This is true by default
142
+ * `skip_dow_if_not_this_week`, if true, will omit the day of week if the date or time is more than 7 days in the past. This is true by default.
143
+
144
+ The way `skip_year_if_same` and `skip_dow_if_not_this_week` work is to append `no_year` and/or `no_dow` to
145
+ existing format strings which are assumed to omit this elements.
146
+
147
+ If you wish to create your own formats, you can add them as well.
148
+
149
+ ### Constraint Violations and Field Names
150
+
151
+ The interpolated value `{field}` is special. It is assumed to be the name of a field in a constraint violation
152
+ message, e.g. `"%{field} is required"`. It is the only interpolated value that can be omitted without causing an
153
+ error.
154
+
155
+ If included, it will work as normal:
156
+
157
+ ```ruby
158
+ t("cv.be.required", field: "Email") # => Email is required
159
+ ```
160
+
161
+ If omitted, the value of `"cv.this_field"` is used. This is included in `1_default.rb`, but if it's
162
+ missing, Brut will raise. Assuming the value is `"This field"`:
163
+
164
+ ```ruby
165
+ t("cv.be.required") # => This field is required
166
+ ```
167
+
168
+ ## Testing
169
+
170
+ In tests, you can call `t` and `l` to examine values as needed.
171
+
172
+ > [!WARNING]
173
+ > This aspect of Brut could use improvement. It likely will not work for non-English locales.
174
+
175
+ ## Recommended Practices
176
+
177
+ Could use help here - The implementation feels like a bare minium
178
+
179
+
180
+ ## Technical Notes
181
+
182
+ > [!IMPORTANT]
183
+ > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
184
+ > internals, the source code is always more correct.
185
+
186
+ _Last Updated May 7, 2025_
187
+
188
+ TBD
@@ -0,0 +1,10 @@
1
+ DOT_FILES = *.dot
2
+ PNG_FILES = $(patsubst %.dot,%.png,$(DOT_FILES))
3
+
4
+ %.png : %.dot
5
+ dot -Gdpi=300 -Tpng $< -o$@
6
+
7
+ %.svg : %.dot
8
+ dot -Gdpi=300 -Tsvg $< -o$@
9
+
10
+ all: $(PNG_FILES)
@@ -0,0 +1,54 @@
1
+ digraph G {
2
+ rankdir="LR"
3
+ Node[fontname=Baskerville]
4
+ subgraph cluster_docker {
5
+ label="Docker Container"
6
+ labeljust=left
7
+ fontname=Avenir
8
+ Ruby
9
+ NodeJS
10
+ OS
11
+ Filesystem
12
+ BrutApp
13
+ }
14
+
15
+ subgraph cluster_host {
16
+ label="Your Computer"
17
+ labeljust=left
18
+ fontname=Avenir
19
+ ProjectFiles
20
+ Editor
21
+ VersionControl
22
+ Browser
23
+ }
24
+
25
+ subgraph cluster_postgres {
26
+ label="Postgres\l«Docker Container»"
27
+ labeljust=left
28
+ fontname=Avenir
29
+ Postgres
30
+ }
31
+ Filesystem[shape=tab label="File system"]
32
+ ProjectFiles[shape=tab label="Project Files"]
33
+ OS[shape=box]
34
+ NodeJS[shape=component]
35
+ Ruby[shape=component]
36
+ Editor[shape=Msquare]
37
+ VersionControl[shape=Msquare label="Version\nControl"]
38
+ Postgres[shape=cylinder]
39
+ BrutApp[shape=box3d]
40
+ Browser[shape="Msquare" label="Web\nBrowser"]
41
+
42
+ BrutApp -> Ruby
43
+ BrutApp -> NodeJS
44
+ BrutApp -> Filesystem
45
+ BrutApp -> Postgres
46
+
47
+ Filesystem -> ProjectFiles[ dir=both label="synced"]
48
+ Editor -> ProjectFiles
49
+ Ruby -> Filesystem
50
+ NodeJS -> Filesystem
51
+ OS -> Filesystem
52
+ VersionControl -> ProjectFiles
53
+ Browser -> BrutApp
54
+ }
@@ -0,0 +1,37 @@
1
+ digraph G {
2
+
3
+ rankdir="LR"
4
+ nodesep=0.55
5
+ compound=true
6
+
7
+ node[shape=box fontname=avenir]
8
+
9
+ Shutdown[label=<
10
+ <FONT face="avenir">Shutdown</FONT>
11
+ <br/>
12
+ <FONT face="Courier New">dx/stop</FONT>
13
+ >]
14
+ Build[label=<
15
+ <FONT face="avenir">Build</FONT>
16
+ <br/>
17
+ <FONT face="Courier New">dx/build</FONT>
18
+ >]
19
+ Start[label=<
20
+ <FONT face="avenir">Start</FONT>
21
+ <br/>
22
+ <FONT face="Courier New">dx/start</FONT>
23
+ >]
24
+ Exec[label=<
25
+ <FONT face="avenir">Execute</FONT>
26
+ <br/>
27
+ <FONT face="Courier New">dx/exec</FONT>
28
+ >]
29
+ DevCommands[label=<
30
+ <FONT face="avenir">Workspace</FONT>
31
+ <br/>
32
+ <FONT face="Courier New">bin/\*</FONT>
33
+ >]
34
+ Build -> Start -> Exec -> Shutdown
35
+ Exec -> DevCommands[style=dotted dir=none]
36
+ { rank=same; Exec; DevCommands }
37
+ }
Binary file