brut 0.9.2 → 0.11.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 (385) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/Gemfile.lock +15 -15
  4. data/assets/YouTubeThumb.pxd +0 -0
  5. data/bin/new-version +3 -3
  6. data/brut-css/package-lock.json +2 -2
  7. data/brut-css/package.json +1 -1
  8. data/brut-js/package-lock.json +2 -2
  9. data/brut-js/package.json +1 -1
  10. data/brut-js/specs/ConstraintViolationMessages.spec.js +4 -1
  11. data/brut-js/specs/Form.spec.js +2 -46
  12. data/brut-js/src/ConstraintViolationMessage.js +17 -2
  13. data/brut-js/src/Form.js +19 -23
  14. data/brutrb.com/.vitepress/config.mjs +1 -0
  15. data/brutrb.com/form-constraints.md +6 -6
  16. data/brutrb.com/getting-started.md +3 -0
  17. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element-styled.png +0 -0
  18. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element.png +0 -0
  19. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser.png +0 -0
  20. data/brutrb.com/images/tutorial/02-confirmation-flow.graffle +0 -0
  21. data/brutrb.com/images/tutorial/02-confirmation-flow.png +0 -0
  22. data/brutrb.com/instrumentation.md +142 -3
  23. data/brutrb.com/recipes/form-errors.md +148 -0
  24. data/brutrb.com/tutorial.md +29 -1627
  25. data/brutrb.com/tutorials/01-intro.md +1630 -0
  26. data/brutrb.com/tutorials/02-dialog.md +569 -0
  27. data/docs/404.html +2 -2
  28. data/docs/adrs.html +4 -4
  29. data/docs/ai.html +4 -4
  30. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  31. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  32. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  33. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  34. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  35. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  36. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  37. data/docs/api/Brut/BackEnd.html +1 -1
  38. data/docs/api/Brut/CLI/App.html +1 -1
  39. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  67. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  69. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  70. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  71. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  72. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  73. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  74. data/docs/api/Brut/CLI/Apps.html +1 -1
  75. data/docs/api/Brut/CLI/Command.html +1 -1
  76. data/docs/api/Brut/CLI/Error.html +1 -1
  77. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  78. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  79. data/docs/api/Brut/CLI/Executor.html +1 -1
  80. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  81. data/docs/api/Brut/CLI/Options.html +1 -1
  82. data/docs/api/Brut/CLI/Output.html +1 -1
  83. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  84. data/docs/api/Brut/CLI.html +1 -1
  85. data/docs/api/Brut/FactoryBot.html +1 -1
  86. data/docs/api/Brut/Framework/App.html +1 -1
  87. data/docs/api/Brut/Framework/Config.html +1 -1
  88. data/docs/api/Brut/Framework/Container.html +1 -1
  89. data/docs/api/Brut/Framework/Error.html +1 -1
  90. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  91. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  92. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  93. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  94. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  95. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  96. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  97. data/docs/api/Brut/Framework/Errors.html +1 -1
  98. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  99. data/docs/api/Brut/Framework/MCP.html +1 -1
  100. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  101. data/docs/api/Brut/Framework.html +1 -1
  102. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +48 -27
  106. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  120. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  136. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  145. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  146. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  147. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  148. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  157. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  160. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  161. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  162. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  163. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  164. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  165. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  166. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  167. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  172. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  175. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  176. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  177. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  178. data/docs/api/Brut/FrontEnd.html +1 -1
  179. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  180. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  181. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  182. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  183. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  184. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  185. data/docs/api/Brut/I18n.html +1 -1
  186. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  187. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  188. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  189. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  190. data/docs/api/Brut/Instrumentation.html +1 -1
  191. data/docs/api/Brut/RubocopConfig.html +1 -1
  192. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  193. data/docs/api/Brut/SinatraHelpers.html +1 -1
  194. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  195. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  197. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  199. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  200. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  201. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  202. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  210. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  211. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  212. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  213. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  214. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  215. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  216. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  217. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  218. data/docs/api/Brut/SpecSupport.html +1 -1
  219. data/docs/api/Brut.html +1 -1
  220. data/docs/api/Clock.html +1 -1
  221. data/docs/api/ModuleName.html +1 -1
  222. data/docs/api/RichString.html +1 -1
  223. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  224. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  225. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  226. data/docs/api/Sequel/Extensions.html +1 -1
  227. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  228. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  229. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  230. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  231. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  232. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  233. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  234. data/docs/api/Sequel/Plugins.html +1 -1
  235. data/docs/api/Sequel.html +1 -1
  236. data/docs/api/_index.html +1 -1
  237. data/docs/api/file.README.html +1 -1
  238. data/docs/api/index.html +1 -1
  239. data/docs/api/top-level-namespace.html +1 -1
  240. data/docs/assets/02-confirmation-dialog-browser-element-styled.3NEGM20-.png +0 -0
  241. data/docs/assets/02-confirmation-dialog-browser-element.DPsf0xUW.png +0 -0
  242. data/docs/assets/02-confirmation-dialog-browser.DH8ALFO4.png +0 -0
  243. data/docs/assets/02-confirmation-flow.D9gZ0S5U.png +0 -0
  244. data/docs/assets/{app.DmIaHRc4.js → app.0-aKXKdt.js} +1 -1
  245. data/docs/assets/chunks/@localSearchIndexroot.DPhqaz1b.js +1 -0
  246. data/docs/assets/chunks/{VPLocalSearchBox.C2QeRbCY.js → VPLocalSearchBox.CW-UBkNA.js} +1 -1
  247. data/docs/assets/chunks/{theme.CFKmBRX5.js → theme.a6feKWJO.js} +2 -2
  248. data/docs/assets/{components.md.BBHib1uV.js → components.md.BzVRwegp.js} +3 -3
  249. data/docs/assets/{configuration.md.CJ0MmIps.js → configuration.md.eM5wFVi5.js} +1 -1
  250. data/docs/assets/{form-constraints.md.DK5adCgM.js → form-constraints.md.KTv5cdR4.js} +6 -6
  251. data/docs/assets/{forms.md.D5jFZIJz.js → forms.md.B3BHvCV3.js} +1 -1
  252. data/docs/assets/{getting-started.md.abLTnd_n.js → getting-started.md.BgR0ZHsl.js} +5 -2
  253. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.js +66 -0
  254. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +1 -0
  255. data/docs/assets/tutorial.md.BM40jnoq.js +27 -0
  256. data/docs/assets/tutorial.md.BM40jnoq.lean.js +1 -0
  257. data/docs/assets/{tutorial.md.C4zR5XPG.js → tutorials_01-intro.md.BXvYWcO9.js} +5 -25
  258. data/docs/assets/tutorials_01-intro.md.BXvYWcO9.lean.js +1 -0
  259. data/docs/assets/tutorials_02-dialog.md.CIeg8R--.js +274 -0
  260. data/docs/assets/tutorials_02-dialog.md.CIeg8R--.lean.js +1 -0
  261. data/docs/assets.html +4 -4
  262. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  263. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  264. data/docs/brut-js/api/Autosubmit.html +1 -1
  265. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  266. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  267. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  268. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  269. data/docs/brut-js/api/BufferedLogger.html +1 -1
  270. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  271. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  272. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  273. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  274. data/docs/brut-js/api/ConstraintViolationMessage.html +55 -5
  275. data/docs/brut-js/api/ConstraintViolationMessage.js.html +18 -3
  276. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  277. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  278. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  279. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  280. data/docs/brut-js/api/Form.html +7 -10
  281. data/docs/brut-js/api/Form.js.html +20 -24
  282. data/docs/brut-js/api/I18nTranslation.html +1 -1
  283. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  284. data/docs/brut-js/api/LocaleDetection.html +1 -1
  285. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  286. data/docs/brut-js/api/Logger.html +1 -1
  287. data/docs/brut-js/api/Logger.js.html +1 -1
  288. data/docs/brut-js/api/Message.html +1 -1
  289. data/docs/brut-js/api/Message.js.html +1 -1
  290. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  291. data/docs/brut-js/api/RichString.html +1 -1
  292. data/docs/brut-js/api/RichString.js.html +1 -1
  293. data/docs/brut-js/api/Tabs.html +1 -1
  294. data/docs/brut-js/api/Tabs.js.html +1 -1
  295. data/docs/brut-js/api/Tracing.html +1 -1
  296. data/docs/brut-js/api/Tracing.js.html +1 -1
  297. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  298. data/docs/brut-js/api/external-Performance.html +1 -1
  299. data/docs/brut-js/api/external-Promise.html +1 -1
  300. data/docs/brut-js/api/external-ValidityState.html +1 -1
  301. data/docs/brut-js/api/external-Window.html +1 -1
  302. data/docs/brut-js/api/external-fetch.html +1 -1
  303. data/docs/brut-js/api/global.html +1 -1
  304. data/docs/brut-js/api/index.html +1 -1
  305. data/docs/brut-js/api/index.js.html +1 -1
  306. data/docs/brut-js/api/module-testing.html +1 -1
  307. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  308. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  309. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  310. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  311. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  312. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  313. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  314. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  315. data/docs/brut-js/api/testing_index.js.html +1 -1
  316. data/docs/brut-js.html +4 -4
  317. data/docs/business-logic.html +4 -4
  318. data/docs/cli.html +4 -4
  319. data/docs/components.html +8 -8
  320. data/docs/configuration.html +5 -5
  321. data/docs/css.html +4 -4
  322. data/docs/custom-element-tests.html +4 -4
  323. data/docs/database-access.html +4 -4
  324. data/docs/database-schema.html +4 -4
  325. data/docs/deployment.html +4 -4
  326. data/docs/dev-environment.html +4 -4
  327. data/docs/dir-structure.html +4 -4
  328. data/docs/doc-conventions.html +4 -4
  329. data/docs/end-to-end-tests.html +4 -4
  330. data/docs/features.html +4 -4
  331. data/docs/flash-and-session.html +4 -4
  332. data/docs/form-constraints.html +11 -11
  333. data/docs/forms.html +6 -6
  334. data/docs/getting-started.html +10 -7
  335. data/docs/handlers.html +4 -4
  336. data/docs/hashmap.json +1 -1
  337. data/docs/hooks.html +4 -4
  338. data/docs/i18n.html +4 -4
  339. data/docs/index.html +3 -3
  340. data/docs/instrumentation.html +4 -4
  341. data/docs/javascript.html +4 -4
  342. data/docs/jobs.html +4 -4
  343. data/docs/keyword-injection.html +4 -4
  344. data/docs/layouts.html +4 -4
  345. data/docs/lsp.html +4 -4
  346. data/docs/markdown-examples.html +4 -4
  347. data/docs/middleware.html +4 -4
  348. data/docs/overview.html +4 -4
  349. data/docs/pages.html +4 -4
  350. data/docs/recipes/alternate-layouts.html +4 -4
  351. data/docs/recipes/authentication.html +5 -5
  352. data/docs/recipes/blank-layouts.html +4 -4
  353. data/docs/recipes/custom-flash.html +4 -4
  354. data/docs/recipes/form-errors.html +94 -0
  355. data/docs/recipes/indexed-forms.html +4 -4
  356. data/docs/recipes/migrations.html +5 -5
  357. data/docs/recipes/text-field-component.html +4 -4
  358. data/docs/roadmap.html +4 -4
  359. data/docs/routes.html +4 -4
  360. data/docs/security.html +4 -4
  361. data/docs/seed-data.html +4 -4
  362. data/docs/space-time-continuum.html +4 -4
  363. data/docs/tutorial.html +12 -713
  364. data/docs/tutorials/01-intro.html +736 -0
  365. data/docs/tutorials/02-dialog.html +302 -0
  366. data/docs/unit-tests.html +4 -4
  367. data/docs/why.html +4 -4
  368. data/lib/brut/front_end/components/constraint_violations.rb +23 -10
  369. data/lib/brut/instrumentation/methods.rb +153 -0
  370. data/lib/brut/instrumentation/open_telemetry.rb +1 -0
  371. data/lib/brut/instrumentation.rb +1 -0
  372. data/lib/brut/version.rb +1 -1
  373. data/mkbrut/Gemfile.lock +1 -1
  374. data/mkbrut/bin/publish +1 -1
  375. data/mkbrut/lib/mkbrut/version.rb +1 -1
  376. data/specs/brut/instrumentation/methods.spec.rb +399 -0
  377. metadata +40 -18
  378. data/.nvim.lua +0 -1
  379. data/docs/assets/chunks/@localSearchIndexroot.D0RGp49A.js +0 -1
  380. data/docs/assets/tutorial.md.C4zR5XPG.lean.js +0 -1
  381. /data/docs/assets/{components.md.BBHib1uV.lean.js → components.md.BzVRwegp.lean.js} +0 -0
  382. /data/docs/assets/{configuration.md.CJ0MmIps.lean.js → configuration.md.eM5wFVi5.lean.js} +0 -0
  383. /data/docs/assets/{form-constraints.md.DK5adCgM.lean.js → form-constraints.md.KTv5cdR4.lean.js} +0 -0
  384. /data/docs/assets/{forms.md.D5jFZIJz.lean.js → forms.md.B3BHvCV3.lean.js} +0 -0
  385. /data/docs/assets/{getting-started.md.abLTnd_n.lean.js → getting-started.md.BgR0ZHsl.lean.js} +0 -0
@@ -80,7 +80,99 @@ Here is a non-exhaustive list of what Brut automatically instruments:
80
80
 
81
81
  ### Adding Your Own Instrumentation
82
82
 
83
- You can add instrumentation in a few ways:
83
+ You can add instrumentation in two main ways, both of which can be used together.
84
+
85
+ #### Instrumenting Existing Methods
86
+
87
+ Although Brut instruments the entrypoints to pages, handlers, and components, you will likely have your own set of back-end business logic that needs to be instrumented. If you aren't trying to diagnose a specific problem and just want to see your back-end class' methods show up in your instrumentation vendor's dashboard, `Brut::Instrumentation::Methods` will be the easiest way to do that.
88
+
89
+ `Brut::Instrumentation::Methods` can be included in any class, and provides three class methods, which are *mutually exclusive*:
90
+
91
+ * `instrument_all` instruments all methods, public and private.
92
+ * `instrument_public` instruments only public methods.
93
+ * `instrument` instruments one or more named methods.
94
+
95
+ `initialize` is never instrumented.
96
+
97
+ Consider this class:
98
+
99
+ ```ruby
100
+ class Widget
101
+ def initialize
102
+ # ...
103
+ end
104
+
105
+ def search
106
+ # ...
107
+ end
108
+
109
+ def save
110
+ # ...
111
+ end
112
+
113
+ private
114
+
115
+ def delete_orphans
116
+ # ...
117
+ end
118
+ end
119
+ ```
120
+
121
+ If we use `instrument_all`…
122
+
123
+ ```ruby
124
+ class Widget
125
+ include Brut::Instrumentation::Methods
126
+ instrument_all
127
+
128
+ # ...
129
+ end
130
+ ```
131
+
132
+ …`search`, `save`, and `delete_orphans` will be instrumented. If we use `instrument_public`…
133
+
134
+ ```ruby
135
+ class Widget
136
+ include Brut::Instrumentation::Methods
137
+ instrument_public
138
+
139
+ # ...
140
+ end
141
+ ```
142
+
143
+ …then only `search` and `save` are instrumented.
144
+
145
+ We can pick and choose by using `instrument`.
146
+
147
+ ```ruby{2,7,20}
148
+ class Widget
149
+ include Brut::Instrumentation::Methods
150
+ def initialize
151
+ # ...
152
+ end
153
+
154
+ instrument def search
155
+ # ...
156
+ end
157
+
158
+ def save
159
+ # ...
160
+ end
161
+
162
+ private
163
+
164
+ def delete_orphans
165
+ # ...
166
+ end
167
+ instrument :delete_orphans
168
+ end
169
+ ```
170
+
171
+ Above, `search` and `delete_orphans` are instrumented. Since `def` in Ruby returns a symbol, `instrument def search` is the same as `instrument :search`.
172
+
173
+ #### Explicit Instrumentation with Spans, Attributes, and Events
174
+
175
+ To add explicit instrumentation, you'll create one or more of the following:
84
176
 
85
177
  * *Spans* record a block of code. They are shown as a sub-span if one is already in effect. When you
86
178
  create a span, that means it will be shown in the context of the HTTP request.
@@ -164,6 +256,39 @@ aforementioned `InstrumentationHandler`.
164
256
 
165
257
  You should then see client-side tracing information as a sub-span of your HTTP request. The information available depends on the browser, and some browsers don't send much. Also keep in mind that clock drift is real and while client-side timings are accurate, the timestamps will not be.
166
258
 
259
+ ### Logging
260
+
261
+ Brut configures [SemanticLogger](https://logger.rocketjob.io/), but uses it sparingly. Currently, Brut performs very little logging, and no request logging. You may have noticed that your app doesn't produce a lot of output in development. Brut's assumption is that you will use an OpenTelemetry vendor to understand your app in production or the otel-desktop-viewer in developoment.
262
+
263
+ That said, since SemanticLogger is configured, you can use it at will:
264
+
265
+ ```ruby
266
+ class HomePage
267
+ def page_template
268
+ SemanticLogger[self.class].debug "page being rendered"
269
+
270
+ # ...
271
+
272
+ end
273
+ end
274
+ ```
275
+
276
+ The logging system is currently not very configurable, and works as follows:
277
+
278
+ * In development, log messages are written to the standard output and to `logs/development.log`
279
+ * In test, log messages are written to `logs/test.log`
280
+ * In production, log messages are written to the standard output
281
+
282
+ The default log level is "debug" for the web app at "fatal" for CLI apps. You can set `LOG_LEVEL` in the environment to change this:
283
+
284
+ * `"debug"` - Show all messages
285
+ * `"info"` - Show info and above (not debug messages)
286
+ * `"warn"` - Show warnings and above (not info, not debug)
287
+ * `"error"` - Show errors and fatals only
288
+ * `"fatal"` - Show fatals only
289
+
290
+ Most CLIs also allow `--log-level` to accept one of these strings as wel ass `--verbose` to set the log level to debug.
291
+
167
292
  ## Testing
168
293
 
169
294
  Generally you don't want to test instrumentation unless it's highly complex and critical to the app's
@@ -182,11 +307,25 @@ specific issues.
182
307
  > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
183
308
  > internals, the source code is always more correct.
184
309
 
185
- _Last Updated June 12, 2025_
310
+ _Last Updated Aug 27, 2025_
311
+
312
+ OpenTelemetry is notoriously opaque and, ironically, unobservable in its own behavior. Thus, the implementation is subject to change as I figure out what actually does what.
186
313
 
314
+ ### Web Requests
187
315
 
188
- Brut does not have plans to support non-OTel instrumentation, nor does it have plans to provide hooks to use proprietary formats.
316
+ `Brut::FrontEnd::Middlewares::OpenTelemetrySpan` is configured in `Brut::Framework::MCP` as the first middleware. It sets up the outer span for all web requests. Inside each request block (the code internal to Brut that calls handlers or pages), this span's name is modified with the HTTP method and path via the `brut.otel.root_span` in the Rack environment.
317
+
318
+ ### Client-Side
189
319
 
190
320
  The client-side portion of this is highly customized. The Otel open source code for the client side is
191
321
  massive and hugely complex, so Brut decided to try to produce something simple and straightforward as a
192
322
  start. This can and will evolve over time.
323
+
324
+ ### CLI Commands
325
+
326
+ Brut CLI commands are instrumented as well,
327
+ in `Brut::CLI::App` in `execute!`, however the trace only begins if the underlying command is going to be executed. This may change.
328
+
329
+ ### Sidekiq Jobs
330
+
331
+ Although Brut currently does not provide a default Sidekiq configuration, if you set up Sidekiq and include the `opentelemetry-instrumentation-sidekiq` gem in your app's `Gemfile`, you should see instrumentation of your Sidekiq jobs. In practice, this default set up doesn't seem to work very well, so expect this to change for the better.
@@ -0,0 +1,148 @@
1
+ # Styling Form Errors
2
+
3
+ Brut makes it as easy as possible to unify client-side and server-side constraint violation handling, including
4
+ how you style those messages.
5
+
6
+ ## Requirements
7
+
8
+ What you want:
9
+
10
+ * When a form is rendered for the first time, there should be errors shown.
11
+ * When a visitor interacts with a form before submissions, no errors are shown.
12
+ * When the form is submitted, client-side constraint violations should be shown.
13
+ * When JavaScript is circumvented and the form is submitted with client-side constraint violations, the form should be re-generated, showing those violations the same is if JavaScripts was *not* circumvented
14
+ * When there are no client-side constraint violations, but there *are* server-side violations, the form should be re-generated, showing those violations the same is if JavaScripts was *not* circumvented
15
+
16
+ This can be achieved through CSS.
17
+
18
+ ## Recipe
19
+
20
+ ### Create Pages and HTML
21
+
22
+ First, create a form and handler:
23
+
24
+ ```
25
+ bin/scaffold form /new_widget
26
+ ```
27
+
28
+ Edit `app/src/front_end/forms/new_widget_form.rb`
29
+
30
+ ```ruby
31
+ class NewWidgetForm < AppForm
32
+ input :name, minlength: 3
33
+ input :description
34
+ end
35
+ ```
36
+
37
+ Now, implement the handler in `app/src/front_end/handlers/new_widget_handler.rb` to check for client-side violations *and* require that the description have at
38
+ least 5 words in it.
39
+
40
+ ```ruby
41
+ class NewWidgetHandler < AppHandler
42
+ def initialize(form:)
43
+ @form = form
44
+ end
45
+
46
+ def handle
47
+ if @form.valid?
48
+ if @form.description.split(/\s+/).length < 5
49
+ @form.server_side_constraint_violation(
50
+ input_name: :description,
51
+ key: :not_enough_words
52
+ )
53
+ end
54
+
55
+ if @form.constraint_violations?
56
+ NewWidgetPage.new(form: @form)
57
+ else
58
+ redirect_to(HomePage)
59
+ end
60
+ end
61
+ end
62
+ ```
63
+
64
+ Add the new error message to `app/config/i18n/en/2_app.rb`
65
+
66
+ ```ruby {6}
67
+ {
68
+ en: {
69
+ nevermind: "Nevermind",
70
+ cv: {
71
+ ss: {
72
+ not_enough_words: "%{field} must have at least %{minwords} words",
73
+ },
74
+ # ...
75
+ ```
76
+
77
+ Now, build a minimal `NewWidgetPage`:
78
+
79
+ ```
80
+ bin/scaffold page /new_widget`
81
+ ```
82
+
83
+ We'll create the bare minimum in `app/src/front_end/pages/new_widget_page.rb`
84
+
85
+ ```ruby
86
+ class NewWidgetPage < AppPage
87
+ def initialize(form: nil)
88
+ @form = form || NewWidgetForm.new
89
+ end
90
+
91
+ def page_template
92
+ brut_form do
93
+ FormTag(for: @form) do
94
+ label do
95
+ Inputs::InputTag(form: @form, input_name: :name)
96
+ ConstraintViolations(form: @form, input_name: :name)
97
+ span { "Name" }
98
+ end
99
+ label do
100
+ Inputs::TextareaTag(form: @form, input_name: :name)
101
+ ConstraintViolations(form: @form, input_name: :name)
102
+ span { "Description" }
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ ```
109
+
110
+ ### Create CSS
111
+
112
+ The most minimal CSS would be as followed, which you can place in `app/src/front_end/css/index.css`
113
+
114
+ ```css
115
+ brut-cv {
116
+ display: none;
117
+ }
118
+
119
+ brut-form[submitted-invalid] brut-cv,
120
+ brut-cv[server-generated] {
121
+ display: block;
122
+ color: red;
123
+ }
124
+ ```
125
+
126
+ This will show the messages in `<brut-cv>` *only* if:
127
+
128
+ * Form submission was attempted (`submitted-invalid` would be set on `<brut-form>`)
129
+ * The server generated via `ConstraintViolations` (only if the handler was triggered)
130
+
131
+ Because more than one `<brut-cv>` could be generated or inserted, you may want to style the
132
+ `<brut-cv-messages>` that contains them, but you only want it to show up if it has contents.
133
+
134
+ You can't just do `brut-cv-messages:has(brut-cv)`, because your container would show up before the form
135
+ submission was attempted.
136
+
137
+ Here is what you want instead. This will put the error messages in a red box:
138
+
139
+ ```css
140
+ brut-form[submitted-invalid] brut-cv-messages:has(brut-cv),
141
+ brut-cv-messages:has(brut-cv[server-generated]) {
142
+ color: red;
143
+ background-color: pink;
144
+ border: solid thin red;
145
+ border-radius: 1rem;
146
+ }
147
+ ```
148
+