brut 0.17.0 → 0.18.1

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 (1103) hide show
  1. checksums.yaml +4 -4
  2. data/exe/brut +34 -0
  3. data/lib/brut/cli/apps/build_assets.rb +78 -48
  4. data/lib/brut/cli/apps/db.rb +168 -202
  5. data/lib/brut/cli/apps/deploy.rb +291 -0
  6. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +6 -0
  7. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/add_segment.rb +5 -5
  8. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/add_segment_options.rb +1 -1
  9. data/lib/brut/cli/apps/new/app.rb +240 -0
  10. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/app_id.rb +1 -1
  11. data/lib/brut/cli/apps/new/app_name.rb +29 -0
  12. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/base.rb +9 -6
  13. data/lib/brut/cli/apps/new/erb_binding_delegate.rb +23 -0
  14. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/internet_identifier.rb +5 -5
  15. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/invalid_identifier.rb +1 -1
  16. data/{mkbrut/lib/mkbrut/app.rb → lib/brut/cli/apps/new/old_app.rb} +8 -11
  17. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/add_css_import.rb +1 -1
  18. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/add_i18n_message.rb +1 -1
  19. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/add_method.rb +1 -1
  20. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/append_to_file.rb +1 -1
  21. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/base_op.rb +3 -3
  22. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/copy_file.rb +1 -1
  23. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/insert_code_in_method.rb +1 -1
  24. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/insert_into_file.rb +1 -1
  25. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/insert_route.rb +1 -1
  26. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/mkdir.rb +1 -1
  27. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/prism_parsing_op.rb +1 -1
  28. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/render_template.rb +1 -1
  29. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/ops/skip_file.rb +1 -1
  30. data/lib/brut/cli/apps/new/ops.rb +17 -0
  31. data/lib/brut/cli/apps/new/organization.rb +5 -0
  32. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/prefix.rb +1 -1
  33. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/segments/bare_bones.rb +12 -11
  34. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/segments/demo.rb +16 -15
  35. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/segments/heroku.rb +9 -5
  36. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/segments/sidekiq.rb +44 -21
  37. data/lib/brut/cli/apps/new/segments.rb +8 -0
  38. data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/versions.rb +3 -3
  39. data/lib/brut/cli/apps/new.rb +26 -0
  40. data/lib/brut/cli/apps/scaffold.rb +150 -141
  41. data/lib/brut/cli/apps/test.rb +92 -68
  42. data/lib/brut/cli/commands/base_command.rb +174 -0
  43. data/lib/brut/cli/commands/compound_command.rb +29 -0
  44. data/lib/brut/cli/commands/execution_context.rb +32 -0
  45. data/lib/brut/cli/commands/help.rb +26 -0
  46. data/lib/brut/cli/commands/output_error.rb +13 -0
  47. data/lib/brut/cli/commands/raise_error.rb +11 -0
  48. data/lib/brut/cli/commands.rb +8 -0
  49. data/lib/brut/cli/execute_result.rb +39 -0
  50. data/lib/brut/cli/executor.rb +9 -4
  51. data/lib/brut/cli/output.rb +13 -0
  52. data/lib/brut/cli/parsed_command_line.rb +143 -0
  53. data/lib/brut/cli/runner.rb +124 -0
  54. data/lib/brut/cli.rb +7 -29
  55. data/lib/brut/framework/container.rb +1 -1
  56. data/lib/brut/framework/mcp.rb +59 -13
  57. data/lib/brut/framework/project_environment.rb +3 -1
  58. data/lib/brut/junk_drawer.rb +3 -1
  59. data/lib/brut/spec_support/cli_command_support.rb +45 -0
  60. data/lib/brut/spec_support/e2e_test_server.rb +3 -0
  61. data/lib/brut/spec_support/general_support.rb +1 -1
  62. data/lib/brut/spec_support/matchers/have_executed.rb +35 -0
  63. data/lib/brut/spec_support/rspec_setup.rb +4 -8
  64. data/lib/brut/spec_support.rb +1 -0
  65. data/lib/brut/tui/markup_string.rb +2 -0
  66. data/lib/brut/tui/script/events/command_std_out.rb +3 -2
  67. data/lib/brut/tui/script/exec_step.rb +11 -4
  68. data/lib/brut/tui/script/puts_subscriber.rb +4 -4
  69. data/lib/brut/tui/script.rb +7 -3
  70. data/lib/brut/tui/terminal_theme.rb +15 -11
  71. data/lib/brut/version.rb +1 -1
  72. data/templates/Base/.env.development.local +2 -0
  73. data/templates/Base/bin/ci +42 -0
  74. data/{mkbrut/templates → templates}/Base/bin/release +2 -2
  75. data/templates/Base/bin/setup +174 -0
  76. data/{mkbrut/templates → templates}/Base/bin/watch-and-build-assets +1 -1
  77. data/{mkbrut/templates → templates}/Base/dx/docker-compose.env.erb +1 -1
  78. data/{mkbrut/templates → templates}/segments/Demo/app/src/front_end/css/fonts.css +1 -1
  79. data/{mkbrut/templates → templates}/segments/Heroku/deploy/Dockerfile +2 -2
  80. data/templates/segments/Heroku/deploy/docker_config.rb +30 -0
  81. metadata +190 -1055
  82. data/.gitignore +0 -61
  83. data/.projections.json +0 -10
  84. data/CHANGELOG.md +0 -172
  85. data/CODE_OF_CONDUCT.txt +0 -99
  86. data/Dockerfile.dx +0 -82
  87. data/Gemfile +0 -6
  88. data/Gemfile.lock +0 -246
  89. data/LICENSE.txt +0 -370
  90. data/README.md +0 -90
  91. data/Rakefile +0 -25
  92. data/assets/Logo-Square.pxd +0 -0
  93. data/assets/LogoPylon.pxd +0 -0
  94. data/assets/LogoStop.pxd +0 -0
  95. data/assets/LogoTall.pxd +0 -0
  96. data/assets/MetroIcon.graffle +0 -0
  97. data/assets/MetroLogo.graffle +0 -0
  98. data/assets/SocialImage.png +0 -0
  99. data/assets/SocialImage.pxd +0 -0
  100. data/assets/YouTubeThumb.pxd +0 -0
  101. data/bin/bin_kit.rb +0 -51
  102. data/bin/build +0 -86
  103. data/bin/ci +0 -40
  104. data/bin/dev +0 -20
  105. data/bin/docs +0 -86
  106. data/bin/generate-and-run-rubocop +0 -52
  107. data/bin/new-version +0 -8
  108. data/bin/publish +0 -61
  109. data/bin/rake +0 -27
  110. data/bin/rspec +0 -27
  111. data/bin/rubocop +0 -27
  112. data/bin/setup +0 -252
  113. data/bin/test +0 -18
  114. data/brut-css/.nvim.lua +0 -1
  115. data/brut-css/README.md +0 -28
  116. data/brut-css/bin/build +0 -50
  117. data/brut-css/bin/ci +0 -19
  118. data/brut-css/bin/dev +0 -1
  119. data/brut-css/bin/docs +0 -34
  120. data/brut-css/bin/publish +0 -21
  121. data/brut-css/bin/setup +0 -6
  122. data/brut-css/config/media-queries-all.css +0 -15
  123. data/brut-css/config/media-queries-minimal.css +0 -5
  124. data/brut-css/config/postcss.config.cjs +0 -7
  125. data/brut-css/config/pseudo-classes-all.css +0 -9
  126. data/brut-css/dx +0 -1
  127. data/brut-css/package-lock.json +0 -3165
  128. data/brut-css/package.json +0 -36
  129. data/brut-css/src/css/appearance.css +0 -145
  130. data/brut-css/src/css/border.css +0 -522
  131. data/brut-css/src/css/colors.css +0 -3502
  132. data/brut-css/src/css/dimensions.css +0 -548
  133. data/brut-css/src/css/flex.css +0 -179
  134. data/brut-css/src/css/index.css +0 -13
  135. data/brut-css/src/css/layout.css +0 -120
  136. data/brut-css/src/css/list.css +0 -41
  137. data/brut-css/src/css/positioning.css +0 -354
  138. data/brut-css/src/css/properties/colors.css +0 -455
  139. data/brut-css/src/css/properties/index.css +0 -3
  140. data/brut-css/src/css/properties/spacing.css +0 -140
  141. data/brut-css/src/css/properties/typography.css +0 -224
  142. data/brut-css/src/css/reset.css +0 -107
  143. data/brut-css/src/css/spacing.css +0 -585
  144. data/brut-css/src/css/typography.css +0 -519
  145. data/brut-css/src/css/utils.css +0 -104
  146. data/brut-css/src/docs/1_getting-started/1_overview.md +0 -46
  147. data/brut-css/src/docs/1_getting-started/2_installation.md +0 -25
  148. data/brut-css/src/docs/1_getting-started/3_core-concepts.md +0 -75
  149. data/brut-css/src/docs/1_getting-started/4_simple-example.md +0 -132
  150. data/brut-css/src/docs/1_getting-started/page.html.ejs +0 -10
  151. data/brut-css/src/docs/2_properties/page.html.ejs +0 -71
  152. data/brut-css/src/docs/3_classes/color-demo.html.ejs +0 -31
  153. data/brut-css/src/docs/3_classes/page.html.ejs +0 -87
  154. data/brut-css/src/docs/4_customization/1_design-system.md +0 -36
  155. data/brut-css/src/docs/4_customization/2_breakpoints.md +0 -75
  156. data/brut-css/src/docs/4_customization/3_pseudo-classes.md +0 -74
  157. data/brut-css/src/docs/4_customization/4_advanced-configuration.md +0 -40
  158. data/brut-css/src/docs/4_customization/page.html.ejs +0 -10
  159. data/brut-css/src/docs/docs.css +0 -98
  160. data/brut-css/src/docs/includes/body-and-header.html.ejs +0 -30
  161. data/brut-css/src/docs/includes/footer-and-rest.html.ejs +0 -9
  162. data/brut-css/src/docs/includes/head.html.ejs +0 -5
  163. data/brut-css/src/docs/includes/nav.html.ejs +0 -10
  164. data/brut-css/src/docs/index.html.ejs +0 -32
  165. data/brut-css/src/docs/prism-twilight.min.css +0 -1
  166. data/brut-css/src/js/Logger.js +0 -71
  167. data/brut-css/src/js/build.js +0 -111
  168. data/brut-css/src/js/cli/CLIArgError.js +0 -7
  169. data/brut-css/src/js/cli/Debug.js +0 -27
  170. data/brut-css/src/js/cli/DocsDir.js +0 -16
  171. data/brut-css/src/js/cli/DocsTemplateSourceDir.js +0 -16
  172. data/brut-css/src/js/cli/InputFile.js +0 -31
  173. data/brut-css/src/js/cli/MediaQueryConfigFile.js +0 -10
  174. data/brut-css/src/js/cli/OutputFile.js +0 -22
  175. data/brut-css/src/js/cli/ParsedArg.js +0 -17
  176. data/brut-css/src/js/cli/PathToBrutCSSRoot.js +0 -19
  177. data/brut-css/src/js/cli/PseudoClassConfigFile.js +0 -11
  178. data/brut-css/src/js/cli.js +0 -108
  179. data/brut-css/src/js/docGenerator.js +0 -467
  180. data/brut-css/src/js/mediaQueryConfigParser.js +0 -98
  181. data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +0 -49
  182. data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +0 -42
  183. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +0 -9
  184. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +0 -185
  185. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +0 -8
  186. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +0 -7
  187. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +0 -73
  188. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +0 -9
  189. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +0 -4
  190. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +0 -8
  191. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +0 -12
  192. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +0 -4
  193. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +0 -8
  194. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +0 -5
  195. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +0 -9
  196. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +0 -49
  197. data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +0 -45
  198. data/brut-css/src/js/pseudoClassConfigParser.js +0 -145
  199. data/brut-js/.projections.json +0 -10
  200. data/brut-js/README.md +0 -118
  201. data/brut-js/bin/build +0 -19
  202. data/brut-js/bin/ci +0 -5
  203. data/brut-js/bin/docs +0 -25
  204. data/brut-js/bin/publish +0 -21
  205. data/brut-js/bin/setup +0 -6
  206. data/brut-js/docs/README.md +0 -8
  207. data/brut-js/docs/jsdoc-plugins/customElementTag.js +0 -8
  208. data/brut-js/docs/jsdoc-theme/publish.js +0 -692
  209. data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +0 -25
  210. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +0 -202
  211. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +0 -2
  212. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +0 -28
  213. data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +0 -327
  214. data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +0 -111
  215. data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +0 -132
  216. data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +0 -10
  217. data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +0 -199
  218. data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +0 -143
  219. data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +0 -2
  220. data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +0 -13
  221. data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +0 -32
  222. data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +0 -38
  223. data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +0 -14
  224. data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +0 -38
  225. data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +0 -131
  226. data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +0 -14
  227. data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +0 -131
  228. data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +0 -108
  229. data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +0 -19
  230. data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +0 -8
  231. data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +0 -19
  232. data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +0 -7
  233. data/brut-js/docs/jsdoc.config.json +0 -23
  234. data/brut-js/docs/package-lock.json +0 -343
  235. data/brut-js/docs/package.json +0 -7
  236. data/brut-js/dx +0 -1
  237. data/brut-js/package-lock.json +0 -2210
  238. data/brut-js/package.json +0 -36
  239. data/brut-js/specs/AjaxSubmit.spec.js +0 -453
  240. data/brut-js/specs/Autosubmit.spec.js +0 -127
  241. data/brut-js/specs/ConfirmSubmit.spec.js +0 -224
  242. data/brut-js/specs/ConstraintViolationMessage.spec.js +0 -33
  243. data/brut-js/specs/ConstraintViolationMessages.spec.js +0 -32
  244. data/brut-js/specs/CopyToClipboard.spec.js +0 -35
  245. data/brut-js/specs/Form.spec.js +0 -137
  246. data/brut-js/specs/I18nTranslation.spec.js +0 -19
  247. data/brut-js/specs/LocaleDetection.spec.js +0 -22
  248. data/brut-js/specs/Message.spec.js +0 -15
  249. data/brut-js/specs/SpecHelper.js +0 -23
  250. data/brut-js/specs/Tabs.spec.js +0 -41
  251. data/brut-js/specs/Toast.spec.js +0 -34
  252. data/brut-js/specs/config/asset_metadata.json +0 -7
  253. data/brut-js/src/AjaxSubmit.js +0 -499
  254. data/brut-js/src/Autosubmit.js +0 -63
  255. data/brut-js/src/BaseCustomElement.js +0 -261
  256. data/brut-js/src/ConfirmSubmit.js +0 -137
  257. data/brut-js/src/ConfirmationDialog.js +0 -143
  258. data/brut-js/src/ConstraintViolationMessage.js +0 -140
  259. data/brut-js/src/ConstraintViolationMessages.js +0 -98
  260. data/brut-js/src/CopyToClipboard.js +0 -96
  261. data/brut-js/src/Form.js +0 -147
  262. data/brut-js/src/I18nTranslation.js +0 -64
  263. data/brut-js/src/LocaleDetection.js +0 -117
  264. data/brut-js/src/Logger.js +0 -90
  265. data/brut-js/src/Message.js +0 -62
  266. data/brut-js/src/RichString.js +0 -116
  267. data/brut-js/src/Tabs.js +0 -168
  268. data/brut-js/src/Toast.js +0 -102
  269. data/brut-js/src/Tracing.js +0 -247
  270. data/brut-js/src/appForTestingOnly.js +0 -15
  271. data/brut-js/src/index.js +0 -133
  272. data/brut-js/src/testing/AssetMetadata.js +0 -35
  273. data/brut-js/src/testing/AssetMetadataLoader.js +0 -25
  274. data/brut-js/src/testing/CustomElementTest.js +0 -235
  275. data/brut-js/src/testing/DOMCreator.js +0 -45
  276. data/brut-js/src/testing/index.js +0 -48
  277. data/brut.gemspec +0 -73
  278. data/brutrb.com/.vitepress/config.mjs +0 -164
  279. data/brutrb.com/.vitepress/plugins/jsdocLinker.js +0 -34
  280. data/brutrb.com/.vitepress/plugins/rdocLinker.js +0 -18
  281. data/brutrb.com/.vitepress/theme/custom.css +0 -14
  282. data/brutrb.com/.vitepress/theme/index.js +0 -18
  283. data/brutrb.com/.vitepress/theme/style.css +0 -139
  284. data/brutrb.com/adrs.md +0 -16
  285. data/brutrb.com/ai.md +0 -68
  286. data/brutrb.com/assets.md +0 -131
  287. data/brutrb.com/bin/build +0 -5
  288. data/brutrb.com/bin/deploy +0 -7
  289. data/brutrb.com/bin/dev +0 -5
  290. data/brutrb.com/bin/setup +0 -6
  291. data/brutrb.com/brut-js.md +0 -128
  292. data/brutrb.com/business-logic.md +0 -55
  293. data/brutrb.com/cli.md +0 -274
  294. data/brutrb.com/components.md +0 -265
  295. data/brutrb.com/configuration.md +0 -256
  296. data/brutrb.com/css.md +0 -103
  297. data/brutrb.com/custom-element-tests.md +0 -148
  298. data/brutrb.com/database-access.md +0 -201
  299. data/brutrb.com/database-schema.md +0 -320
  300. data/brutrb.com/deployment.md +0 -158
  301. data/brutrb.com/dev-environment.md +0 -186
  302. data/brutrb.com/dir-structure.md +0 -120
  303. data/brutrb.com/doc-conventions.md +0 -41
  304. data/brutrb.com/dx +0 -1
  305. data/brutrb.com/end-to-end-tests.md +0 -176
  306. data/brutrb.com/features.md +0 -373
  307. data/brutrb.com/flash-and-session.md +0 -208
  308. data/brutrb.com/form-constraints.md +0 -266
  309. data/brutrb.com/forms.md +0 -238
  310. data/brutrb.com/getting-started.md +0 -142
  311. data/brutrb.com/handlers.md +0 -177
  312. data/brutrb.com/hooks.md +0 -176
  313. data/brutrb.com/i18n.md +0 -190
  314. data/brutrb.com/images/DevEnvironment.graffle +0 -0
  315. data/brutrb.com/images/DevEnvironment.png +0 -0
  316. data/brutrb.com/images/LogoSquare.png +0 -0
  317. data/brutrb.com/images/LogoStop.png +0 -0
  318. data/brutrb.com/images/LogoTall.png +0 -0
  319. data/brutrb.com/images/Makefile +0 -10
  320. data/brutrb.com/images/OverviewMetro.graffle +0 -0
  321. data/brutrb.com/images/OverviewMetro.png +0 -0
  322. data/brutrb.com/images/dev-env-overview.dot +0 -54
  323. data/brutrb.com/images/dev-env-overview.png +0 -0
  324. data/brutrb.com/images/dev-env-protocol.dot +0 -37
  325. data/brutrb.com/images/dev-env-protocol.png +0 -0
  326. data/brutrb.com/images/overview.graffle +0 -0
  327. data/brutrb.com/images/overview.png +0 -0
  328. data/brutrb.com/images/spa.dot +0 -19
  329. data/brutrb.com/images/spa.png +0 -0
  330. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element-styled.png +0 -0
  331. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element.png +0 -0
  332. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser.png +0 -0
  333. data/brutrb.com/images/tutorial/02-confirmation-flow.graffle +0 -0
  334. data/brutrb.com/images/tutorial/02-confirmation-flow.png +0 -0
  335. data/brutrb.com/images/tutorial/basic-form-with-violations.png +0 -0
  336. data/brutrb.com/images/tutorial/basic-form.png +0 -0
  337. data/brutrb.com/images/tutorial/initial-home-page.png +0 -0
  338. data/brutrb.com/images/tutorial/new-post-editor.png +0 -0
  339. data/brutrb.com/images/tutorial/new-post-home-page.png +0 -0
  340. data/brutrb.com/images/tutorial/styled-form-with-server-side-violations.png +0 -0
  341. data/brutrb.com/images/tutorial/styled-form-with-violations.png +0 -0
  342. data/brutrb.com/images/tutorial/styled-home-page-with-posts.png +0 -0
  343. data/brutrb.com/images/tutorial/styled-home-page.png +0 -0
  344. data/brutrb.com/images/tutorial/welcome-to-brut.png +0 -0
  345. data/brutrb.com/images/workspace-protocol.dot +0 -44
  346. data/brutrb.com/images/workspace-protocol.png +0 -0
  347. data/brutrb.com/index.md +0 -34
  348. data/brutrb.com/instrumentation.md +0 -331
  349. data/brutrb.com/javascript.md +0 -122
  350. data/brutrb.com/jobs.md +0 -114
  351. data/brutrb.com/keyword-injection.md +0 -195
  352. data/brutrb.com/layouts.md +0 -156
  353. data/brutrb.com/lsp.md +0 -23
  354. data/brutrb.com/markdown-examples.md +0 -85
  355. data/brutrb.com/middleware.md +0 -80
  356. data/brutrb.com/overview.md +0 -68
  357. data/brutrb.com/package-lock.json +0 -2451
  358. data/brutrb.com/package.json +0 -11
  359. data/brutrb.com/pages.md +0 -290
  360. data/brutrb.com/public/SocialImage.png +0 -0
  361. data/brutrb.com/public/favicon.ico +0 -0
  362. data/brutrb.com/recipes/alternate-layouts.md +0 -32
  363. data/brutrb.com/recipes/authentication.md +0 -336
  364. data/brutrb.com/recipes/custom-flash.md +0 -51
  365. data/brutrb.com/recipes/dev-env-secrets.md +0 -87
  366. data/brutrb.com/recipes/form-errors.md +0 -148
  367. data/brutrb.com/recipes/indexed-forms.md +0 -149
  368. data/brutrb.com/recipes/migrations.md +0 -210
  369. data/brutrb.com/recipes/text-field-component.md +0 -182
  370. data/brutrb.com/roadmap.md +0 -52
  371. data/brutrb.com/routes.md +0 -189
  372. data/brutrb.com/security.md +0 -102
  373. data/brutrb.com/seed-data.md +0 -63
  374. data/brutrb.com/space-time-continuum.md +0 -81
  375. data/brutrb.com/tutorial.md +0 -138
  376. data/brutrb.com/tutorials/01-intro.md +0 -1654
  377. data/brutrb.com/tutorials/02-dialog.md +0 -569
  378. data/brutrb.com/unit-tests.md +0 -148
  379. data/brutrb.com/why.md +0 -19
  380. data/docker-compose.dx.yml +0 -25
  381. data/docs/404.html +0 -26
  382. data/docs/CNAME +0 -1
  383. data/docs/SocialImage.png +0 -0
  384. data/docs/adrs.html +0 -29
  385. data/docs/ai.html +0 -29
  386. data/docs/api/Brut/BackEnd/SeedData.html +0 -493
  387. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +0 -214
  388. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +0 -125
  389. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +0 -125
  390. data/docs/api/Brut/BackEnd/Sidekiq.html +0 -125
  391. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +0 -414
  392. data/docs/api/Brut/BackEnd/Validators.html +0 -128
  393. data/docs/api/Brut/BackEnd.html +0 -132
  394. data/docs/api/Brut/CLI/App.html +0 -1601
  395. data/docs/api/Brut/CLI/AppRunner.html +0 -491
  396. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +0 -264
  397. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +0 -306
  398. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +0 -262
  399. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +0 -314
  400. data/docs/api/Brut/CLI/Apps/BuildAssets.html +0 -183
  401. data/docs/api/Brut/CLI/Apps/DB/Create.html +0 -365
  402. data/docs/api/Brut/CLI/Apps/DB/Drop.html +0 -357
  403. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +0 -389
  404. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +0 -339
  405. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +0 -329
  406. data/docs/api/Brut/CLI/Apps/DB/Seed.html +0 -347
  407. data/docs/api/Brut/CLI/Apps/DB/Status.html +0 -383
  408. data/docs/api/Brut/CLI/Apps/DB.html +0 -183
  409. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +0 -270
  410. data/docs/api/Brut/CLI/Apps/DeployBase.html +0 -257
  411. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +0 -587
  412. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +0 -196
  413. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +0 -303
  414. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +0 -508
  415. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +0 -398
  416. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +0 -374
  417. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +0 -384
  418. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +0 -410
  419. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +0 -262
  420. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +0 -303
  421. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +0 -480
  422. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +0 -450
  423. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +0 -380
  424. data/docs/api/Brut/CLI/Apps/Scaffold.html +0 -253
  425. data/docs/api/Brut/CLI/Apps/Test/Audit.html +0 -474
  426. data/docs/api/Brut/CLI/Apps/Test/E2e.html +0 -407
  427. data/docs/api/Brut/CLI/Apps/Test/JS.html +0 -262
  428. data/docs/api/Brut/CLI/Apps/Test/Run.html +0 -578
  429. data/docs/api/Brut/CLI/Apps/Test.html +0 -253
  430. data/docs/api/Brut/CLI/Apps.html +0 -125
  431. data/docs/api/Brut/CLI/Command.html +0 -2425
  432. data/docs/api/Brut/CLI/Error.html +0 -139
  433. data/docs/api/Brut/CLI/ExecutionResults/Result.html +0 -664
  434. data/docs/api/Brut/CLI/ExecutionResults.html +0 -675
  435. data/docs/api/Brut/CLI/Executor.html +0 -561
  436. data/docs/api/Brut/CLI/InvalidOption.html +0 -245
  437. data/docs/api/Brut/CLI/Options.html +0 -880
  438. data/docs/api/Brut/CLI/Output.html +0 -699
  439. data/docs/api/Brut/CLI/SystemExecError.html +0 -451
  440. data/docs/api/Brut/CLI.html +0 -263
  441. data/docs/api/Brut/FactoryBot.html +0 -225
  442. data/docs/api/Brut/Framework/App.html +0 -1097
  443. data/docs/api/Brut/Framework/Config.html +0 -1071
  444. data/docs/api/Brut/Framework/Container.html +0 -1464
  445. data/docs/api/Brut/Framework/Error.html +0 -140
  446. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +0 -232
  447. data/docs/api/Brut/Framework/Errors/Bug.html +0 -234
  448. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +0 -257
  449. data/docs/api/Brut/Framework/Errors/MissingParameter.html +0 -273
  450. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +0 -471
  451. data/docs/api/Brut/Framework/Errors/NotFound.html +0 -308
  452. data/docs/api/Brut/Framework/Errors/NotImplemented.html +0 -234
  453. data/docs/api/Brut/Framework/Errors.html +0 -351
  454. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +0 -392
  455. data/docs/api/Brut/Framework/MCP.html +0 -871
  456. data/docs/api/Brut/Framework/ProjectEnvironment.html +0 -648
  457. data/docs/api/Brut/Framework.html +0 -129
  458. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +0 -317
  459. data/docs/api/Brut/FrontEnd/Component/Helpers.html +0 -420
  460. data/docs/api/Brut/FrontEnd/Component.html +0 -434
  461. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +0 -491
  462. data/docs/api/Brut/FrontEnd/Components/FormTag.html +0 -526
  463. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +0 -313
  464. data/docs/api/Brut/FrontEnd/Components/Input.html +0 -195
  465. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +0 -447
  466. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +0 -339
  467. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +0 -568
  468. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +0 -419
  469. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +0 -610
  470. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +0 -534
  471. data/docs/api/Brut/FrontEnd/Components/Inputs.html +0 -125
  472. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +0 -367
  473. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +0 -355
  474. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +0 -655
  475. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +0 -352
  476. data/docs/api/Brut/FrontEnd/Components.html +0 -156
  477. data/docs/api/Brut/FrontEnd/CsrfProtector.html +0 -250
  478. data/docs/api/Brut/FrontEnd/Download.html +0 -467
  479. data/docs/api/Brut/FrontEnd/Flash.html +0 -1150
  480. data/docs/api/Brut/FrontEnd/Form.html +0 -1227
  481. data/docs/api/Brut/FrontEnd/Forms/Button.html +0 -331
  482. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +0 -537
  483. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +0 -590
  484. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +0 -201
  485. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +0 -535
  486. data/docs/api/Brut/FrontEnd/Forms/Input.html +0 -1567
  487. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +0 -635
  488. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +0 -1336
  489. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +0 -730
  490. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +0 -587
  491. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +0 -734
  492. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +0 -582
  493. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +0 -659
  494. data/docs/api/Brut/FrontEnd/Forms.html +0 -127
  495. data/docs/api/Brut/FrontEnd/GenericResponse.html +0 -377
  496. data/docs/api/Brut/FrontEnd/Handler.html +0 -442
  497. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +0 -318
  498. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +0 -336
  499. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +0 -399
  500. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +0 -354
  501. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +0 -151
  502. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +0 -315
  503. data/docs/api/Brut/FrontEnd/Handlers.html +0 -125
  504. data/docs/api/Brut/FrontEnd/HandlingResults.html +0 -339
  505. data/docs/api/Brut/FrontEnd/HttpMethod.html +0 -661
  506. data/docs/api/Brut/FrontEnd/HttpStatus.html +0 -496
  507. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +0 -284
  508. data/docs/api/Brut/FrontEnd/Layout.html +0 -486
  509. data/docs/api/Brut/FrontEnd/Middleware.html +0 -135
  510. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +0 -288
  511. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +0 -292
  512. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +0 -324
  513. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +0 -376
  514. data/docs/api/Brut/FrontEnd/Middlewares.html +0 -125
  515. data/docs/api/Brut/FrontEnd/Page.html +0 -781
  516. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +0 -797
  517. data/docs/api/Brut/FrontEnd/Pages.html +0 -125
  518. data/docs/api/Brut/FrontEnd/RequestContext.html +0 -1312
  519. data/docs/api/Brut/FrontEnd/RouteHook.html +0 -424
  520. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +0 -242
  521. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +0 -249
  522. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +0 -264
  523. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +0 -261
  524. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +0 -284
  525. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +0 -252
  526. data/docs/api/Brut/FrontEnd/RouteHooks.html +0 -115
  527. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +0 -227
  528. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +0 -305
  529. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +0 -324
  530. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +0 -319
  531. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +0 -315
  532. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +0 -315
  533. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +0 -327
  534. data/docs/api/Brut/FrontEnd/Routing/Route.html +0 -761
  535. data/docs/api/Brut/FrontEnd/Routing.html +0 -927
  536. data/docs/api/Brut/FrontEnd/Session.html +0 -1195
  537. data/docs/api/Brut/FrontEnd.html +0 -134
  538. data/docs/api/Brut/I18n/BaseMethods.html +0 -931
  539. data/docs/api/Brut/I18n/ForBackEnd.html +0 -302
  540. data/docs/api/Brut/I18n/ForCLI.html +0 -302
  541. data/docs/api/Brut/I18n/ForHTML.html +0 -296
  542. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +0 -316
  543. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +0 -930
  544. data/docs/api/Brut/I18n.html +0 -127
  545. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +0 -435
  546. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +0 -596
  547. data/docs/api/Brut/Instrumentation/Methods.html +0 -173
  548. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +0 -286
  549. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +0 -302
  550. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +0 -866
  551. data/docs/api/Brut/Instrumentation.html +0 -128
  552. data/docs/api/Brut/RubocopConfig.html +0 -237
  553. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +0 -534
  554. data/docs/api/Brut/SinatraHelpers.html +0 -281
  555. data/docs/api/Brut/SpecSupport/ClockSupport.html +0 -383
  556. data/docs/api/Brut/SpecSupport/ComponentSupport.html +0 -496
  557. data/docs/api/Brut/SpecSupport/E2ETestServer.html +0 -503
  558. data/docs/api/Brut/SpecSupport/E2eSupport.html +0 -142
  559. data/docs/api/Brut/SpecSupport/EnhancedNode.html +0 -403
  560. data/docs/api/Brut/SpecSupport/FlashSupport.html +0 -278
  561. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +0 -401
  562. data/docs/api/Brut/SpecSupport/GeneralSupport.html +0 -195
  563. data/docs/api/Brut/SpecSupport/HandlerSupport.html +0 -160
  564. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +0 -142
  565. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +0 -142
  566. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +0 -155
  567. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +0 -583
  568. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +0 -149
  569. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +0 -466
  570. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +0 -149
  571. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +0 -149
  572. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +0 -165
  573. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +0 -158
  574. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +0 -156
  575. data/docs/api/Brut/SpecSupport/Matchers.html +0 -125
  576. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +0 -335
  577. data/docs/api/Brut/SpecSupport/RSpecSetup.html +0 -637
  578. data/docs/api/Brut/SpecSupport/SessionSupport.html +0 -196
  579. data/docs/api/Brut/SpecSupport.html +0 -129
  580. data/docs/api/Brut/TUI/AnsiEscapeCode/Mod.html +0 -409
  581. data/docs/api/Brut/TUI/AnsiEscapeCode.html +0 -426
  582. data/docs/api/Brut/TUI/EventLoop/Deque.html +0 -531
  583. data/docs/api/Brut/TUI/EventLoop.html +0 -676
  584. data/docs/api/Brut/TUI/Events/BaseEvent.html +0 -449
  585. data/docs/api/Brut/TUI/Events/EventBus.html +0 -485
  586. data/docs/api/Brut/TUI/Events/EventLoopStarted.html +0 -211
  587. data/docs/api/Brut/TUI/Events/Exception.html +0 -523
  588. data/docs/api/Brut/TUI/Events/Tick.html +0 -294
  589. data/docs/api/Brut/TUI/Events.html +0 -131
  590. data/docs/api/Brut/TUI/MarkupString.html +0 -537
  591. data/docs/api/Brut/TUI/Script/BlockStep.html +0 -300
  592. data/docs/api/Brut/TUI/Script/Events/CommandExecutionFailed.html +0 -252
  593. data/docs/api/Brut/TUI/Script/Events/CommandExecutionSucceeded.html +0 -163
  594. data/docs/api/Brut/TUI/Script/Events/CommandStdErr.html +0 -163
  595. data/docs/api/Brut/TUI/Script/Events/CommandStdOut.html +0 -300
  596. data/docs/api/Brut/TUI/Script/Events/ExecutingCommand.html +0 -298
  597. data/docs/api/Brut/TUI/Script/Events/Message.html +0 -345
  598. data/docs/api/Brut/TUI/Script/Events/PhaseCompleted.html +0 -229
  599. data/docs/api/Brut/TUI/Script/Events/PhaseStarted.html +0 -350
  600. data/docs/api/Brut/TUI/Script/Events/ScriptCompleted.html +0 -282
  601. data/docs/api/Brut/TUI/Script/Events/ScriptStarted.html +0 -343
  602. data/docs/api/Brut/TUI/Script/Events/StepCompleted.html +0 -163
  603. data/docs/api/Brut/TUI/Script/Events/StepStarted.html +0 -346
  604. data/docs/api/Brut/TUI/Script/Events.html +0 -115
  605. data/docs/api/Brut/TUI/Script/ExecStep/ProcessStatusFailed.html +0 -210
  606. data/docs/api/Brut/TUI/Script/ExecStep.html +0 -493
  607. data/docs/api/Brut/TUI/Script/LoggingSubscriber.html +0 -914
  608. data/docs/api/Brut/TUI/Script/PutsSubscriber.html +0 -783
  609. data/docs/api/Brut/TUI/Script/Step.html +0 -313
  610. data/docs/api/Brut/TUI/Script.html +0 -1250
  611. data/docs/api/Brut/TUI/Terminal.html +0 -593
  612. data/docs/api/Brut/TUI/TerminalTheme.html +0 -1403
  613. data/docs/api/Brut/TUI/Themes/Dark.html +0 -706
  614. data/docs/api/Brut/TUI/Themes/Light.html +0 -804
  615. data/docs/api/Brut/TUI/Themes/None.html +0 -218
  616. data/docs/api/Brut/TUI/Themes.html +0 -115
  617. data/docs/api/Brut/TUI.html +0 -129
  618. data/docs/api/Brut.html +0 -341
  619. data/docs/api/Clock.html +0 -603
  620. data/docs/api/ModuleName.html +0 -595
  621. data/docs/api/RichString.html +0 -775
  622. data/docs/api/SemanticLogger/Appender/Async.html +0 -219
  623. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +0 -119
  624. data/docs/api/Sequel/Extensions/BrutMigrations.html +0 -541
  625. data/docs/api/Sequel/Extensions.html +0 -117
  626. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +0 -105
  627. data/docs/api/Sequel/Plugins/CreatedAt.html +0 -125
  628. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +0 -207
  629. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +0 -186
  630. data/docs/api/Sequel/Plugins/ExternalId.html +0 -218
  631. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +0 -202
  632. data/docs/api/Sequel/Plugins/FindBang.html +0 -125
  633. data/docs/api/Sequel/Plugins.html +0 -117
  634. data/docs/api/Sequel.html +0 -117
  635. data/docs/api/SpecSupport/Matchers/BeABug.html +0 -143
  636. data/docs/api/_index.html +0 -1964
  637. data/docs/api/class_list.html +0 -54
  638. data/docs/api/css/common.css +0 -1
  639. data/docs/api/css/full_list.css +0 -59
  640. data/docs/api/css/style.css +0 -504
  641. data/docs/api/file.README.html +0 -172
  642. data/docs/api/file_list.html +0 -59
  643. data/docs/api/frames.html +0 -22
  644. data/docs/api/index.html +0 -172
  645. data/docs/api/js/app.js +0 -344
  646. data/docs/api/js/full_list.js +0 -242
  647. data/docs/api/js/jquery.js +0 -4
  648. data/docs/api/method_list.html +0 -5542
  649. data/docs/api/top-level-namespace.html +0 -112
  650. data/docs/assets/02-confirmation-dialog-browser-element-styled.3NEGM20-.png +0 -0
  651. data/docs/assets/02-confirmation-dialog-browser-element.DPsf0xUW.png +0 -0
  652. data/docs/assets/02-confirmation-dialog-browser.DH8ALFO4.png +0 -0
  653. data/docs/assets/02-confirmation-flow.D9gZ0S5U.png +0 -0
  654. data/docs/assets/DevEnvironment.DaFcVfwP.png +0 -0
  655. data/docs/assets/LogoStop.Gb3tDhL1.png +0 -0
  656. data/docs/assets/OverviewMetro.DUS-5fUZ.png +0 -0
  657. data/docs/assets/adrs.md.YglbWtQe.js +0 -1
  658. data/docs/assets/adrs.md.YglbWtQe.lean.js +0 -1
  659. data/docs/assets/ai.md.ChLnvDAX.js +0 -1
  660. data/docs/assets/ai.md.ChLnvDAX.lean.js +0 -1
  661. data/docs/assets/app.B8jAEB7R.js +0 -1
  662. data/docs/assets/assets.md.BEF6Oz6K.js +0 -19
  663. data/docs/assets/assets.md.BEF6Oz6K.lean.js +0 -1
  664. data/docs/assets/basic-form-with-violations.Cv6Y9-Q_.png +0 -0
  665. data/docs/assets/basic-form.DbHnu0oW.png +0 -0
  666. data/docs/assets/brut-js.md.BMz0X1Rz.js +0 -12
  667. data/docs/assets/brut-js.md.BMz0X1Rz.lean.js +0 -1
  668. data/docs/assets/business-logic.md.DbuaOYGU.js +0 -1
  669. data/docs/assets/business-logic.md.DbuaOYGU.lean.js +0 -1
  670. data/docs/assets/chunks/@localSearchIndexroot.DJ8mocCj.js +0 -1
  671. data/docs/assets/chunks/VPLocalSearchBox.gF-Po_fz.js +0 -8
  672. data/docs/assets/chunks/framework.C4nOkCZI.js +0 -18
  673. data/docs/assets/chunks/theme.BjPAOJkz.js +0 -2
  674. data/docs/assets/cli.md.DDMar_51.js +0 -122
  675. data/docs/assets/cli.md.DDMar_51.lean.js +0 -1
  676. data/docs/assets/components.md.Ber8UBM0.js +0 -96
  677. data/docs/assets/components.md.Ber8UBM0.lean.js +0 -1
  678. data/docs/assets/configuration.md.DrJ6YVoZ.js +0 -78
  679. data/docs/assets/configuration.md.DrJ6YVoZ.lean.js +0 -1
  680. data/docs/assets/css.md.K5rOCOQY.js +0 -21
  681. data/docs/assets/css.md.K5rOCOQY.lean.js +0 -1
  682. data/docs/assets/custom-element-tests.md.DiLe-eFw.js +0 -69
  683. data/docs/assets/custom-element-tests.md.DiLe-eFw.lean.js +0 -1
  684. data/docs/assets/database-access.md.Dc8l2Plf.js +0 -63
  685. data/docs/assets/database-access.md.Dc8l2Plf.lean.js +0 -1
  686. data/docs/assets/database-schema.md.BJ_JhXmO.js +0 -70
  687. data/docs/assets/database-schema.md.BJ_JhXmO.lean.js +0 -1
  688. data/docs/assets/deployment.md.CHTx2eTR.js +0 -55
  689. data/docs/assets/deployment.md.CHTx2eTR.lean.js +0 -1
  690. data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
  691. data/docs/assets/dev-environment.md.B1S9p5ZK.js +0 -16
  692. data/docs/assets/dev-environment.md.B1S9p5ZK.lean.js +0 -1
  693. data/docs/assets/dir-structure.md.D1T2kGwj.js +0 -46
  694. data/docs/assets/dir-structure.md.D1T2kGwj.lean.js +0 -1
  695. data/docs/assets/doc-conventions.md.CDnWaEFg.js +0 -1
  696. data/docs/assets/doc-conventions.md.CDnWaEFg.lean.js +0 -1
  697. data/docs/assets/end-to-end-tests.md.BJJdNDYL.js +0 -28
  698. data/docs/assets/end-to-end-tests.md.BJJdNDYL.lean.js +0 -1
  699. data/docs/assets/features.md.BDWxnyNO.js +0 -154
  700. data/docs/assets/features.md.BDWxnyNO.lean.js +0 -1
  701. data/docs/assets/flash-and-session.md.CUsMxoNl.js +0 -79
  702. data/docs/assets/flash-and-session.md.CUsMxoNl.lean.js +0 -1
  703. data/docs/assets/form-constraints.md.KlfXSKm2.js +0 -90
  704. data/docs/assets/form-constraints.md.KlfXSKm2.lean.js +0 -1
  705. data/docs/assets/forms.md.RK0zkhm0.js +0 -64
  706. data/docs/assets/forms.md.RK0zkhm0.lean.js +0 -1
  707. data/docs/assets/getting-started.md.CGJ44juQ.js +0 -31
  708. data/docs/assets/getting-started.md.CGJ44juQ.lean.js +0 -1
  709. data/docs/assets/handlers.md.C5tUwmmo.js +0 -54
  710. data/docs/assets/handlers.md.C5tUwmmo.lean.js +0 -1
  711. data/docs/assets/hooks.md.CoiYCKRc.js +0 -80
  712. data/docs/assets/hooks.md.CoiYCKRc.lean.js +0 -1
  713. data/docs/assets/i18n.md.DxkCKhUw.js +0 -23
  714. data/docs/assets/i18n.md.DxkCKhUw.lean.js +0 -1
  715. data/docs/assets/index.md.DnphWyQd.js +0 -1
  716. data/docs/assets/index.md.DnphWyQd.lean.js +0 -1
  717. data/docs/assets/initial-home-page.DNIaYmgP.png +0 -0
  718. data/docs/assets/instrumentation.md.BcxjC4jd.js +0 -90
  719. data/docs/assets/instrumentation.md.BcxjC4jd.lean.js +0 -1
  720. data/docs/assets/javascript.md.D6fxhaQb.js +0 -31
  721. data/docs/assets/javascript.md.D6fxhaQb.lean.js +0 -1
  722. data/docs/assets/jobs.md.Bi3qb3v6.js +0 -25
  723. data/docs/assets/jobs.md.Bi3qb3v6.lean.js +0 -1
  724. data/docs/assets/keyword-injection.md.CqLnnzIz.js +0 -21
  725. data/docs/assets/keyword-injection.md.CqLnnzIz.lean.js +0 -1
  726. data/docs/assets/layouts.md.HEbeK7Jr.js +0 -68
  727. data/docs/assets/layouts.md.HEbeK7Jr.lean.js +0 -1
  728. data/docs/assets/lsp.md.bE9dW8n9.js +0 -1
  729. data/docs/assets/lsp.md.bE9dW8n9.lean.js +0 -1
  730. data/docs/assets/markdown-examples.md.BPmtHlc-.js +0 -33
  731. data/docs/assets/markdown-examples.md.BPmtHlc-.lean.js +0 -1
  732. data/docs/assets/middleware.md.BhOIsg59.js +0 -20
  733. data/docs/assets/middleware.md.BhOIsg59.lean.js +0 -1
  734. data/docs/assets/new-post-editor.DrHr-5oh.png +0 -0
  735. data/docs/assets/new-post-home-page.Bm34lyMg.png +0 -0
  736. data/docs/assets/overview.md.BpWAgPFH.js +0 -1
  737. data/docs/assets/overview.md.BpWAgPFH.lean.js +0 -1
  738. data/docs/assets/pages.md.B3sQXpEd.js +0 -45
  739. data/docs/assets/pages.md.B3sQXpEd.lean.js +0 -1
  740. data/docs/assets/recipes_alternate-layouts.md.C1QzVkA7.js +0 -22
  741. data/docs/assets/recipes_alternate-layouts.md.C1QzVkA7.lean.js +0 -1
  742. data/docs/assets/recipes_authentication.md.CyvoIW82.js +0 -157
  743. data/docs/assets/recipes_authentication.md.CyvoIW82.lean.js +0 -1
  744. data/docs/assets/recipes_custom-flash.md.6gFqf2uL.js +0 -26
  745. data/docs/assets/recipes_custom-flash.md.6gFqf2uL.lean.js +0 -1
  746. data/docs/assets/recipes_dev-env-secrets.md.DC_jVY9U.js +0 -12
  747. data/docs/assets/recipes_dev-env-secrets.md.DC_jVY9U.lean.js +0 -1
  748. data/docs/assets/recipes_form-errors.md.B5ptSzMO.js +0 -66
  749. data/docs/assets/recipes_form-errors.md.B5ptSzMO.lean.js +0 -1
  750. data/docs/assets/recipes_indexed-forms.md.BYYQGW2C.js +0 -74
  751. data/docs/assets/recipes_indexed-forms.md.BYYQGW2C.lean.js +0 -1
  752. data/docs/assets/recipes_migrations.md.Cid7-3cu.js +0 -97
  753. data/docs/assets/recipes_migrations.md.Cid7-3cu.lean.js +0 -1
  754. data/docs/assets/recipes_text-field-component.md.VhOsCtKI.js +0 -101
  755. data/docs/assets/recipes_text-field-component.md.VhOsCtKI.lean.js +0 -1
  756. data/docs/assets/roadmap.md.DqC1Y7Zt.js +0 -1
  757. data/docs/assets/roadmap.md.DqC1Y7Zt.lean.js +0 -1
  758. data/docs/assets/routes.md.C1dgIBtD.js +0 -21
  759. data/docs/assets/routes.md.C1dgIBtD.lean.js +0 -1
  760. data/docs/assets/security.md.Jn4SY1uK.js +0 -1
  761. data/docs/assets/security.md.Jn4SY1uK.lean.js +0 -1
  762. data/docs/assets/seed-data.md.UZW0WxYN.js +0 -14
  763. data/docs/assets/seed-data.md.UZW0WxYN.lean.js +0 -1
  764. data/docs/assets/spa.qejUdp-5.png +0 -0
  765. data/docs/assets/space-time-continuum.md.D9rYGDFH.js +0 -1
  766. data/docs/assets/space-time-continuum.md.D9rYGDFH.lean.js +0 -1
  767. data/docs/assets/style.B1z60PPQ.css +0 -1
  768. data/docs/assets/styled-form-with-server-side-violations.Bjxd8Dpv.png +0 -0
  769. data/docs/assets/styled-form-with-violations.Bv_sa9tg.png +0 -0
  770. data/docs/assets/styled-home-page-with-posts.Dd4kG89D.png +0 -0
  771. data/docs/assets/styled-home-page.BzdI7dWz.png +0 -0
  772. data/docs/assets/tutorial.md.BX6f6l00.js +0 -27
  773. data/docs/assets/tutorial.md.BX6f6l00.lean.js +0 -1
  774. data/docs/assets/tutorials_01-intro.md.CzZ3kpF_.js +0 -708
  775. data/docs/assets/tutorials_01-intro.md.CzZ3kpF_.lean.js +0 -1
  776. data/docs/assets/tutorials_02-dialog.md.DE5WfCXI.js +0 -274
  777. data/docs/assets/tutorials_02-dialog.md.DE5WfCXI.lean.js +0 -1
  778. data/docs/assets/unit-tests.md.vDsdBbO_.js +0 -13
  779. data/docs/assets/unit-tests.md.vDsdBbO_.lean.js +0 -1
  780. data/docs/assets/welcome-to-brut.VSWzl17-.png +0 -0
  781. data/docs/assets/why.md.4WpxdrQ2.js +0 -1
  782. data/docs/assets/why.md.4WpxdrQ2.lean.js +0 -1
  783. data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
  784. data/docs/assets.html +0 -47
  785. data/docs/brut-css/brut.css +0 -1
  786. data/docs/brut-css/brut.max.css +0 -22372
  787. data/docs/brut-css/classes/appearances.html +0 -783
  788. data/docs/brut-css/classes/background-colors.html +0 -3529
  789. data/docs/brut-css/classes/border-colors.html +0 -3529
  790. data/docs/brut-css/classes/borders.html +0 -2293
  791. data/docs/brut-css/classes/dimensions.html +0 -2581
  792. data/docs/brut-css/classes/flex.html +0 -917
  793. data/docs/brut-css/classes/foreground-colors.html +0 -3261
  794. data/docs/brut-css/classes/junk-drawer.html +0 -431
  795. data/docs/brut-css/classes/layout.html +0 -668
  796. data/docs/brut-css/classes/lists.html +0 -331
  797. data/docs/brut-css/classes/positioning.html +0 -1751
  798. data/docs/brut-css/classes/spacings.html +0 -2633
  799. data/docs/brut-css/classes/typography.html +0 -2206
  800. data/docs/brut-css/customization/advanced-configuration.html +0 -204
  801. data/docs/brut-css/customization/breakpoints.html +0 -227
  802. data/docs/brut-css/customization/design-system.html +0 -197
  803. data/docs/brut-css/customization/pseudo-classes.html +0 -228
  804. data/docs/brut-css/docs.css +0 -98
  805. data/docs/brut-css/getting-started/core-concepts.html +0 -234
  806. data/docs/brut-css/getting-started/installation.html +0 -190
  807. data/docs/brut-css/getting-started/overview.html +0 -210
  808. data/docs/brut-css/getting-started/simple-example.html +0 -285
  809. data/docs/brut-css/index.html +0 -193
  810. data/docs/brut-css/prism-twilight.min.css +0 -1
  811. data/docs/brut-css/properties/colors.html +0 -1548
  812. data/docs/brut-css/properties/spacings.html +0 -614
  813. data/docs/brut-css/properties/typography.html +0 -777
  814. data/docs/brut-js/api/AjaxSubmit.html +0 -452
  815. data/docs/brut-js/api/AjaxSubmit.js.html +0 -550
  816. data/docs/brut-js/api/Autosubmit.html +0 -192
  817. data/docs/brut-js/api/Autosubmit.js.html +0 -114
  818. data/docs/brut-js/api/BaseCustomElement.html +0 -1091
  819. data/docs/brut-js/api/BaseCustomElement.js.html +0 -312
  820. data/docs/brut-js/api/BrutCustomElements.html +0 -172
  821. data/docs/brut-js/api/BufferedLogger.html +0 -173
  822. data/docs/brut-js/api/ConfirmSubmit.html +0 -286
  823. data/docs/brut-js/api/ConfirmSubmit.js.html +0 -188
  824. data/docs/brut-js/api/ConfirmationDialog.html +0 -425
  825. data/docs/brut-js/api/ConfirmationDialog.js.html +0 -194
  826. data/docs/brut-js/api/ConstraintViolationMessage.html +0 -498
  827. data/docs/brut-js/api/ConstraintViolationMessage.js.html +0 -191
  828. data/docs/brut-js/api/ConstraintViolationMessages.html +0 -590
  829. data/docs/brut-js/api/ConstraintViolationMessages.js.html +0 -149
  830. data/docs/brut-js/api/CopyToClipboard.html +0 -345
  831. data/docs/brut-js/api/CopyToClipboard.js.html +0 -147
  832. data/docs/brut-js/api/Form.html +0 -291
  833. data/docs/brut-js/api/Form.js.html +0 -198
  834. data/docs/brut-js/api/I18nTranslation.html +0 -409
  835. data/docs/brut-js/api/I18nTranslation.js.html +0 -115
  836. data/docs/brut-js/api/LocaleDetection.html +0 -312
  837. data/docs/brut-js/api/LocaleDetection.js.html +0 -168
  838. data/docs/brut-js/api/Logger.html +0 -702
  839. data/docs/brut-js/api/Logger.js.html +0 -141
  840. data/docs/brut-js/api/Message.html +0 -238
  841. data/docs/brut-js/api/Message.js.html +0 -113
  842. data/docs/brut-js/api/PrefixedLogger.html +0 -369
  843. data/docs/brut-js/api/RichString.html +0 -1049
  844. data/docs/brut-js/api/RichString.js.html +0 -167
  845. data/docs/brut-js/api/Tabs.html +0 -295
  846. data/docs/brut-js/api/Tabs.js.html +0 -219
  847. data/docs/brut-js/api/Toast.html +0 -270
  848. data/docs/brut-js/api/Toast.js.html +0 -153
  849. data/docs/brut-js/api/Tracing.html +0 -277
  850. data/docs/brut-js/api/Tracing.js.html +0 -298
  851. data/docs/brut-js/api/external-CustomElementRegistry.html +0 -140
  852. data/docs/brut-js/api/external-Performance.html +0 -138
  853. data/docs/brut-js/api/external-Promise.html +0 -138
  854. data/docs/brut-js/api/external-ValidityState.html +0 -138
  855. data/docs/brut-js/api/external-Window.html +0 -233
  856. data/docs/brut-js/api/external-fetch.html +0 -138
  857. data/docs/brut-js/api/global.html +0 -400
  858. data/docs/brut-js/api/index.html +0 -168
  859. data/docs/brut-js/api/index.js.html +0 -184
  860. data/docs/brut-js/api/module-testing.html +0 -383
  861. data/docs/brut-js/api/scripts/linenumber.js +0 -25
  862. data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +0 -202
  863. data/docs/brut-js/api/scripts/prettify/lang-css.js +0 -2
  864. data/docs/brut-js/api/scripts/prettify/prettify.js +0 -28
  865. data/docs/brut-js/api/styles/jsdoc-default.css +0 -327
  866. data/docs/brut-js/api/styles/prettify-jsdoc.css +0 -111
  867. data/docs/brut-js/api/styles/prettify-tomorrow.css +0 -132
  868. data/docs/brut-js/api/testing.AssetMetadata.html +0 -172
  869. data/docs/brut-js/api/testing.AssetMetadataLoader.html +0 -171
  870. data/docs/brut-js/api/testing.CustomElementTest.html +0 -679
  871. data/docs/brut-js/api/testing.DOMCreator.html +0 -171
  872. data/docs/brut-js/api/testing_AssetMetadata.js.html +0 -86
  873. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +0 -76
  874. data/docs/brut-js/api/testing_CustomElementTest.js.html +0 -286
  875. data/docs/brut-js/api/testing_DOMCreator.js.html +0 -96
  876. data/docs/brut-js/api/testing_index.js.html +0 -99
  877. data/docs/brut-js.html +0 -40
  878. data/docs/business-logic.html +0 -29
  879. data/docs/cli.html +0 -150
  880. data/docs/components.html +0 -124
  881. data/docs/configuration.html +0 -106
  882. data/docs/css.html +0 -49
  883. data/docs/custom-element-tests.html +0 -97
  884. data/docs/database-access.html +0 -91
  885. data/docs/database-schema.html +0 -98
  886. data/docs/deployment.html +0 -83
  887. data/docs/dev-environment.html +0 -44
  888. data/docs/dir-structure.html +0 -74
  889. data/docs/doc-conventions.html +0 -29
  890. data/docs/end-to-end-tests.html +0 -56
  891. data/docs/favicon.ico +0 -0
  892. data/docs/features.html +0 -182
  893. data/docs/flash-and-session.html +0 -107
  894. data/docs/form-constraints.html +0 -118
  895. data/docs/forms.html +0 -92
  896. data/docs/getting-started.html +0 -59
  897. data/docs/handlers.html +0 -82
  898. data/docs/hashmap.json +0 -1
  899. data/docs/hooks.html +0 -108
  900. data/docs/i18n.html +0 -51
  901. data/docs/index.html +0 -29
  902. data/docs/instrumentation.html +0 -118
  903. data/docs/javascript.html +0 -59
  904. data/docs/jobs.html +0 -53
  905. data/docs/keyword-injection.html +0 -49
  906. data/docs/layouts.html +0 -96
  907. data/docs/lsp.html +0 -29
  908. data/docs/markdown-examples.html +0 -61
  909. data/docs/middleware.html +0 -48
  910. data/docs/overview.html +0 -29
  911. data/docs/pages.html +0 -73
  912. data/docs/recipes/alternate-layouts.html +0 -50
  913. data/docs/recipes/authentication.html +0 -185
  914. data/docs/recipes/custom-flash.html +0 -54
  915. data/docs/recipes/dev-env-secrets.html +0 -40
  916. data/docs/recipes/form-errors.html +0 -94
  917. data/docs/recipes/indexed-forms.html +0 -102
  918. data/docs/recipes/migrations.html +0 -125
  919. data/docs/recipes/text-field-component.html +0 -129
  920. data/docs/roadmap.html +0 -29
  921. data/docs/routes.html +0 -49
  922. data/docs/security.html +0 -29
  923. data/docs/seed-data.html +0 -42
  924. data/docs/space-time-continuum.html +0 -29
  925. data/docs/tutorial.html +0 -55
  926. data/docs/tutorials/01-intro.html +0 -736
  927. data/docs/tutorials/02-dialog.html +0 -302
  928. data/docs/unit-tests.html +0 -41
  929. data/docs/vp-icons.css +0 -1
  930. data/docs/why.html +0 -29
  931. data/docs-todo.md +0 -32
  932. data/dx/bash_customizations +0 -6
  933. data/dx/build +0 -73
  934. data/dx/build.pre +0 -15
  935. data/dx/docker-compose.env +0 -22
  936. data/dx/dx.sh.lib +0 -24
  937. data/dx/exec +0 -75
  938. data/dx/setupkit.sh.lib +0 -144
  939. data/dx/show-help-in-app-container-then-wait.sh +0 -38
  940. data/lib/brut/cli/app.rb +0 -238
  941. data/lib/brut/cli/app_runner.rb +0 -252
  942. data/lib/brut/cli/command.rb +0 -258
  943. data/lib/brut/cli/execution_results.rb +0 -119
  944. data/lib/brut/front_end/layouts/_internal.html.erb +0 -68
  945. data/lib/brut/front_end/pages/_missing_page.html.erb +0 -17
  946. data/mkbrut/.gitignore +0 -16
  947. data/mkbrut/CODE_OF_CONDUCT.txt +0 -100
  948. data/mkbrut/Gemfile +0 -3
  949. data/mkbrut/Gemfile.lock +0 -20
  950. data/mkbrut/LICENSE.txt +0 -370
  951. data/mkbrut/README.md +0 -145
  952. data/mkbrut/Rakefile +0 -2
  953. data/mkbrut/bin/build +0 -36
  954. data/mkbrut/bin/ci +0 -19
  955. data/mkbrut/bin/docs +0 -19
  956. data/mkbrut/bin/publish +0 -129
  957. data/mkbrut/bin/rake +0 -16
  958. data/mkbrut/bin/setup +0 -30
  959. data/mkbrut/brut-welcome.png +0 -0
  960. data/mkbrut/deploy/.dockerignore +0 -2
  961. data/mkbrut/deploy/Dockerfile +0 -25
  962. data/mkbrut/dx +0 -1
  963. data/mkbrut/exe/mkbrut +0 -5
  964. data/mkbrut/lib/mkbrut/app_name.rb +0 -29
  965. data/mkbrut/lib/mkbrut/app_options.rb +0 -36
  966. data/mkbrut/lib/mkbrut/cli.rb +0 -189
  967. data/mkbrut/lib/mkbrut/erb_binding_delegate.rb +0 -20
  968. data/mkbrut/lib/mkbrut/ops.rb +0 -17
  969. data/mkbrut/lib/mkbrut/organization.rb +0 -5
  970. data/mkbrut/lib/mkbrut/segments.rb +0 -8
  971. data/mkbrut/lib/mkbrut/version.rb +0 -3
  972. data/mkbrut/lib/mkbrut.rb +0 -20
  973. data/mkbrut/mkbrut.gemspec +0 -34
  974. data/mkbrut/templates/Base/app/src/front_end/images/LogoPylon.png +0 -0
  975. data/mkbrut/templates/Base/bin/build-assets +0 -7
  976. data/mkbrut/templates/Base/bin/ci +0 -39
  977. data/mkbrut/templates/Base/bin/db +0 -9
  978. data/mkbrut/templates/Base/bin/scaffold +0 -9
  979. data/mkbrut/templates/Base/bin/setup +0 -287
  980. data/mkbrut/templates/Base/bin/test +0 -9
  981. data/mkbrut/templates/Base/bin/test-server +0 -29
  982. data/mkbrut/templates/Base/dx/prune +0 -19
  983. data/mkbrut/templates/Base/dx/start +0 -30
  984. data/mkbrut/templates/Base/dx/stop +0 -23
  985. data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +0 -27
  986. data/specs/brut/front_end/forms/input.spec.rb +0 -978
  987. data/specs/brut/front_end/forms/radio_button_group_input.spec.rb +0 -54
  988. data/specs/brut/front_end/forms/select_input.spec.rb +0 -54
  989. data/specs/brut/instrumentation/methods.spec.rb +0 -399
  990. data/specs/brut/junk_drawer.spec.rb +0 -79
  991. data/specs/brut/tui/ansi_escape_code.spec.rb +0 -30
  992. data/specs/brut/tui/event_loop.spec.rb +0 -70
  993. data/specs/brut/tui/events/base_event.spec.rb +0 -26
  994. data/specs/brut/tui/events/event_bus.spec.rb +0 -141
  995. data/specs/brut/tui/events/exception.spec.rb +0 -19
  996. data/specs/brut/tui/events/test_event.rb +0 -5
  997. data/specs/spec_helper.rb +0 -31
  998. data/specs/support/matchers/have_constraint_violation.rb +0 -23
  999. data/specs/support/matchers.rb +0 -5
  1000. data/specs/support.rb +0 -3
  1001. /data/{mkbrut/lib/mkbrut → lib/brut/cli/apps/new}/prefixed_io.rb +0 -0
  1002. /data/{mkbrut/templates → templates}/Base/.dockerignore +0 -0
  1003. /data/{mkbrut/templates → templates}/Base/.env.development.erb +0 -0
  1004. /data/{mkbrut/templates → templates}/Base/.env.test.erb +0 -0
  1005. /data/{mkbrut/templates → templates}/Base/.gitignore +0 -0
  1006. /data/{mkbrut/templates → templates}/Base/.projections.json +0 -0
  1007. /data/{mkbrut/templates → templates}/Base/Dockerfile.dx +0 -0
  1008. /data/{mkbrut/templates → templates}/Base/Gemfile.erb +0 -0
  1009. /data/{mkbrut/templates → templates}/Base/Procfile.development +0 -0
  1010. /data/{mkbrut/templates → templates}/Base/Procfile.test +0 -0
  1011. /data/{mkbrut/templates → templates}/Base/README.md +0 -0
  1012. /data/{mkbrut/templates → templates}/Base/README.md.erb +0 -0
  1013. /data/{mkbrut/templates → templates}/Base/app/bootstrap.rb +0 -0
  1014. /data/{mkbrut/templates → templates}/Base/app/config/i18n/en/1_defaults.rb +0 -0
  1015. /data/{mkbrut/templates → templates}/Base/app/config/i18n/en/2_app.rb +0 -0
  1016. /data/{mkbrut/templates → templates}/Base/app/public/static/manifest.json.erb +0 -0
  1017. /data/{mkbrut/templates → templates}/Base/app/src/app.rb.erb +0 -0
  1018. /data/{mkbrut/templates → templates}/Base/app/src/back_end/data_models/app_data_model.rb +0 -0
  1019. /data/{mkbrut/templates → templates}/Base/app/src/back_end/data_models/db.rb +0 -0
  1020. /data/{mkbrut/templates → templates}/Base/app/src/back_end/data_models/migrations/20240101130000_citext.rb +0 -0
  1021. /data/{mkbrut/templates → templates}/Base/app/src/back_end/data_models/seed/seed_data.rb +0 -0
  1022. /data/{mkbrut/templates → templates}/Base/app/src/front_end/components/app_component.rb +0 -0
  1023. /data/{mkbrut/templates → templates}/Base/app/src/front_end/components/custom_element_registration.rb.erb +0 -0
  1024. /data/{mkbrut/templates → templates}/Base/app/src/front_end/css/index.css +0 -0
  1025. /data/{mkbrut/templates → templates}/Base/app/src/front_end/css/svgs.css +0 -0
  1026. /data/{mkbrut/templates → templates}/Base/app/src/front_end/forms/app_form.rb +0 -0
  1027. /data/{mkbrut/templates → templates}/Base/app/src/front_end/handlers/app_handler.rb +0 -0
  1028. /data/{brutrb.com → templates/Base/app/src/front_end}/images/LogoPylon.png +0 -0
  1029. /data/{mkbrut/templates → templates}/Base/app/src/front_end/images/LogoTransit.png +0 -0
  1030. /data/{mkbrut/templates → templates}/Base/app/src/front_end/images/apple-touch-icon-120x120.png +0 -0
  1031. /data/{mkbrut/templates → templates}/Base/app/src/front_end/images/apple-touch-icon-152x152.png +0 -0
  1032. /data/{mkbrut/templates → templates}/Base/app/src/front_end/images/apple-touch-icon-167x167.png +0 -0
  1033. /data/{mkbrut/templates → templates}/Base/app/src/front_end/images/apple-touch-icon-180x180.png +0 -0
  1034. /data/{mkbrut/templates → templates}/Base/app/src/front_end/images/favicon.ico +0 -0
  1035. /data/{mkbrut/templates → templates}/Base/app/src/front_end/images/icon.png +0 -0
  1036. /data/{mkbrut/templates → templates}/Base/app/src/front_end/images/mkicons.sh +0 -0
  1037. /data/{mkbrut/templates → templates}/Base/app/src/front_end/js/index.js +0 -0
  1038. /data/{mkbrut/templates → templates}/Base/app/src/front_end/layouts/blank_layout.rb +0 -0
  1039. /data/{mkbrut/templates → templates}/Base/app/src/front_end/layouts/default_layout.rb.erb +0 -0
  1040. /data/{mkbrut/templates → templates}/Base/app/src/front_end/pages/app_page.rb +0 -0
  1041. /data/{mkbrut/templates → templates}/Base/app/src/front_end/pages/home_page.rb +0 -0
  1042. /data/{mkbrut/templates → templates}/Base/app/src/front_end/support/app_session.rb +0 -0
  1043. /data/{mkbrut/templates → templates}/Base/app/src/front_end/svgs/README.md +0 -0
  1044. /data/{mkbrut/templates → templates}/Base/app/src/front_end/svgs/comment-button.svg +0 -0
  1045. /data/{mkbrut/templates → templates}/Base/bin/README.md.erb +0 -0
  1046. /data/{mkbrut/templates → templates}/Base/bin/console +0 -0
  1047. /data/{mkbrut/templates → templates}/Base/bin/dbconsole +0 -0
  1048. /data/{mkbrut/templates → templates}/Base/bin/dev +0 -0
  1049. /data/{mkbrut/templates → templates}/Base/bin/run +0 -0
  1050. /data/{mkbrut/templates → templates}/Base/bin/run.run +0 -0
  1051. /data/{mkbrut/templates → templates}/Base/bin/startup-message +0 -0
  1052. /data/{mkbrut/templates → templates}/Base/config.ru +0 -0
  1053. /data/{mkbrut/templates → templates}/Base/docker-compose.dx.yml +0 -0
  1054. /data/{mkbrut/templates → templates}/Base/dx/README.md +0 -0
  1055. /data/{mkbrut/templates → templates}/Base/dx/bash_customizations +0 -0
  1056. /data/{mkbrut/templates → templates}/Base/dx/bash_customizations.local +0 -0
  1057. /data/{mkbrut/templates → templates}/Base/dx/build +0 -0
  1058. /data/{mkbrut/templates → templates}/Base/dx/dx.sh.lib +0 -0
  1059. /data/{mkbrut/templates → templates}/Base/dx/exec +0 -0
  1060. /data/{dx → templates/Base/dx}/prune +0 -0
  1061. /data/{mkbrut/templates → templates}/Base/dx/show-help-in-app-container-then-wait.sh +0 -0
  1062. /data/{dx → templates/Base/dx}/start +0 -0
  1063. /data/{dx → templates/Base/dx}/stop +0 -0
  1064. /data/{mkbrut/templates → templates}/Base/package.json.erb +0 -0
  1065. /data/{mkbrut/templates → templates}/Base/puma.config.rb +0 -0
  1066. /data/{mkbrut/templates → templates}/Base/specs/e2e/home_page.spec.rb.erb +0 -0
  1067. /data/{mkbrut/templates → templates}/Base/specs/front_end/js/SpecHelper.js +0 -0
  1068. /data/{mkbrut/templates → templates}/Base/specs/front_end/pages/home_page.spec.rb +0 -0
  1069. /data/{mkbrut/templates → templates}/Base/specs/lint_factories.spec.rb +0 -0
  1070. /data/{mkbrut/templates → templates}/Base/specs/spec_helper.rb +0 -0
  1071. /data/{mkbrut/templates → templates}/Base/specs/support.rb +0 -0
  1072. /data/{mkbrut/templates → templates}/segments/BareBones/app/src/front_end/handlers/trigger_exception_handler.rb +0 -0
  1073. /data/{mkbrut/templates → templates}/segments/BareBones/app/src/front_end/js/Example.js.erb +0 -0
  1074. /data/{mkbrut/templates → templates}/segments/BareBones/specs/front_end/handlers/trigger_exception_handler.spec.rb +0 -0
  1075. /data/{mkbrut/templates → templates}/segments/BareBones/specs/front_end/js/Example.spec.js.erb +0 -0
  1076. /data/{mkbrut/templates → templates}/segments/Demo/app/src/back_end/data_models/db/guestbook_message.rb +0 -0
  1077. /data/{mkbrut/templates → templates}/segments/Demo/app/src/back_end/data_models/migrations/20250628194124_guestbook.rb +0 -0
  1078. /data/{mkbrut/templates → templates}/segments/Demo/app/src/front_end/components/flash_component.rb +0 -0
  1079. /data/{mkbrut/templates → templates}/segments/Demo/app/src/front_end/css/constraint-violations.css +0 -0
  1080. /data/{mkbrut/templates → templates}/segments/Demo/app/src/front_end/fonts/monaspace-xenon.ttf +0 -0
  1081. /data/{mkbrut/templates → templates}/segments/Demo/app/src/front_end/forms/guestbook_message_form.rb +0 -0
  1082. /data/{mkbrut/templates → templates}/segments/Demo/app/src/front_end/handlers/guestbook_message_handler.rb +0 -0
  1083. /data/{mkbrut/templates → templates}/segments/Demo/app/src/front_end/pages/guestbook_page/message_component.rb +0 -0
  1084. /data/{mkbrut/templates → templates}/segments/Demo/app/src/front_end/pages/guestbook_page.rb +0 -0
  1085. /data/{mkbrut/templates → templates}/segments/Demo/app/src/front_end/pages/new_guestbook_message_page.rb +0 -0
  1086. /data/{mkbrut/templates → templates}/segments/Demo/specs/back_end/data_models/db/guestbook_message.spec.rb +0 -0
  1087. /data/{mkbrut/templates → templates}/segments/Demo/specs/e2e/guest_message.spec.rb +0 -0
  1088. /data/{mkbrut/templates → templates}/segments/Demo/specs/factories/db/guestbook_message.factory.rb +0 -0
  1089. /data/{mkbrut/templates → templates}/segments/Demo/specs/front_end/components/flash_component.spec.rb +0 -0
  1090. /data/{mkbrut/templates → templates}/segments/Demo/specs/front_end/handlers/guestbook_message_handler.spec.rb +0 -0
  1091. /data/{mkbrut/templates → templates}/segments/Demo/specs/front_end/pages/guestbook_page/message_component.spec.rb +0 -0
  1092. /data/{mkbrut/templates → templates}/segments/Demo/specs/front_end/pages/guestbook_page.spec.rb +0 -0
  1093. /data/{mkbrut/templates → templates}/segments/Demo/specs/front_end/pages/new_guestbook_message_page.spec.rb +0 -0
  1094. /data/{mkbrut/templates → templates}/segments/Heroku/bin/deploy +0 -0
  1095. /data/{mkbrut/templates → templates}/segments/Heroku/deploy/docker-entrypoint +0 -0
  1096. /data/{mkbrut/templates → templates}/segments/Sidekiq/app/boot_sidekiq.rb +0 -0
  1097. /data/{mkbrut/templates → templates}/segments/Sidekiq/app/config/sidekiq.yml +0 -0
  1098. /data/{mkbrut/templates → templates}/segments/Sidekiq/app/src/back_end/jobs/app_job.rb +0 -0
  1099. /data/{mkbrut/templates → templates}/segments/Sidekiq/app/src/back_end/jobs/example_job.rb +0 -0
  1100. /data/{mkbrut/templates → templates}/segments/Sidekiq/app/src/back_end/segments/sidekiq_segment.rb +0 -0
  1101. /data/{mkbrut/templates → templates}/segments/Sidekiq/bin/run.sidekiq +0 -0
  1102. /data/{mkbrut/templates → templates}/segments/Sidekiq/specs/back_end/jobs/example_job.spec.rb +0 -0
  1103. /data/{mkbrut/templates → templates}/segments/Sidekiq/specs/integration/sidekiq_works.spec.rb +0 -0
@@ -1,70 +0,0 @@
1
- import{_ as e,c as a,o as i,ag as t}from"./chunks/framework.C4nOkCZI.js";const u=JSON.parse('{"title":"Database Schema / Migrations","description":"","frontmatter":{},"headers":[],"relativePath":"database-schema.md","filePath":"database-schema.md"}'),n={name:"database-schema.md"};function l(o,s,r,h,p,d){return i(),a("div",null,[...s[0]||(s[0]=[t(`<h1 id="database-schema-migrations" tabindex="-1">Database Schema / Migrations <a class="header-anchor" href="#database-schema-migrations" aria-label="Permalink to &quot;Database Schema / Migrations&quot;">​</a></h1><p>Brut provides access to the database via the <a href="https://sequel.jeremyevans.net/" target="_blank" rel="noreferrer">Sequel library</a>. To manage your database schema, Brut uses Sequel&#39;s facility for this, with some of its own enhancements.</p><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p>Brut currently only supports Postgres. Sequel supports many database systems, however Brut&#39;s extensions are currently geared toward Postgres only.</p></div><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>Your database schema is managed by a series of changes that build upon one another called <em>migrations</em>.</p><p>For example, if you have a table <code>widgets</code> that has a <code>name</code> and <code>description</code>, to add a <code>status</code> field, you cannot <code>drop table widgets</code> and then <code>create table widgets(...)</code> with the fields. You must instead <code>alter table widgets(...)</code> to add the new column.</p><p>Thus, each migration file is a change to the schema produced by all previous migration files.</p><p>Brut&#39;s provides this via Sequel. See <a href="https://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html" target="_blank" rel="noreferrer">both</a> <a href="https://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html" target="_blank" rel="noreferrer">docs</a> for details on the API. Any schema modification method Sequel documents is available, however some default behavior has changed.</p><h3 id="creating-migrations" tabindex="-1">Creating Migrations <a class="header-anchor" href="#creating-migrations" aria-label="Permalink to &quot;Creating Migrations&quot;">​</a></h3><p>To create a migration, use <code>bin/db new-migration</code>. It accepts any number of arguments that will be joined together to form the filename:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>&gt; bin/db new-migration user accounts</span></span>
2
- <span class="line"><span>[ bin/db ] Migration created:</span></span>
3
- <span class="line"><span> app/src/back_end/data_models/migrations/20250508132646_user-accounts.rb</span></span></code></pre></div><p>If you will be creating <a href="/database-access.html">database models</a> as well, you may find it easier to use <code>bin/scaffold db_model</code>, which will create an empty database model class, empty test, and empty factory, along with an outline of your migration:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>bin/scaffold db_model widget</span></span>
4
- <span class="line"><span>[ bin/scaffold ] Executing [&quot;bin/db new_migration create_widget&quot;]</span></span>
5
- <span class="line"><span>[ bin/db ] Migration created:</span></span>
6
- <span class="line"><span> app/src/back_end/data_models/migrations/20250712182257_create_widgets.rb</span></span>
7
- <span class="line"><span>[ bin/scaffold ] [&quot;bin/db new_migration create_widgets&quot;] succeeded</span></span>
8
- <span class="line"><span>[ bin/scaffold ] Creating DB::Foo in app/src/back_end/data_models/db/widget.rb</span></span>
9
- <span class="line"><span>[ bin/scaffold ] Creating spec for DB::Foo in specs/back_end/data_models/db/widget.spec.rb</span></span>
10
- <span class="line"><span>[ bin/scaffold ] Creating factory for DB::Foo in specs/factories/db/widget.factory.rb</span></span></code></pre></div><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Brut doesn&#39;t do pluralization logic. Although Sequel does do some, you should not refer to your database model plurally. If you were do run <code>bin/scaffold db_model widgets</code>, you&#39;d create the class <code>DB::Widgets</code>, which would not work. Be aware.</p></div><p>Note that the files are located in <code>app/src/back_end/data_models/migrations</code> and have a name prefixed with a timestamp. This timestamp determins an ordering of how the files are applied to the database.</p><p>The file is created mostly blank:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">migration</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
11
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> up </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
12
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
13
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p>Sequels&#39; migration API is similar in concept to Rails&#39;, but differs significantly in specifics. Please consult Sequel&#39;s documentation and don&#39;t assume Railsism will work the same way.</p></div><p>Brut encourages only &quot;up&quot; migrations. Since Brut treats your development database as ephemeral, there is little value to managing &quot;down&quot; migrations.</p><p>This is why Sequel&#39;s <code>change</code> method is not included in the scaffolded code. <code>change</code>, like Active Record&#39;s method of the same name, automagically creates both &quot;up&quot; and &quot;down&quot; migrations, but <em>only</em> if you use the DSL. If you use raw SQL, <code>change</code> doesn&#39;t work. By using only <code>up</code>, you won&#39;t have to worry about this.</p><p>Let&#39;s create an accounts table that has an email field, a <code>deactivated_at</code> timestamp, and a <code>created_at</code> timestamp:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">migration</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
14
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> up </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
15
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> create_table </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:accounts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
16
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> comment:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;People or systems who can access this system&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
17
- <span class="line"></span>
18
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> column </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:email</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">unique:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span></span>
19
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> column </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:deactivated_at</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:timestamptz</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">null:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span></span>
20
- <span class="line"></span>
21
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
22
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
23
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>A few notes that aren&#39;t obvious without knowing about Brut&#39;s extensions:</p><ul><li><code>comment:</code> is required. You must provide documentation about what table is for</li><li>The table has a primary key named <code>id</code> of type <code>int</code> that is a serial.</li><li><code>created_at</code> is created by default, with time <code>timestamptz</code> (AKA <code>timestamp with time zone</code>, see <a href="/space-time-continuum.html">Space/Time Continuum</a>).</li><li><code>email</code> is not null by default. <code>deactivated_at</code> <em>is</em> null because it&#39;s specified as such.</li></ul><p>To apply this migration use <code>bin/db migrate</code></p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>&gt; bin/db migrate</span></span></code></pre></div><h3 id="managing-migrations" tabindex="-1">Managing Migrations <a class="header-anchor" href="#managing-migrations" aria-label="Permalink to &quot;Managing Migrations&quot;">​</a></h3><p>Sequel uses a special database table to understand which migrations have been run. This table will exist in production and prevent you from applying migrations twice or skipping a migration.</p><p>Note that managing a production database in this way requires knowledge of both your database system and the data itself. Brut can only provide so much to make this process manageable. You should consult <a href="https://github.com/ankane/strong_migrations?tab=readme-ov-file" target="_blank" rel="noreferrer">Strong Migrations&#39; README</a> and learn it deeply. Although it&#39;s targeted at Rails developers, the information here applies to any database management system.</p><h3 id="brut-extensions-and-changes-in-sequel-s-behavior" tabindex="-1">Brut Extensions and Changes in Sequel&#39;s Behavior <a class="header-anchor" href="#brut-extensions-and-changes-in-sequel-s-behavior" aria-label="Permalink to &quot;Brut Extensions and Changes in Sequel&#39;s Behavior&quot;">​</a></h3><p>Brut includes the following standard plugins and extensions:</p><ul><li><a href="https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/pg_array_rb.html" target="_blank" rel="noreferrer"><code>pg_array</code></a></li><li><a href="https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/pg_json_rb.html" target="_blank" rel="noreferrer"><code>pg_json</code></a></li><li><a href="https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/TableSelect.html" target="_blank" rel="noreferrer"><code>table_select</code></a>, which changes queries to prepend <code>*</code> with the table name, e.g. <code>select accounts.*</code> instead of <code>select *</code>.</li><li><a href="https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/SkipSavingColumns.html" target="_blank" rel="noreferrer"><code>skip_saving_columns</code></a> which will skip saving columns that the database generates.</li></ul><p>Brut also provides the following plugins and behavior changes:</p><ul><li><code>Sequel::Extensions::BrutInstrumentation</code>, which adds OpenTelemetry instrumentation to Sequel (see <a href="/instrumentation.html">Instrumentation</a>).</li><li><code>Sequel::Plugins::FindBang</code>, which adds <code>find!</code> to all models. This wraps Sequel&#39;s <code>first!</code> method, but provides a more helpful error message when no records are found</li><li><code>Sequel::Plugins::CreatedAt</code>, which automatically sets <code>created_at</code> when a record is created.</li><li><code>Sequel::Plugins::ExternalId</code>, which adds support for external IDs (see below)</li><li><code>Sequel::Extensions::BrutMigrations</code>, which enhances the migrations API (see below)</li></ul><h4 id="external-ids" tabindex="-1">External IDs <a class="header-anchor" href="#external-ids" aria-label="Permalink to &quot;External IDs&quot;">​</a></h4><p>It&#39;s often useful to provide a unique identifier for a record that is not the database primary key. There are many advantages to doing so, the main being that your primary and foreign keys are considered private and for developer use only. Creating additional externalizable unique keys is trivial, so Brut provides a way to do that.</p><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p><strong>Primary keys</strong> and <strong>keys</strong> are not the same thing. <strong>Primary keys</strong> are what is used to identify a record for the purposes of referential integrity. A <strong>key</strong> simply uniquely identifies a row or is a unique constraint on a table. Tables have only one primary key, but potentially many keys. Brut uses <em>synthetic</em> (sometimes called <em>surrogate</em>) keys as primary keys. This means they have no business meaning and can be safely used for foreign keys and other cases without conflating them with domain concepts.</p></div><p>In Brut, an external ID is automatically generated by the database when a record is created. By convention, it is prefixed with a short string representing your app and a short string representing the table, followed by a unique hash.</p><p>For example, if our app&#39;s prefix is, say, &quot;my&quot; (for &quot;my app&quot;), and the accounts table&#39;s prefix is &quot;ac&quot; (for &quot;accounts&quot;), an external ID might look like <code>myac_3457238947239487</code>. This double-prefixing is extremely useful when sharing these values with the outside world. You can immediately identify an ID from your app <em>and</em> know what sort of thing it refers to.</p><p>To use external IDs in Brut, you must do three things:</p><ol><li><p>You must set your external ID prefix in <code>app/src/app.rb</code>. This should have been done when you created your Brut app, but it looks like so:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> App</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">App</span></span>
24
- <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
25
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span></span>
26
- <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
27
- <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">container</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">override</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;external_id_prefix&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;my&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
28
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
29
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> </span></span>
30
- <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
31
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div></li><li><p>When creating the table in a migration, use <code>external_id: true</code>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">migration</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
32
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> up </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
33
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> create_table </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:accounts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
34
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> comment:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;People or systems who can access this system&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
35
- <span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> external_id:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
36
- <span class="line"></span>
37
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> column </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:email</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">unique:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span></span>
38
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> column </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:deactivated_at</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:timestamptz</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">null:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span></span>
39
- <span class="line"></span>
40
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
41
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
42
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div></li><li><p>In your <a href="/database-access.html">data model class</a>, use <code>has_external_id</code> to specify the prefix for this table:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>class DB::Account &lt; AppDataModel</span></span>
43
- <span class="line"><span> has_external_id :ac</span></span>
44
- <span class="line"><span></span></span>
45
- <span class="line"><span> # ...</span></span>
46
- <span class="line"><span>end</span></span></code></pre></div></li></ol><p>Brut creates the external ID using Ruby code as part of Sequel&#39;s lifecycle hooks. It&#39;s only set a) on creation, and b) if there is no value provided when creating the record.</p><p>This means that you can set values explicitly if you like, <em>and</em> you can change them later. This is useful if you shared the value with someone you didn&#39;t mean to. Because these external IDs aren&#39;t use for referential integrity/foreign keys, they can be changed at any time, as long as the value is unique (which will be enforced by the database).</p><h3 id="brut-migration-changes-and-enhancement" tabindex="-1">Brut Migration Changes and Enhancement <a class="header-anchor" href="#brut-migration-changes-and-enhancement" aria-label="Permalink to &quot;Brut Migration Changes and Enhancement&quot;">​</a></h3><p>Brut attempts to set default behavior for migrations to encourage a modicum of best practices. These are:</p><ul><li><p><strong>Automatic synthetic primary key named <code>id</code> of type <code>int</code>.</strong> You almost always want this. You can change the primary key configuration per table if you like, but if you do nothing, you get a primary key that works for 99% of your needs.</p></li><li><p><strong>Automatic <code>created_at</code> of type <code>timestamptz</code>.</strong> It&#39;s a good practice to store the date a record was created. This can help with debugging and provide a reliable sort key for data that otherwise has none. It uses <code>timestamp with time zone</code>, which you are encouraged to use always. See <a href="/space-time-continuum.html">Space/Time Continuum</a> for details.</p></li><li><p><strong>No automatic <code>updated_at</code>.</strong> While you are free to add <code>updated_at</code>, in practice this column creates more problems than it solves. If you need to know when data has changed, it is almost always better to do this with an audit table, event log, or special-purpose field.</p></li><li><p><strong><code>create_table</code> requires <code>comment:</code>.</strong> Just document your tables. It takes two seconds and can save a lot of time later.</p></li><li><p><strong>Support for external IDs via <code>external_id:</code>.</strong> As discussed above, this will create a unique <code>external_id</code> column on your table and ensure it has a value on creation.</p></li><li><p><strong>Columns are <code>NOT NULL</code> by default.</strong> Null is not a valid value. In many cases, your columns should not allow <code>NULL</code> (<code>nil</code>), so in Brut apps, you must opt into nullable columns. You can use <code>null: true</code> to make a column nullable.</p></li><li><p><strong>Foreign keys are <code>NOT NULL</code> and have an index created for them by default.</strong> Foreign keys should rarely be <code>NULL</code> and you almost always want an index on them, since you are likely to using them in queries, e.g. <code>account.widgets</code> would join on <code>accounts.widget_id</code>. You can opt out of either via <code>null: true</code> and <code>index: false</code>.</p></li><li><p><strong>The method <code>key</code> allows you to specify a non-primary key, AKA a unique index</strong>. Suppose our <code>accounts</code> table allowed duplicate email addresses, but only one per <code>organization_id</code>. You&#39;d model this by creating a unique index on <code>(email,organization_id)</code>. In Brut:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">migration</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
47
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> up </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
48
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> create_table </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:accounts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
49
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> comment:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;People or systems who can access this system&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
50
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> external_id:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
51
- <span class="line"></span>
52
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> column </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:email</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">unique:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span></span>
53
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> foreign_key </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:organization_id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:organizations</span></span>
54
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> column </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:deactivated_at</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:timestamptz</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">null:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span></span>
55
- <span class="line"></span>
56
- <span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> key [</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:email</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:organization_id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
57
- <span class="line"></span>
58
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
59
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
60
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This allows your migrations to be more expressive <em>and</em> make it easier to set up unique constraints that relate to your business logic or domain.</p></li></ul><h2 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to &quot;Testing&quot;">​</a></h2><p>Generally, you don&#39;t test database migrations, however you may want to test constraints or other logic you have set up. Techniques for doing this are in the <a href="/database-access.html#testing">database access</a> section.</p><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to &quot;Recommended Practices&quot;">​</a></h2><h3 id="ephemeral-dev-database" tabindex="-1">Ephemeral Dev Database <a class="header-anchor" href="#ephemeral-dev-database" aria-label="Permalink to &quot;Ephemeral Dev Database&quot;">​</a></h3><p>Brut intends for your develompent database to be ephemeral. Your entire workflow should be built around it being OK and normal to completely blow away your development database and recreate it. This is why down migrations (and the use of <code>change</code>) are discouraged. You really don&#39;t need them.</p><p>Assuming you have <a href="/seed-data.html">seed data</a> set up properly, you can reliably reset everything like so:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>&gt; bin/db rebuild</span></span>
61
- <span class="line"><span>&gt; bin/db seed</span></span>
62
- <span class="line"><span>&gt; bin/db rebuild -e test</span></span></code></pre></div><p>As long as you don&#39;t change migrations that have been applied in production, you can safely run the above commands to iterate on a schema change.</p><p>This does imply that you should not run business logic or make <em>data</em> changes in your migration files. No migration should rely on specific data being in the database.</p><p>This workflow my be much different from what you are used to, but you will be quite happy when you adopt it. The days of downloading a carefully-curated database image and taking great care to never delete it are over.</p><h3 id="use-your-database-it-is-awesome" tabindex="-1">Use Your Database, It is Awesome <a class="header-anchor" href="#use-your-database-it-is-awesome" aria-label="Permalink to &quot;Use Your Database, It is Awesome&quot;">​</a></h3><p>Your database is the only part of the system that has any chance of ensuring data integrity. You can use constraints, types, foreign keys, etc. to ensure that the data in your database is correct, based on your current understandings. Code-based validation systems <strong>cannot achieve this on any level</strong>.</p><p>Thus, you are encouraged to learn about your database&#39;s features and use them!</p><p>For example, here&#39;s a way to add full text search to an existing table in Postgres:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">add_column </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:full_text_search</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
63
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :tsvector</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
64
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> generated_always_as:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">lit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">%{</span></span>
65
- <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> (</span></span>
66
- <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> setweight(to_tsvector(&#39;english&#39;, name),&#39;A&#39;) ||</span></span>
67
- <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> setweight(to_tsvector(&#39;english&#39;, coalesce(description,&#39;&#39;)),&#39;B&#39;)</span></span>
68
- <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> )</span></span>
69
- <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> }</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
70
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> generated_type:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :stored</span></span></code></pre></div><p>If you are using Postgtes, why <em>not</em> use its features? Unless your app is database-agnostic, you should be using the features of your database, even if they aren&#39;t explicitly exposed via Sequel&#39;s Ruby API (that&#39;s why <code>Sequel.lit</code> exists).</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to &quot;Technical Notes&quot;">​</a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut&#39;s internals, the source code is always more correct.</p></div><p><em>Last Updated May 8, 2025</em></p><p>As mentioned, Brut uses Sequel under the covers. This is unlikely to change.</p><p>As also mentioned, Brut&#39;s extensions often rely on Postgres. While we can all dream of a world where every developer uses the same database server, we don&#39;t live in that world. Brut should, some day, support all the databases that Sequel supports. For now, however, it only supports Postgres.</p><p>This hard-coded support is due to:</p><ul><li><code>pg_array</code></li><li><code>pg_json</code></li><li>Reliance on <code>citext</code> and <code>comment</code></li><li>Reliance on <code>timestamptz</code></li></ul><p>Brut is likely to add more Postgres-specific features before adding support for other databases.</p>`,70)])])}const k=e(n,[["render",l]]);export{u as __pageData,k as default};
@@ -1 +0,0 @@
1
- import{_ as e,c as a,o as i,ag as t}from"./chunks/framework.C4nOkCZI.js";const u=JSON.parse('{"title":"Database Schema / Migrations","description":"","frontmatter":{},"headers":[],"relativePath":"database-schema.md","filePath":"database-schema.md"}'),n={name:"database-schema.md"};function l(o,s,r,h,p,d){return i(),a("div",null,[...s[0]||(s[0]=[t("",70)])])}const k=e(n,[["render",l]]);export{u as __pageData,k as default};
@@ -1,55 +0,0 @@
1
- import{_ as a,c as n,o as s,ag as o}from"./chunks/framework.C4nOkCZI.js";const h=JSON.parse('{"title":"Deployment","description":"","frontmatter":{},"headers":[],"relativePath":"deployment.md","filePath":"deployment.md"}'),t={name:"deployment.md"};function p(i,e,l,r,d,c){return s(),n("div",null,[...e[0]||(e[0]=[o(`<h1 id="deployment" tabindex="-1">Deployment <a class="header-anchor" href="#deployment" aria-label="Permalink to &quot;Deployment&quot;">​</a></h1><p>Brut apps are Rack apps, so they can be deployed in conventional ways.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>There are just too many ways to deploy. Brut attempts to address this by adhering to <a href="https://12factor.net" target="_blank" rel="noreferrer">12-factor principles</a>. Brut also tries not to create artifacts like <code>Procfile</code> or <code>Dockerfile</code> that would conflict with the artifacts you&#39;d need to manage deployment.</p><p>That said, Brut includes first-class support for deploying to Heroku using containers. More options will be included as necessary, either through direct support in code/tooling, or documentation here.</p><h3 id="heroku-container-based-deployment" tabindex="-1">Heroku Container-based Deployment <a class="header-anchor" href="#heroku-container-based-deployment" aria-label="Permalink to &quot;Heroku Container-based Deployment&quot;">​</a></h3><p>When creating your Brut app with <code>mkbrut</code>, the Heroku segment can be used to create files and scripts for a <a href="https://devcenter.heroku.com/articles/container-registry-and-runtime" target="_blank" rel="noreferrer">Heroku container-based deployment</a>.</p><table tabindex="0"><thead><tr><th>File</th><th>Purpose</th><th>Notes</th></tr></thead><tbody><tr><td><code>bin/deploy</code></td><td>Script to use to perform the deployment</td><td>This wraps <code>HerokuContainerBasedDeploy</code> in <a href="/api/Brut/CLI/Apps.html" target="_self" rel="noopener" data-no-router><code>Brut::CLI::Apps</code></a></td></tr><tr><td><code>deploy/Dockerfile</code></td><td>Template <code>Dockerfile</code> used to create a <code>Dockerfile</code> for each process type</td><td>Heroku requires each process (web, worker, release, etc.) to have its own <code>Dockerfile</code> and own image</td></tr><tr><td><code>deploy/heroku_config.rb</code></td><td>Class that exports optional processes</td><td>By default, your app has a web and release process. <code>HerokuConfig</code> can export others, like Sidekiq</td></tr><tr><td><code>deploy/docker-entrypoint</code></td><td>The <a href="https://docs.docker.com/reference/dockerfile/#entrypoint" target="_blank" rel="noreferrer"><code>ENTRYPOINT</code></a> for production Docker images, which is set up to use jemalloc</td><td>You can modify or remove this as needed</td></tr></tbody></table><p>How to deploy:</p><ol><li><p>Get an auth token from Heroku, which you can do from inside the container, and save it to <code>bash_customizations.local</code>:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>your-computer&gt; dx/exec bash</span></span>
2
- <span class="line"><span>devcontainer&gt; heroku auth:login</span></span>
3
- <span class="line"><span># You will need to copy/paste the URL to log in</span></span>
4
- <span class="line"><span>devcontainer&gt; heroku authorizations:create -d &quot;container pushes&quot; --expires-in 31536000</span></span>
5
- <span class="line"><span># Copy the token output by this command</span></span>
6
- <span class="line"><span>devcontainer&gt; echo &quot;HEROKU_API_KEY=«TOKEN YOU COPIED»&quot; &gt;&gt; dx/bash_customizations.local</span></span></code></pre></div></li><li><p>Exit the devcontainer and stop <code>dx/start</code> (e.g. hit <code>Ctrl-C</code> wherever you ran it)</p></li><li><p>Rebuild and restart the devcontainer (this will set <code>HEROKU_API_KEY</code> for you)</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>your-computer&gt; dx/build</span></span>
7
- <span class="line"><span>your-computer&gt; dx/start</span></span>
8
- <span class="line"><span># In another terminal window</span></span>
9
- <span class="line"><span>your-computer&gt; dx/exec bash</span></span>
10
- <span class="line"><span>devcontainer&gt; echo $HEROKU_API_KEY</span></span>
11
- <span class="line"><span># You should see the token</span></span></code></pre></div><p>Setting this environment variable avoids having to constantly re-authenticate to Heroku.</p></li><li><p>Create your app using the container stack:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>&gt; heroku create --stack container -a «your heroku app name»</span></span></code></pre></div></li><li><p>Ensure your app&#39;s source code is all checked in, there are no uncommitted or unadded files, and you have pushed to the <code>main</code> branch of your remote Git repository.</p></li><li><p><code>bin/deploy</code></p><p>This will generate a <code>Dockerfile</code> for each process (by default, <code>Dockerfile.web</code> and <code>Dockerfile.release</code>), build images, push those images to Heroku, and ask Heroku to release them.</p></li></ol><p>Debugging Tips:</p><ul><li><p>Keep in mind it&#39;s hard to make general deployment tools. You are expected to understand your deployment and be capable of deploying an arbitrary Rack app manually. Brut&#39;s tooling automates what you need to do based on what you already need to know.</p></li><li><p><code>bin/deploy</code> runs the <code>deploy</code> subcommand, so <code>bin/deploy help deploy</code> can provide some options for debugging issues:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>devcontainer&gt; bin/deploy help deploy</span></span>
12
- <span class="line"><span>Usage: bin/deploy [global options] deploy [command options] </span></span>
13
- <span class="line"><span></span></span>
14
- <span class="line"><span> Build images, push them to Heroku, and deploy them</span></span>
15
- <span class="line"><span></span></span>
16
- <span class="line"><span> Manages a deploy process based on using Heroku&#39;s Container Registry. See</span></span>
17
- <span class="line"><span></span></span>
18
- <span class="line"><span> https://devcenter.heroku.com/articles/container-registry-and-runtime</span></span>
19
- <span class="line"><span></span></span>
20
- <span class="line"><span> for details. You are assumed to understand this.</span></span>
21
- <span class="line"><span> This command will make the process somewhat easier.</span></span>
22
- <span class="line"><span></span></span>
23
- <span class="line"><span> This will use deploy/Dockerfile as a template to create</span></span>
24
- <span class="line"><span> one Dockerfile for each process you want to run in Heroku.</span></span>
25
- <span class="line"><span> deploy/heroku_config.rb is where the processes and their</span></span>
26
- <span class="line"><span> commands are configured.</span></span>
27
- <span class="line"><span></span></span>
28
- <span class="line"><span> The release phase is included automatically, based on bin/release.</span></span>
29
- <span class="line"><span></span></span>
30
- <span class="line"><span>GLOBAL OPTIONS</span></span>
31
- <span class="line"><span></span></span>
32
- <span class="line"><span> -h, --help Get help</span></span>
33
- <span class="line"><span> --log-level=LEVEL Set log level. Allowed values: debug,</span></span>
34
- <span class="line"><span> info, warn, error, fatal. Default &#39;fatal&#39;</span></span>
35
- <span class="line"><span> --verbose Set log level to &#39;debug&#39;, which will produce</span></span>
36
- <span class="line"><span> maximum output</span></span>
37
- <span class="line"><span></span></span>
38
- <span class="line"><span>ENVIRONMENT VARIABLES</span></span>
39
- <span class="line"><span></span></span>
40
- <span class="line"><span> BRUT_CLI_RAISE_ON_ERROR - if set, shows backtrace on errors</span></span>
41
- <span class="line"><span> LOG_LEVEL - log level if --log-level or --verbose is omitted</span></span>
42
- <span class="line"><span></span></span>
43
- <span class="line"><span></span></span>
44
- <span class="line"><span>COMMAND OPTIONS</span></span>
45
- <span class="line"><span></span></span>
46
- <span class="line"><span> --platform=PLATFORM Override default platform. Can be any Docker</span></span>
47
- <span class="line"><span> platform.</span></span>
48
- <span class="line"><span> --[no-]dry-run Print the commands that would be run and</span></span>
49
- <span class="line"><span> don&#39;t actually do anything. Implies --skip-checks</span></span>
50
- <span class="line"><span> --[no-]skip-checks Skip checks for code having been</span></span>
51
- <span class="line"><span> committed and pushed</span></span>
52
- <span class="line"><span> --[no-]deploy After images are pushed, actually deploy them</span></span>
53
- <span class="line"><span> --[no-]push After images are created, push them</span></span>
54
- <span class="line"><span> to Heroku&#39;s registry. If false,</span></span>
55
- <span class="line"><span> implies --no-deploy</span></span></code></pre></div></li><li><p>Try building images first: <code>bin/deploy deploy --no-push --skip-checks</code></p></li><li><p>It&#39;s possible to run the images locally. If you are on Apple Silicon, you&#39;ll need to set --platform:</p><ul><li><code>bin/deploy deploy --no-push --skip-checks --platform linux/arm64</code></li><li>Create <code>docker-compose.yml</code> for your image and any other services e.g. databases</li><li>Set required environment variables in <code>docker-compose.yml</code></li><li>Start up Docker compose and poke around</li></ul><p>You&#39;ll need to have a better understanding of Docker to do this, however if you are deploying with Docker, this is an understanding you hopefully already have.</p></li></ul><h3 id="other-mechanisms-for-deployment" tabindex="-1">Other Mechanisms for Deployment <a class="header-anchor" href="#other-mechanisms-for-deployment" aria-label="Permalink to &quot;Other Mechanisms for Deployment&quot;">​</a></h3><p>As a Rack app, other deployments should be possible. To make the app work, you&#39;ll need to make sure a few things are dealt with:</p><ul><li><code>RACK_ENV</code> <strong>must</strong> be <code>&quot;production&quot;</code></li><li><code>bin/build-assets</code> will build all assets by default. This must either be done on production servers or done ahead of time and the results packaged with the app.</li><li><code>bin/build-assets</code> outputs files in <code>app/public</code> and <code>app/config</code>. Those files are used at runtime. Brut <strong>will not</strong> initiate the build of any assets.</li><li>If you are going to build assets on production servers, you <em>must</em> included developer tooling. This means NodeJS, all modules in <code>package.json</code> and all RubyGems in <code>Gemfile</code>.</li></ul><p>The <code>deploy/Dockerfile</code> created by <code>mkbrut --segment-heroku</code> is not very Heroku-specific and could serve as a reference.</p><h2 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to &quot;Testing&quot;">​</a></h2><p>Testing deployments is a bit out of scope, but in general:</p><ul><li>A container-based deployment can theoretically be run on your computer as a test.</li><li>Non-production, but production-like environments can be used to validate production configurations.</li><li>You own the means of production…not Brut.</li></ul><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to &quot;Recommended Practices&quot;">​</a></h2><ul><li>Avoid a lot of code that checks <code>Brut.container.project_env</code>. Try to consolidate all prod/test/dev differences in environment variables.</li><li>Have a way to get a shell into your production environment for debugging.</li><li>Brut doesn&#39;t log much, but if you remove the <code>OTEL_*</code> environment variables, Brut will log OTel telemetry to the console, which may be useful.</li><li>Setting <code>OTEL_LOG_LEVEL=debug</code> is advised if the app isn&#39;t starting or you aren&#39;t seeing any telemetry or logging</li></ul><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to &quot;Technical Notes&quot;">​</a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut&#39;s internals, the source code is always more correct.</p></div><p><em>Last Updated July 3, 2025</em></p><p>None at this time.</p>`,25)])])}const m=a(t,[["render",p]]);export{h as __pageData,m as default};
@@ -1 +0,0 @@
1
- import{_ as a,c as n,o as s,ag as o}from"./chunks/framework.C4nOkCZI.js";const h=JSON.parse('{"title":"Deployment","description":"","frontmatter":{},"headers":[],"relativePath":"deployment.md","filePath":"deployment.md"}'),t={name:"deployment.md"};function p(i,e,l,r,d,c){return s(),n("div",null,[...e[0]||(e[0]=[o("",25)])])}const m=a(t,[["render",p]]);export{h as __pageData,m as default};
@@ -1,16 +0,0 @@
1
- import{_ as t,c as a,o,ag as s}from"./chunks/framework.C4nOkCZI.js";const i="/assets/DevEnvironment.DaFcVfwP.png",n="/assets/dev-env-protocol.DysDAtnz.png",d="/assets/workspace-protocol.C0gXsoDb.png",g=JSON.parse('{"title":"Dev Environment","description":"","frontmatter":{},"headers":[],"relativePath":"dev-environment.md","filePath":"dev-environment.md"}'),r={name:"dev-environment.md"};function l(c,e,p,h,u,m){return o(),a("div",null,[...e[0]||(e[0]=[s('<h1 id="dev-environment" tabindex="-1">Dev Environment <a class="header-anchor" href="#dev-environment" aria-label="Permalink to &quot;Dev Environment&quot;">​</a></h1><p>Brut provides sophisticated tooling to manage your dev environment</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>A development environments or <em>dev environment</em> is made up of two parts:</p><ul><li><em>Foundational Core</em> - the operating system and tools needed to run the app and <em>its</em> tools. This includes language runtimes, system libraries (like ImageMagick), and system tools like web browsers.</li><li><em>Workspace</em> - the tools and code bundled with the app that you use day-to-day to work on the app itself. This would include scripts to run the app in development, run tests, perform scaffolding, or manage the database.</li></ul><p>On many teams, the Foundational Core is different per developer, since some run Linux, some run MacOs. Some might use mise to manage their version of Ruby while others use rbenv. Some will set up Postgres via homebrew, while others might use Popstgres.app.</p><p>Brut takes a different approach. Everyone shares the same Foundational Core, and this is defined by a <code>Dockerfile</code>, a <code>docker-compose.yml</code> file, and some lightweight Bash scripts.</p><p>This means that everyone uses the same version of everything, and they are all managed the same way.</p><p>Brut also provides sophisticated tooling for the Workspace. Like Rails, Brut provides a command-line based flow that can be scripted into any editor. Unlike Rails, Brut&#39;s Workspace is comprised of separate command-line apps and not Rake tasks.</p><h3 id="conceptual-overview" tabindex="-1">Conceptual Overview <a class="header-anchor" href="#conceptual-overview" aria-label="Permalink to &quot;Conceptual Overview&quot;">​</a></h3><p>Your dev environment consists of a Docker container that has languages, an operating system, and other system components installed in it. It will have access to the files on your computer so that it can run your app. The app will be exposed so that a browser on your computer can access it. Postgres will be run as a separate Docker container available to the dev Docker container.</p><p>Your editor and version control system run on your computer.</p><p><img src="'+i+`" alt="Diagram showing the parts of the dev environment. The foundational core is also
2
- labeled &quot;Docker containers&quot; and it contains three boxes labeled &quot;Container&quot;. One
3
- box contains ValKey, another contains Postrgres. The third box contains &quot;Your Brut
4
- App&quot;, &quot;NodeJS&quot;, &quot;Ruby&quot;, and part of &quot;Source Code&quot;. The &quot;Source Code&quot; also also
5
- partially inside a box containing the foundational core labeled &quot;Your Computer&quot;.
6
- This box contains &quot;Browser&quot; and &quot;source code editor&quot;."></p><h3 id="foundational-core-command-line-apps" tabindex="-1">Foundational Core Command Line Apps <a class="header-anchor" href="#foundational-core-command-line-apps" aria-label="Permalink to &quot;Foundational Core Command Line Apps&quot;">​</a></h3><p>These are the commands you will use to manage the <em>foundational core</em>, which is the Docker containers and their contents.</p><p>A few brief terminology notes if you aren&#39;t familiar with Docker:</p><ul><li>A Docker <em>container</em> is akin to a virtual machine. On Linux this isn&#39;t strictly true, but conceptually, you can think of this like a virtual computer.</li><li>A Docker <em>image</em> is what you use to start a container. This is akin to a disk image you might use to create a new computer or virtual machine.</li><li>A Dockerfile (often named <code>Dockerfile</code>) is a set of instructions to create an image.</li></ul><p>A few verbs to provide additional help:</p><ul><li>One <em>builds</em> a Docker image from a Dockerfile.</li><li>One <em>starts</em> a Docker container from an image.</li><li>One <em>stops</em> a Docker container when it&#39;s no longer needed.</li></ul><table tabindex="0"><thead><tr><th>App</th><th>Purpose</th></tr></thead><tbody><tr><td><code>dx/build</code></td><td>Builds a Docker <em>image</em> from a <code>Dockerfile.dx</code></td></tr><tr><td><code>dx/start</code></td><td>Starts all Docker containers, including those for databases and caches</td></tr><tr><td><code>dx/stop</code></td><td>Stops all Docker containers</td></tr><tr><td><code>dx/exec</code></td><td>Execute a command inside a running Docker container</td></tr></tbody></table><p>The workflow for the foundational core is shown in this diagram.</p><p><img src="`+n+'" alt="Foundational Core Workflow"></p><p>In words:</p><ol><li>You build the images based on the latest instructions via <code>dx/build</code>.</li><li>You start up the environment with <code>dx/start</code>.</li><li>You then use <code>dx/exec</code> to execute commands from the Workspace (see below).</li><li>When you are done working for the day, <code>dx/stop</code> shuts everything down.</li></ol><h3 id="workspace-command-line-apps" tabindex="-1">Workspace Command Line Apps <a class="header-anchor" href="#workspace-command-line-apps" aria-label="Permalink to &quot;Workspace Command Line Apps&quot;">​</a></h3><p>The workspace is where you&#39;ll run your day-to-day commands, such as running tests, starting the dev server, managing the database schema, etc.</p><p>Several of the commands accept or require subcommands. Each CLI app responds to <code>--help</code> and will show you full documentation about what the command and subcommands do.</p><table tabindex="0"><thead><tr><th>App</th><th>Subcommand</th><th>Descriptions</th></tr></thead><tbody><tr><td><code style="white-space:nowrap;">bin/ci</code></td><td>None</td><td>Runs all tests and security checks</td></tr><tr><td><code style="white-space:nowrap;">bin/console</code></td><td>None</td><td>Starts up a local IRB session with your app loaded</td></tr><tr><td><code style="white-space:nowrap;">bin/db</code></td><td></td><td>Tools for managing the database</td></tr><tr><td></td><td><code>create</code></td><td>Create the database if it does not exist</td></tr><tr><td></td><td><code>drop</code></td><td>Drop the database if it exists</td></tr><tr><td></td><td><code>migrate</code></td><td>Apply any outstanding migrations to the database</td></tr><tr><td></td><td><code>new_migration</code></td><td>Create a new migration file</td></tr><tr><td></td><td><code>rebuild</code></td><td>Drop, re-create, and run migrations, effectively rebuilding the entire database</td></tr><tr><td></td><td><code>seed</code></td><td>Load seed data into the database</td></tr><tr><td></td><td><code>status</code></td><td>Check the status of the database and migrations</td></tr><tr><td><code style="white-space:nowrap;">bin/dbconsole</code></td><td>None</td><td>Starts up a <code>psql</code> session to your database</td></tr><tr><td><code style="white-space:nowrap;">bin/dev</code></td><td>None</td><td>Starts the app in dev mode, rebuilding assets and reload as needed</td></tr><tr><td><code style="white-space:nowrap;">bin/setup</code></td><td>None</td><td>Install and setup all third party libraries and other configuration needed to use the app</td></tr><tr><td><code style="white-space:nowrap;">bin/scaffold</code></td><td></td><td>Generate Brut classes or files like database migrations or page classes</td></tr><tr><td></td><td><code>action</code></td><td>Create a handler for an action</td></tr><tr><td></td><td><code>component</code></td><td>Create a new component and associated test</td></tr><tr><td></td><td><code>custom_element_test</code></td><td>Create a test for a custom element in your app</td></tr><tr><td></td><td><code>form</code></td><td>Create a form and handler</td></tr><tr><td></td><td><code>page</code></td><td>Create a new page and associated test</td></tr><tr><td></td><td><code>db_model</code></td><td>Create one or more database models, specs, and factories, plus a migration to create the tables for those models</td></tr><tr><td></td><td><code>test</code></td><td>Create the shell of a unit test based on an existing source file</td></tr><tr><td></td><td><code>test:e2e</code></td><td>Create the shell of an end-to-end test</td></tr><tr><td><code style="white-space:nowrap;">bin/test</code></td><td></td><td>Run tests</td></tr><tr><td></td><td><code>audit</code></td><td>Audits all of the app&#39;s classes to see if test files exist</td></tr><tr><td></td><td><code>e2e</code></td><td>Run e2e tests</td></tr><tr><td></td><td><code>js</code></td><td>Run JavaScript unit tests</td></tr><tr><td></td><td><code>run</code></td><td>Run non-e2e tests (default)</td></tr></tbody></table><p>The workflow for your Workspace is shown in this diagram</p><p><img src="'+d+`" alt="Workspace Workflow"></p><p>In words:</p><ol><li>You&#39;ll run <code>bin/setup</code> to get everything set up for working.</li><li>You&#39;ll start your dev server with <code>bin/dev</code>.</li><li>You&#39;ll write code, using tools like <code>bin/db</code> and <code>bin/scaffold</code> to assist.</li><li>Using <code>bin/test</code>, you can test any code you&#39;ve written a test for.</li><li>When you are at a stopping point, use <code>bin/ci</code> to test the entire app.</li></ol><h3 id="extending-and-enhancing" tabindex="-1">Extending and Enhancing <a class="header-anchor" href="#extending-and-enhancing" aria-label="Permalink to &quot;Extending and Enhancing&quot;">​</a></h3><p>TBD</p><h2 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to &quot;Testing&quot;">​</a></h2><p>There aren&#39;t tests for this code, because you are using all day every day. Brut&#39;s test suite will ensure that the versions of these command line apps provided when you set up your app are working.</p><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to &quot;Recommended Practices&quot;">​</a></h2><p>While you are free to set up mise or rbenv or whatever to run all this on your computer, this way of working is currently not supported nor encouraged. For now, Brut will focus on the Docker-based approach.</p><p>The primary reason is that it&#39;s a tightly controlled environment that is almost entirely scriptable, but does not require devs to abandon their preferred editor. Environment manager-based approaches tend to be more fussy and require documentation to ensure they are set up.</p><p>Keep in mind a few things when adding your own automation:</p><ul><li>The <em>Foundational Core</em> is bootstrapped in a degenerate environment without reliable tools beyond Bash. This is why it&#39;s almost entirely written in Bash, since it&#39;s available everywhere and relatively stable.</li><li>The <em>Workspace</em> <strong>can and should</strong> rely on the languages and third party modules that are part of your app. The only exception is <code>bin/setup</code>, since it installs third party modules. As such, it should work entirely based on Ruby and its standard library.</li></ul><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to &quot;Technical Notes&quot;">​</a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut&#39;s internals, the source code is always more correct.</p></div><p><em>Last Updated June 12, 2025</em></p><p>Everything in <code>bin/</code> is intended to be a short shim that calls into classes managed either by Brut or by your app. For example, here is <code>bin/db</code>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">#!/usr/bin/env ruby</span></span>
7
- <span class="line"></span>
8
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;bundler&quot;</span></span>
9
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Bundler</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">require</span></span>
10
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;pathname&quot;</span></span>
11
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;brut/cli/apps/db&quot;</span></span>
12
- <span class="line"></span>
13
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">exit</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">app</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
14
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">CLI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Apps</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">DB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
15
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> project_root:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Pathname</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">($0).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dirname</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> /</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;..&quot;</span></span>
16
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> )</span></span></code></pre></div><p>These files have some duplication, but should be relatively stable.</p><p>This means that Brut-provided CLIs <em>will</em> be updated when you update Brut. Compare this to the files in <code>dx/</code> which are entire Bash scripts that will not be updated when Brut is updated.</p>`,48)])])}const b=t(r,[["render",l]]);export{g as __pageData,b as default};
@@ -1 +0,0 @@
1
- import{_ as t,c as a,o,ag as s}from"./chunks/framework.C4nOkCZI.js";const i="/assets/DevEnvironment.DaFcVfwP.png",n="/assets/dev-env-protocol.DysDAtnz.png",d="/assets/workspace-protocol.C0gXsoDb.png",g=JSON.parse('{"title":"Dev Environment","description":"","frontmatter":{},"headers":[],"relativePath":"dev-environment.md","filePath":"dev-environment.md"}'),r={name:"dev-environment.md"};function l(c,e,p,h,u,m){return o(),a("div",null,[...e[0]||(e[0]=[s("",48)])])}const b=t(r,[["render",l]]);export{g as __pageData,b as default};
@@ -1,46 +0,0 @@
1
- import{_ as a,c as t,o as e,ag as n}from"./chunks/framework.C4nOkCZI.js";const h=JSON.parse('{"title":"Directory Structure","description":"","frontmatter":{},"headers":[],"relativePath":"dir-structure.md","filePath":"dir-structure.md"}'),d={name:"dir-structure.md"};function o(p,s,c,r,l,i){return e(),t("div",null,[...s[0]||(s[0]=[n(`<h1 id="directory-structure" tabindex="-1">Directory Structure <a class="header-anchor" href="#directory-structure" aria-label="Permalink to &quot;Directory Structure&quot;">​</a></h1><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>.</span></span>
2
- <span class="line"><span>├── app</span></span>
3
- <span class="line"><span>│   ├── config</span></span>
4
- <span class="line"><span>│   │   └── i18n</span></span>
5
- <span class="line"><span>│   │   └── en</span></span>
6
- <span class="line"><span>│   ├── public</span></span>
7
- <span class="line"><span>│   │   ├── css</span></span>
8
- <span class="line"><span>│   │   ├── js</span></span>
9
- <span class="line"><span>│   │   └── static</span></span>
10
- <span class="line"><span>│   │   └── images</span></span>
11
- <span class="line"><span>│   └── src</span></span>
12
- <span class="line"><span>│   ├── back_end</span></span>
13
- <span class="line"><span>│   │   ├── data_models</span></span>
14
- <span class="line"><span>│   │      ├── db</span></span>
15
- <span class="line"><span>│   │      ├── migrations</span></span>
16
- <span class="line"><span>│   │      └── seed</span></span>
17
- <span class="line"><span>│   ├── cli</span></span>
18
- <span class="line"><span>│   └── front_end</span></span>
19
- <span class="line"><span>│   ├── components</span></span>
20
- <span class="line"><span>│   ├── css</span></span>
21
- <span class="line"><span>│   ├── fonts</span></span>
22
- <span class="line"><span>│   ├── forms</span></span>
23
- <span class="line"><span>│   ├── handlers</span></span>
24
- <span class="line"><span>│   ├── images</span></span>
25
- <span class="line"><span>│   ├── js</span></span>
26
- <span class="line"><span>│   ├── layouts</span></span>
27
- <span class="line"><span>│   ├── pages</span></span>
28
- <span class="line"><span>│   ├── route_hooks</span></span>
29
- <span class="line"><span>│   ├── support</span></span>
30
- <span class="line"><span>│   └── svgs</span></span>
31
- <span class="line"><span>├── bin</span></span>
32
- <span class="line"><span>├── deploy</span></span>
33
- <span class="line"><span>├── dx</span></span>
34
- <span class="line"><span>└── specs</span></span>
35
- <span class="line"><span> ├── back_end</span></span>
36
- <span class="line"><span> │   ├── data_models</span></span>
37
- <span class="line"><span> │      └── db</span></span>
38
- <span class="line"><span> ├── e2e</span></span>
39
- <span class="line"><span> ├── factories</span></span>
40
- <span class="line"><span> │   └── db</span></span>
41
- <span class="line"><span> └── front_end</span></span>
42
- <span class="line"><span> ├── components</span></span>
43
- <span class="line"><span> ├── handlers</span></span>
44
- <span class="line"><span> ├── js</span></span>
45
- <span class="line"><span> ├── pages</span></span>
46
- <span class="line"><span> └── support</span></span></code></pre></div><h2 id="top-level" tabindex="-1">Top Level <a class="header-anchor" href="#top-level" aria-label="Permalink to &quot;Top Level&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Directory</th><th>Purpose</th></tr></thead><tbody><tr><td><code>app/</code></td><td>Contains all configuration and source code specific to your app</td></tr><tr><td><code>bin/</code></td><td>Contains tasks and other CLIs to do development of your app, such as <code>bin/test</code></td></tr><tr><td><code>dx/</code></td><td>Contains scripts to manage your development environment</td></tr><tr><td><code>specs/</code></td><td>Contains all tests</td></tr></tbody></table><h2 id="inside-app" tabindex="-1">Inside <code>app</code>/ <a class="header-anchor" href="#inside-app" aria-label="Permalink to &quot;Inside \`app\`/&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Directory</th><th>Purpose</th></tr></thead><tbody><tr><td><code>bootstrap.rb</code></td><td>A ruby file that sets up your app and ensures everything is <code>require</code>d in the right way.</td></tr><tr><td><code>config/</code></td><td>Configuration for your app, such as localizations and translations. Brut tries very hard to make sure there is no YAML in here at all. YAML is not good for you.</td></tr><tr><td><code>public/</code></td><td>Root of public assets served by the app.</td></tr><tr><td><code>src/</code></td><td>All source code for your app</td></tr></tbody></table><p>Inside <code>app/src</code></p><table tabindex="0"><thead><tr><th>Directory</th><th>Purpose</th></tr></thead><tbody><tr><td><code>app.rb</code></td><td>The core of your app, mostly configuration, such as routes, hooks, middleware, etc.</td></tr><tr><td><code>back_end/</code></td><td>Back end classes for your app including database schema, DB models, seed data, and your domain logic</td></tr><tr><td><code>cli/</code></td><td>Any CLIs or tasks for your app</td></tr><tr><td><code>front_end/</code></td><td>The front-end for your app, including pages, components, forms, handlers, JavaScript, and assets</td></tr></tbody></table><p>Inside <code>app/src/back_end</code></p><table tabindex="0"><thead><tr><th>Directory</th><th>Purpose</th></tr></thead><tbody><tr><td><code>data_models/app_data_model.rb</code></td><td>Base class for all DB model classes</td></tr><tr><td><code>data_models/db</code></td><td>DB model classes</td></tr><tr><td><code>data_models/db.rb</code></td><td>Namespace module for DB model classes</td></tr><tr><td><code>data_models/migrations</code></td><td>Database schema migrations</td></tr><tr><td><code>data_models/seed</code></td><td>Seed data used for local development</td></tr></tbody></table><p>Inside <code>app/src/front_end</code></p><table tabindex="0"><thead><tr><th>Directory</th><th>Purpose</th></tr></thead><tbody><tr><td><code>components/</code></td><td>Component classes</td></tr><tr><td><code>css/</code></td><td>CSS, managed by esbuild and <code>bin/build-assets</code></td></tr><tr><td><code>fonts/</code></td><td>Custom fonts, managed by esbuild and <code>bin/build-assets</code></td></tr><tr><td><code>forms/</code></td><td>Form classes</td></tr><tr><td><code>handlers/</code></td><td>Handler classes</td></tr><tr><td><code>images/</code></td><td>Images, copied to <code>app/public</code> by <code>bin/build-assets</code></td></tr><tr><td><code>js/</code></td><td>JavaScript, managed by esbuild and <code>bin/build-assets</code></td></tr><tr><td><code>layouts/</code></td><td>Layout classes</td></tr><tr><td><code>middlewares/</code></td><td>Rack Middleware, if any</td></tr><tr><td><code>pages/</code></td><td>Page classes</td></tr><tr><td><code>route_hooks/</code></td><td>Route hooks, if any</td></tr><tr><td><code>support/</code></td><td>General support classes/junk drawer.</td></tr><tr><td><code>svgs/</code></td><td>SVGs you want to render inline</td></tr></tbody></table><h2 id="inside-specs" tabindex="-1">Inside <code>specs/</code> <a class="header-anchor" href="#inside-specs" aria-label="Permalink to &quot;Inside \`specs/\`&quot;">​</a></h2><p><code>specs/</code> is intended to mirror <code>app/src</code>, but has a few extra directories:</p><table tabindex="0"><thead><tr><th>Directory</th><th>Purpose</th></tr></thead><tbody><tr><td><code>specs/back_end</code></td><td>tests for all back-end code, organized the same as <code>app/src/back_end</code></td></tr><tr><td><code>specs/back_end/data_models/db</code></td><td>tests for all DB classes, if needed</td></tr><tr><td><code>specs/e2e</code></td><td>End-to-end tests, organized however you like</td></tr><tr><td><code>specs/factories</code></td><td>Root of all factories for FactoryBot. You can create subdirectories here for non-DB classes you may want to be able to create</td></tr><tr><td><code>specs/factories/db</code></td><td>Factories to create DB records</td></tr><tr><td><code>specs/front_end</code></td><td>tests for all front-end code, organized the same as <code>app/src/front_end</code></td></tr><tr><td><code>specs/js</code></td><td><em>JavaScript</em> code to test any autonomous custom elements you have created</td></tr></tbody></table>`,15)])])}const b=a(d,[["render",o]]);export{h as __pageData,b as default};
@@ -1 +0,0 @@
1
- import{_ as a,c as t,o as e,ag as n}from"./chunks/framework.C4nOkCZI.js";const h=JSON.parse('{"title":"Directory Structure","description":"","frontmatter":{},"headers":[],"relativePath":"dir-structure.md","filePath":"dir-structure.md"}'),d={name:"dir-structure.md"};function o(p,s,c,r,l,i){return e(),t("div",null,[...s[0]||(s[0]=[n("",15)])])}const b=a(d,[["render",o]]);export{h as __pageData,b as default};
@@ -1 +0,0 @@
1
- import{_ as t,c as o,o as s,ag as r}from"./chunks/framework.C4nOkCZI.js";const m=JSON.parse('{"title":"Documentation Conventions","description":"","frontmatter":{},"headers":[],"relativePath":"doc-conventions.md","filePath":"doc-conventions.md"}'),n={name:"doc-conventions.md"};function a(i,e,l,u,c,d){return s(),o("div",null,[...e[0]||(e[0]=[r('<h1 id="documentation-conventions" tabindex="-1">Documentation Conventions <a class="header-anchor" href="#documentation-conventions" aria-label="Permalink to &quot;Documentation Conventions&quot;">​</a></h1><h2 id="terminology" tabindex="-1">Terminology <a class="header-anchor" href="#terminology" aria-label="Permalink to &quot;Terminology&quot;">​</a></h2><p>Brut attempts to use existing terminology where possible, particularly where that technology applies to the web platform. For example, there is not a thing called &quot;CSS variables&quot;, rather the term is &quot;custom properties&quot;.</p><p>Here are some common exampels:</p><ul><li>HTML entities are <strong>elements</strong> or <strong>tags</strong></li><li>HTML elements have <strong>attributes</strong>.</li><li>Forms don&#39;t have validations, they have <strong>constraints</strong> which are <strong>violated</strong> by invalid data.</li><li>Ruby classes don&#39;t have constructors, they have <strong>initializers</strong>.</li><li>Invoking behavior on a Ruby object is <strong>calling a method</strong>, not sending a message.</li><li>Despite being in <code>specs/</code>, the files in there are <strong>tests</strong>, not specifications or &quot;specs&quot;.</li><li>Tests that use a browser are <strong>end to end</strong> or <strong>e2e</strong> tests.</li><li>HTML is not rendered, but <strong>generated</strong>. The browser renders the HTML sent to it by the server, along with the CSS.</li><li>Your app or site doesn&#39;t have users, it has <strong>visitors</strong>.</li></ul><h2 id="structure-of-these-documents" tabindex="-1">Structure of These Documents <a class="header-anchor" href="#structure-of-these-documents" aria-label="Permalink to &quot;Structure of These Documents&quot;">​</a></h2><p>Each page here documents on aspect of Brut, called a <em>module</em>, and these pages are organized along four sections:</p><ul><li><strong>Overview</strong> - What the module does, how it works, and a brief example.</li><li><strong>Testing</strong> - How to test the code you write in this module.</li><li><strong>Recommended Practices</strong> - Opinions from the creators about how best to think about the code in this module.</li><li><strong>Technical Notes</strong> - details about the technical implementations that may be useful as context.</li></ul><h2 id="names-of-the-library-and-associated-modules" tabindex="-1">Names of the Library and Associated Modules <a class="header-anchor" href="#names-of-the-library-and-associated-modules" aria-label="Permalink to &quot;Names of the Library and Associated Modules&quot;">​</a></h2><p>This framework is called &quot;Brut&quot; though may be called &quot;BrutRB&quot; or &quot;brut-rp&quot;. It lives at <code>brutrb.com</code>. Never use &quot;brutRB&quot;, &quot;brut_rb&quot;, etc.</p><p>The JavaScript library is called &quot;BrutJS&quot;, but is <code>brut-js</code> in code or the filesystem. &quot;Brut-JS&quot; is wrong, as is <code>brut_js</code>.</p><p>The CSS library is called &quot;BrutCSS&quot;, but is <code>brut-css</code> in code or the filesystem. &quot;Brut-CSS&quot; is wrong, as is &quot;brut-css&quot;.</p><h2 id="on-using-vitepress" tabindex="-1">On Using VitePress <a class="header-anchor" href="#on-using-vitepress" aria-label="Permalink to &quot;On Using VitePress&quot;">​</a></h2><p>This site is built using <a href="https://vitepress.dev" target="_blank" rel="noreferrer">VitePress</a>, which is a client-side heavy framework. It kinda goes against the ethos of Brut, but it is allowing me to write documentation that looks decent and is mostly navigable. I would like to use a more accessible, customized system for documenting Brut, but for now, it&#39;s more important to get the documentation out. A better documentation experience is planned.</p>',14)])])}const g=t(n,[["render",a]]);export{m as __pageData,g as default};
@@ -1 +0,0 @@
1
- import{_ as t,c as o,o as s,ag as r}from"./chunks/framework.C4nOkCZI.js";const m=JSON.parse('{"title":"Documentation Conventions","description":"","frontmatter":{},"headers":[],"relativePath":"doc-conventions.md","filePath":"doc-conventions.md"}'),n={name:"doc-conventions.md"};function a(i,e,l,u,c,d){return s(),o("div",null,[...e[0]||(e[0]=[r("",14)])])}const g=t(n,[["render",a]]);export{m as __pageData,g as default};
@@ -1,28 +0,0 @@
1
- import{_ as e,c as t,o as a,ag as i}from"./chunks/framework.C4nOkCZI.js";const k=JSON.parse('{"title":"End to End Tests","description":"","frontmatter":{},"headers":[],"relativePath":"end-to-end-tests.md","filePath":"end-to-end-tests.md"}'),n={name:"end-to-end-tests.md"};function o(l,s,h,r,p,d){return a(),t("div",null,[...s[0]||(s[0]=[i(`<h1 id="end-to-end-tests" tabindex="-1">End to End Tests <a class="header-anchor" href="#end-to-end-tests" aria-label="Permalink to &quot;End to End Tests&quot;">​</a></h1><p>Is there a greater pain the world than an end-to-end test? Is there a more punishing API than trying to convince a browser to browse and use a website? Is there an answer for why the way we interact with browsers when writing code is 100% different than the we do when writing a test?</p><p>Brut cannot answer these things, but it does provide a way to write end-to-end tests with a browser, with a somewhat slightly reduced amount of pain.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to &quot;Overview&quot;">​</a></h2><p>Brut uses <a href="https://playwright.dev/" target="_blank" rel="noreferrer">Playwright</a> and the <a href="https://playwright-ruby-client.vercel.app/" target="_blank" rel="noreferrer">playwright-ruby-client</a> to allow you to write end-to-end tests that use a web browser. Brut sets up headless Chromium to do this.</p><p>You can run End-to-End (e2e) tests with <code>bin/test e2e</code>. You must use this to run individual tests as well, since this will ensure proper set up for the tests, which is more than is needed for a normal unit test.</p><h3 id="using-playwright" tabindex="-1">Using Playwright <a class="header-anchor" href="#using-playwright" aria-label="Permalink to &quot;Using Playwright&quot;">​</a></h3><p>At a high level, e2e tests look like normal RSpec tests:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;spec_helper&quot;</span></span>
2
- <span class="line"></span>
3
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">RSpec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;logging into the website&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
4
- <span class="line"></span>
5
- <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
6
- <span class="line"></span>
7
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>The contents of your <code>it</code> blocks will use <code>playwright-ruby-client</code> to interact with the browser and make assertions. The value <code>page</code> is available to use this API.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;spec_helper&quot;</span></span>
8
- <span class="line"></span>
9
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">RSpec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;logging into the website&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
10
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> it </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;shows an error when login is invalid&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
11
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">goto</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;/&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
12
- <span class="line"></span>
13
- <span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> email</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">locator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;form input[type=&#39;email&#39;]&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
14
- <span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> password</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">locator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;form input[type=&#39;password&#39;]&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
15
- <span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">locator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;form button&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
16
- <span class="line"></span>
17
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> email.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fill</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;pat@example.com&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
18
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> password.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fill</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;12345678&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
19
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span></span>
20
- <span class="line"></span>
21
- <span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> flash</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">locator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;[role=&#39;alert&#39;]&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
22
- <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(flash).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> have_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;No email/password in our system&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
23
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
24
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p><code>playwright-ruby-client</code> provides excellent documentation on how it has adapted Playwright&#39;s API for use in Ruby.</p><h3 id="test-setup" tabindex="-1">Test Setup <a class="header-anchor" href="#test-setup" aria-label="Permalink to &quot;Test Setup&quot;">​</a></h3><p>Brut will run your app via <a href="/api/Brut/SpecSupport/E2ETestServer.html" target="_self" rel="noopener" data-no-router><code>Brut::SpecSupport::E2ETestServer</code></a>. It will run it before the first e2e test, leave it running during the remainder of the test suite, then stop it. This means that database changes your test makes will persist across tests.</p><p>If you are using Sidekiq, Sidekiq will be set up like normal. Jobs queued will go to Redis, and those jobs will be processed by Sidekiq, just like in production. Thus, you should not assert things about specific jobs, but rather assert the effects those jobs will have. Redis is flushed between each test.</p><h3 id="test-helpers-and-configuration" tabindex="-1">Test Helpers and Configuration <a class="header-anchor" href="#test-helpers-and-configuration" aria-label="Permalink to &quot;Test Helpers and Configuration&quot;">​</a></h3><p>Inside your test, <code>t</code> is available to produce translations. You can also access all your page and handler classes, so you can (and should) use <code>.routing</code>, e.g. <code>DashboardPage.routing</code>, to generate or access routes for your app.</p><p>You can set the <code>e2e_timeout</code> metadata on any test to override the default amount of time Playwright will wait for a locator to locate an element. The default is 5 seconds.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">RSpec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;Test for login&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">e2e_timeout:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 10</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
25
- <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
26
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>You can also configure behavior with environment variables:</p><table tabindex="0"><thead><tr><th>Variable</th><th>Default</th><th>Purpose</th></tr></thead><tbody><tr><td><code>E2E_TIMEOUT_MS</code></td><td>5000</td><td>Number of milliseconds Playwright will wait for a locator to appear</td></tr><tr><td><code>E2E_SLOW_MO</code></td><td>0</td><td>Number of milliseconds Playwright will pause between operations. Useful to detect race conditions</td></tr><tr><td><code>E2E_RECORD_VIDEOS</code></td><td>unset</td><td>If set, videos of each test run are saved in <code>tmp/e2e-videos</code></td></tr></tbody></table><h3 id="quirks-of-playwright" tabindex="-1">Quirks of Playwright <a class="header-anchor" href="#quirks-of-playwright" aria-label="Permalink to &quot;Quirks of Playwright&quot;">​</a></h3><p>The Playwright JavaScript API is heavily asycnhronous, requiring liberal use of <code>await</code>. The <code>playwright-ruby-client</code> wrapper abstracts that so you can write more straightfoward code.</p><p>The main thing to be aware of is that locators that fail only fail when you attempt to assert something.</p><p>For example</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = page.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">locator</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;form button&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># suppose this button doesn&#39;t exist</span></span>
27
- <span class="line"></span>
28
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">button.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">click</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # This is where you&#39;ll see a failure</span></span></code></pre></div><p>This hidden asynchronous behavior also means that certain calls will wait a period of time for the element you are locating to appear. This is why the example test above works without having to explicitly wait for a page refresh. After <code>button.click</code>, presumably the back-end is contacted and the page is re-rendered with an error. As long as that happens within a second or so, the code will wait for an element matching <code>[role=&#39;alert&#39;]</code> to show up.</p><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to &quot;Recommended Practices&quot;">​</a></h2><p>E2e tests are slow. They can also be flaky if you aren&#39;t careful in how you write them and how you author your HTML.</p><h3 id="test-major-flows-not-exhaustive-branches" tabindex="-1">Test Major Flows, Not Exhaustive Branches <a class="header-anchor" href="#test-major-flows-not-exhaustive-branches" aria-label="Permalink to &quot;Test Major Flows, Not Exhaustive Branches&quot;">​</a></h3><p>E2e tests give the most value when they assert that a sequence of actions the visitor takes result in what you expect—a &quot;major flow&quot;. Testing some error cases can be useful, but you should not use e2e tests to assert every single possible thing that could happen on a page.</p><p>In fact, your app might be better off leaving some behaviors better untested instead of tested by an e2e test. Use your judgement and be aware of the carrying cost of each e2e test.</p><h3 id="use-css-selectors" tabindex="-1">Use CSS Selectors <a class="header-anchor" href="#use-css-selectors" aria-label="Permalink to &quot;Use CSS Selectors&quot;">​</a></h3><p>The main Playwright documentation encourages you to locate elements by &quot;accessible names&quot; and other indirect ways of finding elements. In practice, this is error prone and tedious. Determining the accessible name of an element is not always easy.</p><p>We recommend you assess your app&#39;s accessibility in another way than trying to do it while performing end-to-end tests. Instead, locate elements with CSS selectors—this is what you&#39;d use to debug your app so it makes sense as a testing technique.</p><p>Insulating your end-to-end tests from markup changes does not produce significant savings and can make tests more difficult to write.</p><h3 id="testing-must-inform-your-html" tabindex="-1">Testing Must Inform your HTML <a class="header-anchor" href="#testing-must-inform-your-html" aria-label="Permalink to &quot;Testing Must Inform your HTML&quot;">​</a></h3><p>To allow CSS selectors to survive minor changes to a page, your HTML should be authored with testing in mind. In the example above, we locate the flash by looking for <code>[role=&#39;alert&#39;]</code>, since this is the most semantically correct way to mark up a flash message that contains an error.</p><p>ARIA roles that should be applied for accessibility purposes can be leveraged as locators, as can custom elements. Remember that any custom element is valid, even if it has no associated JavaScript. Custom elements are an excellent way to &quot;tag&quot; markup for use in tests or progressively-enhanced behavior.</p><p>CSS classes, on the other hand, are not a good candidate for identifying markup in a test. CSS classes exist to afford visual styling of elements and are the most likely to change as the app evolves. A better fallback if there is no other way to locate an element is to use <code>data-testid</code>. It makes itself painfully clear why it&#39;s there. Use this sparingly, but it&#39;s there if you need it.</p><h3 id="asserting-the-lack-of-content-basically-doesn-t-work" tabindex="-1">Asserting the Lack of Content Basically Doesn&#39;t Work <a class="header-anchor" href="#asserting-the-lack-of-content-basically-doesn-t-work" aria-label="Permalink to &quot;Asserting the Lack of Content Basically Doesn&#39;t Work&quot;">​</a></h3><p>To assert that some content or an element <strong>is not</strong> on the page requires locating it and waiting the timeout for that locate to fail. This sucks. Don&#39;t do it.</p><p>If you need to assert that something did not happen, you may want to design your page or app such that markup appears that indicates whatever it is didn&#39;t happen. This is not ideal, but a web page is a living thing that never stops changing, so your test can&#39;t just assume it&#39;s all synchronous.</p><h3 id="try-to-use-the-defaults-for-timeouts" tabindex="-1">Try to Use the Defaults for Timeouts <a class="header-anchor" href="#try-to-use-the-defaults-for-timeouts" aria-label="Permalink to &quot;Try to Use the Defaults for Timeouts&quot;">​</a></h3><p>Your app should not take 5 seconds to do anythning, especially not inside a test. You may need to bump up the timeout to figure out what&#39;s going wrong, or set <code>E2E_SLOW_MO</code> to watch a video test, but once you&#39;ve sorted out the issue, restore these to their defaults.</p><p>If you <em>must</em> set <code>e2e_timeout</code> as metadata on test, <strong>explain why</strong> and try removing it every so often to make sure it&#39;s still needed.</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to &quot;Technical Notes&quot;">​</a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut&#39;s internals, the source code is always more correct.</p></div><p><em>Last Updated June 13, 2025</em></p><p>The test server is run via <code>bin/test-server</code>.</p>`,50)])])}const u=e(n,[["render",o]]);export{k as __pageData,u as default};
@@ -1 +0,0 @@
1
- import{_ as e,c as t,o as a,ag as i}from"./chunks/framework.C4nOkCZI.js";const k=JSON.parse('{"title":"End to End Tests","description":"","frontmatter":{},"headers":[],"relativePath":"end-to-end-tests.md","filePath":"end-to-end-tests.md"}'),n={name:"end-to-end-tests.md"};function o(l,s,h,r,p,d){return a(),t("div",null,[...s[0]||(s[0]=[i("",50)])])}const u=e(n,[["render",o]]);export{k as __pageData,u as default};