brut 0.4.0 → 0.8.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 (568) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/CHANGELOG.md +36 -1
  4. data/Dockerfile.dx +19 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +19 -0
  7. data/assets/MetroIcon.graffle +0 -0
  8. data/assets/YouTubeThumb.pxd +0 -0
  9. data/bin/build +86 -0
  10. data/bin/ci +36 -0
  11. data/bin/docs +39 -9
  12. data/bin/publish +61 -0
  13. data/bin/setup +6 -0
  14. data/brut-css/bin/build +19 -0
  15. data/brut-css/bin/ci +19 -0
  16. data/brut-css/bin/docs +19 -0
  17. data/brut-css/bin/publish +21 -0
  18. data/brut-css/bin/setup +1 -0
  19. data/brut-css/package-lock.json +2 -2
  20. data/brut-css/package.json +1 -1
  21. data/brut-css/src/css/flex.css +1 -1
  22. data/brut-css/src/docs/includes/body-and-header.html.ejs +1 -1
  23. data/brut-js/bin/build +15 -6
  24. data/brut-js/bin/docs +25 -0
  25. data/brut-js/bin/publish +21 -0
  26. data/brut-js/bin/setup +1 -0
  27. data/brut-js/dx +1 -0
  28. data/brut-js/package-lock.json +2 -2
  29. data/brut-js/package.json +1 -1
  30. data/brut-js/specs/AjaxSubmit.spec.js +100 -7
  31. data/brut-js/src/AjaxSubmit.js +76 -40
  32. data/brut.gemspec +2 -2
  33. data/brutrb.com/adrs.md +1 -0
  34. data/brutrb.com/bin/setup +1 -0
  35. data/brutrb.com/getting-started.md +3 -0
  36. data/brutrb.com/overview.md +6 -0
  37. data/brutrb.com/tutorial.md +7 -3
  38. data/docs/404.html +2 -2
  39. data/docs/adrs.html +5 -5
  40. data/docs/ai.html +3 -3
  41. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  42. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  43. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  44. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  45. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  46. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  47. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  48. data/docs/api/Brut/BackEnd.html +1 -1
  49. data/docs/api/Brut/CLI/App.html +1 -1
  50. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  67. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  69. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  70. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  71. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  72. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +2 -2
  73. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  74. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  75. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  76. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  77. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  78. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  79. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  80. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  81. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  82. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  83. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  84. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  85. data/docs/api/Brut/CLI/Apps.html +1 -1
  86. data/docs/api/Brut/CLI/Command.html +1 -1
  87. data/docs/api/Brut/CLI/Error.html +1 -1
  88. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  89. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  90. data/docs/api/Brut/CLI/Executor.html +1 -1
  91. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  92. data/docs/api/Brut/CLI/Options.html +1 -1
  93. data/docs/api/Brut/CLI/Output.html +1 -1
  94. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  95. data/docs/api/Brut/CLI.html +1 -1
  96. data/docs/api/Brut/FactoryBot.html +1 -1
  97. data/docs/api/Brut/Framework/App.html +1 -1
  98. data/docs/api/Brut/Framework/Config.html +1 -1
  99. data/docs/api/Brut/Framework/Container.html +1 -1
  100. data/docs/api/Brut/Framework/Error.html +1 -1
  101. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  102. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  103. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  104. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  105. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  106. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  107. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  108. data/docs/api/Brut/Framework/Errors.html +1 -1
  109. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  110. data/docs/api/Brut/Framework/MCP.html +1 -1
  111. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  112. data/docs/api/Brut/Framework.html +1 -1
  113. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Component/Helpers.html +86 -2
  115. data/docs/api/Brut/FrontEnd/Component.html +78 -9
  116. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +3 -3
  117. data/docs/api/Brut/FrontEnd/Components/FormTag.html +3 -3
  118. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +3 -3
  119. data/docs/api/Brut/FrontEnd/Components/Input.html +3 -3
  120. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +3 -3
  121. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +3 -3
  122. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +3 -3
  123. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +3 -3
  124. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +3 -3
  125. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +3 -3
  127. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +3 -3
  128. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +3 -3
  129. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +3 -3
  130. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  146. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  155. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  156. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  157. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  158. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Layout.html +3 -3
  160. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Page.html +31 -23
  167. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +3 -3
  168. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  169. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  170. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  171. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  172. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  173. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  174. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  175. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  176. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  177. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  178. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  179. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  180. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  181. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  182. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  183. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  184. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  185. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  186. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  187. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  188. data/docs/api/Brut/FrontEnd.html +1 -1
  189. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  190. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  191. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  192. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  193. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  194. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  195. data/docs/api/Brut/I18n.html +1 -1
  196. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  197. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  198. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  199. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  200. data/docs/api/Brut/Instrumentation.html +1 -1
  201. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +13 -11
  202. data/docs/api/Brut/SinatraHelpers.html +1 -1
  203. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  204. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  205. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  206. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  207. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  208. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  209. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  210. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  211. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  212. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  213. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  214. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  215. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  216. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  217. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  218. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  219. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  220. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  221. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  222. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  223. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  224. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  225. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  226. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  227. data/docs/api/Brut/SpecSupport.html +1 -1
  228. data/docs/api/Brut.html +1 -1
  229. data/docs/api/Clock.html +1 -1
  230. data/docs/api/ModuleName.html +1 -1
  231. data/docs/api/RichString.html +1 -1
  232. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  233. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  234. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  235. data/docs/api/Sequel/Extensions.html +1 -1
  236. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  237. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  238. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  239. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  240. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  241. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  242. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  243. data/docs/api/Sequel/Plugins.html +1 -1
  244. data/docs/api/Sequel.html +1 -1
  245. data/docs/api/_index.html +1 -1
  246. data/docs/api/file.README.html +1 -1
  247. data/docs/api/index.html +1 -1
  248. data/docs/api/method_list.html +92 -76
  249. data/docs/api/top-level-namespace.html +1 -1
  250. data/docs/assets/{adrs.md.JRxZ5uYE.js → adrs.md.BxjHi9-8.js} +1 -1
  251. data/docs/assets/adrs.md.BxjHi9-8.lean.js +1 -0
  252. data/docs/assets/{app.AkS4fHzI.js → app.DyQLb4Ot.js} +1 -1
  253. data/docs/assets/chunks/@localSearchIndexroot.CmtZyrFA.js +1 -0
  254. data/docs/assets/chunks/{VPLocalSearchBox.By6un1FD.js → VPLocalSearchBox.T1iA-eJx.js} +1 -1
  255. data/docs/assets/chunks/{theme.DwiUPdci.js → theme.ChwsbWjK.js} +2 -2
  256. data/docs/assets/{components.md.BfeWD9sX.js → components.md.DHh-NwKs.js} +3 -3
  257. data/docs/assets/{configuration.md.DoSBNc0H.js → configuration.md.D8Wz3oJU.js} +1 -1
  258. data/docs/assets/{forms.md.DFveP5g_.js → forms.md.BRE85eju.js} +1 -1
  259. data/docs/assets/{getting-started.md.DZWjCVC0.js → getting-started.md.2ioiTe-B.js} +6 -3
  260. data/docs/assets/{getting-started.md.DZWjCVC0.lean.js → getting-started.md.2ioiTe-B.lean.js} +1 -1
  261. data/docs/assets/overview.md.DlKiRRG_.js +1 -0
  262. data/docs/assets/overview.md.DlKiRRG_.lean.js +1 -0
  263. data/docs/assets/tutorial.md.BIb7XT6j.js +1 -0
  264. data/docs/assets/tutorial.md.BIb7XT6j.lean.js +1 -0
  265. data/docs/assets.html +3 -3
  266. data/docs/brut-css/brut.max.css +1 -1
  267. data/docs/brut-css/classes/appearances.html +1 -1
  268. data/docs/brut-css/classes/background-colors.html +1 -1
  269. data/docs/brut-css/classes/border-colors.html +1 -1
  270. data/docs/brut-css/classes/borders.html +1 -1
  271. data/docs/brut-css/classes/dimensions.html +1 -1
  272. data/docs/brut-css/classes/flex.html +3 -3
  273. data/docs/brut-css/classes/foreground-colors.html +1 -1
  274. data/docs/brut-css/classes/junk-drawer.html +1 -1
  275. data/docs/brut-css/classes/layout.html +1 -1
  276. data/docs/brut-css/classes/lists.html +1 -1
  277. data/docs/brut-css/classes/positioning.html +1 -1
  278. data/docs/brut-css/classes/spacings.html +1 -1
  279. data/docs/brut-css/classes/typography.html +1 -1
  280. data/docs/brut-css/customization/advanced-configuration.html +1 -1
  281. data/docs/brut-css/customization/breakpoints.html +1 -1
  282. data/docs/brut-css/customization/design-system.html +1 -1
  283. data/docs/brut-css/customization/pseudo-classes.html +1 -1
  284. data/docs/brut-css/getting-started/core-concepts.html +1 -1
  285. data/docs/brut-css/getting-started/installation.html +1 -1
  286. data/docs/brut-css/getting-started/overview.html +1 -1
  287. data/docs/brut-css/getting-started/simple-example.html +1 -1
  288. data/docs/brut-css/index.html +1 -1
  289. data/docs/brut-css/properties/colors.html +1 -1
  290. data/docs/brut-css/properties/spacings.html +1 -1
  291. data/docs/brut-css/properties/typography.html +1 -1
  292. data/docs/brut-js/api/AjaxSubmit.html +56 -7
  293. data/docs/brut-js/api/AjaxSubmit.js.html +77 -41
  294. data/docs/brut-js/api/Autosubmit.html +1 -1
  295. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  296. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  297. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  298. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  299. data/docs/brut-js/api/BufferedLogger.html +1 -1
  300. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  301. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  302. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  303. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  304. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  305. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  306. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  307. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  308. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  309. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  310. data/docs/brut-js/api/Form.html +1 -1
  311. data/docs/brut-js/api/Form.js.html +1 -1
  312. data/docs/brut-js/api/I18nTranslation.html +1 -1
  313. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  314. data/docs/brut-js/api/LocaleDetection.html +1 -1
  315. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  316. data/docs/brut-js/api/Logger.html +1 -1
  317. data/docs/brut-js/api/Logger.js.html +1 -1
  318. data/docs/brut-js/api/Message.html +1 -1
  319. data/docs/brut-js/api/Message.js.html +1 -1
  320. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  321. data/docs/brut-js/api/RichString.html +1 -1
  322. data/docs/brut-js/api/RichString.js.html +1 -1
  323. data/docs/brut-js/api/Tabs.html +1 -1
  324. data/docs/brut-js/api/Tabs.js.html +1 -1
  325. data/docs/brut-js/api/Tracing.html +1 -1
  326. data/docs/brut-js/api/Tracing.js.html +1 -1
  327. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  328. data/docs/brut-js/api/external-Performance.html +1 -1
  329. data/docs/brut-js/api/external-Promise.html +1 -1
  330. data/docs/brut-js/api/external-ValidityState.html +1 -1
  331. data/docs/brut-js/api/external-Window.html +1 -1
  332. data/docs/brut-js/api/external-fetch.html +1 -1
  333. data/docs/brut-js/api/global.html +1 -1
  334. data/docs/brut-js/api/index.html +1 -1
  335. data/docs/brut-js/api/index.js.html +1 -1
  336. data/docs/brut-js/api/module-testing.html +1 -1
  337. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  338. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  339. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  340. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  341. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  342. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  343. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  344. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  345. data/docs/brut-js/api/testing_index.js.html +1 -1
  346. data/docs/brut-js.html +3 -3
  347. data/docs/business-logic.html +3 -3
  348. data/docs/cli.html +3 -3
  349. data/docs/components.html +7 -7
  350. data/docs/configuration.html +5 -5
  351. data/docs/css.html +3 -3
  352. data/docs/custom-element-tests.html +3 -3
  353. data/docs/database-access.html +3 -3
  354. data/docs/database-schema.html +3 -3
  355. data/docs/deployment.html +3 -3
  356. data/docs/dev-environment.html +3 -3
  357. data/docs/dir-structure.html +3 -3
  358. data/docs/doc-conventions.html +3 -3
  359. data/docs/end-to-end-tests.html +3 -3
  360. data/docs/features.html +3 -3
  361. data/docs/flash-and-session.html +3 -3
  362. data/docs/form-constraints.html +3 -3
  363. data/docs/forms.html +5 -5
  364. data/docs/getting-started.html +9 -6
  365. data/docs/handlers.html +3 -3
  366. data/docs/hashmap.json +1 -1
  367. data/docs/hooks.html +3 -3
  368. data/docs/i18n.html +3 -3
  369. data/docs/index.html +3 -3
  370. data/docs/instrumentation.html +3 -3
  371. data/docs/javascript.html +3 -3
  372. data/docs/jobs.html +3 -3
  373. data/docs/keyword-injection.html +3 -3
  374. data/docs/layouts.html +3 -3
  375. data/docs/lsp.html +3 -3
  376. data/docs/markdown-examples.html +3 -3
  377. data/docs/middleware.html +3 -3
  378. data/docs/overview.html +5 -5
  379. data/docs/pages.html +3 -3
  380. data/docs/recipes/alternate-layouts.html +3 -3
  381. data/docs/recipes/authentication.html +3 -3
  382. data/docs/recipes/blank-layouts.html +3 -3
  383. data/docs/recipes/custom-flash.html +3 -3
  384. data/docs/recipes/indexed-forms.html +3 -3
  385. data/docs/recipes/migrations.html +3 -3
  386. data/docs/recipes/text-field-component.html +3 -3
  387. data/docs/roadmap.html +3 -3
  388. data/docs/routes.html +3 -3
  389. data/docs/security.html +3 -3
  390. data/docs/seed-data.html +3 -3
  391. data/docs/space-time-continuum.html +3 -3
  392. data/docs/tutorial.html +5 -5
  393. data/docs/unit-tests.html +3 -3
  394. data/docs/why.html +3 -3
  395. data/lib/brut/cli/apps/scaffold.rb +1 -1
  396. data/lib/brut/framework/mcp.rb +1 -1
  397. data/lib/brut/front_end/component.rb +19 -0
  398. data/lib/brut/front_end/components/form_tag.rb +2 -2
  399. data/lib/brut/front_end/page.rb +11 -7
  400. data/lib/brut/sinatra_helpers.rb +1 -0
  401. data/lib/brut/version.rb +1 -1
  402. data/mkbrut/.gitignore +16 -0
  403. data/mkbrut/CODE_OF_CONDUCT.txt +100 -0
  404. data/mkbrut/Gemfile +3 -0
  405. data/mkbrut/Gemfile.lock +19 -0
  406. data/mkbrut/LICENSE.txt +370 -0
  407. data/mkbrut/README.md +145 -0
  408. data/mkbrut/Rakefile +2 -0
  409. data/mkbrut/bin/build +36 -0
  410. data/mkbrut/bin/ci +19 -0
  411. data/mkbrut/bin/docs +19 -0
  412. data/mkbrut/bin/publish +129 -0
  413. data/mkbrut/bin/rake +16 -0
  414. data/mkbrut/bin/setup +30 -0
  415. data/mkbrut/brut-welcome.png +0 -0
  416. data/mkbrut/deploy/.dockerignore +2 -0
  417. data/mkbrut/deploy/Dockerfile +25 -0
  418. data/mkbrut/exe/mkbrut +5 -0
  419. data/mkbrut/lib/mkbrut/app.rb +79 -0
  420. data/mkbrut/lib/mkbrut/app_id.rb +8 -0
  421. data/mkbrut/lib/mkbrut/app_name.rb +29 -0
  422. data/mkbrut/lib/mkbrut/app_options.rb +36 -0
  423. data/mkbrut/lib/mkbrut/base.rb +57 -0
  424. data/mkbrut/lib/mkbrut/cli.rb +107 -0
  425. data/mkbrut/lib/mkbrut/erb_binding_delegate.rb +20 -0
  426. data/mkbrut/lib/mkbrut/internet_identifier.rb +32 -0
  427. data/mkbrut/lib/mkbrut/invalid_identifier.rb +4 -0
  428. data/mkbrut/lib/mkbrut/ops/add_css_import.rb +42 -0
  429. data/mkbrut/lib/mkbrut/ops/add_i18n_message.rb +74 -0
  430. data/mkbrut/lib/mkbrut/ops/add_method.rb +48 -0
  431. data/mkbrut/lib/mkbrut/ops/append_to_file.rb +20 -0
  432. data/mkbrut/lib/mkbrut/ops/base_op.rb +21 -0
  433. data/mkbrut/lib/mkbrut/ops/copy_file.rb +12 -0
  434. data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +58 -0
  435. data/mkbrut/lib/mkbrut/ops/insert_route.rb +52 -0
  436. data/mkbrut/lib/mkbrut/ops/mkdir.rb +13 -0
  437. data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +70 -0
  438. data/mkbrut/lib/mkbrut/ops/render_template.rb +26 -0
  439. data/mkbrut/lib/mkbrut/ops/skip_file.rb +10 -0
  440. data/mkbrut/lib/mkbrut/ops.rb +16 -0
  441. data/mkbrut/lib/mkbrut/organization.rb +5 -0
  442. data/mkbrut/lib/mkbrut/prefix.rb +26 -0
  443. data/mkbrut/lib/mkbrut/prefixed_io.rb +16 -0
  444. data/mkbrut/lib/mkbrut/segments/bare_bones.rb +185 -0
  445. data/mkbrut/lib/mkbrut/segments/demo.rb +121 -0
  446. data/mkbrut/lib/mkbrut/segments/heroku.rb +30 -0
  447. data/mkbrut/lib/mkbrut/segments/sidekiq.rb +3 -0
  448. data/mkbrut/lib/mkbrut/segments.rb +8 -0
  449. data/mkbrut/lib/mkbrut/version.rb +3 -0
  450. data/mkbrut/lib/mkbrut/versions.rb +13 -0
  451. data/mkbrut/lib/mkbrut.rb +18 -0
  452. data/mkbrut/mkbrut.gemspec +32 -0
  453. data/mkbrut/templates/Base/.dockerignore +25 -0
  454. data/mkbrut/templates/Base/.env.development.erb +60 -0
  455. data/mkbrut/templates/Base/.env.test.erb +8 -0
  456. data/mkbrut/templates/Base/.gitignore +31 -0
  457. data/mkbrut/templates/Base/.projections.json +59 -0
  458. data/mkbrut/templates/Base/Dockerfile.dx +205 -0
  459. data/mkbrut/templates/Base/Gemfile.erb +53 -0
  460. data/mkbrut/templates/Base/Procfile.development +5 -0
  461. data/mkbrut/templates/Base/Procfile.test +1 -0
  462. data/mkbrut/templates/Base/README.md +4 -0
  463. data/mkbrut/templates/Base/README.md.erb +40 -0
  464. data/mkbrut/templates/Base/app/bootstrap.rb +61 -0
  465. data/mkbrut/templates/Base/app/config/i18n/en/1_defaults.rb +128 -0
  466. data/mkbrut/templates/Base/app/config/i18n/en/2_app.rb +24 -0
  467. data/mkbrut/templates/Base/app/public/static/manifest.json.erb +33 -0
  468. data/mkbrut/templates/Base/app/src/app.rb.erb +37 -0
  469. data/mkbrut/templates/Base/app/src/back_end/data_models/app_data_model.rb +5 -0
  470. data/mkbrut/templates/Base/app/src/back_end/data_models/db.rb +19 -0
  471. data/mkbrut/templates/Base/app/src/back_end/data_models/migrations/20240101130000_citext.rb +6 -0
  472. data/mkbrut/templates/Base/app/src/back_end/data_models/seed/seed_data.rb +9 -0
  473. data/mkbrut/templates/Base/app/src/front_end/components/app_component.rb +8 -0
  474. data/mkbrut/templates/Base/app/src/front_end/components/custom_element_registration.rb.erb +7 -0
  475. data/mkbrut/templates/Base/app/src/front_end/css/index.css +2 -0
  476. data/mkbrut/templates/Base/app/src/front_end/css/svgs.css +12 -0
  477. data/mkbrut/templates/Base/app/src/front_end/forms/app_form.rb +4 -0
  478. data/mkbrut/templates/Base/app/src/front_end/handlers/app_handler.rb +4 -0
  479. data/mkbrut/templates/Base/app/src/front_end/images/LogoPylon.png +0 -0
  480. data/mkbrut/templates/Base/app/src/front_end/images/LogoTransit.png +0 -0
  481. data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-120x120.png +0 -0
  482. data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-152x152.png +0 -0
  483. data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-167x167.png +0 -0
  484. data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-180x180.png +0 -0
  485. data/mkbrut/templates/Base/app/src/front_end/images/favicon.ico +0 -0
  486. data/mkbrut/templates/Base/app/src/front_end/images/icon.png +0 -0
  487. data/mkbrut/templates/Base/app/src/front_end/images/mkicons.sh +6 -0
  488. data/mkbrut/templates/Base/app/src/front_end/js/index.js +6 -0
  489. data/mkbrut/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +73 -0
  490. data/mkbrut/templates/Base/app/src/front_end/pages/app_page.rb +11 -0
  491. data/mkbrut/templates/Base/app/src/front_end/pages/home_page.rb +62 -0
  492. data/mkbrut/templates/Base/app/src/front_end/support/app_session.rb +6 -0
  493. data/mkbrut/templates/Base/app/src/front_end/svgs/README.md +5 -0
  494. data/mkbrut/templates/Base/app/src/front_end/svgs/comment-button.svg +59 -0
  495. data/mkbrut/templates/Base/bin/README.md.erb +5 -0
  496. data/mkbrut/templates/Base/bin/build-assets +7 -0
  497. data/mkbrut/templates/Base/bin/ci +39 -0
  498. data/mkbrut/templates/Base/bin/console +31 -0
  499. data/mkbrut/templates/Base/bin/db +9 -0
  500. data/mkbrut/templates/Base/bin/dbconsole +51 -0
  501. data/mkbrut/templates/Base/bin/dev +25 -0
  502. data/mkbrut/templates/Base/bin/release +26 -0
  503. data/mkbrut/templates/Base/bin/run +86 -0
  504. data/mkbrut/templates/Base/bin/scaffold +9 -0
  505. data/mkbrut/templates/Base/bin/setup +256 -0
  506. data/mkbrut/templates/Base/bin/startup-message +65 -0
  507. data/mkbrut/templates/Base/bin/test +9 -0
  508. data/mkbrut/templates/Base/bin/test-server +29 -0
  509. data/mkbrut/templates/Base/bin/watch-and-build-assets +37 -0
  510. data/mkbrut/templates/Base/config.ru +16 -0
  511. data/mkbrut/templates/Base/docker-compose.dx.yml +92 -0
  512. data/mkbrut/templates/Base/dx/README.md +28 -0
  513. data/mkbrut/templates/Base/dx/bash_customizations +12 -0
  514. data/mkbrut/templates/Base/dx/bash_customizations.local +8 -0
  515. data/mkbrut/templates/Base/dx/build +107 -0
  516. data/mkbrut/templates/Base/dx/docker-compose.env.erb +25 -0
  517. data/mkbrut/templates/Base/dx/dx.sh.lib +137 -0
  518. data/mkbrut/templates/Base/dx/exec +68 -0
  519. data/mkbrut/templates/Base/dx/prune +19 -0
  520. data/mkbrut/templates/Base/dx/show-help-in-app-container-then-wait.sh +38 -0
  521. data/mkbrut/templates/Base/dx/start +30 -0
  522. data/mkbrut/templates/Base/dx/stop +23 -0
  523. data/mkbrut/templates/Base/package.json.erb +37 -0
  524. data/mkbrut/templates/Base/puma.config.rb +53 -0
  525. data/mkbrut/templates/Base/specs/e2e/home_page.spec.rb.erb +23 -0
  526. data/mkbrut/templates/Base/specs/front_end/js/SpecHelper.js +24 -0
  527. data/mkbrut/templates/Base/specs/front_end/pages/home_page.spec.rb +22 -0
  528. data/mkbrut/templates/Base/specs/lint_factories.spec.rb +7 -0
  529. data/mkbrut/templates/Base/specs/spec_helper.rb +78 -0
  530. data/mkbrut/templates/Base/specs/support.rb +2 -0
  531. data/mkbrut/templates/segments/BareBones/app/src/front_end/handlers/trigger_exception_handler.rb +24 -0
  532. data/mkbrut/templates/segments/BareBones/app/src/front_end/js/Example.js.erb +49 -0
  533. data/mkbrut/templates/segments/BareBones/specs/front_end/handlers/trigger_exception_handler.spec.rb +41 -0
  534. data/mkbrut/templates/segments/BareBones/specs/front_end/js/Example.spec.js.erb +38 -0
  535. data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/db/guestbook_message.rb +3 -0
  536. data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/migrations/20250628194124_guestbook.rb +14 -0
  537. data/mkbrut/templates/segments/Demo/app/src/front_end/components/flash_component.rb +36 -0
  538. data/mkbrut/templates/segments/Demo/app/src/front_end/css/constraint-violations.css +18 -0
  539. data/mkbrut/templates/segments/Demo/app/src/front_end/css/fonts.css +19 -0
  540. data/mkbrut/templates/segments/Demo/app/src/front_end/fonts/monaspace-xenon.ttf +0 -0
  541. data/mkbrut/templates/segments/Demo/app/src/front_end/forms/guestbook_message_form.rb +4 -0
  542. data/mkbrut/templates/segments/Demo/app/src/front_end/handlers/guestbook_message_handler.rb +64 -0
  543. data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page/message_component.rb +41 -0
  544. data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page.rb +43 -0
  545. data/mkbrut/templates/segments/Demo/app/src/front_end/pages/new_guestbook_message_page.rb +64 -0
  546. data/mkbrut/templates/segments/Demo/specs/back_end/data_models/db/guestbook_message.spec.rb +5 -0
  547. data/mkbrut/templates/segments/Demo/specs/e2e/guest_message.spec.rb +54 -0
  548. data/mkbrut/templates/segments/Demo/specs/factories/db/guestbook_message.factory.rb +7 -0
  549. data/mkbrut/templates/segments/Demo/specs/front_end/components/flash_component.spec.rb +5 -0
  550. data/mkbrut/templates/segments/Demo/specs/front_end/handlers/guestbook_message_handler.spec.rb +122 -0
  551. data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page/message_component.spec.rb +5 -0
  552. data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page.spec.rb +52 -0
  553. data/mkbrut/templates/segments/Demo/specs/front_end/pages/new_guestbook_message_page.spec.rb +5 -0
  554. data/mkbrut/templates/segments/Heroku/bin/deploy +11 -0
  555. data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +125 -0
  556. data/mkbrut/templates/segments/Heroku/deploy/docker-entrypoint +15 -0
  557. data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +26 -0
  558. metadata +188 -24
  559. data/brut-js/CHANGELOG.md +0 -5
  560. data/docs/assets/adrs.md.JRxZ5uYE.lean.js +0 -1
  561. data/docs/assets/chunks/@localSearchIndexroot.C9FqcQxD.js +0 -1
  562. data/docs/assets/overview.md.iMnwLO4x.js +0 -1
  563. data/docs/assets/overview.md.iMnwLO4x.lean.js +0 -1
  564. data/docs/assets/tutorial.md.BYXj4cOu.js +0 -1
  565. data/docs/assets/tutorial.md.BYXj4cOu.lean.js +0 -1
  566. /data/docs/assets/{components.md.BfeWD9sX.lean.js → components.md.DHh-NwKs.lean.js} +0 -0
  567. /data/docs/assets/{configuration.md.DoSBNc0H.lean.js → configuration.md.D8Wz3oJU.lean.js} +0 -0
  568. /data/docs/assets/{forms.md.DFveP5g_.lean.js → forms.md.BRE85eju.lean.js} +0 -0
@@ -0,0 +1,41 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe TriggerExceptionHandler do
4
+ describe "#handle!" do
5
+ context "key is correct" do
6
+ context "http status given" do
7
+ it "returns that status" do
8
+ handler = described_class.new(status: 401, key: "test-trigger-exception")
9
+ result = handler.handle!
10
+ expect(result).to have_returned_http_status(401)
11
+ end
12
+ end
13
+ context "no http status given" do
14
+ context "message provided" do
15
+ it "raises with that message" do
16
+ handler = described_class.new(message: "test message", key: "test-trigger-exception")
17
+ expect {
18
+ handler.handle!
19
+ }.to raise_error("test message")
20
+ end
21
+ end
22
+ context "no message provided" do
23
+ it "raises with a default message" do
24
+ handler = described_class.new(key: "test-trigger-exception")
25
+ expect {
26
+ handler.handle!
27
+ }.to raise_error("no message provided")
28
+ end
29
+ end
30
+ end
31
+ end
32
+ context "key is incorrect" do
33
+ it "returns 404" do
34
+ handler = described_class.new(status: 401, message: "test message")
35
+ result = handler.handle!
36
+ expect(result).to have_returned_http_status(404)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,38 @@
1
+ import { withHTML } from "./SpecHelper.js"
2
+
3
+ // Autonomouse custom element support is very basic.
4
+ // In this spec, we use `withHTML` which was defined in SpecHelper.js,
5
+ // which ultimately uses `createTestBasedOnHTML` from BrutJS.
6
+ // This will configure JSDom with the HTML you provide, then execute
7
+ // your test as if that HTML is the document.
8
+ describe("<<%= prefix %>-example>", () => {
9
+
10
+ // The example custom element will transform its contents to
11
+ // either upper or lower case. In this test, we assert that, by
12
+ // default, it transforms to upper case. To do that,
13
+ // create an HTML document you can see below. The element
14
+ // will have been connected (connectedCallback will have been
15
+ // called) by the time the test executes. Thus,
16
+ // document.querySelector(...) will find the element and its
17
+ // .textContent will have already been transformed.
18
+ withHTML(`
19
+ <<%= prefix %>-example>This is some Text</<%= prefix %>-example>
20
+ `).test("upper case by default", ({document,assert}) => {
21
+ const element = document.querySelector("<%= prefix %>-example")
22
+ assert.equal(element.textContent,"THIS IS SOME TEXT")
23
+ })
24
+
25
+ withHTML(`
26
+ <<%= prefix %>-example transform="lower">This is some Text</<%= prefix %>-example>
27
+ `).test("lower case when asked", ({document,assert}) => {
28
+ const element = document.querySelector("<%= prefix %>-example")
29
+ assert.equal(element.textContent,"this is some text")
30
+ // Remember that setAttribute is synchronous, so by the time
31
+ // its done executing, attributeChangedCallback will have been called,
32
+ // and thus the element's `update()` method will have been called and
33
+ // its textContent transformed.
34
+ element.setAttribute("transform","upper")
35
+ assert.equal(element.textContent,"THIS IS SOME TEXT")
36
+ })
37
+ })
38
+
@@ -0,0 +1,3 @@
1
+ class DB::GuestbookMessage < AppDataModel
2
+ has_external_id "gm"
3
+ end
@@ -0,0 +1,14 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table :guestbook_messages,
4
+ comment: "Messages people have left in the guestbook",
5
+ external_id: true do
6
+
7
+ column :name, :text
8
+ column :message, :text
9
+ column :ip_address, :inet
10
+
11
+ key [ :ip_address ]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ class FlashComponent < AppComponent
2
+ def initialize(flash:)
3
+ @flash = flash
4
+ end
5
+
6
+ CLASSES = [
7
+ "f-1",
8
+ "fw-bold",
9
+ "w-50",
10
+ "mv-3",
11
+ "shadow-3",
12
+ "pa-3",
13
+ "ba",
14
+ "br-3",
15
+ "tc"
16
+ ]
17
+ COLORS = [
18
+ "%{color}-300",
19
+ "bg-%{color}-800",
20
+ "bc-%{color}-700",
21
+ ]
22
+
23
+ def view_template
24
+ if @flash.notice?
25
+ div(role: "status",
26
+ class: CLASSES + COLORS.map { it % { color: "blue" } }) do
27
+ t(@flash.notice)
28
+ end
29
+ elsif @flash.alert?
30
+ div(role: "alert",
31
+ class: CLASSES + COLORS.map { it % { color: "orange" } }) do
32
+ t(@flash.alert)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ /* Hide <brut-cv> tags so that we don't see
2
+ * client-side errors before the form is submitted */
3
+ brut-cv {
4
+ display: none;
5
+ color: var(--red-400);
6
+ }
7
+
8
+ /* Server-side errors are always visible */
9
+ brut-cv[server-side] {
10
+ display: block;
11
+ }
12
+
13
+ /* Only when a form submission is attempted do
14
+ * we show the <brut-cv>s that are not server-side,
15
+ * and thus client-side */
16
+ brut-form[submitted-invalid] brut-cv {
17
+ display: block;
18
+ }
@@ -0,0 +1,19 @@
1
+ /* Example of font-management. */
2
+ @font-face {
3
+ font-family: "Monaspace Xenon";
4
+ /** url is relative to this file. When bin/build-assets runs,
5
+ * esbuild will see this and modify it to work with the bundle
6
+ * that is ultimately produced */
7
+ src: url("../fonts/monaspace-xenon.ttf") format("truetype");
8
+ font-display: swap;
9
+ }
10
+
11
+ :root {
12
+ /* Sets the mono font for BrutCSS */
13
+ --ff-mono: 'Monaspace Xenon', monospace;
14
+ }
15
+
16
+ code {
17
+ /* Uses the mono font for <code> elements */
18
+ font-family: var(--ff-mono);
19
+ }
@@ -0,0 +1,4 @@
1
+ class GuestbookMessageForm < AppForm
2
+ input :name, minlength: 2
3
+ input :message, minlength: 10
4
+ end
@@ -0,0 +1,64 @@
1
+ class GuestbookMessageHandler < AppHandler
2
+ def initialize(form:, flash:, rack_request_ip:, session:)
3
+ @form = form
4
+ @flash = flash
5
+ @rack_request_ip = rack_request_ip
6
+ @session = session
7
+ end
8
+ def handle
9
+ # If client-side constraint violation checking was skipped,
10
+ # but there ARE such violations, the form will be able to check
11
+ # that server-side and return true for #constraint_violations?
12
+ if @form.valid?
13
+ save_message
14
+ end
15
+
16
+ if @form.constraint_violations?
17
+ @flash.alert = :guestbook_not_saved
18
+ return NewGuestbookMessagePage.new(form: @form,
19
+ session: @session)
20
+ else
21
+ @flash.notice = :guestbook_saved
22
+ redirect_to(
23
+ GuestbookPage,
24
+ anchor: @session.guestbook_message&.external_id
25
+ )
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def save_message
32
+ # While we don't recommend business logic be placed
33
+ # inside handlers, we're leaving it here to avoid
34
+ # anchoring you on any particular way of managing
35
+ # the logic in your app.
36
+ if @form.message.split(/\s+/).length < 2
37
+ @form.server_side_constraint_violation(
38
+ input_name: "message",
39
+ key: :not_enough_words
40
+ )
41
+ else
42
+ if DB::GuestbookMessage.find(ip_address: @rack_request_ip)
43
+ @form.server_side_constraint_violation(
44
+ input_name: "name",
45
+ key: :already_posted
46
+ )
47
+ else
48
+ begin
49
+ guestbook_message = DB::GuestbookMessage.create(
50
+ name: @form.name,
51
+ message: @form.message,
52
+ ip_address: @rack_request_ip,
53
+ )
54
+ @session.signed_guestbook(guestbook_message)
55
+ rescue Sequel::UniqueConstraintViolation => ex
56
+ @form.server_side_constraint_violation(
57
+ input_name: "name",
58
+ key: :already_posted
59
+ )
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,41 @@
1
+ class GuestbookPage::MessageComponent < AppComponent
2
+ def initialize(message:, index:, current_visitor_message: false)
3
+ @message = message
4
+ @current_visitor_message = current_visitor_message
5
+ @background_color = [
6
+ "bg-gray-800",
7
+ "bg-blue-800",
8
+ "bg-green-800",
9
+ "bg-yellow-800",
10
+ ][index % 4]
11
+ end
12
+
13
+ def view_template
14
+ div_classes = [
15
+ "measure",
16
+ "mh-auto",
17
+ "pa-3",
18
+ "br-4",
19
+ "br-tr-0",
20
+ "br-bl-0",
21
+ "mt-3",
22
+ @background_color,
23
+ ]
24
+ border_class = if @current_visitor_message
25
+ div_classes << "ba" << "bc-gray-600" << "bw-2"
26
+ else
27
+ div_classes << "bn"
28
+ end
29
+ div(class: div_classes, id: @message.external_id) do
30
+ blockquote(class: "ff-cursive p mh-auto i f-4") do
31
+ @message.message
32
+ end
33
+ p(class: "p mh-auto tr f-2") do
34
+ plain("—")
35
+ plain(@message.name)
36
+ plain(" on ")
37
+ TimeTag(date: @message.created_at, format: :date, class: "gray-400 f-1 ttu")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,43 @@
1
+ class GuestbookPage < AppPage
2
+
3
+ def initialize(session:)
4
+ @session = session
5
+ end
6
+
7
+ def page_template
8
+ div(class: "flex flex-column items-center ff-sans") do
9
+ article(class: "w-50 pv-3 bt bc-gray-700", id: "guestbook") do
10
+ h2(class: "ff-sans ma-0 lh-title f-4 tc") do
11
+ "Check out the Guestbook!"
12
+ end
13
+ if !@session.signed_guestbook?
14
+ a(
15
+ href: NewGuestbookMessagePage.routing,
16
+ class: "db tc mv-3 f-2 fw-5 blue-300"
17
+ ) do
18
+ "Sign it, yourself!"
19
+ end
20
+ else
21
+ a(
22
+ href: "##{@session.guestbook_message.external_id}",
23
+ class: "db tc mv-3 f-2 fw-5 blue-300"
24
+ ) do
25
+ "Check out your message"
26
+ end
27
+
28
+ end
29
+ DB::GuestbookMessage.order(
30
+ Sequel.function(:random)
31
+ ).each_with_index do |message,index|
32
+ render(
33
+ GuestbookPage::MessageComponent.new(
34
+ message:,
35
+ index:,
36
+ current_visitor_message: message == @session.guestbook_message
37
+ )
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,64 @@
1
+ class NewGuestbookMessagePage < AppPage
2
+ def initialize(form: nil, session:)
3
+ @form = form || GuestbookMessageForm.new
4
+ @session = session
5
+ end
6
+
7
+ def before_generate
8
+ if @session.signed_guestbook?
9
+ redirect_to(GuestbookPage)
10
+ end
11
+ end
12
+
13
+ def page_template
14
+ div(class: "flex flex-column items-center ff-sans") do
15
+ render global_component(FlashComponent)
16
+ div(class: "w-50 mv-5") do
17
+ brut_form(class: "w-100") do
18
+ FormTag(for: @form, class: "flex flex-column gap-3 items-end") do
19
+ h1(class: "ff-sans ma-0 lh-title f-5 self-start flex items-center gap-3") do
20
+ img(src: "/static/images/icon.png", class: "w-5")
21
+ span { "Sign The Guestbook" }
22
+ end
23
+ label(class: "flex flex-column gap-1 w-100") do
24
+ Inputs::InputTag(
25
+ form: @form,
26
+ input_name: "name",
27
+ placeholder: "e.g. Pat",
28
+ class: "f-3 pa-2 inset-shadow-1 br-2",
29
+ "data-1p-ignore": true
30
+ )
31
+ div(class: "flex items-start gap-3") do
32
+ span { "Your Message" }
33
+ ConstraintViolations(
34
+ form: @form,
35
+ input_name: "name"
36
+ )
37
+ end
38
+
39
+ end
40
+ label(class: "flex flex-column gap-1 w-100") do
41
+ Inputs::TextareaTag(
42
+ form: @form,
43
+ input_name: "message",
44
+ placeholder: "e.g. What a great site!",
45
+ rows: 6,
46
+ class: "f-3 pa-2 inset-shadow-1 br-2"
47
+ )
48
+ div(class: "flex items-start gap-3") do
49
+ span { "Your Message" }
50
+ ConstraintViolations(
51
+ form: @form,
52
+ input_name: "message"
53
+ )
54
+ end
55
+ end
56
+ button(class: "bg-green-900 green-300 ph-5 pv-3 f-3 ba bc-green-600 br-pill shadow-1 pointer") do
57
+ "Sign!"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe DB::GuestbookMessage do
4
+ implementation_is_trivial
5
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe "You can leave a guestbook message" do
4
+ it "accepts your message after validation, then shows it with the others" do
5
+ page.goto(HomePage.routing)
6
+
7
+ link = page.locator("a[href='#{NewGuestbookMessagePage.routing}']")
8
+
9
+ link.click
10
+
11
+ expect(page).to be_page_for(NewGuestbookMessagePage)
12
+
13
+ button = page.locator("form button")
14
+
15
+ button.click
16
+ expect(page).to be_page_for(NewGuestbookMessagePage)
17
+
18
+ name_error_message = page.locator("brut-cv-messages[input-name='name']")
19
+ message_error_message = page.locator("brut-cv-messages[input-name='message']")
20
+
21
+ this_field = t("cv.this_field")
22
+
23
+ expect(name_error_message).to have_text(
24
+ t("cv.cs.valueMissing", field: this_field)
25
+ )
26
+ expect(message_error_message).to have_text(
27
+ t("cv.cs.valueMissing", field: this_field)
28
+ )
29
+
30
+ name_field = page.locator("form input[name='name']")
31
+ message_field = page.locator("form textarea[name='message']")
32
+
33
+ name_field.fill("Pat")
34
+ message_field.fill("FIRSTPOST!")
35
+
36
+ button.click
37
+ expect(page).to be_page_for(NewGuestbookMessagePage)
38
+
39
+ flash = page.locator("[role='alert']")
40
+ expect(flash).to have_text(t(:guestbook_not_saved))
41
+ expect(message_error_message).to have_text(
42
+ t("cv.ss.not_enough_words", field: this_field)
43
+ )
44
+
45
+ message_field.fill("OK, fine, this site is awesome!")
46
+ button.click
47
+
48
+ expect(page).to be_page_for(GuestbookPage)
49
+
50
+ quotes = page.locator("blockquote")
51
+ expect(quotes).to have_text("OK, fine, this site is awesome!")
52
+
53
+ end
54
+ end
@@ -0,0 +1,7 @@
1
+ FactoryBot.define do
2
+ factory :guestbook_message, class: "DB::GuestbookMessage" do
3
+ name { Faker::Name.name }
4
+ message { Faker::Lorem.paragraph }
5
+ ip_address { Faker::Internet.unique.ip_v4_address }
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe FlashComponent do
4
+ implementation_is_trivial
5
+ end
@@ -0,0 +1,122 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe GuestbookMessageHandler do
4
+ describe "#handle!" do
5
+ context "there are client-side constraint violations" do
6
+ it "generates the new guestbook message page" do
7
+ form = GuestbookMessageForm.new(params: {})
8
+ flash = empty_flash
9
+ session = empty_session
10
+ rack_request_ip = Faker::Internet.ip_v4_address
11
+
12
+ confidence_check do
13
+ expect(form).to have_constraint_violation(:name,key: :valueMissing)
14
+ end
15
+
16
+ handler = described_class.new(
17
+ form:,
18
+ flash:,
19
+ session:,
20
+ rack_request_ip:
21
+ )
22
+ result = nil
23
+ expect {
24
+ result = handler.handle
25
+ }.not_to change { DB::GuestbookMessage.count }
26
+
27
+ expect(result).to have_generated(NewGuestbookMessagePage)
28
+ expect(flash.alert).to eq("guestbook_not_saved")
29
+ end
30
+ end
31
+ context "there are server-side constraint violations" do
32
+ it "indicates a server-side error and generates the new guestbook message page" do
33
+ form = GuestbookMessageForm.new(params: {
34
+ name: "Pat",
35
+ message: "FIRSTPOST!!!!",
36
+ })
37
+ flash = empty_flash
38
+ session = empty_session
39
+ rack_request_ip = Faker::Internet.ip_v4_address
40
+
41
+ handler = described_class.new(
42
+ form:,
43
+ flash:,
44
+ session:,
45
+ rack_request_ip:
46
+ )
47
+ result = nil
48
+ expect {
49
+ result = handler.handle
50
+ }.not_to change { DB::GuestbookMessage.count }
51
+
52
+ expect(result).to have_generated(NewGuestbookMessagePage)
53
+ expect(form).to have_constraint_violation(:message, key: :not_enough_words)
54
+ expect(flash.alert).to eq("guestbook_not_saved")
55
+ end
56
+ end
57
+ context "the ip address has already been used" do
58
+
59
+ it "indicates a server-side error and generates the new guestbook message page" do
60
+ existing_message = create(:guestbook_message)
61
+
62
+ form = GuestbookMessageForm.new(params: {
63
+ name: "Pat",
64
+ message: "This website is amazing!",
65
+ })
66
+ flash = empty_flash
67
+ session = empty_session
68
+ rack_request_ip = existing_message.ip_address
69
+
70
+ handler = described_class.new(
71
+ form:,
72
+ flash:,
73
+ session:,
74
+ rack_request_ip:
75
+ )
76
+ result = nil
77
+ expect {
78
+ result = handler.handle
79
+ }.not_to change { DB::GuestbookMessage.count }
80
+
81
+ expect(result).to have_generated(NewGuestbookMessagePage)
82
+ expect(form).to have_constraint_violation(:name, key: :already_posted)
83
+ expect(flash.alert).to eq("guestbook_not_saved")
84
+ end
85
+ end
86
+ context "no constraint violations, unused ip address" do
87
+ it "saves the message and redirects back to the guestbook page" do
88
+ form = GuestbookMessageForm.new(params: {
89
+ name: "Pat",
90
+ message: "This website is amazing!",
91
+ })
92
+ flash = empty_flash
93
+ session = empty_session
94
+ rack_request_ip = Faker::Internet.ip_v4_address
95
+
96
+ handler = described_class.new(
97
+ form:,
98
+ flash:,
99
+ session:,
100
+ rack_request_ip:
101
+ )
102
+ result = nil
103
+ expect {
104
+ result = handler.handle
105
+ }.to change { DB::GuestbookMessage.count }.by(1)
106
+
107
+ message = DB::GuestbookMessage.order(Sequel.desc(:created_at)).first
108
+
109
+ expect(message.name).to eq("Pat")
110
+ expect(message.message).to eq("This website is amazing!")
111
+ expect(message.ip_address).to eq(rack_request_ip)
112
+
113
+ expect(result).to have_redirected_to(GuestbookPage,
114
+ anchor: message.external_id)
115
+ expect(form).not_to have_constraint_violation(:name,
116
+ key: :already_posted)
117
+ expect(flash.notice).to eq("guestbook_saved")
118
+
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe GuestbookPage::MessageComponent do
4
+ implementation_is_covered_by_other_tests "e2e tests and page's test"
5
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe GuestbookPage do
4
+ it "shows current guestbook messages" do
5
+ messages = 5.times.map {
6
+ create(:guestbook_message)
7
+ }
8
+ confidence_check { expect(DB::GuestbookMessage.all.length).to eq(5) }
9
+
10
+ result = generate_and_parse(self.described_class.new(session: empty_session))
11
+
12
+ message_elements = result.css("div:has(> blockquote)")
13
+ expect(message_elements.length).to eq(5)
14
+ messages.each do |message|
15
+ element = message_elements.select {
16
+ it.css("blockquote").text.strip == message.message
17
+ }
18
+ expect(element.size).to eq(1),
19
+ "Found #{element.size} blockquotes for message #{message.external_id}, instead of exactly 1"
20
+ expect(element[0].css("p").text).to include(message.name)
21
+ end
22
+ end
23
+ context "when you've signed the guestbook" do
24
+ it "shows a link to view your message and not a link to make one" do
25
+ message = create(:guestbook_message)
26
+ session = empty_session
27
+
28
+ session.signed_guestbook(message)
29
+
30
+ result = generate_and_parse(self.described_class.new(session:))
31
+
32
+ add_link = result.e("a[href='#{NewGuestbookMessagePage.routing}']")
33
+ expect(add_link).to eq(nil)
34
+
35
+ view_link = result.e("a[href='##{message.external_id}']")
36
+ expect(view_link).not_to eq(nil)
37
+ end
38
+ end
39
+ context "when you have not signed the guestbook" do
40
+ it "shows a link to view your message and not a link to make one" do
41
+ message = create(:guestbook_message)
42
+
43
+ result = generate_and_parse(self.described_class.new(session: empty_session))
44
+
45
+ add_link = result.e("a[href='#{NewGuestbookMessagePage.routing}']")
46
+ expect(add_link).not_to eq(nil)
47
+
48
+ view_link = result.e("a[href='##{message.external_id}']")
49
+ expect(view_link).to eq(nil)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe NewGuestbookMessagePage do
4
+ implementation_is_covered_by_other_tests "e2e tests"
5
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler"
4
+ Bundler.require
5
+ require "pathname"
6
+ require "brut/cli/apps/heroku_container_based_deploy"
7
+
8
+ exit Brut::CLI.app(
9
+ Brut::CLI::Apps::HerokuContainerBasedDeploy,
10
+ project_root: Pathname($0).dirname / ".."
11
+ )