brut 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/Gemfile.lock +1 -1
  4. data/assets/MetroIcon.graffle +0 -0
  5. data/brut-css/package-lock.json +2 -2
  6. data/brut-css/package.json +1 -1
  7. data/brut-css/src/css/flex.css +1 -1
  8. data/brut-css/src/docs/includes/body-and-header.html.ejs +1 -1
  9. data/brut-js/package-lock.json +2 -2
  10. data/brut-js/package.json +1 -1
  11. data/brut-js/specs/AjaxSubmit.spec.js +100 -7
  12. data/brut-js/specs/ConstraintViolationMessage.spec.js +1 -1
  13. data/brut-js/specs/ConstraintViolationMessages.spec.js +2 -2
  14. data/brut-js/specs/Form.spec.js +5 -5
  15. data/brut-js/src/AjaxSubmit.js +76 -40
  16. data/brut-js/src/ConstraintViolationMessage.js +3 -3
  17. data/brut-js/src/ConstraintViolationMessages.js +2 -2
  18. data/brutrb.com/adrs.md +1 -0
  19. data/brutrb.com/database-schema.md +22 -2
  20. data/brutrb.com/dev-environment.md +1 -0
  21. data/brutrb.com/form-constraints.md +2 -2
  22. data/brutrb.com/handlers.md +1 -1
  23. data/brutrb.com/i18n.md +2 -2
  24. data/brutrb.com/layouts.md +1 -1
  25. data/docs/404.html +2 -2
  26. data/docs/adrs.html +5 -5
  27. data/docs/ai.html +3 -3
  28. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  29. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  30. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  31. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  32. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  33. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  34. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  35. data/docs/api/Brut/BackEnd.html +1 -1
  36. data/docs/api/Brut/CLI/App.html +1 -1
  37. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +2 -2
  39. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +6 -6
  40. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +6 -6
  42. data/docs/api/Brut/CLI/Apps/BuildAssets.html +2 -2
  43. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +6 -2
  47. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/DB/Status.html +9 -9
  50. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +8 -12
  57. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +5 -5
  59. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +384 -0
  60. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +5 -5
  62. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +36 -36
  65. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Scaffold.html +2 -2
  67. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  69. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  70. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  71. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  72. data/docs/api/Brut/CLI/Apps.html +1 -1
  73. data/docs/api/Brut/CLI/Command.html +29 -31
  74. data/docs/api/Brut/CLI/Error.html +1 -1
  75. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  76. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  77. data/docs/api/Brut/CLI/Executor.html +1 -1
  78. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  79. data/docs/api/Brut/CLI/Options.html +1 -1
  80. data/docs/api/Brut/CLI/Output.html +1 -1
  81. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  82. data/docs/api/Brut/CLI.html +1 -1
  83. data/docs/api/Brut/FactoryBot.html +1 -1
  84. data/docs/api/Brut/Framework/App.html +1 -1
  85. data/docs/api/Brut/Framework/Config.html +16 -2
  86. data/docs/api/Brut/Framework/Container.html +1 -1
  87. data/docs/api/Brut/Framework/Error.html +1 -1
  88. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  89. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  90. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  91. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  92. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  93. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  94. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  95. data/docs/api/Brut/Framework/Errors.html +1 -1
  96. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  97. data/docs/api/Brut/Framework/MCP.html +1 -1
  98. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  99. data/docs/api/Brut/Framework.html +1 -1
  100. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Component/Helpers.html +86 -2
  102. data/docs/api/Brut/FrontEnd/Component.html +78 -9
  103. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +5 -5
  104. data/docs/api/Brut/FrontEnd/Components/FormTag.html +3 -3
  105. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +12 -16
  106. data/docs/api/Brut/FrontEnd/Components/Input.html +3 -3
  107. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +3 -3
  108. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +3 -3
  109. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +3 -3
  110. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +5 -5
  111. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +3 -3
  112. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +3 -3
  114. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +3 -3
  115. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +3 -3
  116. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +3 -3
  117. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Form.html +92 -20
  121. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  133. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +2 -2
  140. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  142. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  143. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  144. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  145. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Layout.html +3 -3
  147. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Page.html +31 -23
  154. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +3 -3
  155. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  156. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  158. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  160. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  161. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  162. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  163. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  164. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  172. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  175. data/docs/api/Brut/FrontEnd.html +1 -1
  176. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  177. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  178. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  179. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  180. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  181. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  182. data/docs/api/Brut/I18n.html +1 -1
  183. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  184. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  185. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  186. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  187. data/docs/api/Brut/Instrumentation.html +1 -1
  188. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +13 -11
  189. data/docs/api/Brut/SinatraHelpers.html +1 -1
  190. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  191. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  192. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  193. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  194. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  195. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  197. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  199. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  200. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  201. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  210. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  211. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  212. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  213. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  214. data/docs/api/Brut/SpecSupport.html +1 -1
  215. data/docs/api/Brut.html +1 -1
  216. data/docs/api/Clock.html +1 -1
  217. data/docs/api/ModuleName.html +595 -0
  218. data/docs/api/RichString.html +1 -1
  219. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  220. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  221. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  222. data/docs/api/Sequel/Extensions.html +1 -1
  223. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  224. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  225. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  226. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  227. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  228. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  229. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  230. data/docs/api/Sequel/Plugins.html +1 -1
  231. data/docs/api/Sequel.html +1 -1
  232. data/docs/api/_index.html +24 -12
  233. data/docs/api/class_list.html +1 -1
  234. data/docs/api/file.README.html +1 -1
  235. data/docs/api/index.html +1 -1
  236. data/docs/api/method_list.html +416 -328
  237. data/docs/api/top-level-namespace.html +2 -2
  238. data/docs/assets/{adrs.md.JRxZ5uYE.js → adrs.md.BxjHi9-8.js} +1 -1
  239. data/docs/assets/adrs.md.BxjHi9-8.lean.js +1 -0
  240. data/docs/assets/{app.C0n_5kBs.js → app.D6BuVHo9.js} +1 -1
  241. data/docs/assets/chunks/@localSearchIndexroot.COP2Bcmp.js +1 -0
  242. data/docs/assets/chunks/{VPLocalSearchBox.yICoU-Ly.js → VPLocalSearchBox.BpvHMbx6.js} +1 -1
  243. data/docs/assets/chunks/{theme.gMVDgzXI.js → theme.wlAOvi2f.js} +2 -2
  244. data/docs/assets/{components.md.DGVPNB9a.js → components.md.iLiv2E9X.js} +3 -3
  245. data/docs/assets/{configuration.md.DK3YrtYn.js → configuration.md.DmuAdsli.js} +1 -1
  246. data/docs/assets/{database-schema.md.CSYk6E6v.js → database-schema.md.C5gXexJi.js} +10 -3
  247. data/docs/assets/{database-schema.md.CSYk6E6v.lean.js → database-schema.md.C5gXexJi.lean.js} +1 -1
  248. data/docs/assets/{dev-environment.md.Dy6EldaM.js → dev-environment.md.DRH2D2-O.js} +3 -3
  249. data/docs/assets/dev-environment.md.DRH2D2-O.lean.js +1 -0
  250. data/docs/assets/{form-constraints.md.x5tNpTTI.js → form-constraints.md.DK5adCgM.js} +2 -2
  251. data/docs/assets/{forms.md.DwdQGwOK.js → forms.md.D8aa_qI-.js} +1 -1
  252. data/docs/assets/{getting-started.md.DuiTvmpG.js → getting-started.md.DLplsDUd.js} +3 -3
  253. data/docs/assets/{getting-started.md.DuiTvmpG.lean.js → getting-started.md.DLplsDUd.lean.js} +1 -1
  254. data/docs/assets/{handlers.md.Chyri6KA.js → handlers.md.h84MMB1R.js} +1 -1
  255. data/docs/assets/{i18n.md.xQhiGo1G.js → i18n.md.BAm9t9JJ.js} +1 -1
  256. data/docs/assets/{layouts.md.CJGDFY-m.js → layouts.md.CVGl9xIO.js} +1 -1
  257. data/docs/assets.html +3 -3
  258. data/docs/brut-css/brut.max.css +1 -1
  259. data/docs/brut-css/classes/appearances.html +1 -1
  260. data/docs/brut-css/classes/background-colors.html +1 -1
  261. data/docs/brut-css/classes/border-colors.html +1 -1
  262. data/docs/brut-css/classes/borders.html +1 -1
  263. data/docs/brut-css/classes/dimensions.html +1 -1
  264. data/docs/brut-css/classes/flex.html +3 -3
  265. data/docs/brut-css/classes/foreground-colors.html +1 -1
  266. data/docs/brut-css/classes/junk-drawer.html +1 -1
  267. data/docs/brut-css/classes/layout.html +1 -1
  268. data/docs/brut-css/classes/lists.html +1 -1
  269. data/docs/brut-css/classes/positioning.html +1 -1
  270. data/docs/brut-css/classes/spacings.html +1 -1
  271. data/docs/brut-css/classes/typography.html +1 -1
  272. data/docs/brut-css/customization/advanced-configuration.html +1 -1
  273. data/docs/brut-css/customization/breakpoints.html +1 -1
  274. data/docs/brut-css/customization/design-system.html +1 -1
  275. data/docs/brut-css/customization/pseudo-classes.html +1 -1
  276. data/docs/brut-css/getting-started/core-concepts.html +1 -1
  277. data/docs/brut-css/getting-started/installation.html +1 -1
  278. data/docs/brut-css/getting-started/overview.html +1 -1
  279. data/docs/brut-css/getting-started/simple-example.html +1 -1
  280. data/docs/brut-css/index.html +1 -1
  281. data/docs/brut-css/properties/colors.html +1 -1
  282. data/docs/brut-css/properties/spacings.html +1 -1
  283. data/docs/brut-css/properties/typography.html +1 -1
  284. data/docs/brut-js/api/AjaxSubmit.html +56 -7
  285. data/docs/brut-js/api/AjaxSubmit.js.html +77 -41
  286. data/docs/brut-js/api/Autosubmit.html +1 -1
  287. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  288. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  289. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  290. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  291. data/docs/brut-js/api/BufferedLogger.html +1 -1
  292. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  293. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  294. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  295. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  296. data/docs/brut-js/api/ConstraintViolationMessage.html +2 -2
  297. data/docs/brut-js/api/ConstraintViolationMessage.js.html +4 -4
  298. data/docs/brut-js/api/ConstraintViolationMessages.html +3 -3
  299. data/docs/brut-js/api/ConstraintViolationMessages.js.html +3 -3
  300. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  301. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  302. data/docs/brut-js/api/Form.html +1 -1
  303. data/docs/brut-js/api/Form.js.html +1 -1
  304. data/docs/brut-js/api/I18nTranslation.html +1 -1
  305. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  306. data/docs/brut-js/api/LocaleDetection.html +1 -1
  307. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  308. data/docs/brut-js/api/Logger.html +1 -1
  309. data/docs/brut-js/api/Logger.js.html +1 -1
  310. data/docs/brut-js/api/Message.html +1 -1
  311. data/docs/brut-js/api/Message.js.html +1 -1
  312. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  313. data/docs/brut-js/api/RichString.html +1 -1
  314. data/docs/brut-js/api/RichString.js.html +1 -1
  315. data/docs/brut-js/api/Tabs.html +1 -1
  316. data/docs/brut-js/api/Tabs.js.html +1 -1
  317. data/docs/brut-js/api/Tracing.html +1 -1
  318. data/docs/brut-js/api/Tracing.js.html +1 -1
  319. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  320. data/docs/brut-js/api/external-Performance.html +1 -1
  321. data/docs/brut-js/api/external-Promise.html +1 -1
  322. data/docs/brut-js/api/external-ValidityState.html +1 -1
  323. data/docs/brut-js/api/external-Window.html +1 -1
  324. data/docs/brut-js/api/external-fetch.html +1 -1
  325. data/docs/brut-js/api/global.html +1 -1
  326. data/docs/brut-js/api/index.html +1 -1
  327. data/docs/brut-js/api/index.js.html +1 -1
  328. data/docs/brut-js/api/module-testing.html +1 -1
  329. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  330. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  331. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  332. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  333. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  334. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  335. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  336. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  337. data/docs/brut-js/api/testing_index.js.html +1 -1
  338. data/docs/brut-js.html +3 -3
  339. data/docs/business-logic.html +3 -3
  340. data/docs/cli.html +3 -3
  341. data/docs/components.html +7 -7
  342. data/docs/configuration.html +5 -5
  343. data/docs/css.html +3 -3
  344. data/docs/custom-element-tests.html +3 -3
  345. data/docs/database-access.html +3 -3
  346. data/docs/database-schema.html +12 -5
  347. data/docs/deployment.html +3 -3
  348. data/docs/dev-environment.html +5 -5
  349. data/docs/dir-structure.html +3 -3
  350. data/docs/doc-conventions.html +3 -3
  351. data/docs/end-to-end-tests.html +3 -3
  352. data/docs/features.html +3 -3
  353. data/docs/flash-and-session.html +3 -3
  354. data/docs/form-constraints.html +6 -6
  355. data/docs/forms.html +5 -5
  356. data/docs/getting-started.html +6 -6
  357. data/docs/handlers.html +5 -5
  358. data/docs/hashmap.json +1 -1
  359. data/docs/hooks.html +3 -3
  360. data/docs/i18n.html +5 -5
  361. data/docs/index.html +3 -3
  362. data/docs/instrumentation.html +3 -3
  363. data/docs/javascript.html +3 -3
  364. data/docs/jobs.html +3 -3
  365. data/docs/keyword-injection.html +3 -3
  366. data/docs/layouts.html +5 -5
  367. data/docs/lsp.html +3 -3
  368. data/docs/markdown-examples.html +3 -3
  369. data/docs/middleware.html +3 -3
  370. data/docs/overview.html +3 -3
  371. data/docs/pages.html +3 -3
  372. data/docs/recipes/alternate-layouts.html +3 -3
  373. data/docs/recipes/authentication.html +3 -3
  374. data/docs/recipes/blank-layouts.html +3 -3
  375. data/docs/recipes/custom-flash.html +3 -3
  376. data/docs/recipes/indexed-forms.html +3 -3
  377. data/docs/recipes/migrations.html +3 -3
  378. data/docs/recipes/text-field-component.html +3 -3
  379. data/docs/roadmap.html +3 -3
  380. data/docs/routes.html +3 -3
  381. data/docs/security.html +3 -3
  382. data/docs/seed-data.html +3 -3
  383. data/docs/space-time-continuum.html +3 -3
  384. data/docs/tutorial.html +3 -3
  385. data/docs/unit-tests.html +3 -3
  386. data/docs/why.html +3 -3
  387. data/lib/brut/cli/apps/scaffold.rb +77 -0
  388. data/lib/brut/cli/command.rb +1 -2
  389. data/lib/brut/framework/config.rb +7 -0
  390. data/lib/brut/front_end/component.rb +19 -0
  391. data/lib/brut/front_end/components/constraint_violations.rb +2 -2
  392. data/lib/brut/front_end/components/i18n_translations.rb +6 -10
  393. data/lib/brut/front_end/form.rb +5 -2
  394. data/lib/brut/front_end/page.rb +11 -7
  395. data/lib/brut/junk_drawer.rb +53 -0
  396. data/lib/brut/sinatra_helpers.rb +1 -0
  397. data/lib/brut/version.rb +1 -1
  398. metadata +30 -27
  399. data/docs/assets/adrs.md.JRxZ5uYE.lean.js +0 -1
  400. data/docs/assets/chunks/@localSearchIndexroot.BF2caq1f.js +0 -1
  401. data/docs/assets/dev-environment.md.Dy6EldaM.lean.js +0 -1
  402. /data/docs/assets/{components.md.DGVPNB9a.lean.js → components.md.iLiv2E9X.lean.js} +0 -0
  403. /data/docs/assets/{configuration.md.DK3YrtYn.lean.js → configuration.md.DmuAdsli.lean.js} +0 -0
  404. /data/docs/assets/{form-constraints.md.x5tNpTTI.lean.js → form-constraints.md.DK5adCgM.lean.js} +0 -0
  405. /data/docs/assets/{forms.md.DwdQGwOK.lean.js → forms.md.D8aa_qI-.lean.js} +0 -0
  406. /data/docs/assets/{handlers.md.Chyri6KA.lean.js → handlers.md.h84MMB1R.lean.js} +0 -0
  407. /data/docs/assets/{i18n.md.xQhiGo1G.lean.js → i18n.md.BAm9t9JJ.lean.js} +0 -0
  408. /data/docs/assets/{layouts.md.CJGDFY-m.lean.js → layouts.md.CVGl9xIO.lean.js} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 740d14b192a1f496bec06f3154a761ecd0bfaf0e838a6b8b156eda122e625c32
4
- data.tar.gz: 5c4d510c355c4c08b5ea71b984fe6fc14d6a029c9e185ca48f3040bb3a166cc4
3
+ metadata.gz: 6ddbdd2db5bb6ea9013486bc8ad279affec3d3a824d05ebbd8c4b4933a1c5468
4
+ data.tar.gz: 823232c021e8c3d5477bc5655fefe6b51c54efcfcc73ca2ad0cab535964a23f1
5
5
  SHA512:
6
- metadata.gz: 84a9428fbf87c5cb6e37303b23a508ab603936d67a276a9a60793944d3d29ef296e749d5cddb04b45ddf28250f6d35557514c9c8dcd579f9ca5aaf9538af9f78
7
- data.tar.gz: 39a687879cacc21c3c73d6ad948c0e307c13fa8dcee31575aef02e39ce0b3f45e07a81c30876a83dcdd7a1c66e4bac917cbeb80be2437fcdada2f6066fa3e49f
6
+ metadata.gz: 152283ed984e5c7c8cd8faebdc5efbb24867efbe9a18a02a6896ab28654cc9b2b5fa39fe1ac9a13a6d477bd34ec3cb42a96034492d317f5355eb9e4434825395
7
+ data.tar.gz: 52b69e78ad33ceb908bb6617960a33022f36df621b5d9f3da327a167600cd0c9dbc1902d90d86ec0bdfbef400fa3fded9261b4980feb2dd4264d5c87afb7535f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Brut CHANGELOG
2
2
 
3
+ ## v0.5.0 - July 21, 2025
4
+
5
+ * **New helper** `entity` for creating HTML entities without needing `raw(safe(...))`
6
+
7
+ ```ruby
8
+ # BEFORE
9
+ a(href:HomePage.routing) do
10
+ raw(safe("←"))
11
+ whitespace
12
+ plain("Back")
13
+ end
14
+
15
+ # AFTER
16
+ a(href:HomePage.routing) do
17
+ entity("larr") # <----
18
+ whitespace
19
+ plain("Back")
20
+ end
21
+ ```
22
+
23
+ * Insturment each component's `view_template` method to give a breakdown on
24
+ performance per component
25
+ * Added a few additional events for tracing
26
+ * Fix bug in `bin/scaffold db_model` where `implementation_is_trivial` was misspelled
27
+
28
+ ## v0.4.0 - July 12, 2025 and July 16, 2025 (see note)
29
+
30
+ * **Breaking Change** - changed `cv.fe` and `cv.be` I18n keys to `cv.cs` and `cv.ss`
31
+ - This is to be consistent with Brut terminology
32
+ - To address this change:
33
+ 1. Update brut-js to 0.0.22
34
+ 2. Modify your `app/config/*/*.rb` i18n files to change `cv:` to `cs:` and `be:`
35
+ to `ss:`
36
+ 3. Change `app/src/front_end/layouts/default_layout.rb`:
37
+
38
+ ```diff
39
+ - I18nTranslations("cv.fe")
40
+ + I18nTranslations("cv.cs")
41
+ ```
42
+ 4. Change anywhere in your code or tests that you refer to those keys
43
+ * Added `#valid?` to `Brut::FrontEnd::Form` to make it easier to check a lack
44
+ of constraint violations
45
+ * Added response body to `<brut-ajax-submit>`
46
+
47
+ **NOTE:** BrutJS and BrutCSS versions were changed to 0.4.0 on July 16 to mirror the Brut RubyGem. Intention is to keep all three in sync to avoid confusion about what the versions mean.
48
+
3
49
  ## v0.3.1 - July 12, 2025
4
50
 
5
51
  * **`bin/db new_migration` includes link to the migrations recipe**
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brut (0.3.1)
4
+ brut (0.5.0)
5
5
  concurrent-ruby
6
6
  i18n
7
7
  irb
Binary file
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "brut-css",
3
- "version": "0.0.1",
3
+ "version": "0.5.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "brut-css",
9
- "version": "0.0.1",
9
+ "version": "0.5.0",
10
10
  "license": "Hippocratic-2.1",
11
11
  "devDependencies": {
12
12
  "comment-parser": "^1.4.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brut-css",
3
- "version": "0.0.1",
3
+ "version": "0.5.0",
4
4
  "description": "Utility CSS Library for Full Stack Developers",
5
5
  "keywords": [
6
6
  "css"
@@ -138,7 +138,7 @@
138
138
  }
139
139
  /* Flex shrink, using a six-step scale.
140
140
  *
141
- * @scale flex-grow
141
+ * @scale flex-shrink
142
142
  */
143
143
  .flex-shrink-0 {
144
144
  flex-shrink: 0;
@@ -3,7 +3,7 @@
3
3
  <div class="flex gap-3 justify-between items-center">
4
4
  <h1><a href="<%= pathToBrutCSSRoot %>" class="black tdn">BrutCSS Reference Documentation</a></h1>
5
5
  <ul class="lst-none pl-0 flex gap-2 justify-end items-center">
6
- <form class="flex items-center gap-1 mr-3">
6
+ <form class="dn flex items-center gap-1 mr-3">
7
7
  <label>
8
8
  <input type="search" placeholder="e.g. padding-left" class="pa-2 br-2 ba bc-gray-600 bg-white-ish gray-200">
9
9
  <span class="sr-only">Search term</span>
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "brut-js",
3
- "version": "0.0.21",
3
+ "version": "0.5.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "brut-js",
9
- "version": "0.0.21",
9
+ "version": "0.5.0",
10
10
  "license": "Hippocratic-2.1",
11
11
  "devDependencies": {
12
12
  "esbuild": "^0.24.0",
data/brut-js/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brut-js",
3
- "version": "0.0.21",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "keywords": [ "WebComponents", "Custom Elements" ],
6
6
  "bugs": {
@@ -10,7 +10,7 @@ describe("<brut-ajax-submit>", () => {
10
10
  </brut-ajax-submit>
11
11
  </form>
12
12
  `).onFetch( "/foo", [
13
- { then: { status: 200 }},
13
+ { then: { status: 200, text: "<div>some html</div>" }},
14
14
  ]
15
15
  ).test("submits the form, setting various attributes during the lifecycle",
16
16
  ({document,assert,fetchRequests,waitForSetTimeout,readRequestBodyIntoString}) => {
@@ -21,8 +21,10 @@ describe("<brut-ajax-submit>", () => {
21
21
  const number = document.querySelector("input[name=some-number]")
22
22
 
23
23
  let okReceived = false
24
- element.addEventListener("brut:submitok", () => {
24
+ let detailReceived = null
25
+ element.addEventListener("brut:submitok", (event) => {
25
26
  okReceived = true
27
+ detailReceived = event.detail
26
28
  })
27
29
 
28
30
  text.value = "Some Text"
@@ -37,6 +39,8 @@ describe("<brut-ajax-submit>", () => {
37
39
 
38
40
  return Promise.all(promises).then( () => {
39
41
  assert(okReceived)
42
+ assert(detailReceived)
43
+ assert.equal(detailReceived.body.innerHTML,"<div>some html</div>")
40
44
  assert(element.getAttribute("requesting") == null)
41
45
  assert(element.getAttribute("submitted") != null)
42
46
  waitForSetTimeout(11).then( () => {
@@ -58,7 +62,7 @@ describe("<brut-ajax-submit>", () => {
58
62
  const number = form.querySelector("input[name=some-number]")
59
63
 
60
64
  let okReceived = false
61
- element.addEventListener("brut:submitok", () => {
65
+ element.addEventListener("brut:submitok", (event) => {
62
66
  okReceived = true
63
67
  })
64
68
 
@@ -204,16 +208,22 @@ Error that should be ignored
204
208
  }
205
209
  },
206
210
  ]
207
- ).test("when we get a 422, parses the result from the server", ({document,window,assert,fetchRequests,waitForSetTimeout}) => {
211
+ ).test("when we get a 422, parses the result from the server and inserts them into the DOM, event detail is null", ({document,window,assert,fetchRequests,waitForSetTimeout}) => {
208
212
  const form = document.querySelector("form")
209
213
  const element = form.querySelector("brut-ajax-submit")
210
214
  const button = element.querySelector("button")
211
215
  const text = form.querySelector("input[name=some-text]")
212
216
  const number = form.querySelector("input[name=some-number]")
213
217
 
214
- let okReceived = 0
218
+ let okReceived = false
219
+ let submittedInvalidReceived = false
220
+ let submittedInvalidDetail = "not null"
215
221
  element.addEventListener("brut:submitok", () => {
216
- okReceived++
222
+ okReceived = true
223
+ })
224
+ element.addEventListener("brut:submitinvalid", (event) => {
225
+ submittedInvalidReceived = true
226
+ submittedInvalidDetail = event.detail
217
227
  })
218
228
 
219
229
  text.value = "Some Text"
@@ -227,7 +237,9 @@ Error that should be ignored
227
237
  map( (fetchRequest) => fetchRequest.promiseReturned )
228
238
 
229
239
  return Promise.all(promises).then( () => {
230
- assert.equal(okReceived,0)
240
+ assert(!okReceived)
241
+ assert( submittedInvalidReceived)
242
+ assert(!submittedInvalidDetail)
231
243
  const textFieldErrors = form.querySelectorAll("brut-cv-messages[input-name='some-text'] brut-cv")
232
244
  assert.equal(3,textFieldErrors.length) // prevous client-side and 2 new server-side
233
245
  const serverSideTextFieldErrors = form.querySelectorAll("brut-cv-messages[input-name='some-text'] brut-cv[server-side]")
@@ -253,4 +265,85 @@ Error that should be ignored
253
265
  })
254
266
  })
255
267
  })
268
+ withHTML(`
269
+ <form action="http://example.net/foo" method="POST">
270
+
271
+ <input required type="text" name="some-text">
272
+ <brut-cv-messages input-name="some-text">
273
+ <brut-cv input-name="some-text">
274
+ A client-side-error
275
+ </brut-cv>
276
+ <brut-cv server-side input-name="some-text">
277
+ A previous server-side-error
278
+ </brut-cv>
279
+ </brut-cv-messages>
280
+
281
+ <input required type="number" name="some-number">
282
+ <brut-cv-messages input-name="some-number">
283
+ </brut-cv-messages>
284
+
285
+ <brut-ajax-submit submitted-lifetime="10" no-server-side-error-parsing>
286
+ <button>Submit</button>
287
+ </brut-ajax-submit>
288
+ </form>
289
+ `).onFetch( "/foo", [
290
+ {
291
+ then: {
292
+ status: 422,
293
+ text: `
294
+ <brut-cv input-name="some-text">
295
+ A sever-side error
296
+ </brut-cv>
297
+ <brut-cv input-name="some-text">
298
+ Another sever-side error
299
+ </brut-cv>
300
+ <brut-cv input-name="some-other-text">
301
+ Irrelevant server-side error
302
+ </brut-cv>
303
+ <brut-cv>
304
+ Error that should be ignored
305
+ </brut-cv>
306
+ <div>element that should be ignored</div>
307
+ `
308
+ }
309
+ },
310
+ ]
311
+ ).test("when we get a 422, does not parse the results, but includes them in the detail", ({document,window,assert,fetchRequests,waitForSetTimeout}) => {
312
+ const form = document.querySelector("form")
313
+ const element = form.querySelector("brut-ajax-submit")
314
+ const button = element.querySelector("button")
315
+ const text = form.querySelector("input[name=some-text]")
316
+ const number = form.querySelector("input[name=some-number]")
317
+
318
+ let okReceived = false
319
+ let submittedInvalidReceived = false
320
+ let submittedInvalidDetail = null
321
+ element.addEventListener("brut:submitok", () => {
322
+ okReceived = true
323
+ })
324
+ element.addEventListener("brut:submitinvalid", (event) => {
325
+ submittedInvalidReceived = true
326
+ submittedInvalidDetail = event.detail
327
+ })
328
+
329
+ text.value = "Some Text"
330
+ number.value = "11"
331
+
332
+ button.click()
333
+ return waitForSetTimeout(5).then( () => {
334
+
335
+ const promises = fetchRequests.
336
+ filter( (fetchRequest) => fetchRequest.promiseReturned ).
337
+ map( (fetchRequest) => fetchRequest.promiseReturned )
338
+
339
+ return Promise.all(promises).then( () => {
340
+ assert(!okReceived)
341
+ assert( submittedInvalidReceived)
342
+ assert.equal(submittedInvalidDetail.body.children.length,5)
343
+ const textFieldErrors = form.querySelectorAll("brut-cv-messages[input-name='some-text'] brut-cv")
344
+ assert.equal(2,textFieldErrors.length) // what was initially rendered
345
+
346
+ })
347
+ })
348
+ })
256
349
  })
@@ -23,7 +23,7 @@ describe("<brut-cv>", () => {
23
23
  withHTML(`
24
24
  <brut-i18n-translation key="problem" value="%{field} has a problem"></brut-i18n-translation>
25
25
  <brut-i18n-translation key="cv.this_field" value="THAT FIELD"></brut-i18n-translation>
26
- <brut-i18n-translation key="cv.fe.fieldNames.some-field" value="Some Field"></brut-i18n-translation>
26
+ <brut-i18n-translation key="cv.cs.fieldNames.some-field" value="Some Field"></brut-i18n-translation>
27
27
 
28
28
  <brut-cv input-name="some-field" key="problem"></brut-cv>
29
29
  `).test("Inserts using the this_field key as the placeholder", ({document,assert}) => {
@@ -2,8 +2,8 @@ import { withHTML } from "./SpecHelper.js"
2
2
 
3
3
  describe("<brut-cv-messages>", () => {
4
4
  withHTML(`
5
- <brut-i18n-translation key="cv.fe.patternMismatch" value="%{field} does not match the pattern"></brut-i18n-translation>
6
- <brut-i18n-translation key="cv.fe.rangeOverflow" value="%{field} is above the range"></brut-i18n-translation>
5
+ <brut-i18n-translation key="cv.cs.patternMismatch" value="%{field} does not match the pattern"></brut-i18n-translation>
6
+ <brut-i18n-translation key="cv.cs.rangeOverflow" value="%{field} is above the range"></brut-i18n-translation>
7
7
 
8
8
  <brut-cv-messages input-name="some-field"></brut-cv-messages>
9
9
  `).test("Inserts constraint violation messages based on validity state", ({document,assert}) => {
@@ -51,9 +51,9 @@ describe("<brut-form>", () => {
51
51
  assert(gotInvalid)
52
52
  assert.equal(brutForm.getAttribute("submitted-invalid"),"")
53
53
 
54
- let error = textFieldLabel.querySelector("brut-cv[key='cv.fe.valueMissing']")
54
+ let error = textFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
55
55
  assert(error)
56
- error = numberFieldLabel.querySelector("brut-cv[key='cv.fe.valueMissing']")
56
+ error = numberFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
57
57
  assert(error)
58
58
 
59
59
  const textField = textFieldLabel.querySelector("input")
@@ -71,9 +71,9 @@ describe("<brut-form>", () => {
71
71
  assert(gotInvalid)
72
72
  assert.equal(brutForm.getAttribute("submitted-invalid"),"")
73
73
 
74
- error = textFieldLabel.querySelector("brut-cv[key='cv.fe.valueMissing']")
74
+ error = textFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
75
75
  assert(!error)
76
- error = numberFieldLabel.querySelector("brut-cv[key='cv.fe.valueMissing']")
76
+ error = numberFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
77
77
  assert(error)
78
78
 
79
79
  const numberField = numberFieldLabel.querySelector("input")
@@ -174,7 +174,7 @@ describe("<brut-form>", () => {
174
174
  assert(gotInvalid)
175
175
  assert.equal(brutForm.getAttribute("submitted-invalid"),"")
176
176
 
177
- let error = brutForm.querySelector("brut-cv[input-name='text'][key='cv.fe.valueMissing']")
177
+ let error = brutForm.querySelector("brut-cv[input-name='text'][key='cv.cs.valueMissing']")
178
178
  assert(error)
179
179
 
180
180
  })
@@ -13,7 +13,13 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
13
13
  * 4. If the request returns OK:
14
14
  * - `requesting` will be removed and `submitted` will be added.
15
15
  * - `submitted` will be removed after `submitted-lifetime` ms.
16
- * 5. If the request returned a 422, error messages are parsed. See below.
16
+ * - the `brut:submitok` event will be fired with the response text, **parsed as HTML**, as `event.detail`.
17
+ * 5. If the request returned a 422:
18
+ * - If you have set `no-server-side-error-parsing`, the results will be included in the
19
+ * detail field of the `brut:submitinvalid` event.
20
+ * - If you have NOT set `no-server-side-error-parsing`, the response is parsed as
21
+ * errors to be inserted into the DOM. See below for how that works. In this case,
22
+ * `brut:submitinvalid`'s detail bill be null.
17
23
  * 6. If the request returns not OK and not 422:
18
24
  * - if it has been `request-timeout` ms or more since the button was first clicked, the operation is aborted (see below).
19
25
  * - if it has been less than `request-timeout` ms and the HTTP status code was 5xx, the operation is retried.
@@ -23,7 +29,8 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
23
29
  * Aborting the operation will submit the form in the normal way, allowing the browser to deal with whatever the issue is. You can set
24
30
  * `log-request-errors` to introspect this process.
25
31
  *
26
- * For a 422 response, this element assumes the response is `text/html` and contains one or more `<brut-cv>`
32
+ * For a 422 response (where `no-server-side-error-parsing` is *not* set),
33
+ * this element assumes the response is `text/html` and contains one or more `<brut-cv>`
27
34
  * elements. These elements will be inserted into the proper `<brut-cv-messages>` element, as follows:
28
35
  *
29
36
  * 1. The `input-name` is examined.
@@ -37,6 +44,17 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
37
44
  * 9. The first input located is scrolled into view
38
45
  * 10. If the input is modified after this all happens, custom validity is cleared
39
46
  *
47
+ * For the server you are contacting, this element has a few requirements:
48
+ *
49
+ * - If everything is OK/the operation did what it was intended to do:
50
+ * - the server will respond with a 2xx
51
+ * - the response body, if it contains anything, be `text/html` (this is provided in the event detail)
52
+ * - If there are server-side constraint violations.
53
+ * - the server will return 422
54
+ * - the response body will be `text/html`
55
+ * - the response body will contain one or more `<brut-cv>` elements
56
+ *
57
+ * @property {boolean} no-server-side-error-parsing - if set, the response body for a 422 will not be parsed and inserted into the DOM. Instead, the body will be part of the detail of the `brut:submitinvalid` event.
40
58
  * @property {number} request-timeout - number of ms that the entire operation is expected to complete within. Default is 5000
41
59
  * @property {number} submitted-lifetime - number of ms that "submitted" should remain on the element after the form has completed. Default is 2000
42
60
  * @property {boolean} requesting - boolean attribute that indicates the request has been made, but not yet returned. Don't set this yourself outside of development. It will be set and removed by this element.
@@ -44,8 +62,8 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
44
62
  * @property {boolean} log-request-errors - if set, logging related to request error handling will appear in the console. It will also
45
63
  * cause any form submission to be delayed by 2s to allow you to read the console.
46
64
  *
47
- * @fires brut:submitok Fired when the AJAX request initated by this returns OK and all processing has completed
48
- * @fires brut:submitinvalid Fired when the AJAX request initated by this returns a 422 and all logic around managing the reponse has completed
65
+ * @fires brut:submitok Fired when the AJAX request initated by this returns OK and all processing has completed. The detail will include the *parsed document* of the HTML returned in the response.
66
+ * @fires brut:submitinvalid Fired when the AJAX request initated by this returns a 422 and all logic around managing the reponse has completed. The detail will be null unless `no-server-side-error-parsing` is set, in which case it will be the parsed document of the HTML returned in the response.
49
67
  *
50
68
  * @example
51
69
  * <form action="/widgets" method="post">
@@ -68,6 +86,7 @@ class AjaxSubmit extends BaseCustomElement {
68
86
  "request-timeout",
69
87
  "max-retry-attempts",
70
88
  "log-request-errors",
89
+ "no-server-side-error-parsing",
71
90
  ]
72
91
 
73
92
  #requestErrorLogger = () => {}
@@ -75,6 +94,12 @@ class AjaxSubmit extends BaseCustomElement {
75
94
  #submittedLifetime = 2000
76
95
  #requestTimeout = 5000
77
96
  #maxRetryAttempts = 25
97
+ #serverSideErrorParsing = true
98
+
99
+ constructor() {
100
+ super()
101
+ this.domParser = new DOMParser()
102
+ }
78
103
 
79
104
  submittedLifetimeChangedCallback({newValue}) {
80
105
  const newValueAsInt = parseInt(newValue)
@@ -84,6 +109,10 @@ class AjaxSubmit extends BaseCustomElement {
84
109
  this.#submittedLifetime = newValueAsInt
85
110
  }
86
111
 
112
+ noServerSideErrorParsingChangedCallback({newValueAsBoolean}) {
113
+ this.#serverSideErrorParsing = !newValueAsBoolean
114
+ }
115
+
87
116
  maxRetryAttemptsChangedCallback({newValue}) {
88
117
  const num = parseInt(newValue)
89
118
  if (isNaN(num)) {
@@ -187,7 +216,10 @@ class AjaxSubmit extends BaseCustomElement {
187
216
  this.setAttribute("submitted",true)
188
217
 
189
218
  setTimeout( () => this.removeAttribute("submitted"), this.#submittedLifetime )
190
- this.dispatchEvent(new CustomEvent("brut:submitok"))
219
+ response.text().then( (text) => {
220
+ const parsedDocument = this.domParser.parseFromString(text,"text/html")
221
+ this.dispatchEvent(new CustomEvent("brut:submitok", { detail: parsedDocument }))
222
+ })
191
223
  }
192
224
  else {
193
225
 
@@ -250,49 +282,53 @@ class AjaxSubmit extends BaseCustomElement {
250
282
  #handleConstraintViolations(response) {
251
283
  let resubmit = false
252
284
  response.text().then( (text) => {
253
- try {
254
- const inputsToMessages = ErrorMessagesForInput.mapInputsToErrorMessages(
255
- this.#errorMessagesFromServer(text),
256
- this.#requestErrorLogger
257
- )
285
+ const parsedDocument = this.domParser.parseFromString(text,"text/html")
286
+ let event
287
+ if (this.#serverSideErrorParsing) {
288
+ event = new CustomEvent("brut:submitinvalid")
289
+ }
290
+ else {
291
+ event = new CustomEvent("brut:submitinvalid", { detail: parsedDocument })
292
+ }
293
+ this.dispatchEvent(event)
294
+ if (this.#serverSideErrorParsing) {
295
+ const constraintViolationNodes = parsedDocument.querySelectorAll(ConstraintViolationMessage.tagName)
296
+ try {
297
+ const inputsToMessages = ErrorMessagesForInput.mapInputsToErrorMessages(
298
+ constraintViolationNodes,
299
+ this.#requestErrorLogger
300
+ )
301
+
302
+ let inputToScrollToAfterReportingValidity
303
+ for (const [inputName, {input, messagesElement, errorMessages}] of Object.entries(inputsToMessages)) {
304
+ if (!inputToScrollToAfterReportingValidity) {
305
+ inputToScrollToAfterReportingValidity = input
306
+ }
307
+ messagesElement.clearServerSideMessages()
308
+ errorMessages.forEach( (element) => {
309
+ ConstraintViolationMessage.markServerSide(element)
310
+ messagesElement.appendChild(element)
311
+ })
312
+ this.#setCustomValidityThatClearsOnChange(input,errorMessages)
313
+ }
258
314
 
259
- let inputToScrollToAfterReportingValidity
260
- for (const [inputName, {input, messagesElement, errorMessages}] of Object.entries(inputsToMessages)) {
261
- if (!inputToScrollToAfterReportingValidity) {
262
- inputToScrollToAfterReportingValidity = input
315
+ if (inputToScrollToAfterReportingValidity) {
316
+ inputToScrollToAfterReportingValidity.scrollIntoView()
263
317
  }
264
- messagesElement.clearServerSideMessages()
265
- errorMessages.forEach( (element) => {
266
- ConstraintViolationMessage.markServerSide(element)
267
- messagesElement.appendChild(element)
268
- })
269
- this.#setCustomValidityThatClearsOnChange(input,errorMessages)
318
+ resubmit = false
319
+ this.removeAttribute("requesting")
270
320
  }
271
-
272
- if (inputToScrollToAfterReportingValidity) {
273
- inputToScrollToAfterReportingValidity.scrollIntoView()
321
+ catch (e) {
322
+ this.#requestErrorLogger("While parsing %s, got %s", text, e)
323
+ resubmit = true
324
+ }
325
+ if (resubmit) {
326
+ this.#submitFormThroughBrowser(form)
274
327
  }
275
- resubmit = false
276
- this.removeAttribute("requesting")
277
- this.dispatchEvent(new CustomEvent("brut:submitinvalid"))
278
- }
279
- catch (e) {
280
- this.#requestErrorLogger("While parsing %s, got %s", text, e)
281
- resubmit = true
282
- }
283
- if (resubmit) {
284
- this.#submitFormThroughBrowser(form)
285
328
  }
286
329
  })
287
330
  }
288
331
 
289
- #errorMessagesFromServer(text) {
290
- const parser = new DOMParser()
291
- const fragment = parser.parseFromString(text,"text/html")
292
-
293
- return fragment.querySelectorAll(ConstraintViolationMessage.tagName)
294
- }
295
-
296
332
  #setCustomValidityThatClearsOnChange(input,errorMessages) {
297
333
  input.setCustomValidity(errorMessages[0].textContent)
298
334
  input.reportValidity()
@@ -7,7 +7,7 @@ import I18nTranslation from "./I18nTranslation"
7
7
  *
8
8
  * Here is how the field's name is determined:
9
9
  *
10
- * 1. It will look for a `<brut-i18n-translation>` element with the `key` `cv.fe.fieldNames.«input-name»`.
10
+ * 1. It will look for a `<brut-i18n-translation>` element with the `key` `cv.cs.fieldNames.«input-name»`.
11
11
  * 2. If that's not found, it will attempt to use "this field" by locating a `<brut-i18n-translation>` element with the `key`
12
12
  * `cv.this_field` (the underscore being what is used on Brut's server side).
13
13
  * 3. If that is not found, it will use the literaly string "this field" and emit a console warning.
@@ -37,7 +37,7 @@ class ConstraintViolationMessage extends BaseCustomElement {
37
37
 
38
38
  static createElement(document,attributes) {
39
39
  const element = document.createElement(ConstraintViolationMessage.tagName)
40
- element.setAttribute("key",this.i18nKey("fe", attributes.key))
40
+ element.setAttribute("key",this.i18nKey("cs", attributes.key))
41
41
  element.setAttribute("input-name",attributes["input-name"])
42
42
  if (Object.hasOwn(attributes,"show-warnings")) {
43
43
  element.setAttribute("show-warnings",attributes["show-warnings"])
@@ -79,7 +79,7 @@ class ConstraintViolationMessage extends BaseCustomElement {
79
79
  }
80
80
 
81
81
  inputNameChangedCallback({newValue}) {
82
- this.#inputNameKey = this.#i18nKey("fe", "fieldNames", newValue)
82
+ this.#inputNameKey = this.#i18nKey("cs", "fieldNames", newValue)
83
83
  }
84
84
 
85
85
  serverSideChangedCallback({newValueAsBoolean}) {
@@ -38,9 +38,9 @@ class ConstraintViolationMessages extends BaseCustomElement {
38
38
  * This should be called as part of a Form validation event to provide a customized UX for
39
39
  * the error messages, beyond what the browser would do by default. The keys used are the same
40
40
  * as the attributes of a `ValidityState`, so for example, a range underflow would mean that `validity.rangeUnderflow` would return
41
- * true. Thus, a `<brut-cv>` would be created with `key="cv.fe.rangeUnderflow"`.
41
+ * true. Thus, a `<brut-cv>` would be created with `key="cv.cs.rangeUnderflow"`.
42
42
  *
43
- * The `cv.fe` is hard-coded to be consistent with Brut's server-side translation management.
43
+ * The `cv.cs` is hard-coded to be consistent with Brut's server-side translation management.
44
44
  *
45
45
  * @param {ValidityState} validityState - the return from an element's `validity` when it's found to have constraint violations.
46
46
  * @param {String} inputName - the element's `name`.
data/brutrb.com/adrs.md CHANGED
@@ -13,3 +13,4 @@ them, and mostly hold up.
13
13
  * [Brut will not provider mailers, cloud storage, or background jobs](https://adrs.cloud/shared_adrs/padr_e7078d29f00710848ebbe43aea6094a6)
14
14
  * [Requiring files should not execute code](https://adrs.cloud/shared_adrs/padr_03b5cf8e34bb7900a38f6de27e50adaa)
15
15
  * [Phlex will be used for HTML templating](https://adrs.cloud/shared_adrs/padr_9529a5ada671254b5e5865b45abde92d)
16
+ * [RSpec is the default testing framework](https://adrs.cloud/shared_adrs/padr_724a65340d734970ea222593a4a2760f)
@@ -19,8 +19,7 @@ Brut's provides this via Sequel. See [both](https://sequel.jeremyevans.net/rdoc/
19
19
 
20
20
  ### Creating Migrations
21
21
 
22
- To create a migration, use `bin/db new-migration`. It accepts any number of arguments that will be joined
23
- together to form the filename:
22
+ To create a migration, use `bin/db new-migration`. It accepts any number of arguments that will be joined together to form the filename:
24
23
 
25
24
  ```
26
25
  > bin/db new-migration user accounts
@@ -28,6 +27,27 @@ together to form the filename:
28
27
  app/src/back_end/data_models/migrations/20250508132646_user-accounts.rb
29
28
  ```
30
29
 
30
+ If you will be creating [database models](/database-access) as well, you may find it
31
+ easier to use `bin/scaffold db_model`, which will create an empty database model
32
+ class, empty test, and empty factory, along with an outline of your migration:
33
+
34
+ ```
35
+ bin/scaffold db_model widget
36
+ [ bin/scaffold ] Executing ["bin/db new_migration create_widget"]
37
+ [ bin/db ] Migration created:
38
+ app/src/back_end/data_models/migrations/20250712182257_create_widgets.rb
39
+ [ bin/scaffold ] ["bin/db new_migration create_widgets"] succeeded
40
+ [ bin/scaffold ] Creating DB::Foo in app/src/back_end/data_models/db/widget.rb
41
+ [ bin/scaffold ] Creating spec for DB::Foo in specs/back_end/data_models/db/widget.spec.rb
42
+ [ bin/scaffold ] Creating factory for DB::Foo in specs/factories/db/widget.factory.rb
43
+ ```
44
+
45
+ > [!IMPORTANT]
46
+ > Brut doesn't do pluralization logic. Although Sequel does do some, you should
47
+ > not refer to your database model plurally. If you were do run `bin/scaffold
48
+ > db_model widgets`, you'd create the class `DB::Widgets`, which would not work.
49
+ > Be aware.
50
+
31
51
  Note that the files are located in `app/src/back_end/data_models/migrations` and
32
52
  have a name prefixed with a timestamp. This timestamp determins an ordering of how
33
53
  the files are applied to the database.
@@ -107,6 +107,7 @@ you full documentation about what the command and subcommands do.
107
107
  | | `custom_element_test` | Create a test for a custom element in your app |
108
108
  | | `form` | Create a form and handler |
109
109
  | | `page` | Create a new page and associated test |
110
+ | | `db_model` | Create one or more database models, specs, and factories, plus a migration to create the tables for those models |
110
111
  | | `test` | Create the shell of a unit test based on an existing source file |
111
112
  | | `test:e2e` | Create the shell of an end-to-end test |
112
113
  | <code style="white-space: nowrap">bin/test</code> | | Run tests |