brut 0.10.0 → 0.12.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 (403) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/Gemfile.lock +21 -21
  4. data/assets/YouTubeThumb.pxd +0 -0
  5. data/bin/new-version +3 -3
  6. data/brut-css/package-lock.json +94 -97
  7. data/brut-css/package.json +2 -2
  8. data/brut-js/package-lock.json +3 -3
  9. data/brut-js/package.json +6 -3
  10. data/brut-js/specs/AjaxSubmit.spec.js +62 -6
  11. data/brut-js/src/AjaxSubmit.js +26 -6
  12. data/brutrb.com/forms.md +1 -0
  13. data/brutrb.com/getting-started.md +3 -0
  14. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element-styled.png +0 -0
  15. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element.png +0 -0
  16. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser.png +0 -0
  17. data/brutrb.com/images/tutorial/02-confirmation-flow.graffle +0 -0
  18. data/brutrb.com/images/tutorial/02-confirmation-flow.png +0 -0
  19. data/brutrb.com/instrumentation.md +142 -3
  20. data/brutrb.com/recipes/authentication.md +1 -0
  21. data/brutrb.com/tutorial.md +29 -1627
  22. data/brutrb.com/tutorials/01-intro.md +1654 -0
  23. data/brutrb.com/tutorials/02-dialog.md +569 -0
  24. data/docs/404.html +2 -2
  25. data/docs/adrs.html +4 -4
  26. data/docs/ai.html +4 -4
  27. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  28. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  29. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  30. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  31. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  32. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  33. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  34. data/docs/api/Brut/BackEnd.html +1 -1
  35. data/docs/api/Brut/CLI/App.html +1 -1
  36. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  67. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  69. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  70. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  71. data/docs/api/Brut/CLI/Apps.html +1 -1
  72. data/docs/api/Brut/CLI/Command.html +1 -1
  73. data/docs/api/Brut/CLI/Error.html +1 -1
  74. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  75. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  76. data/docs/api/Brut/CLI/Executor.html +1 -1
  77. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  78. data/docs/api/Brut/CLI/Options.html +1 -1
  79. data/docs/api/Brut/CLI/Output.html +1 -1
  80. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  81. data/docs/api/Brut/CLI.html +1 -1
  82. data/docs/api/Brut/FactoryBot.html +1 -1
  83. data/docs/api/Brut/Framework/App.html +1 -1
  84. data/docs/api/Brut/Framework/Config.html +1 -1
  85. data/docs/api/Brut/Framework/Container.html +1 -1
  86. data/docs/api/Brut/Framework/Error.html +1 -1
  87. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  88. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  89. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  90. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  91. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  92. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  93. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  94. data/docs/api/Brut/Framework/Errors.html +1 -1
  95. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +2 -2
  96. data/docs/api/Brut/Framework/MCP.html +1 -1
  97. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  98. data/docs/api/Brut/Framework.html +1 -1
  99. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +48 -27
  103. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Input.html +2 -2
  106. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +443 -0
  107. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +14 -9
  109. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +8 -9
  111. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +10 -11
  112. data/docs/api/Brut/FrontEnd/Components/Inputs.html +2 -2
  113. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  118. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Form.html +39 -39
  122. data/docs/api/Brut/FrontEnd/Forms/Button.html +331 -0
  123. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +544 -0
  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 +6 -2
  128. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +111 -23
  129. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +20 -12
  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 +2 -2
  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 +2 -2
  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/Methods/ClassMethods.html +596 -0
  188. data/docs/api/Brut/Instrumentation/Methods.html +173 -0
  189. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +7 -7
  190. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +7 -7
  191. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +19 -17
  192. data/docs/api/Brut/Instrumentation.html +3 -1
  193. data/docs/api/Brut/RubocopConfig.html +1 -1
  194. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  195. data/docs/api/Brut/SinatraHelpers.html +1 -1
  196. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  197. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  199. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  200. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  201. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  202. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  203. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  204. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  210. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  211. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  212. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  213. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  214. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  215. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  216. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  217. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  218. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  219. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  220. data/docs/api/Brut/SpecSupport.html +1 -1
  221. data/docs/api/Brut.html +1 -1
  222. data/docs/api/Clock.html +1 -1
  223. data/docs/api/ModuleName.html +1 -1
  224. data/docs/api/RichString.html +1 -1
  225. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  226. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  227. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  228. data/docs/api/Sequel/Extensions.html +1 -1
  229. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  230. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  231. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  232. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  233. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  234. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  235. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  236. data/docs/api/Sequel/Plugins.html +1 -1
  237. data/docs/api/Sequel.html +1 -1
  238. data/docs/api/_index.html +36 -1
  239. data/docs/api/class_list.html +1 -1
  240. data/docs/api/file.README.html +1 -1
  241. data/docs/api/index.html +1 -1
  242. data/docs/api/method_list.html +364 -252
  243. data/docs/api/top-level-namespace.html +1 -1
  244. data/docs/assets/02-confirmation-dialog-browser-element-styled.3NEGM20-.png +0 -0
  245. data/docs/assets/02-confirmation-dialog-browser-element.DPsf0xUW.png +0 -0
  246. data/docs/assets/02-confirmation-dialog-browser.DH8ALFO4.png +0 -0
  247. data/docs/assets/02-confirmation-flow.D9gZ0S5U.png +0 -0
  248. data/docs/assets/{app.vjGWMSnJ.js → app.V2GOcOrg.js} +1 -1
  249. data/docs/assets/chunks/@localSearchIndexroot.3Lsq4QTb.js +1 -0
  250. data/docs/assets/chunks/{VPLocalSearchBox.C-ymMW2k.js → VPLocalSearchBox.CZTadcAy.js} +1 -1
  251. data/docs/assets/chunks/{theme.pJUosGlI.js → theme.FeuYNxyp.js} +2 -2
  252. data/docs/assets/{components.md.B543a3Lm.js → components.md.f4cdTyvV.js} +3 -3
  253. data/docs/assets/{configuration.md.-foE_iVv.js → configuration.md.Bs4-rxnS.js} +1 -1
  254. data/docs/assets/{form-constraints.md.DK5adCgM.js → form-constraints.md.KTv5cdR4.js} +6 -6
  255. data/docs/assets/{forms.md.D5-2rgHh.js → forms.md.Sys-XxVf.js} +3 -3
  256. data/docs/assets/{forms.md.D5-2rgHh.lean.js → forms.md.Sys-XxVf.lean.js} +1 -1
  257. data/docs/assets/{getting-started.md.Cd4XSZb_.js → getting-started.md.CFIW0bcE.js} +6 -3
  258. data/docs/assets/{getting-started.md.Cd4XSZb_.lean.js → getting-started.md.CFIW0bcE.lean.js} +1 -1
  259. data/docs/assets/instrumentation.md._lNSriEZ.js +90 -0
  260. data/docs/assets/instrumentation.md._lNSriEZ.lean.js +1 -0
  261. data/docs/assets/{recipes_authentication.md.Dzvi_g69.js → recipes_authentication.md.BAISoxmN.js} +1 -0
  262. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.js +66 -0
  263. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +1 -0
  264. data/docs/assets/tutorial.md.BM40jnoq.js +27 -0
  265. data/docs/assets/tutorial.md.BM40jnoq.lean.js +1 -0
  266. data/docs/assets/{tutorial.md.C4zR5XPG.js → tutorials_01-intro.md.B4sUBY3X.js} +13 -33
  267. data/docs/assets/tutorials_01-intro.md.B4sUBY3X.lean.js +1 -0
  268. data/docs/assets/tutorials_02-dialog.md.QTFeHdiA.js +274 -0
  269. data/docs/assets/tutorials_02-dialog.md.QTFeHdiA.lean.js +1 -0
  270. data/docs/assets.html +4 -4
  271. data/docs/brut-js/api/AjaxSubmit.html +16 -6
  272. data/docs/brut-js/api/AjaxSubmit.js.html +27 -7
  273. data/docs/brut-js/api/Autosubmit.html +1 -1
  274. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  275. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  276. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  277. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  278. data/docs/brut-js/api/BufferedLogger.html +1 -1
  279. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  280. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  281. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  282. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  283. data/docs/brut-js/api/ConstraintViolationMessage.html +55 -5
  284. data/docs/brut-js/api/ConstraintViolationMessage.js.html +18 -3
  285. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  286. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  287. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  288. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  289. data/docs/brut-js/api/Form.html +7 -10
  290. data/docs/brut-js/api/Form.js.html +20 -24
  291. data/docs/brut-js/api/I18nTranslation.html +1 -1
  292. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  293. data/docs/brut-js/api/LocaleDetection.html +1 -1
  294. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  295. data/docs/brut-js/api/Logger.html +1 -1
  296. data/docs/brut-js/api/Logger.js.html +1 -1
  297. data/docs/brut-js/api/Message.html +1 -1
  298. data/docs/brut-js/api/Message.js.html +1 -1
  299. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  300. data/docs/brut-js/api/RichString.html +1 -1
  301. data/docs/brut-js/api/RichString.js.html +1 -1
  302. data/docs/brut-js/api/Tabs.html +1 -1
  303. data/docs/brut-js/api/Tabs.js.html +1 -1
  304. data/docs/brut-js/api/Tracing.html +1 -1
  305. data/docs/brut-js/api/Tracing.js.html +1 -1
  306. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  307. data/docs/brut-js/api/external-Performance.html +1 -1
  308. data/docs/brut-js/api/external-Promise.html +1 -1
  309. data/docs/brut-js/api/external-ValidityState.html +1 -1
  310. data/docs/brut-js/api/external-Window.html +1 -1
  311. data/docs/brut-js/api/external-fetch.html +1 -1
  312. data/docs/brut-js/api/global.html +1 -1
  313. data/docs/brut-js/api/index.html +1 -1
  314. data/docs/brut-js/api/index.js.html +1 -1
  315. data/docs/brut-js/api/module-testing.html +1 -1
  316. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  317. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  318. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  319. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  320. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  321. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  322. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  323. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  324. data/docs/brut-js/api/testing_index.js.html +1 -1
  325. data/docs/brut-js.html +4 -4
  326. data/docs/business-logic.html +4 -4
  327. data/docs/cli.html +4 -4
  328. data/docs/components.html +8 -8
  329. data/docs/configuration.html +5 -5
  330. data/docs/css.html +4 -4
  331. data/docs/custom-element-tests.html +4 -4
  332. data/docs/database-access.html +4 -4
  333. data/docs/database-schema.html +4 -4
  334. data/docs/deployment.html +4 -4
  335. data/docs/dev-environment.html +4 -4
  336. data/docs/dir-structure.html +4 -4
  337. data/docs/doc-conventions.html +4 -4
  338. data/docs/end-to-end-tests.html +4 -4
  339. data/docs/features.html +4 -4
  340. data/docs/flash-and-session.html +4 -4
  341. data/docs/form-constraints.html +11 -11
  342. data/docs/forms.html +7 -7
  343. data/docs/getting-started.html +10 -7
  344. data/docs/handlers.html +4 -4
  345. data/docs/hashmap.json +1 -1
  346. data/docs/hooks.html +4 -4
  347. data/docs/i18n.html +4 -4
  348. data/docs/index.html +3 -3
  349. data/docs/instrumentation.html +61 -6
  350. data/docs/javascript.html +4 -4
  351. data/docs/jobs.html +4 -4
  352. data/docs/keyword-injection.html +4 -4
  353. data/docs/layouts.html +4 -4
  354. data/docs/lsp.html +4 -4
  355. data/docs/markdown-examples.html +4 -4
  356. data/docs/middleware.html +4 -4
  357. data/docs/overview.html +4 -4
  358. data/docs/pages.html +4 -4
  359. data/docs/recipes/alternate-layouts.html +4 -4
  360. data/docs/recipes/authentication.html +7 -6
  361. data/docs/recipes/blank-layouts.html +4 -4
  362. data/docs/recipes/custom-flash.html +4 -4
  363. data/docs/recipes/form-errors.html +94 -0
  364. data/docs/recipes/indexed-forms.html +4 -4
  365. data/docs/recipes/migrations.html +5 -5
  366. data/docs/recipes/text-field-component.html +4 -4
  367. data/docs/roadmap.html +4 -4
  368. data/docs/routes.html +4 -4
  369. data/docs/security.html +4 -4
  370. data/docs/seed-data.html +4 -4
  371. data/docs/space-time-continuum.html +4 -4
  372. data/docs/tutorial.html +12 -713
  373. data/docs/tutorials/01-intro.html +736 -0
  374. data/docs/tutorials/02-dialog.html +302 -0
  375. data/docs/unit-tests.html +4 -4
  376. data/docs/why.html +4 -4
  377. data/lib/brut/front_end/components/input.rb +1 -0
  378. data/lib/brut/front_end/components/inputs/button_tag.rb +40 -0
  379. data/lib/brut/front_end/components/inputs/input_tag.rb +3 -1
  380. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +0 -1
  381. data/lib/brut/front_end/components/inputs/textarea_tag.rb +0 -1
  382. data/lib/brut/front_end/form.rb +7 -5
  383. data/lib/brut/front_end/forms/button.rb +21 -0
  384. data/lib/brut/front_end/forms/button_input_definition.rb +37 -0
  385. data/lib/brut/front_end/forms/input_declarations.rb +10 -0
  386. data/lib/brut/front_end/forms/input_definition.rb +7 -2
  387. data/lib/brut/instrumentation/methods.rb +153 -0
  388. data/lib/brut/instrumentation/open_telemetry.rb +1 -0
  389. data/lib/brut/instrumentation.rb +1 -0
  390. data/lib/brut/version.rb +1 -1
  391. data/mkbrut/Gemfile.lock +1 -1
  392. data/mkbrut/bin/publish +1 -1
  393. data/mkbrut/lib/mkbrut/version.rb +1 -1
  394. data/specs/brut/instrumentation/methods.spec.rb +399 -0
  395. metadata +51 -21
  396. data/docs/assets/chunks/@localSearchIndexroot.Dn1xGMv_.js +0 -1
  397. data/docs/assets/instrumentation.md.BgcaGVYH.js +0 -35
  398. data/docs/assets/instrumentation.md.BgcaGVYH.lean.js +0 -1
  399. data/docs/assets/tutorial.md.C4zR5XPG.lean.js +0 -1
  400. /data/docs/assets/{components.md.B543a3Lm.lean.js → components.md.f4cdTyvV.lean.js} +0 -0
  401. /data/docs/assets/{configuration.md.-foE_iVv.lean.js → configuration.md.Bs4-rxnS.lean.js} +0 -0
  402. /data/docs/assets/{form-constraints.md.DK5adCgM.lean.js → form-constraints.md.KTv5cdR4.lean.js} +0 -0
  403. /data/docs/assets/{recipes_authentication.md.Dzvi_g69.lean.js → recipes_authentication.md.BAISoxmN.lean.js} +0 -0
data/brut-js/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "brut-js",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
- "keywords": [ "WebComponents", "Custom Elements" ],
5
+ "keywords": [
6
+ "WebComponents",
7
+ "Custom Elements"
8
+ ],
6
9
  "bugs": {
7
10
  "url": "https://github.com/thirdtank/brut-js/issues",
8
11
  "email": "davec@naildrivin5.com"
@@ -25,7 +28,7 @@
25
28
  "bundle": "esbuild --sourcemap --bundle src/appForTestingOnly.js --outfile=specs/public/js/bundle.js"
26
29
  },
27
30
  "devDependencies": {
28
- "esbuild": "^0.24.0",
31
+ "esbuild": "^0.24.2",
29
32
  "jsdom": "^25.0.1",
30
33
  "mocha": "^10.8.2"
31
34
  }
@@ -6,7 +6,7 @@ describe("<brut-ajax-submit>", () => {
6
6
  <input required type="text" name="some-text">
7
7
  <input required type="number" name="some-number">
8
8
  <brut-ajax-submit submitted-lifetime="10">
9
- <button>Submit</button>
9
+ <button name="button-name" value="button-value">Submit</button>
10
10
  </brut-ajax-submit>
11
11
  </form>
12
12
  `).onFetch( "/foo", [
@@ -43,12 +43,13 @@ describe("<brut-ajax-submit>", () => {
43
43
  assert.equal(detailReceived.body.innerHTML,"<div>some html</div>")
44
44
  assert(element.getAttribute("requesting") == null)
45
45
  assert(element.getAttribute("submitted") != null)
46
- waitForSetTimeout(11).then( () => {
46
+ return waitForSetTimeout(11).then( () => {
47
47
  assert(element.getAttribute("submitted") == null)
48
48
  return readRequestBodyIntoString(fetchRequests[0]).then( (string) => {
49
49
  const params = new URLSearchParams(string)
50
50
  assert.equal(params.get("some-text"),"Some Text")
51
51
  assert.equal(params.get("some-number"),"11")
52
+ assert.equal(params.get("button-name"),"button-value")
52
53
  })
53
54
  })
54
55
 
@@ -79,7 +80,7 @@ describe("<brut-ajax-submit>", () => {
79
80
  <input required type="text" name="some-text">
80
81
  <input required type="number" name="some-number">
81
82
  <brut-ajax-submit submitted-lifetime="10">
82
- <button>Submit</button>
83
+ <button name="button-name" value="button-value">Submit</button>
83
84
  </brut-ajax-submit>
84
85
  </form>
85
86
  `).onFetch( "/foo", [
@@ -114,6 +115,7 @@ describe("<brut-ajax-submit>", () => {
114
115
  const params = new URLSearchParams(string)
115
116
  assert.equal(params.get("some-text"),"Some Text")
116
117
  assert.equal(params.get("some-number"),"11")
118
+ assert.equal(params.get("button-name"),"button-value")
117
119
  })
118
120
  })
119
121
  })
@@ -123,7 +125,7 @@ describe("<brut-ajax-submit>", () => {
123
125
  <input required type="text" name="some-text">
124
126
  <input required type="number" name="some-number">
125
127
  <brut-ajax-submit submitted-lifetime="10" max-retry-attempts=2>
126
- <button>Submit</button>
128
+ <button name="button-name" value="button-value">Submit</button>
127
129
  </brut-ajax-submit>
128
130
  </form>
129
131
  `).onFetch( "/foo", [
@@ -183,7 +185,7 @@ describe("<brut-ajax-submit>", () => {
183
185
  </brut-cv-messages>
184
186
 
185
187
  <brut-ajax-submit submitted-lifetime="10">
186
- <button>Submit</button>
188
+ <button name="button-name" value="button-value">Submit</button>
187
189
  </brut-ajax-submit>
188
190
  </form>
189
191
  `).onFetch( "/foo", [
@@ -283,7 +285,7 @@ Error that should be ignored
283
285
  </brut-cv-messages>
284
286
 
285
287
  <brut-ajax-submit submitted-lifetime="10" no-server-side-error-parsing>
286
- <button>Submit</button>
288
+ <button name="button-name" value="button-value">Submit</button>
287
289
  </brut-ajax-submit>
288
290
  </form>
289
291
  `).onFetch( "/foo", [
@@ -346,4 +348,58 @@ Error that should be ignored
346
348
  })
347
349
  })
348
350
  })
351
+ withHTML(`
352
+ <form action="http://example.net/foo" method="POST">
353
+ <input required type="text" name="some-text">
354
+ <input required type="number" name="some-number">
355
+ <brut-ajax-submit submitted-lifetime="10">
356
+ <input type="submit" name="button-name" value="button-value">
357
+ </brut-ajax-submit>
358
+ </form>
359
+ `).onFetch( "/foo", [
360
+ { then: { status: 200, text: "<div>some html</div>" }},
361
+ ]
362
+ ).test("submits the form, when wrapped around an input type=submi, setting various attributes during the lifecycle",
363
+ ({document,assert,fetchRequests,waitForSetTimeout,readRequestBodyIntoString}) => {
364
+
365
+ const element = document.querySelector("brut-ajax-submit")
366
+ const button = element.querySelector("input[type=submit]")
367
+ const text = document.querySelector("input[name=some-text]")
368
+ const number = document.querySelector("input[name=some-number]")
369
+
370
+ let okReceived = false
371
+ let detailReceived = null
372
+ element.addEventListener("brut:submitok", (event) => {
373
+ okReceived = true
374
+ detailReceived = event.detail
375
+ })
376
+
377
+ text.value = "Some Text"
378
+ number.value = "11"
379
+
380
+ button.click()
381
+ assert(element.getAttribute("requesting") != null)
382
+
383
+ const promises = fetchRequests.
384
+ filter( (fetchRequest) => fetchRequest.promiseReturned ).
385
+ map( (fetchRequest) => fetchRequest.promiseReturned )
386
+
387
+ return Promise.all(promises).then( () => {
388
+ assert(okReceived)
389
+ assert(detailReceived)
390
+ assert.equal(detailReceived.body.innerHTML,"<div>some html</div>")
391
+ assert(element.getAttribute("requesting") == null)
392
+ assert(element.getAttribute("submitted") != null)
393
+ return waitForSetTimeout(11).then( () => {
394
+ assert(element.getAttribute("submitted") == null)
395
+ return readRequestBodyIntoString(fetchRequests[0]).then( (string) => {
396
+ const params = new URLSearchParams(string)
397
+ assert.equal(params.get("some-text"),"Some Text")
398
+ assert.equal(params.get("some-number"),"11")
399
+ assert.equal(params.get("button-name"),"button-value")
400
+ })
401
+ })
402
+
403
+ })
404
+ })
349
405
  })
@@ -10,6 +10,8 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
10
10
  * 1. When the button is clicked, the form's validity is checked. If it's not valid, nothing happens.
11
11
  * 2. If the form is valid, this element will be given the `requesting` attribute.
12
12
  * 3. The request will be initiated, set to abort after `request-timeout` ms (see below).
13
+ * The data submitted will be the contents of `new FormData(form)`, along with the
14
+ * name/value of the button that was clicked, if it has a name and value.
13
15
  * 4. If the request returns OK:
14
16
  * - `requesting` will be removed and `submitted` will be added.
15
17
  * - `submitted` will be removed after `submitted-lifetime` ms.
@@ -67,13 +69,21 @@ import ConstraintViolationMessage from "./ConstraintViolationMessage"
67
69
  *
68
70
  * @example
69
71
  * <form action="/widgets" method="post">
70
- * <input type=text name=name>
72
+ * <input type="text" name="name">
71
73
  *
72
74
  * <brut-ajax-submit>
73
- * <button>Save</button>
75
+ * <button name="button" value="save">Save</button>
76
+ * </brut-ajax-submit>
77
+ * <brut-ajax-submit>
78
+ * <button name="button" value="analyze">Analyze</button>
74
79
  * </brut-ajax-submit>
75
80
  * </form>
76
81
  *
82
+ * <!-- When "Save" is clicked, "name" will have the value from the text field,
83
+ * and "button" will have the value "save".
84
+ * When "Analyze" is clicked, "name" will have the value from the text
85
+ * field, and "button" will have the value "analyze". -->
86
+ *
77
87
  * @customelement brut-ajax-submit
78
88
  */
79
89
  class AjaxSubmit extends BaseCustomElement {
@@ -179,17 +189,20 @@ class AjaxSubmit extends BaseCustomElement {
179
189
  if (submitter == this.#button()) {
180
190
  event.preventDefault()
181
191
  const now = Date.now()
182
- this.#submitForm(event.target, now, 0)
192
+ this.#submitForm(event.target, event.submitter, now, 0)
183
193
  }
184
194
  }
185
195
 
186
- #submitForm(form, firstSubmittedAt, numAttempts) {
196
+ #submitForm(form, submitter, firstSubmittedAt, numAttempts) {
187
197
 
188
198
  const headers = new Headers()
189
199
  headers.append("X-Requested-With","XMLHttpRequest")
190
200
  headers.append("Content-Type","application/x-www-form-urlencoded")
191
201
 
192
202
  const formData = new FormData(form)
203
+ if (submitter && submitter.name) {
204
+ formData.append(submitter.name, submitter.value)
205
+ }
193
206
  const urlSearchParams = new URLSearchParams(formData)
194
207
 
195
208
  const timeoutSignal = AbortSignal.timeout(this.#requestTimeout)
@@ -251,7 +264,7 @@ class AjaxSubmit extends BaseCustomElement {
251
264
  }
252
265
  if (retry) {
253
266
  this.#requestErrorLogger("Trying again (attempt %d)",numAttempts +1)
254
- setTimeout( () => this.#submitForm(form, firstSubmittedAt, numAttempts + 1), numAttempts * 10)
267
+ setTimeout( () => this.#submitForm(form, submitter, firstSubmittedAt, numAttempts + 1), numAttempts * 10)
255
268
  }
256
269
  else if (resubmit) {
257
270
  this.#requestErrorLogger("'retry' was marked false, but resubmit is 'true', so submitting through browser")
@@ -265,7 +278,14 @@ class AjaxSubmit extends BaseCustomElement {
265
278
  })
266
279
  }
267
280
 
268
- #button = () => { return this.querySelector("button") }
281
+ #button = () => {
282
+ let button = this.querySelector("button")
283
+ if (!button) {
284
+ button = this.querySelector("input[type='submit']")
285
+ }
286
+ return button
287
+ }
288
+
269
289
 
270
290
  #submitFormThroughBrowser(form) {
271
291
  form.removeEventListener("submit",this.#formSubmitted)
data/brutrb.com/forms.md CHANGED
@@ -83,6 +83,7 @@ for use in a radio button group. |
83
83
  |`Brut::FrontEnd::Components::SelectTagWithOptions` | Creates a `<select>` tag with
84
84
  `<option>` tags inside. |
85
85
  |`Brut::FrontEnd::Components::TextareaTag` | Creates a `<textarea>` tag. |
86
+ |`Brut::FrontEnd::Components::ButtonTag` | Creates a `<button>` tag to submit the form. |
86
87
 
87
88
  All of these classes have an initializer that accepts:
88
89
 
@@ -9,6 +9,7 @@ The simplest way to use `mkbrut` is to use an existing [Docker image](https://hu
9
9
 
10
10
  ```
11
11
  docker run \
12
+ --pull always \
12
13
  -v "$PWD":"$PWD" \
13
14
  -w "$PWD" \
14
15
  -u $(id -u):$(id -g) \
@@ -33,6 +34,7 @@ For now:
33
34
 
34
35
  ``` [Docker-based]
35
36
  docker run \
37
+ --pull always \
36
38
  -v "$PWD":"$PWD" \
37
39
  -w "$PWD" \
38
40
  -u $(id -u):$(id -g) \
@@ -55,6 +57,7 @@ To create your app without the demo components:
55
57
 
56
58
  ``` [Docker-based]
57
59
  docker run \
60
+ --pull always \
58
61
  -v "$PWD":"$PWD" \
59
62
  -w "$PWD" \
60
63
  -u $(id -u):$(id -g) \
@@ -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.
@@ -42,6 +42,7 @@ We'll also create `app/src/back_end/data_models/db/account.rb`:
42
42
 
43
43
  ```ruby
44
44
  class DB::Account < AppDataModel
45
+ has_external_id :a3 # !IMPORTANT: Make sure this is unique amongst your DB models
45
46
  end
46
47
  ```
47
48